@skate-org/skate_amm_solana v1.0.24
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
- Setup
- Basic Usage
- Client-Side Operations
- User Operations
- Gateway Operations
- Important Notes
- Example Usage
Installation
npm install @skate-org/skate_amm_solana
Setup
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
Environment: The SDK distinguishes between
staging
andproduction
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
(likemanager
,token0
,token1
,fee
,description
,stagedToken0Amount
,stagedToken1Amount
, etc.) poolToken0Balance
: ATokenAmount
object representing the balance of the pool's token 0 account.poolToken1Balance
: ATokenAmount
object representing the balance of the pool's token 1 account.poolToken0Pda
: ThePublicKey
of the pool's token 0 account PDA.poolToken1Pda
: ThePublicKey
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 rawPeripheryPool
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 rawUserDataAccount
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.
- Create a remove staged assets transaction
- 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
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
or10 ** 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
- Use
- The SDK uses two types of decimal multipliers:
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.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 });
- Include compute budget instructions (
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 datafetch-user-data
- Fetch raw user data account for the specified user/poolremove-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:
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.
- Calls
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 month ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago