@skate-org/skate_amm_solana
v1.2.27
Published
Solana AMM implementation for Skate
Downloads
254
Readme
Skate SDK - Solana AMM Integration Guide
A powerful SDK for interacting with Solana AMM (Automated Market Maker) pools. This SDK provides a simple interface for common operations like adding liquidity, swapping tokens, and burning liquidity.
Table of Contents
- Skate SDK - Solana AMM Integration Guide
Installation
npm install @skate-org/skate_amm_solanaSetup
RPC URLs: Ensure you have the correct Solana RPC URLs for the desired networks (e.g., mainnet-beta, devnet, or custom testnets like Eclipse). Store these securely, potentially in a
.envfile:# Example for Eclipse Testnet RPC_URL_902=your_eclipse_rpc_url_here # Example for Solana Devnet (if using ChainId.SOLANA for testing) RPC_URL_901=your_solana_devnet_rpc_url_hereEnvironment: The SDK distinguishes between
stagingandproductionenvironments, which primarily affects which program ID is used and how configuration data is structured. You'll select the environment when initializing the SDK.Validation Constants: The SDK provides important constants for validation and proper usage:
VmType.SVM(3) - Use this for Solana Virtual Machine type instead of magic numbersMIN_TICK_SQRT_PRICEandMAX_TICK_SQRT_PRICE- Use these for price limits in swapsbase58ToEthBytes32()- Use this function to convert Solana addresses to bytes32 format for recipient parametersethToBytes32()- Use this function to convert Ethereum addresses to bytes32 format for recipient parameters
Basic Usage
1. Initialize the SDK
Import the necessary components and initialize the SkateSDK with your connection, environment, and wallet.
import { Connection, Keypair } from "@solana/web3.js";
import { Wallet } from "@coral-xyz/anchor";
import { SkateSDK, Environment } from "@skate-org/skate_amm_solana";
const connection = new Connection(process.env.RPC_URL_902!, "confirmed"); // Use the appropriate RPC URL
// Create wallet from keypair
const keypair = Keypair.generate(); // or load from file
const wallet = new Wallet(keypair);
// Initialize for STAGING environment
const sdkStaging = new SkateSDK(connection, Environment.STAGING, wallet);
// Or initialize for PRODUCTION environment
const sdkProduction = new SkateSDK(connection, Environment.PRODUCTION, wallet);The Environment enum determines which program ID (PROGRAM_ID_STAGING or PROGRAM_ID_PRODUCTION) the SDK instance will interact with. The Wallet parameter is required for SDK initialization.
2. Configure Token Pair
The SDK uses a structured configuration object (CONFIG) accessed by Chain ID, Environment, and Token Pair.
import {
CONFIG,
ChainId,
TokenPair,
Environment,
TokenPairConfig,
} from "@skate-org/skate_amm_solana";
// Example: Using WETH/TETH pool configuration on Eclipse chain for STAGING
const chainId = ChainId.ECLIPSE;
// ensure the environment corresponds to the environment initialised with the sdk
const environment = Environment.STAGING;
const tokenPair = TokenPair.WETH_TETH;
const poolConfig: TokenPairConfig | undefined =
CONFIG[chainId]?.[environment]?.[tokenPair];
console.log(
`Using pool: ${poolConfig.description} in ${environment} on chain ${chainId}`
);Available chain IDs:
ChainId.SOLANA(901)ChainId.ECLIPSE(902)ChainId.SOON(903)
Available environments:
Environment.STAGINGEnvironment.PRODUCTION
Note: Ensure the poolConfig you use corresponds to the Environment you initialized the SDK with.
3. Fetching Pool Data
To get detailed information about a specific pool (for the environment the SDK is configured for), including token balances, use fetchPeripheryPoolData. You must provide the TokenPairConfig for the specific chain and environment you're interested in.
import {
TokenPairConfig,
SkateSDK,
PeripheryPoolDetails,
} from "@skate-org/skate_amm_solana";
async function getPoolDetails(poolConfig: TokenPairConfig, sdk: SkateSDK) {
try {
const poolDetails: PeripheryPoolDetails = await sdk.fetchPeripheryPoolData(
poolConfig
);
console.log("Pool Details:", poolDetails);
// Example field access
console.log("Manager:", poolDetails.manager.toBase58());
console.log(
"Token 0 Balance:",
poolDetails.poolToken0Balance.uiAmountString
);
console.log("Token 0 PDA:", poolDetails.poolToken0Pda.toBase58());
} catch (error) {
console.error("Failed to fetch pool data:", error);
throw error;
}
}
// Example usage
await getPoolDetails(poolConfig, sdkProduction);The fetchPeripheryPoolData function returns a PeripheryPoolDetails object containing:
- All fields from
PeripheryPoolData(likemanager,token0,token1,fee,description,stagedToken0Amount,stagedToken1Amount, etc.) poolToken0Balance: ATokenAmountobject representing the balance of the pool's token 0 account.poolToken1Balance: ATokenAmountobject representing the balance of the pool's token 1 account.poolToken0Pda: ThePublicKeyof the pool's token 0 account PDA.poolToken1Pda: ThePublicKeyof the pool's token 1 account PDA.
4. Fetching Raw Account Data
If you need the raw, deserialized data directly from the on-chain accounts without the extra balance fetching included in fetchPeripheryPoolData, you can use:
fetchPoolAccount(poolConfig): Fetches the rawPeripheryPoolaccount data.fetchUserDataAccount(poolConfig, userPublicKey): Fetches the rawUserDataAccountdata for a specific user and pool.
import {
TokenPairConfig,
SkateSDK,
PeripheryPoolData,
UserDataAccountData,
} from "@skate-org/skate_amm_solana";
import { PublicKey } from "@solana/web3.js";
// Fetch raw pool account
async function getRawPoolAccount(poolConfig: TokenPairConfig, sdk: SkateSDK) {
try {
const poolAccount: PeripheryPoolData | null = await sdk.fetchPoolAccount(
poolConfig
);
if (poolAccount) {
console.log("Raw Pool Account Data:", poolAccount);
} else {
console.log("Pool account not found.");
}
} catch (error) {
console.error("Failed to fetch raw pool account:", error);
}
}
// Fetch user-specific account data
async function getUserData(
poolConfig: TokenPairConfig,
userPublicKey: PublicKey,
sdk: SkateSDK
) {
try {
const userData: UserDataAccountData | null = await sdk.fetchUserDataAccount(
poolConfig,
userPublicKey
);
if (userData) {
console.log("User Data Account:", userData);
} else {
console.log("User data account not found (likely not initialized).");
}
} catch (error) {
console.error("Failed to fetch user data account:", error);
}
}
// Example usage
await getRawPoolAccount(poolConfig, sdkProduction);
await getUserData(poolConfig, userPublicKey, sdkProduction);Whitelisting a Pool in ActionBox
Managers (the ActionBox master signer) can ensure a pool is whitelisted before using the periphery program:
const { instruction, alreadyWhitelisted, nextWhitelist } =
// Whitelists the pool authority PDA derived from the pool config
await sdk.getWhitelistPoolInActionBoxIx(poolConfig);
// If alreadyWhitelisted is false, include `instruction` in a transaction signed by the master/manager.You can also inspect the current ActionBox state directly:
const actionBoxState = await sdk.fetchActionBoxState();
console.log(actionBoxState.whitelist.map((pk) => pk.toBase58()));Address Conversion
The Skate AMM SDK requires recipient addresses to be in bytes32 format (0x + 64 hex characters) for cross-chain operations. The SDK provides helper functions to convert different address formats:
Converting Solana Addresses
Use base58ToEthBytes32() to convert Solana addresses (base58 format) to bytes32:
import { base58ToEthBytes32 } from "@skate-org/skate_amm_solana";
import { PublicKey } from "@solana/web3.js";
const solanaAddress = new PublicKey("9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM");
const bytes32Address = base58ToEthBytes32(solanaAddress.toBase58());
console.log(bytes32Address); // "0x0000000000000000000000009wzdxwbbmkg8ztbnmquxvqrayrzdsgydlvl9zytawwm"Converting Ethereum Addresses
Use ethToBytes32() to convert Ethereum addresses (hex format) to bytes32:
import { ethToBytes32 } from "@skate-org/skate_amm_solana";
const ethAddress = "0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6";
const bytes32Address = ethToBytes32(ethAddress);
console.log(bytes32Address); // "0x000000000000000000000000742d35cc6634c0532925a3b8d4c9db96c4b4d8b6"
// Also works with addresses without 0x prefix
const ethAddressNoPrefix = "742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6";
const bytes32Address2 = ethToBytes32(ethAddressNoPrefix);
console.log(bytes32Address2); // Same result as aboveUsing in Swap Operations
import { SkateSDK, base58ToEthBytes32, ethToBytes32, ChainId, VmType } from "@skate-org/skate_amm_solana";
// ✅ CORRECT: Same-chain swap (Solana to Solana)
const sameChainSwapIx = await sdk.getSwapIx(
poolConfig,
ChainId.SOLANA, // Same as source chain
base58ToEthBytes32(userPublicKey.toBase58()), // Solana recipient
VmType.SVM,
true,
SwapAmount.normalized(0.01),
"4295128740", // ⚠️ Use appropriate price limit, NOT constants
SwapAmount.native(0.15, 6),
Buffer.from([]),
userPublicKey,
userPublicKey,
deadline
);
// ✅ CORRECT: Cross-chain swap (Solana to Ethereum)
const crossChainSwapIx = await sdk.getSwapIx(
poolConfig,
ChainId.ETHEREUM, // Destination chain
ethToBytes32("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6"), // Ethereum recipient
VmType.EVM,
true,
SwapAmount.normalized(0.01),
"4295128740", // ⚠️ Use appropriate price limit for cross-chain too
SwapAmount.native(0.15, 6),
Buffer.from([]),
userPublicKey,
userPublicKey,
deadline
);Client-Side Operations
As a client/user, you only need to implement the following operations:
- Adding liquidity (mint)
- Increasing liquidity
- Swap tokens
- Burn liquidity
- Decreasing liquidity
- Remove staged assets (after burn or failed mint)
The settlement operations (settle-mint, settle-swap, settle-burn) are handled by the gateway service and don't need to be implemented on the client side. These are usually executed server-side by the pool/gateway operator and not the end-user.
Adding Liquidity (Mint)
To add liquidity to a pool, you need to:
- Create a mint transaction
- Submit the transaction
import {
Keypair,
Transaction,
sendAndConfirmTransaction,
Connection,
ComputeBudgetProgram,
} from "@solana/web3.js";
import { SkateSDK, TokenPairConfig, NORMALIZED_DECIMALS } from "@skate-org/skate_amm_solana";
import { BN } from "@coral-xyz/anchor";
// Step 1: Load private key from env
const secretKeyBase58 = process.env.PRIVATE_KEY!;
const privateKeyUint8 = bs58.decode(secretKeyBase58);
const userKeypair = Keypair.fromSecretKey(privateKeyUint8);
const userPublicKey = userKeypair.publicKey;
const DECIMAL_MULTIPLIER = 10 ** NORMALIZED_DECIMALS; // Use NORMALIZED_DECIMALS for client operations
console.log("Keypair successfully created.");
console.log("Public Key:", userPublicKey.toBase58());
// Step 2: Create mint instructions
const mintIx: TransactionInstruction[] = await sdkProduction.getMintIx(
poolConfig, // Token pair configuration
-100, // tickLower
100, // tickUpper
new BN(0.0001 * DECIMAL_MULTIPLIER), // Amount of token0
new BN(0.0001 * DECIMAL_MULTIPLIER), // Amount of token1
new BN(0), // Min token0 (slippage protection)
new BN(0), // Min token1 (slippage protection)
userPublicKey, // Liquidity provider's public key
userPublicKey, // Payer (can be different from user)
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);
// Step 3: Build transaction with compute budget and priority fee
const mintTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
mintTx.add(priorityFeeIx);
}
// Add compute budget
mintTx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }));
// Add mint instructions
mintTx.add(...mintIx);
// Step 4: Send transaction
await sendAndConfirmTransaction(connection, mintTx, [userKeypair]);
console.log(
`View your position on https://api.amm.skatechain.org/position/nft/${chainId}/${userPublicKey}`
);Increasing Liquidity
To increase liquidity for an existing position:
- Find the NFT "tokenId" field on https://api.amm.skatechain.org/position/nft//<your_address>
- Create an increase liquidity transaction
- Submit the transaction
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import { NORMALIZED_DECIMALS } from "@skate-org/skate_amm_solana";
const DECIMAL_MULTIPLIER = 10 ** NORMALIZED_DECIMALS;
// Step 1: Create increase liquidity instructions
const increaseIx: TransactionInstruction[] = await sdk.getIncreaseLiquidityIx(
poolConfig, // Token pair configuration
"22", // tokenId - The NFT token ID of the position
new BN(0.01 * DECIMAL_MULTIPLIER), // Amount of token0 to add
new BN(0.01 * DECIMAL_MULTIPLIER), // Amount of token1 to add
new BN(0), // Minimum amount of token0 to receive (slippage protection)
new BN(0), // Minimum amount of token1 to receive (slippage protection)
userPublicKey, // Public key of the user adding liquidity
userPublicKey, // Payer (can be different from user)
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);
// Step 2: Build transaction
const increaseTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
increaseTx.add(priorityFeeIx);
}
// Add compute budget
increaseTx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }));
// Add increase liquidity instructions
increaseTx.add(...increaseIx);
// Step 3: Submit transaction
await sendAndConfirmTransaction(connection, increaseTx, [userKeypair]);Increasing Liquidity
To increase liquidity for an existing position:
- Create an increase liquidity transaction
- Submit the transaction
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
import { NORMALIZED_DECIMALS } from "@skate-org/skate_amm_solana";
const DECIMAL_MULTIPLIER = 10 ** NORMALIZED_DECIMALS;
// Step 1: Create increase liquidity transaction
const increaseIx: TransactionInstruction[] = await sdk.getIncreaseLiquidityIx(
poolConfig, // Token pair configuration
"22", // tokenId - The NFT token ID of the position
new BN(0.000013 * DECIMAL_MULTIPLIER), // Amount of token0 to add
new BN(0.000013 * DECIMAL_MULTIPLIER), // Amount of token1 to add
new BN(0), // Minimum amount of token0 to receive (slippage protection)
new BN(0), // Minimum amount of token1 to receive (slippage protection)
userPublicKey, // Public key of the user
userPublicKey, // Payer (can be different from user)
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);
// Step 2: Build transaction
const increaseTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
increaseTx.add(priorityFeeIx);
}
// Add compute budget
increaseTx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }));
// Add increase liquidity instructions
increaseTx.add(...increaseIx);
// Step 3: Submit transaction
await sendAndConfirmTransaction(connection, increaseTx, [userKeypair]);Swapping Tokens
To perform a token swap:
- Create a swap transaction
- Submit the transaction
Important: Please read the Important Notes section at the bottom of this README before integrating swaps in your own code. It contains crucial information on decimal handling, recipient address conversion, and price limit selection.
Same-Chain Swap (Recommended for Testing)
import {
ComputeBudgetProgram,
Transaction,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import { ChainId, VmType, SwapAmount, base58ToEthBytes32 } from "@skate-org/skate_amm_solana";
import { BN } from "@coral-xyz/anchor";
const SOLANA_PRIORITY_FEE_MICRO_LAMPORTS = 100000; // Example priority fee
// ✅ CORRECT: Same-chain swap (Solana to Solana)
const swapIx: TransactionInstruction[] = await sdk.getSwapIx(
poolConfig, // Token pair configuration
ChainId.SOLANA, // destChainId - Same as source chain
base58ToEthBytes32(userKeypair.publicKey.toBase58()), // recipientBytes32 - Solana recipient
VmType.SVM, // destVmType - Solana Virtual Machine type (3)
true, // zeroForOne - Direction: true for token0 to token1, false for token1 to token0
SwapAmount.normalizedWithValidation(0.0001, "swap input amount"), // ⚠️ Uses NORMALIZED_DECIMALS (10^6)
"4295128740", // ⚠️ CRITICAL: Use appropriate price limit, NOT constants (causes 100% slippage)
SwapAmount.nativeWithValidation(0.0001, poolConfig.decimal1, "minimum output amount"), // ⚠️ Uses native token decimals
Buffer.from([]), // extraData - Extra data for the swap (optional)
userKeypair.publicKey, // Public key of the user performing the swap
userKeypair.publicKey, // Payer (can be different from user)
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);Cross-Chain Swap
import { ChainId, VmType, SwapAmount, ethToBytes32, MIN_TICK_SQRT_PRICE } from "@skate-org/skate_amm_solana";
// ✅ CORRECT: Cross-chain swap (Solana to Ethereum)
const crossChainSwapIx: TransactionInstruction[] = await sdk.getSwapIx(
poolConfig, // Token pair configuration
ChainId.ETHEREUM, // destChainId - Destination chain
ethToBytes32("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6"), // recipientBytes32 - Ethereum recipient
VmType.EVM, // destVmType - Ethereum Virtual Machine type
true, // zeroForOne - Direction: true for token0 to token1, false for token1 to token0
SwapAmount.normalizedWithValidation(0.0001, "swap input amount"), // ⚠️ Uses NORMALIZED_DECIMALS (10^6)
"4295128740", // ⚠️ Use appropriate price limit for cross-chain too
SwapAmount.nativeWithValidation(0.0001, poolConfig.decimal1, "minimum output amount"), // ⚠️ Uses native token decimals
Buffer.from([]), // extraData - Extra data for the swap (optional)
userKeypair.publicKey, // Public key of the user performing the swap
userKeypair.publicKey, // Payer (can be different from user)
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);
// Step 2: Build transaction
const swapTx = new Transaction();
// Add compute budget first
swapTx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }));
// Add priority fee only for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
swapTx.add(priorityFeeIx);
}
// Add swap instructions
swapTx.add(...swapIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, swapTx, [userKeypair]);Burning Liquidity
To remove liquidity from a pool:
- Create a burn transaction
- Submit the transaction
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
// Step 1: Create burn instructions
const burnIx = await sdk.getBurnIx(
poolConfig, // Token pair configuration
new BN(0), // amount0Min - Minimum amount of token0 to receive (slippage protection)
new BN(0), // amount1Min - Minimum amount of token1 to receive (slippage protection)
"2", // tokenId - The NFT token ID of the position
userKeypair.publicKey, // Public key of the user burning liquidity
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);
// Step 2: Build transaction
const burnTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
burnTx.add(priorityFeeIx);
}
// Add burn instruction
burnTx.add(burnIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, burnTx, [userKeypair]);Decreasing Liquidity
To decrease liquidity for an existing position:
- Create a decrease liquidity transaction
- Submit the transaction
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
// Step 1: Create decrease liquidity transaction
const decreaseIx = await sdk.getDecreaseLiquidityIx(
poolConfig, // Token pair configuration
"22", // tokenId - The NFT token ID of the position
new BN("300"), // Amount of liquidity to decrease
new BN(0), // Minimum amount of token0 to receive (slippage protection)
new BN(0), // Minimum amount of token1 to receive (slippage protection)
userPublicKey, // Public key of the user
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);
// Step 2: Build transaction
const decreaseTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
decreaseTx.add(priorityFeeIx);
}
// Add compute budget
decreaseTx.add(ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }));
// Add decrease liquidity instruction
decreaseTx.add(decreaseIx);
// Step 3: Submit transaction
await sendAndConfirmTransaction(connection, decreaseTx, [userKeypair]);Removing Staged Assets
After initiating a mint or burn, tokens might be held in a temporary staged state within the contract, associated with your user account. If a mint fails off-chain or after the burn delay passes, you can reclaim these staged assets.
- Create a remove staged assets transaction
- Submit the transaction
import {
Transaction,
sendAndConfirmTransaction,
Keypair,
} from "@solana/web3.js";
// Step 1: Create remove staged assets instructions
const removeStagedIx = await sdk.getRemoveStagedAssetsIx(
poolConfig, // Token pair configuration
gatewayKeypair, // Gateway keypair for signing
userPublicKey // Public key of the user removing assets
);
// Step 2: Send transaction
const removeStagedTx = new Transaction().add(...removeStagedIx);
await sendAndConfirmTransaction(connection, removeStagedTx, [userKeypair]);Gateway Operations
These operations are handled by the gateway service and don't need to be implemented on the client side.
Settling Mint
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
// Step 1: Create settle mint instructions
const settleMintIx = await sdk.getSettleMintIx(
poolConfig, // Token pair configuration
new BN(0.000001 * (10 ** poolConfig.decimal0)), // amount0ToSettle using token0 decimals
new BN(0.000001 * (10 ** poolConfig.decimal1)), // amount1ToSettle using token1 decimals
new BN(0.00001111011 * (10 ** 18)), // amount0ToTransfer using token0 decimals
new BN(0.00001111111 * (10 ** 18)), // amount1ToTransfer using token1 decimals
0, // taskId - Task identifier
gatewayKeypair, // Gateway keypair for signing
userKeypair.publicKey // Public key of the user who initiated the mint
);
// Step 2: Build transaction
const settleMintTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
settleMintTx.add(priorityFeeIx);
}
// Add settle mint instructions
settleMintTx.add(...settleMintIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, settleMintTx, [gatewayKeypair]);Settling Swap
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
// Step 1: Create settle swap instructions
const settleSwapIx = await sdk.getSettleSwapIx(
poolConfig, // Token pair configuration
new BN(0), // kernelAmount0Recipient (18 decimals)
new BN("1000000000000"), // kernelAmount1Recipient: 0.000001 * 10^18
new BN("1000000000000"), // kernelAmount0User: 0.000001 * 10^18
new BN(0), // kernelAmount1User (18 decimals)
new BN(0.00000 * (10 ** poolConfig.decimal0)), // amount0ToSettle using token0 decimals
new BN(0.00000 * (10 ** poolConfig.decimal1)), // amount1ToSettle using token1 decimals
538767, // taskId - Task identifier
recipientKeypair.publicKey, // Recipient public key
gatewayKeypair, // Gateway keypair for signing
null, // Optional additional parameter
true // Boolean flag
);
// Step 2: Build transaction
const settleSwapTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
settleSwapTx.add(priorityFeeIx);
}
// Add settle swap instructions
settleSwapTx.add(...settleSwapIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, settleSwapTx, [gatewayKeypair]);Settling Burn
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
// Step 1: Create settle burn instructions
const settleBurnIx = await sdk.getSettleBurnIx(
poolConfig, // Token pair configuration
new BN(0.0001 * (10 ** poolConfig.decimal0)), // amount0 using token0 decimals
new BN(0.0001 * (10 ** poolConfig.decimal1)), // amount1 using token1 decimals
1, // taskId - Task identifier
gatewayKeypair, // Gateway keypair for signing
userKeypair.publicKey // Public key of the user who initiated the burn
);
// Step 2: Build transaction
const settleBurnTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
settleBurnTx.add(priorityFeeIx);
}
// Add settle burn instructions
settleBurnTx.add(...settleBurnIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, settleBurnTx, [gatewayKeypair]);Settling Staged Amounts
This operation settles only the staged amounts in the UserDataAccount and PeripheryPool without performing any token transfers. Useful for adjusting internal balances after an off-chain event.
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
// Step 1: Create settle instructions
const settleIx = await sdk.getSettleIx(
poolConfig, // Token pair configuration
new BN(0.000001 * (10 ** poolConfig.decimal0)), // amount0ToSettle
new BN(0.000001 * (10 ** poolConfig.decimal1)), // amount1ToSettle
1, // taskId - Task identifier
gatewayKeypair, // Gateway keypair for signing
userKeypair.publicKey // Public key of the user whose amounts are being settled
);
// Step 2: Build transaction
const settleTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
settleTx.add(priorityFeeIx);
}
// Add settle instruction
settleTx.add(settleIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, settleTx, [gatewayKeypair]);Transferring to User
Transfers tokens directly from the pool to a specified user's associated token accounts without affecting staged amounts.
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
import { BN } from "@coral-xyz/anchor";
// Step 1: Create transfer to user instructions
const transferToUserIx = await sdk.getTransferToUserIx(
poolConfig, // Token pair configuration
new BN(0.001 * (10 ** poolConfig.decimal0)), // Amount of token0 to transfer
new BN(0.001 * (10 ** poolConfig.decimal1)), // Amount of token1 to transfer
gatewayKeypair, // Gateway keypair for signing (caller)
userKeypair.publicKey // Public key of the user to receive tokens
);
// Step 2: Build transaction
const transferToUserTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
transferToUserTx.add(priorityFeeIx);
}
// Add transfer instructions
transferToUserTx.add(...transferToUserIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, transferToUserTx, [gatewayKeypair]);Initializing Dust Account
Dust accounts are used to track small amounts of tokens that accumulate during operations. Only managers can initialize dust accounts.
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
// Step 1: Create dust initialization instructions
const initDustIx = await sdk.getInitDustIx(
poolConfig, // Token pair configuration
managerKeypair.publicKey // Manager's public key
);
// Step 2: Build transaction
const initDustTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
initDustTx.add(priorityFeeIx);
}
// Add dust initialization instruction
initDustTx.add(initDustIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, initDustTx, [managerKeypair]);Initializing Task Ledger
The task ledger is used to track and manage tasks across the system. Only managers can initialize task ledgers.
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram, Keypair } from "@solana/web3.js";
// Step 1: Create task ledger initialization instructions
const taskLedgerKeypair = Keypair.generate();
const initTaskLedgerIxs = await sdk.getCreateInitializeTaskLedgerIxs(
managerKeypair.publicKey, // Manager's public key
taskLedgerKeypair // Task ledger keypair
);
// Step 2: Build transaction
const initTaskLedgerTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
initTaskLedgerTx.add(priorityFeeIx);
}
// Add task ledger initialization instructions
initTaskLedgerTx.add(...initTaskLedgerIxs);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, initTaskLedgerTx, [managerKeypair, taskLedgerKeypair]);
console.log(`Task Ledger initialized with public key: ${taskLedgerKeypair.publicKey.toBase58()}`);Updating Pool Configuration
Managers can update various pool configuration parameters.
import { Transaction, sendAndConfirmTransaction, ComputeBudgetProgram } from "@solana/web3.js";
// Step 1: Create pool config update instructions
const updateConfigIx = await sdk.getUpdatePoolConfigIx(
poolConfig, // Token pair configuration
newChainId, // New chain ID
null, // srcVmType (optional)
null, // description (optional)
null, // gateway (optional)
null, // kernelFactory (optional)
"0x21527481C7683d6d1d3604bB99cb06e56ae81b40", // kernelPool (optional)
managerKeypair.publicKey // Manager's public key
);
// Step 2: Build transaction
const updateConfigTx = new Transaction();
// Add priority fee for Solana
if (CHAIN_ID === ChainId.SOLANA) {
const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS,
});
updateConfigTx.add(priorityFeeIx);
}
// Add pool config update instruction
updateConfigTx.add(updateConfigIx);
// Step 3: Send transaction
await sendAndConfirmTransaction(connection, updateConfigTx, [managerKeypair]);
console.log(`Pool configuration updated. Chain ID set to ${newChainId}.`);Important Notes
Runtime Validation & Error Prevention:
The SDK includes comprehensive runtime validation to prevent common errors and save development time:
VM Type Validation:
- Use
VmType.SVM(3) for Solana operations instead of magic numbers - The SDK validates that
destVmTypeis between 0-255 - Clear error messages guide you to use the correct enum values
Recipient Address Validation:
- Recipient addresses must be in bytes32 format (
0x+ 64 hex characters) - Use
base58ToEthBytes32()to convert Solana addresses to the required format - Use
ethToBytes32()to convert Ethereum addresses to the required format - Examples:
base58ToEthBytes32(userKeypair.publicKey.toBase58())for Solana addressesethToBytes32("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6")for Ethereum addresses
Price Limit Validation:
sqrtPriceLimitX96must be betweenMIN_TICK_SQRT_PRICEandMAX_TICK_SQRT_PRICE- ⚠️ CRITICAL WARNING:
MIN_TICK_SQRT_PRICE/MAX_TICK_SQRT_PRICEconstants cause 100% slippage in production! - For all swaps (same-chain and cross-chain): Use appropriate price limits based on current pool price (e.g., "4295128740")
- Constants are strictly for development/testing only - never use in production
- Development/Testing: Constants can be used for testing purposes only (e.g.,
MIN_TICK_SQRT_PRICE,MAX_TICK_SQRT_PRICE) - Why: Constants represent absolute price limits that cause maximum slippage
- Never use: Constants in production as they cause 100% slippage losses
Tick Range Validation (for mint operations):
tickLowermust be less thantickUpper- Both ticks must be integers within valid bounds (-887272 to 887272)
- The SDK validates these constraints before creating instructions
Example Error Messages:
// VM Type Error Error: destVmType must be 0-255 (got 901). Did you pass ChainId instead of VmType? Use VmType.SVM (3) for Solana. // Recipient Error Error: recipient must be in bytes32 format (0x followed by 64 hex characters). Got: "invalid_address". Use base58ToEthBytes32() for Solana addresses or ethToBytes32() for Ethereum addresses. // Price Limit Error Error: sqrtPriceLimitX96 must be between 4295128740 and 1461446703485210103287273052203988822378723970340. Got: "invalid_price". Use MIN_TICK_SQRT_PRICE for minimum price limit or MAX_TICK_SQRT_PRICE for maximum price limit.- Use
Decimal Handling:
⚠️ CRITICAL WARNING: DECIMAL CONFUSION CAN CAUSE 1000x FUND LOSS ⚠️
The Problem: The SDK uses two different decimal systems that can cause 1000x errors:
- Input amounts (
amountSpecifiedNormalized): UseNORMALIZED_DECIMALS(10^6) - Output amounts (
minAmountOutOrMaxAmountInNative): Use native token decimals (10^9 for SOL, 10^6 for USDC)
Real Example of What Can Go Wrong:
// ❌ WRONG: This will try to swap 10 SOL instead of 0.01 SOL! const amountIn = new BN(0.01 * 10 ** 9); // Used SOL's native decimals (10^9) // SDK multiplies by 1000 internally (assumes normalized input) // Result: 0.01 * 10^9 * 1000 = 10,000,000,000 lamports = 10 SOL // ✅ CORRECT: Use helper functions to prevent mistakes import { SwapAmount } from "@skate-org/skate_amm_solana"; const amountIn = SwapAmount.normalized(0.01); // Uses 10^6 const minOut = SwapAmount.native(0.15, 6); // USDC has 6 decimalsSolution: Use the new
SwapAmounthelper class to prevent decimal confusion:The SDK uses two types of decimal multipliers:
NORMALIZED_DECIMALS(10^6) for input amounts in client-side operations (mint, increase-liquidity, swap, burn, decrease-liquidity).- Actual token decimals (
10 ** poolConfig.decimal0or10 ** poolConfig.decimal1) for settlement operations, minimum amount out/in calculations, and direct transfers.
Recommended Approach: Use the new
SwapAmounthelper class to prevent decimal confusion:import { SwapAmount, NORMALIZED_DECIMALS } from "@skate-org/skate_amm_solana"; // ✅ SAFE: Use helper functions const amountIn = SwapAmount.normalized(0.01); // Uses 10^6 for input amounts const minAmountOut = SwapAmount.native(0.15, poolConfig.decimal1); // Uses native decimals for output amounts // ✅ SAFE: With validation (recommended for production) const amountInValidated = SwapAmount.normalizedWithValidation(0.01, "swap input amount"); const minAmountOutValidated = SwapAmount.nativeWithValidation(0.15, poolConfig.decimal1, "minimum output amount"); // ❌ DANGEROUS: Manual calculation (can cause 1000x errors) const DECIMAL_MULTIPLIER = 10 ** NORMALIZED_DECIMALS; // 10^6 const amount = new BN(0.000013 * DECIMAL_MULTIPLIER) // Easy to get wrong!Always ensure you're using the correct multiplier based on the operation type:
- Use
NORMALIZED_DECIMALSfor input amounts inmint,increase-liquidity,swap,burn, anddecrease-liquidityoperations. - Use actual token decimals (
10 ** poolConfig.decimal0/1) for minimum amount out/in inswap, minimum amounts inmint,increase-liquidity,burn,decrease-liquidity, and all settlement operations, includingtransfer-to-user.
- Use
- Input amounts (
Transaction Fees & Compute Units:
- Include compute budget instructions (
ComputeBudgetProgram.setComputeUnitLimit) for operations that require more compute units (like swaps/mints). - Solana Priority Fees: For transactions on the Solana network (
ChainId.SOLANA), it's often necessary to add a priority fee usingComputeBudgetProgram.setComputeUnitPriceto ensure timely processing, especially during periods of network congestion. This is generally not required for other chains like Eclipse.
import { ComputeBudgetProgram } from "@solana/web3.js"; const SOLANA_PRIORITY_FEE_MICRO_LAMPORTS = 100000; // Example priority fee // Create compute budget instruction const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }); // Create priority fee instruction const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS, }); // Build transaction const transaction = new Transaction(); // Add compute budget first transaction.add(computeBudgetIx); // Add priority fee only for Solana if (CHAIN_ID === ChainId.SOLANA) { transaction.add(priorityFeeIx); } // Add your instruction(s) transaction.add(...yourInstructions);- Include compute budget instructions (
Gateway Operations: Certain operations (mint, swap, burn) require a gateway to settle the transaction. Make sure you have access to a gateway service.
Environment Consistency: Ensure the
Environmentused to initialize theSkateSDKmatches the environment level used when accessing theCONFIGobject to retrieve theTokenPairConfigpassed into SDK methods. Mismatches will lead to errors (e.g., trying to interact with a staging pool using the production program ID).Wallet Parameter: The SDK now requires a
Walletparameter during initialization. This is used for signing transactions and must be provided when creating the SDK instance.Manager Operations: Some operations like pool initialization, dust account initialization, task ledger initialization, and pool configuration updates require manager privileges and should only be performed by authorized managers.
Error Handling: The SDK provides comprehensive error handling with
PeripheryErrorCodefor better error recognition and handling. UsegetPeripheryError()to extract meaningful error information from transaction failures.
Error Handling
The SDK provides comprehensive error handling to help you identify and respond to different types of errors.
Using getPeripheryError() for Contract Errors
The getPeripheryError() function extracts meaningful error information from transaction failures:
import { getPeripheryError, PeripheryErrorCode } from "@skate-org/skate_amm_solana";
try {
const swapIx = await sdk.getSwapIx(/* ... */);
const transaction = new Transaction().add(...swapIx);
await sendAndConfirmTransaction(connection, transaction, [keypair]);
} catch (error) {
const peripheryError = getPeripheryError(error);
if (peripheryError) {
console.error(`Contract error: ${peripheryError.code} - ${peripheryError.message}`);
// Handle specific errors
switch (peripheryError.code) {
case PeripheryErrorCode.InsufficientLiquidity:
console.log("Pool has insufficient liquidity - try smaller amount");
break;
case PeripheryErrorCode.SlippageExceeded:
console.log("Slippage exceeded - increase tolerance");
break;
case PeripheryErrorCode.DeadlineExceeded:
console.log("Transaction deadline exceeded - retry");
break;
}
} else {
console.error("Non-contract error:", error);
}
}Common Error Scenarios
// 1. Insufficient Liquidity
if (peripheryError?.code === PeripheryErrorCode.InsufficientLiquidity) {
const smallerAmount = SwapAmount.normalized(0.001); // Reduce amount
}
// 2. Slippage Exceeded
if (peripheryError?.code === PeripheryErrorCode.SlippageExceeded) {
const higherSlippage = SwapAmount.native(0.05, 6); // Increase tolerance
}
// 3. Parameter Validation Errors (thrown before transaction)
try {
const swapIx = await sdk.getSwapIx(/* invalid parameters */);
} catch (error) {
console.error("Parameter error:", error.message);
// Fix: Use base58ToEthBytes32() for Solana addresses or ethToBytes32() for Ethereum addresses, SwapAmount helpers for amounts
}Available Error Codes
import { PeripheryErrorCode } from "@skate-org/skate_amm_solana";
PeripheryErrorCode.InsufficientLiquidity // Pool doesn't have enough liquidity
PeripheryErrorCode.SlippageExceeded // Price moved beyond acceptable range
PeripheryErrorCode.DeadlineExceeded // Transaction took too long
PeripheryErrorCode.InvalidTickRange // Tick range is invalid
PeripheryErrorCode.InvalidAmount // Amount is zero or invalid
PeripheryErrorCode.Unauthorized // User doesn't have permissionExample Usage
The repository includes a complete example (example.ts) that demonstrates various SDK operations.
Keypair Setup
When running example.ts, the script manages three keypairs:
user- For user operationsgateway- For settlement operationsmanager- For pool initialization
The keypairs are stored in a keypairs directory in your project root. If the directory or keypairs don't exist, the script will:
- Create a
keypairsdirectory automatically - Generate new keypairs for each role
- Save them as JSON files (user.json, gateway.json, manager.json)
You can either:
- Let the script generate new keypairs automatically
- Place your own keypair JSON files in the
keypairsdirectory
Running Examples
You can run the example using:
# Ensure USE_STAGING_FLAG in example.ts is set as desired first
ts-node example.ts <command>Available commands:
init-pool- Initialize pool (manager only, uses selected environment)init-tokens- Initialize tokens (manager only, uses selected environment)init-dust- Initialize dust account (manager only, uses selected environment)mint- Mint liquidity (user, uses selected environment)increase-liquidity- Increase liquidity for existing position (user, uses selected environment)decrease-liquidity- Decrease liquidity for existing position (user, uses selected environment)swap- Perform swap directly (user, uses selected environment)serialize-swap- Simulate backend/frontend swap flow (user, uses selected environment)settle-mint- Settle mint (gateway, uses selected environment)settle-swap- Settle swap (gateway, uses selected environment)burn- Burn liquidity (user, uses selected environment)settle-burn- Settle burn (gateway, uses selected environment)settle- Settle staged amounts without transfers (gateway, uses selected environment)update-pool-config- Update pool configuration (manager, uses selected environment)init-task-ledger- Initialize task ledger (manager, uses selected environment)fetch-pool- Fetch and display data for the configured pool in the selected environmentfetch-pool-account- Fetch raw periphery pool account datafetch-user-data- Fetch raw user data account for the specified user/poolfetch-dust- Fetch dust account data for the poollog-all-pools- Logs details for all pools across all configured chains and both environments (staging & production)remove-staged- Remove staged assets after delay (user, uses selected environment)remove-specific-staged- Remove specific amounts of staged assets (gateway, uses selected environment)transfer-to- Transfer tokens from pool to self (gateway, uses selected environment)transfer-to-user- Transfer tokens from pool to a specified user (gateway, uses selected environment)print-config- Print the CONFIG object to a JSON filehelp- Show available commands
Backend/Frontend Swap Simulation (serialize-swap)
The serialize-swap command demonstrates a common web application flow:
Backend Simulation:
- Calls
sdk.getSwapIxto generate the necessary swap instructions. - Serializes these instructions into a JSON-friendly format (e.g., using base58 for keys and base64 for data).
- Logs the serialized data, simulating an API response to the frontend.
- Calls
Frontend Simulation:
- Parses the serialized instructions received from the "backend".
- Deserializes them back into
TransactionInstructionobjects. - Constructs a new
Transaction. - Adds necessary prerequisite instructions (like
ComputeBudgetProgram.setComputeUnitLimit(if required)). - Adds the deserialized swap instructions.
- Crucially, sets the
feePayerfor the transaction (e.g., the connected user's wallet). - Fetches the
recentBlockhash. - Signs and sends the transaction using the designated
feePayer's keypair.
This example highlights how the SDK can be used in a backend to prepare instructions, while the frontend handles transaction finalization, fee payment, and signing.
Quick Reference
Common Import Patterns
// Essential imports for most operations
import {
SkateSDK,
Environment,
ChainId,
TokenPair,
VmType,
SwapAmount,
MIN_TICK_SQRT_PRICE,
MAX_TICK_SQRT_PRICE,
base58ToEthBytes32,
ethToBytes32,
getPeripheryError,
PeripheryErrorCode
} from "@skate-org/skate_amm_solana";Correct Parameter Usage
// ✅ CORRECT: Same-chain swap with appropriate price limit
const sameChainSwapIx = await sdk.getSwapIx(
poolConfig,
ChainId.SOLANA, // Same as source chain
base58ToEthBytes32(userKeypair.publicKey.toBase58()), // ✅ Solana recipient
VmType.SVM, // ✅ Use enum, not magic number
true, // zeroForOne: true for token0→token1
SwapAmount.normalizedWithValidation(0.01, "swap input amount"), // ✅ Safe helper with validation
"4295128740", // ✅ Use appropriate price limit, NOT constants (causes 100% slippage)
SwapAmount.nativeWithValidation(0.15, poolConfig.decimal1, "minimum output amount"), // ✅ Safe helper with validation
Buffer.from([]),
userKeypair.publicKey,
userKeypair.publicKey,
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);
// ✅ CORRECT: Cross-chain swap with appropriate price limit
const crossChainSwapIx = await sdk.getSwapIx(
poolConfig,
ChainId.ETHEREUM, // Destination chain
ethToBytes32("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6"), // ✅ Ethereum recipient
VmType.EVM,
true, // zeroForOne: true for token0→token1
SwapAmount.normalizedWithValidation(0.01, "swap input amount"), // ✅ Safe helper with validation
"4295128740", // ✅ Use appropriate price limit for cross-chain too
SwapAmount.nativeWithValidation(0.15, poolConfig.decimal1, "minimum output amount"), // ✅ Safe helper with validation
Buffer.from([]),
userKeypair.publicKey,
userKeypair.publicKey,
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10)) // ⚠️ Deadline in SECONDS (Unix timestamp)
);Validation Error Examples
The SDK will throw helpful errors if you use incorrect parameters:
// This will throw: "destVmType must be 0-255 (got 901). Did you pass ChainId instead of VmType?"
await sdk.getSwapIx(poolConfig, CHAIN_ID, recipient, CHAIN_ID, ...);
// This will throw: "recipient must be in bytes32 format (0x followed by 64 hex characters)"
await sdk.getSwapIx(poolConfig, CHAIN_ID, "invalid_address", VmType.SVM, ...);
// This will throw: "sqrtPriceLimitX96 must be between 4295128740 and 1461446703485210103287273052203988822378723970340"
await sdk.getSwapIx(poolConfig, CHAIN_ID, recipient, VmType.SVM, true, amountIn, "invalid_price", ...);
// This will throw: "deadline must be in the future. Current time: 1703123456, Deadline: 1703120000"
await sdk.getSwapIx(poolConfig, CHAIN_ID, recipient, VmType.SVM, true, amountIn, MIN_TICK_SQRT_PRICE, minAmountOut, Buffer.from([]), user, payer, new BN(1703120000));
// This will show a warning: "⚠️ WARNING: deadline 1703123456789 seems suspiciously large. Did you pass milliseconds instead of seconds?"
await sdk.getSwapIx(poolConfig, CHAIN_ID, recipient, VmType.SVM, true, amountIn, MIN_TICK_SQRT_PRICE, minAmountOut, Buffer.from([]), user, payer, new BN(1703123456789));New Safety Features
Automatic Decimal Validation:
- The SDK now automatically validates amounts to prevent 1000x errors
- Use
SwapAmount.normalizedWithValidation()andSwapAmount.nativeWithValidation()for automatic warnings - Warnings appear in console when suspiciously large amounts are detected
Clear Parameter Names:
amountSpecifiedNormalized- Makes it clear to use NORMALIZED_DECIMALS (10^6)minAmountOutOrMaxAmountInNative- Makes it clear to use native token decimals- Old parameter names are deprecated but still work for backward compatibility
Sqrt Price Limit Safety:
- Only use
MIN_TICK_SQRT_PRICEorMAX_TICK_SQRT_PRICEconstants - Never use custom calculated values or hardcoded strings
- The SDK validates that only these exact constants are used
Deadline Validation:
- ⚠️ CRITICAL: Deadline must be in seconds (Unix timestamp), NOT milliseconds
- Common mistake: Using
Date.now()directly (returns milliseconds) instead ofMath.floor(Date.now() / 1000)(returns seconds) - Automatic validation prevents past deadlines and suspiciously large values
- Correct format:
new BN(Math.floor(Date.now() / 1000)).add(new BN(60 * 10))for 10 minutes from now - Wrong format:
new BN(Date.now()).add(new BN(60 * 10))(causes deadline to be 1000x too large)
