npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

Installation

npm install @skate-org/skate_amm_solana

Setup

  1. 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 .env file:

    # 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_here
  2. Environment: The SDK distinguishes between staging and production environments, which primarily affects which program ID is used and how configuration data is structured. You'll select the environment when initializing the SDK.

  3. 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 numbers
    • MIN_TICK_SQRT_PRICE and MAX_TICK_SQRT_PRICE - Use these for price limits in swaps
    • base58ToEthBytes32() - Use this function to convert Solana addresses to bytes32 format for recipient parameters
    • ethToBytes32() - 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.STAGING
  • Environment.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 (like manager, token0, token1, fee, description, stagedToken0Amount, stagedToken1Amount, etc.)
  • poolToken0Balance: A TokenAmount object representing the balance of the pool's token 0 account.
  • poolToken1Balance: A TokenAmount object representing the balance of the pool's token 1 account.
  • poolToken0Pda: The PublicKey of the pool's token 0 account PDA.
  • poolToken1Pda: The PublicKey of 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 raw PeripheryPool account data.
  • fetchUserDataAccount(poolConfig, userPublicKey): Fetches the raw UserDataAccount data 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 above

Using 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:

  1. Adding liquidity (mint)
  2. Increasing liquidity
  3. Swap tokens
  4. Burn liquidity
  5. Decreasing liquidity
  6. 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:

  1. Create a mint transaction
  2. 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:

  1. Find the NFT "tokenId" field on https://api.amm.skatechain.org/position/nft//<your_address>
  2. Create an increase liquidity transaction
  3. 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:

  1. Create an increase liquidity transaction
  2. 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:

  1. Create a swap transaction
  2. 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:

  1. Create a burn transaction
  2. 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:

  1. Create a decrease liquidity transaction
  2. 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.

  1. Create a remove staged assets transaction
  2. 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

  1. 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 destVmType is 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 addresses
      • ethToBytes32("0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6") for Ethereum addresses

    Price Limit Validation:

    • sqrtPriceLimitX96 must be between MIN_TICK_SQRT_PRICE and MAX_TICK_SQRT_PRICE
    • ⚠️ CRITICAL WARNING: MIN_TICK_SQRT_PRICE/MAX_TICK_SQRT_PRICE constants 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):

    • tickLower must be less than tickUpper
    • 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.
  2. 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): Use NORMALIZED_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 decimals

    Solution: Use the new SwapAmount helper 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.decimal0 or 10 ** poolConfig.decimal1) for settlement operations, minimum amount out/in calculations, and direct transfers.
    • Recommended Approach: Use the new SwapAmount helper 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_DECIMALS for input amounts in mint, increase-liquidity, swap, burn, and decrease-liquidity operations.
      • Use actual token decimals (10 ** poolConfig.decimal0/1) for minimum amount out/in in swap, minimum amounts in mint, increase-liquidity, burn, decrease-liquidity, and all settlement operations, including transfer-to-user.
  3. 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 using ComputeBudgetProgram.setComputeUnitPrice to 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);
  4. Gateway Operations: Certain operations (mint, swap, burn) require a gateway to settle the transaction. Make sure you have access to a gateway service.

  5. Environment Consistency: Ensure the Environment used to initialize the SkateSDK matches the environment level used when accessing the CONFIG object to retrieve the TokenPairConfig passed into SDK methods. Mismatches will lead to errors (e.g., trying to interact with a staging pool using the production program ID).

  6. Wallet Parameter: The SDK now requires a Wallet parameter during initialization. This is used for signing transactions and must be provided when creating the SDK instance.

  7. 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.

  8. Error Handling: The SDK provides comprehensive error handling with PeripheryErrorCode for better error recognition and handling. Use getPeripheryError() 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 permission

Example 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 operations
  • gateway - For settlement operations
  • manager - 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:

  1. Create a keypairs directory automatically
  2. Generate new keypairs for each role
  3. 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 keypairs directory

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 environment
  • fetch-pool-account - Fetch raw periphery pool account data
  • fetch-user-data - Fetch raw user data account for the specified user/pool
  • fetch-dust - Fetch dust account data for the pool
  • log-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 file
  • help - Show available commands

Backend/Frontend Swap Simulation (serialize-swap)

The serialize-swap command demonstrates a common web application flow:

  1. Backend Simulation:

    • Calls sdk.getSwapIx to 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.
  2. Frontend Simulation:

    • Parses the serialized instructions received from the "backend".
    • Deserializes them back into TransactionInstruction objects.
    • Constructs a new Transaction.
    • Adds necessary prerequisite instructions (like ComputeBudgetProgram.setComputeUnitLimit (if required)).
    • Adds the deserialized swap instructions.
    • Crucially, sets the feePayer for 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() and SwapAmount.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_PRICE or MAX_TICK_SQRT_PRICE constants
  • 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 of Math.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)