Saros SDK Docs

Welcome to the Saros SDK Documentation

This site provides comprehensive guides, tutorials, and examples to help you start building on the Solana blockchain with the Saros Finance SDKs.

This documentation is designed to be a practical, developer-first resource that cuts through the complexity and accelerates your time to deployment. Whether you're integrating swaps, providing liquidity, or building complex DeFi applications, these guides are structured to get you from zero to shipping as quickly as possible.

This documentation was created for the Saros SDK Guide Challenge on Superteam Earn.

Target SDKs

This guide focuses primarily on the @saros-finance/dlmm-sdk, but the principles can be applied to other Saros SDKs.

  • @saros-finance/dlmm-sdk (Focus): A dedicated TypeScript SDK for the Dynamic Liquidity Market Maker (DLMM).
  • @saros-finance/sdk: The primary TypeScript SDK for the classic AMM, Staking, and Farming.
  • saros-dlmm-sdk-rs: The Rust SDK for interacting with the DLMM from on-chain programs.

Select a topic from the sidebar to get started.

Quick Start: Your First Interaction

This guide will walk you through setting up a project with the Saros DLMM SDK and performing your first read-only interaction with the protocol.

1. Project Setup & Installation

Create a new directory for your project and initialize a package.json file. We'll use TypeScript for this example.

// Using bash
mkdir saros-starter
cd saros-starter
npm init -y
npm install @saros-finance/dlmm-sdk @solana/web3.js @coral-xyz/anchor bs58
npm install -D typescript @types/node ts-node

Next, create a tsconfig.json file to configure TypeScript:

// tsconfig.json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

2. Setting up the Connection

Create a new file named index.ts. In this file, we'll set up our connection to the Solana devnet and create a wallet instance.

Info

For this example, we're generating a new keypair. In a real-world frontend application, you would use a wallet adapter (like @solana/wallet-adapter-react) to get the user's public key and sign transactions.

// index.ts
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { AnchorProvider, Program } from '@coral-xyz/anchor';
import { IDL as SarosDlmmIdl } from '@saros-finance/dlmm-sdk';
import bs58 from 'bs58';

// --- Configuration ---
const RPC_URL = 'https://api.devnet.solana.com';
const PROGRAM_ID = new PublicKey('LBUZKhRxPF3XG2A2qRFF2a8sFkvv4noG2_your_program_id'); // Replace with actual Program ID
const PAIR_ADDRESS = new PublicKey('58oQChx4yWmvKdwLLZzBi4ChoCc2fqbAaGgBG5eGgX2T'); // Example SOL/USDC pair

async function main() {
  console.log("Setting up connection...");
  const connection = new Connection(RPC_URL, 'confirmed');
  
  const secretKey = bs58.decode('your_base58_secret_key_here'); // Replace with a devnet wallet secret key
  const wallet = Keypair.fromSecretKey(secretKey);
  const provider = new AnchorProvider(connection, { publicKey: wallet.publicKey, signTransaction: wallet.signTransaction, signAllTransactions: wallet.signAllTransactions }, { commitment: 'confirmed' });

  console.log("Initializing Saros DLMM program...");
  const program = new Program(SarosDlmmIdl, PROGRAM_ID, provider);

  console.log(`Fetching state for pair: ${PAIR_ADDRESS.toBase58()}`);
  
  try {
    const pairState = await program.account.pair.fetch(PAIR_ADDRESS);
    
    console.log("\n--- Pair State Fetched Successfully! ---");
    console.log(`Token X Mint: ${pairState.tokenMintX.toBase58()}`);
    console.log(`Token Y Mint: ${pairState.tokenMintY.toBase58()}`);
    console.log(`Active Bin ID: ${pairState.activeId.toString()}`);
    console.log(`Bin Step: ${pairState.binStep}`);
    console.log("---------------------------------------\n");
  } catch (error) {
    console.error("Failed to fetch pair state:", error);
  }
}

main().catch(err => {
  console.error(err);
});

3. Running the Script

You'll need to replace the placeholder PROGRAM_ID and your_base58_secret_key_here with current values. Execute your script using ts-node:

// Using bash
npx ts-node index.ts

If successful, you will see the on-chain state of the liquidity pair printed in your console. Congratulations, you've successfully interacted with the Saros DLMM protocol!

Tutorial: Swapping Tokens

This tutorial provides a step-by-step guide to executing a token swap on a Saros DLMM pool using the SDK.

Info

You can find a complete, runnable script for this tutorial in the Runnable Code Examples section.

Core Concepts

  • Pair & Vaults: A DLMM pool is called a Pair. It holds tokens in two separate Vaults (one for Token X, one for Token Y). When you swap, you're sending tokens to one vault and receiving them from the other.
  • Bin Arrays: Liquidity in a DLMM pool is organized into discrete price ranges called bins. These bins are stored on-chain in Bin Arrays. Any swap or liquidity operation needs to reference these arrays.
  • PDAs (Program Derived Addresses): Most accounts you interact with (vaults, bin arrays) are not standard public keys but are derived from the program's ID and certain seeds. You must derive these addresses correctly in your code.

Step-by-Step Swap Process

Step 1: Initialize SDK and Define Constants

First, set up your connection, wallet, and initialize the Anchor program as shown in the Quick Start guide. You will also need the addresses for the pair, the token mints, and your personal Associated Token Accounts (ATAs) for those mints.

// (Assuming connection, wallet, provider, and program are initialized)
const PAIR_ADDRESS = new PublicKey('...');
const TOKEN_X_MINT = new PublicKey('...'); // e.g., USDC
const TOKEN_Y_MINT = new PublicKey('...'); // e.g., SOL
// Your token accounts
const USER_VAULT_X = new PublicKey('...');
const USER_VAULT_Y = new PublicKey('...');

Step 2: Derive Required PDAs

The swap instruction requires several accounts. We need to derive the PDAs for the pair's token vaults and the active bin arrays.

import { PublicKey } from '@solana/web3.js';
import { utils } from '@coral-xyz/anchor';
// Derive pair's token vaults
const [tokenVaultXPda] = PublicKey.findProgramAddressSync(
    [Buffer.from("token_vault"), PAIR_ADDRESS.toBuffer(), TOKEN_X_MINT.toBuffer()],
    program.programId
);
// Derive bin arrays. Most pairs use indices 0 and 1.
const [binArrayLowerPda] = PublicKey.findProgramAddressSync(
    [Buffer.from("bin_array"), PAIR_ADDRESS.toBuffer(), Buffer.from(new Uint8Array(new BN(0).toArray("le", 4)))],
    program.programId
);

Step 3: Construct and Send the Transaction

Now we can build the swap instruction. We'll perform an Exact Input swap, meaning we specify exactly how much we want to send.

import { BN } from '@coral-xyz/anchor';
// Swap 1 USDC (assuming 6 decimals) for SOL
const amountIn = new BN(1_000_000); 
// Set slippage tolerance. 0 means we accept any amount out.
const otherAmountThreshold = new BN(0);
// `true` to swap X for Y, `false` for Y for X
const swapForY = true; 

console.log("Sending swap transaction...");
const txSignature = await program.methods
    .swap(amountIn, otherAmountThreshold, swapForY, { exactInput: {} })
    .accounts({
        pair: PAIR_ADDRESS,
        binArrayLower: binArrayLowerPda,
        binArrayUpper: binArrayUpperPda,
        tokenMintX: TOKEN_X_MINT,
        userVaultY: USER_VAULT_Y,
        user: wallet.publicKey,
    })
    .rpc();

console.log(`Swap successful! Transaction signature: ${txSignature}`);

Tutorial: Providing Liquidity

Adding liquidity to a Saros DLMM pool is a two-step process:

  1. Create a Position: This mints an NFT that represents your ownership and defines the price range (bins) of your liquidity.
  2. Increase Position: This is where you actually deposit your tokens into the range defined by your position NFT.

Step 1: Create a Position

First, we need to create a new mint for our position NFT and then call the createPosition instruction. This instruction sets the lower and upper bin boundaries for our liquidity.

import { createMint } from "@solana/spl-token";
import { TOKEN_2022_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";

// Define the range relative to the active price bin
const relativeBinIdLeft = -10;  // 10 bins to the left
const relativeBinIdRight = 10; // 10 bins to the right

const positionMint = await createMint(
    connection, wallet.payer, wallet.publicKey, null, 0, undefined, undefined, TOKEN_2022_PROGRAM_ID
);
// Derive PDAs
const [positionPda] = PublicKey.findProgramAddressSync([...]);
const [positionTokenAccount] = PublicKey.findProgramAddressSync([...]);

await program.methods
    .createPosition(new BN(relativeBinIdLeft), new BN(relativeBinIdRight))
    .accounts({...})
    .signers([positionMint])
    .rpc();
console.log(`Position NFT created: ${positionMint.toBase58()}`);

Step 2: Increase Position (Deposit Tokens)

With a position created, we can now deposit tokens. The key part is defining the liquidityDistribution. This tells the program how to allocate your tokens across the bins. For this example, we'll use a uniform "Spot" distribution.

const amountX = new BN(1_000_000); // Amount of Token X
const amountY = new BN(1_000_000); // Amount of Token Y

// Create a uniform distribution across our range
const liquidityDistribution = [];
const totalBins = relativeBinIdRight - relativeBinIdLeft + 1;
const distributionPerBin = 10000 / totalBins;

for (let i = relativeBinIdLeft; i <= relativeBinIdRight; i++) {
    if (i < 0) {
        liquidityDistribution.push({ relativeBinId: i, distributionX: distributionPerBin, distributionY: 0 });
    } else if (i > 0) {
        liquidityDistribution.push({ relativeBinId: i, distributionX: 0, distributionY: distributionPerBin });
    } else {
        liquidityDistribution.push({ relativeBinId: i, distributionX: distributionPerBin / 2, distributionY: distributionPerBin / 2 });
    }
}
await program.methods
    .increasePosition(amountX, amountY, liquidityDistribution)
    .accounts({...})
    .rpc();
console.log("Successfully added liquidity!");

Tutorial: Removing Liquidity

This guide explains how to withdraw your tokens from a Saros DLMM liquidity position. You can either partially withdraw or close the position entirely.

Core Concepts

  • Position NFT: Your liquidity is tied to your Position NFT. You need its mint address to identify which position to withdraw from.
  • Shares: When you deposit liquidity, you receive "shares" in each bin. To withdraw, you specify how many shares you want to remove from each bin.

Step 1: Find Your Position and Calculate Shares to Remove

First, you need the mint address of your Position NFT. Let's say we want to remove 50% of our liquidity. We need to fetch the current state of our position to see how many shares we have.

// (Assuming program, positionPda are already defined)
const positionState = await program.account.position.fetch(positionPda);
const percentageToRemove = 5000; // 50% in basis points
const sharesToRemove = positionState.liquidityShares.map(currentShares => {
    return currentShares.mul(new BN(percentageToRemove)).div(new BN(10000));
});

Step 2: Decrease the Position

With the sharesToRemove array calculated, we can call the decreasePosition instruction. This will burn the specified shares and transfer the corresponding tokens back to your wallet.

console.log("Removing 50% of liquidity...");
const txSignature = await program.methods
    .decreasePosition(sharesToRemove)
    .accounts({...})
    .rpc();
console.log(`Successfully removed liquidity! Tx: ${txSignature}`);

Closing a Position (Optional)

If you want to remove 100% of your liquidity, use the closePosition instruction. It withdraws everything and closes the position account.

await program.methods.closePosition().accounts({...}).rpc();

Runnable Code Examples

This section provides complete, self-contained TypeScript scripts for the main SDK functionalities. You can copy, paste, and run these directly after filling in your wallet's secret key and the required public keys.

Warning

These examples are configured for Solana Devnet. You will need a devnet wallet with some SOL to pay for transaction fees. Do not use your mainnet private key.

1. Full Swap Script (swap.ts)

This script executes an "exact input" swap of Token X for Token Y.

import { Connection, Keypair, PublicKey, SystemProgram } from '@solana/web3.js';
import { AnchorProvider, Program, BN, utils } from '@coral-xyz/anchor';
import { IDL as SarosDlmmIdl } from '@saros-finance/dlmm-sdk';
import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';
import bs58 from 'bs58';

// --- CONFIGURATION ---
const RPC_URL = 'https://api.devnet.solana.com';
const PROGRAM_ID = new PublicKey('LBUZKhRxPF3XG2A2qRFF2a8sFkvv4noG2_your_program_id');
const PAIR_ADDRESS = new PublicKey('58oQChx4yWmvKdwLLZzBi4ChoCc2fqbAaGgBG5eGgX2T');
const WALLET_SECRET_KEY = 'YOUR_DEVNET_WALLET_SECRET_KEY_HERE';

async function main() {
    // Initialization...
    const connection = new Connection(RPC_URL, 'confirmed');
    const wallet = Keypair.fromSecretKey(bs58.decode(WALLET_SECRET_KEY));
    const provider = new AnchorProvider(connection, { publicKey: wallet.publicKey, signTransaction: wallet.signTransaction, signAllTransactions: wallet.signAllTransactions }, { commitment: 'confirmed' });
    const program = new Program(SarosDlmmIdl, PROGRAM_ID, provider);
    
    // Fetching state and preparing accounts...
    const pairState = await program.account.pair.fetch(PAIR_ADDRESS);
    const [TOKEN_X_MINT, TOKEN_Y_MINT] = [pairState.tokenMintX, pairState.tokenMintY];
    const [userVaultX, userVaultY] = await Promise.all([
        getOrCreateAssociatedTokenAccount(connection, wallet, TOKEN_X_MINT, wallet.publicKey),
        getOrCreateAssociatedTokenAccount(connection, wallet, TOKEN_Y_MINT, wallet.publicKey)
    ]);
    
    // Deriving PDAs...
    const [tokenVaultXPda] = PublicKey.findProgramAddressSync([...]);
    const [tokenVaultYPda] = PublicKey.findProgramAddressSync([...]);
    const [binArrayLowerPda] = PublicKey.findProgramAddressSync([...]);
    const [binArrayUpperPda] = PublicKey.findProgramAddressSync([...]);

    // Swap Logic
    const amountIn = new BN(10000);
    const txSignature = await program.methods
        .swap(amountIn, new BN(0), true, { exactInput: {} })
        .accounts({...})
        .rpc();
    console.log(`\nSwap successful! View on Solscan: https://solscan.io/tx/${txSignature}?cluster=devnet`);
}
main().catch(console.error);

API Reference

This page provides a high-level reference for the most common instructions in the Saros DLMM SDK.

Program Instructions

swap

Executes a token trade.

  • Parameters: amount (BN), otherAmountThreshold (BN), swapForY (boolean), swapType (object)
  • Key Accounts: pair, binArrayLower, binArrayUpper, tokenVaultX, tokenVaultY, userVaultX, userVaultY

createPosition

Creates a new liquidity position NFT.

  • Parameters: relativeBinIdLeft (BN), relativeBinIdRight (BN)
  • Key Accounts: pair, position (PDA), positionMint (New Keypair), positionTokenAccount, user

increasePosition

Deposits tokens into an existing position.

  • Parameters: amountX (BN), amountY (BN), liquidityDistribution (array)
  • Key Accounts: pair, position, binArray*, tokenVault*, userVault*, positionTokenAccount

decreasePosition

Withdraws a specific amount of liquidity from a position.

  • Parameters: sharesToRemove (BN[])
  • Key Accounts: Same as increasePosition

SDK Analysis (Bonus)

This section provides a constructive analysis of the Saros DLMM SDK from a developer's perspective, highlighting its strengths and identifying potential areas for improvement.

✅ Current Strengths

  1. Direct Protocol Access: The SDK provides a thin wrapper over the on-chain program, giving developers direct and granular control.
  2. Type Safety: As a TypeScript library with an Anchor IDL, the SDK offers excellent type safety, reducing common errors.
  3. Consistency with Anchor: The SDK follows standard Anchor conventions, making it familiar to Solana developers.

💡 Areas for Improvement & Suggestions

The primary opportunity for improvement lies in creating higher-level abstractions to simplify common developer workflows.

Suggestion 1: A `SarosClient` Helper Class

A helper class could encapsulate much of the boilerplate, especially the repetitive PDA derivations.

Proposed Workflow with `SarosClient`:

// Proposed high-level SDK
const sarosClient = new SarosClient(connection, wallet);
const signature = await sarosClient.swap({
  pair: PAIR_ADDRESS,
  inputMint: TOKEN_X_MINT,
  outputMint: TOKEN_Y_MINT,
  amountIn: 1_000_000,
  slippageBps: 50 // 0.5%
});

Suggestion 2: Liquidity Shape Builders

The liquidityDistribution array is powerful but complex. The SDK could provide helper functions to generate common distributions.

import { LiquidityShapes } from '@saros-finance/dlmm-sdk';
const distribution = LiquidityShapes.spot({ bins: 21 });
const curved = LiquidityShapes.curve({ bins: 21, concentration: 0.8 });