1.0.24 • Published 1 month ago

@skate-org/skate_amm_solana v1.0.24

Weekly downloads
-
License
ISC
Repository
-
Last release
1 month ago

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.

Basic Usage

1. Initialize the SDK

Import the necessary components and initialize the SkateSDK with your connection and the desired environment.

import { Connection } from "@solana/web3.js";
import { SkateSDK, Environment } from "@skate-org/skate_amm_solana";

const connection = new Connection(process.env.RPC_URL_902!, "confirmed"); // Use the appropriate RPC URL

// Initialize for STAGING environment
const sdkStaging = new SkateSDK(connection, Environment.STAGING);

// Or initialize for PRODUCTION environment
const sdkProduction = new SkateSDK(connection, Environment.PRODUCTION); 

The Environment enum determines which program ID (PROGRAM_ID_STAGING or PROGRAM_ID_PRODUCTION) the SDK instance will interact with.

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;
const environment = Environment.STAGING;
const tokenPair = TokenPair.WETH_TETH;

const poolConfig: TokenPairConfig = 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

Available token pairs:

  • TokenPair.USDC_WETH
  • TokenPair.WETH_TETH
  • TokenPair.USDT_USDC

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.

async function getPoolDetails(poolConfig: TokenPairConfig) {
  try {
    const poolDetails: PeripheryPoolDetails = await sdk.fetchPeripheryPoolData(poolConfig);
    console.log("Pool Details:", poolDetails);

    // Example: Accessing specific fields
    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);
  }
}

getPoolDetails(poolConfig);

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.

    async function getRawPoolAccount(poolConfig: TokenPairConfig) {
      try {
        const poolAccount: PeripheryPoolData | null = await sdk.fetchPoolAccount(poolConfig);
        if (poolAccount) {
          console.log("Raw Pool Account Data:", poolAccount);
          // Access fields directly (e.g., poolAccount.manager, poolAccount.stagedToken0Amount)
        } else {
          console.log("Pool account not found.");
        }
      } catch (error) {
        console.error("Failed to fetch raw pool account:", error);
      }
    }
    getRawPoolAccount(poolConfig);
  • fetchUserDataAccount(poolConfig, userPublicKey): Fetches the raw UserDataAccount data for a specific user and pool.

    async function getUserData(poolConfig: TokenPairConfig, userKey: PublicKey) {
      try {
        const userData: UserDataAccountData | null = await sdk.fetchUserDataAccount(poolConfig, userKey);
        if (userData) {
          console.log("User Data Account:", userData);
          // Access fields (e.g., userData.user, userData.amount0, userData.withdrawAfter)
        } else {
          console.log("User data account not found (likely not initialized).");
        }
      } catch (error) {
        console.error("Failed to fetch user data account:", error);
      }
    }
    getUserData(poolConfig, userPublicKey); // Replace userPublicKey with the actual key

Client-Side Operations

As a client/user, you only need to implement the following operations: 1. Mint liquidity 2. Swap tokens 3. Burn liquidity 4. 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.

User Operations

Adding Liquidity (Mint)

To add liquidity to a pool, you need to: 1. Create a mint transaction 2. Submit the transaction

// Step 1: Create mint transaction
const mintIx = await sdk.getMintIx(
  poolConfig,      // Token pair configuration (WETH/TETH, etc.)
  -100,            // Lower price tick for the position
  100,             // Upper price tick for the position
  new BN(0.000013 * DECIMAL_MULTIPLIER), // Amount of token0 to add
  new BN(0.000013 * DECIMAL_MULTIPLIER), // Amount of token1 to add
  userPublicKey,   // Public key of the user adding liquidity
  payerPublicKey   // Optional: Public key of the account paying for transaction fees
);

// Step 2: Submit transaction
const mintTx = new Transaction().add(...mintIx);
await sendAndConfirmTransaction(connection, mintTx, [userKeypair]);

Swapping Tokens

To perform a token swap: 1. Create a swap transaction 2. Submit the transaction

import { ComputeBudgetProgram, Transaction } from "@solana/web3.js";
import { ChainId } from "@skate-org/skate_amm_solana"; // Assuming ChainId is imported

// Define your priority fee (example)
const SOLANA_PRIORITY_FEE_MICRO_LAMPORTS = 10000; 

// Step 1: Create swap transaction instructions
const swapIx = await sdk.getSwapIx(
  poolConfig,      // Token pair configuration
  902,             // Destination chain ID (example: Eclipse)
  true,            // Direction: true for token0 to token1, false for token1 to token0
  new BN(0.00001 * DECIMAL_MULTIPLIER), // Amount of input token to swap
  "4295128740",    // Price limit for the swap (sqrtPriceLimitX96)
  new BN(0),       // Minimum amount of output token expected (slippage protection)
  Buffer.from([]), // Extra data for the swap (optional)
  userPublicKey,   // Public key of the user performing the swap
  payerPublicKey   // Optional: Public key of the account paying for transaction fees
);

// Step 2: Construct and submit transaction
const swapTx = new Transaction();

// Add priority fee instruction *only* if on Solana
if (poolConfig.chainId === ChainId.SOLANA) { // Check the chain ID from your config
  const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS 
  });
  swapTx.add(priorityFeeIx);
}

// Add compute budget limit if needed (adjust units as necessary)
const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({ 
  units: 400000    // Required compute units for swap operation
});
swapTx.add(computeBudgetIx);

// Add the core swap instructions
swapTx.add(...swapIx);

// Set fee payer and recent blockhash before sending
swapTx.feePayer = payerPublicKey; // Or userPublicKey if they are the payer
swapTx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;

// Sign and send
await sendAndConfirmTransaction(connection, swapTx, [userKeypair]); // Assuming userKeypair is the fee payer

Burning Liquidity

To remove liquidity from a pool: 1. Create a burn transaction 2. Submit the transaction

// Step 1: Create burn transaction
const burnIx = await sdk.getBurnIx(
  poolConfig,      // Token pair configuration
  -100,            // Lower price tick of the position
  100,             // Upper price tick of the position
  new BN(0.5 * DECIMAL_MULTIPLIER), // Amount of liquidity tokens to burn
  userPublicKey    // Public key of the user burning liquidity
);

// Step 2: Submit transaction
const burnTx = new Transaction().add(burnIx);
await sendAndConfirmTransaction(connection, burnTx, [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
// Step 1: Create remove staged assets transaction
const removeStagedIx = await sdk.getRemoveStagedAssetsIx(
  poolConfig,      // Token pair configuration
  userPublicKey,   // Public key of the user removing assets
  payerPublicKey   // Optional: Public key of the account paying for transaction fees
);

// Step 2: Submit transaction
const removeStagedTx = new Transaction().add(...removeStagedIx);
await sendAndConfirmTransaction(connection, removeStagedTx, [userKeypair]); // Assuming userKeypair is the payer

Gateway Operations

These operations are handled by the gateway service and don't need to be implemented on the client side.

Settling Mint

const settleMintIx = await sdk.getSettleMintIx(
  poolConfig,      // Token pair configuration
  new BN(0.000001 * (10 ** poolConfig.decimal0)), // Amount of token0 to settle
  new BN(0.000001 * (10 ** poolConfig.decimal1)), // Amount of token1 to settle
  new BN(0.000001 * (10 ** poolConfig.decimal0)), // Amount of token0 to transfer
  new BN(0.000001 * (10 ** poolConfig.decimal1)), // Amount of token1 to transfer
  gatewayKeypair,  // Gateway keypair for signing
  userPublicKey    // Public key of the user who initiated the mint
);

Settling Swap

const settleSwapIx = await sdk.getSettleSwapIx(
  poolConfig,      // Token pair configuration
  new BN(0.00001 * (10 ** poolConfig.decimal0)), // Amount of token0 to transfer
  new BN(0.00001 * (10 ** poolConfig.decimal1)), // Amount of token1 to transfer
  new BN(0.00001 * (10 ** poolConfig.decimal0)), // Amount of token0 to settle
  new BN(0.00001 * (10 ** poolConfig.decimal1)), // Amount of token1 to settle
  gatewayKeypair,  // Gateway keypair for signing
  userPublicKey    // Public key of the user who initiated the swap
);

Settling Burn

const settleBurnIx = await sdk.getSettleBurnIx(
  poolConfig,      // Token pair configuration
  new BN(0.000001 * (10 ** poolConfig.decimal0)), // Amount of token0 to return
  new BN(0.000001 * (10 ** poolConfig.decimal1)), // Amount of token1 to return
  gatewayKeypair,  // Gateway keypair for signing
  userPublicKey    // Public key of the user who initiated the burn
);

Important Notes

  1. Decimal Handling:

    • The SDK uses two types of decimal multipliers:
      • NORMALIZED_DECIMALS (10^6) for non-settle transactions due to normalization at contract level
      • Actual token decimals (10 ** poolConfig.decimal0 or 10 ** poolConfig.decimal1) for settlement transactions and minimum amount out calculations
    • Example:

      // For client-side operations (mint, swap, burn)
      const amount = new BN(0.000013 * (10 ** NORMALIZED_DECIMALS)) // Uses 10^6
      
      // For minimum amount out in swaps
      const minAmountOut = new BN(0.08 * (10 ** poolConfig.decimal1)) // Uses actual token decimals
      
      // For gateway operations (settlements)
      const amount = new BN(0.000013 * (10 ** poolConfig.decimal0)) // Uses actual token decimals
    • Always ensure you're using the correct multiplier based on the operation type:

      • Use NORMALIZED_DECIMALS for input amounts in mint, swap, and burn operations
      • Use actual token decimals (10 ** poolConfig.decimal0/1) for minimum amount out in swaps and all settlement operations
  2. 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. Check the example below for conditional inclusion.
        const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
       microLamports: SOLANA_PRIORITY_FEE_MICRO_LAMPORTS
      });
3. **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).

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

```bash
# 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)
  • mint - Mint liquidity (user, uses selected environment)
  • swap - Perform swap directly (user, uses selected environment)
  • serialize-swap - Simulate backend/frontend swap flow (user, 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)
  • fetch-pool - Fetch and display data for the configured pool in the selected environment.
  • log-all-pools - Logs details for all pools across all configured chains and both environments (staging & production).
  • fetch-pool-account - Fetch raw periphery pool account data
  • fetch-user-data - Fetch raw user data account for the specified user/pool
  • remove-staged - Remove staged assets after delay (user, uses selected environment)
  • 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.

1.0.24

1 month ago

1.0.23

1 month ago

1.0.22

2 months ago

1.0.21

2 months ago

1.0.20

2 months ago

1.0.19

2 months ago

1.0.18

2 months ago

1.0.17

2 months ago

1.0.16

3 months ago

1.0.15

3 months ago

1.0.14

3 months ago

1.0.13

3 months ago

1.0.12

3 months ago

1.0.11

3 months ago

1.0.10

3 months ago

1.0.9

3 months ago

1.0.8

3 months ago

1.0.7

3 months ago

1.0.6

3 months ago

1.0.5

4 months ago

1.0.4

4 months ago

1.0.3

4 months ago

1.0.2

4 months ago

1.0.1

4 months ago

1.0.0

4 months ago