import { LiquidityBookServices, MODE, GetTokenOutputResponse } from '@saros-finance/dlmm-sdk';
import { Connection, PublicKey, Keypair, Transaction } from '@solana/web3.js';
export class OptimalSwapExecutor {
private dlmmService: LiquidityBookServices;
private poolAddress: PublicKey;
constructor(poolAddress: PublicKey, rpcUrl?: string) {
this.poolAddress = poolAddress;
this.dlmmService = new LiquidityBookServices({
mode: MODE.MAINNET, // or MODE.DEVNET
options: {
rpcUrl: rpcUrl || "https://api.mainnet-beta.solana.com"
}
});
}
/**
* Step 1: Get the best available quote
*/
async getSwapQuote({
amount,
swapForY = true,
slippage = 0.5
}: {
amount: number;
swapForY?: boolean;
slippage?: number;
}) {
try {
// First get pool metadata
const metadata = await this.dlmmService.fetchPoolMetadata(this.poolAddress.toString());
// Get quote using correct SDK method
const quoteResult: GetTokenOutputResponse = await this.dlmmService.quote({
amount: amount, // Amount in smallest units (e.g., 1_000_000 for 1 USDC)
metadata,
optional: {
isExactInput: true,
swapForY,
slippage
}
});
return {
amountIn: amount,
amountOut: quoteResult.amountOut,
priceImpact: quoteResult.priceImpact,
fee: quoteResult.fee,
minimumAmountOut: quoteResult.minAmountOut
};
} catch (error) {
throw new Error(`Unable to get quote: ${error}`);
}
}
/**
* Step 2: Execute your swap with protection
*/
async executeSwap({
user,
fromToken,
toToken,
amountIn,
minAmountOut,
swapYtoX = false,
slippage = 0.005 // 0.5% slippage tolerance
}: {
user: Keypair;
fromToken: PublicKey;
toToken: PublicKey;
amountIn: number;
minAmountOut: number;
swapYtoX?: boolean;
slippage?: number;
}) {
try {
// Get fresh quote before execution
const quote = await this.getSwapQuote({
fromToken,
toToken,
amount: amountIn,
swapYtoX
});
// Apply slippage protection - use the stricter limit
const slippageAdjustedMinOut = Math.max(
minAmountOut,
quote.amountOut * (1 - slippage)
);
// Execute the swap across multiple bins atomically
const swapAmount = new BN(amountIn * Math.pow(10, swapYtoX ? 9 : 6));
const minOutAmount = new BN(slippageAdjustedMinOut * Math.pow(10, swapYtoX ? 6 : 9));
const swapInstruction = await this.dlmmPool.swap({
user: user.publicKey,
amountIn: swapAmount,
minAmountOut: minOutAmount,
swapYtoX
});
const transaction = new Transaction().add(swapInstruction);
const signature = await this.connection.sendTransaction(transaction, [user]);
const confirmation = await this.connection.confirmTransaction(signature, 'confirmed');
if (confirmation.value.err) {
throw new Error(`Swap failed: ${confirmation.value.err}`);
}
const swapResult = await this.parseSwapResult(signature);
return {
signature,
amountIn,
amountOut: swapResult.amountOut,
priceImpact: quote.priceImpact,
executionPrice: swapResult.amountOut / amountIn
};
} catch (error) {
throw new Error(`Swap execution failed: ${error}`);
}
}
/**
* Alternative: Buy exact amount (when you need specific output)
*/
async buyExactAmount({
user,
fromToken,
toToken,
exactAmountOut,
maxAmountIn,
swapYtoX = false
}: {
user: Keypair;
fromToken: PublicKey;
toToken: PublicKey;
exactAmountOut: number;
maxAmountIn: number;
swapYtoX?: boolean;
}) {
const exactOutAmount = new BN(exactAmountOut * Math.pow(10, swapYtoX ? 6 : 9));
const reverseQuote = await this.dlmmPool.getQuoteExactOut({
amountOut: exactOutAmount,
swapYtoX
});
const requiredInput = reverseQuote.amountIn.toNumber() / Math.pow(10, swapYtoX ? 9 : 6);
if (requiredInput > maxAmountIn) {
throw new Error(`Need ${requiredInput} but max is ${maxAmountIn}`);
}
const swapInstruction = await this.dlmmPool.swapExactOut({
user: user.publicKey,
amountOut: exactOutAmount,
maxAmountIn: new BN(maxAmountIn * Math.pow(10, swapYtoX ? 9 : 6)),
swapYtoX
});
const transaction = new Transaction().add(swapInstruction);
const signature = await this.connection.sendTransaction(transaction, [user]);
await this.connection.confirmTransaction(signature, 'confirmed');
return {
signature,
amountIn: requiredInput,
amountOut: exactAmountOut
};
}
/**
* Advanced: Route through multiple pools for better rates
*/
async routeThroughMultiplePools({
user,
route,
amountIn,
minAmountOut
}: {
user: Keypair;
route: { pool: PublicKey; swapYtoX: boolean }[];
amountIn: number;
minAmountOut: number;
}) {
let currentAmount = amountIn;
const swapInstructions = [];
// Build instructions for each pool in route
for (let i = 0; i < route.length; i++) {
const hop = route[i];
const hopPool = new DLMM(this.connection, hop.pool);
const hopQuote = await hopPool.getQuote({
amountIn: new BN(currentAmount * Math.pow(10, hop.swapYtoX ? 9 : 6)),
swapYtoX: hop.swapYtoX
});
const minOut = i === route.length - 1 ?
new BN(minAmountOut * Math.pow(10, hop.swapYtoX ? 6 : 9)) :
new BN(0);
const swapInstruction = await hopPool.swap({
user: user.publicKey,
amountIn: new BN(currentAmount * Math.pow(10, hop.swapYtoX ? 9 : 6)),
minAmountOut: minOut,
swapYtoX: hop.swapYtoX
});
swapInstructions.push(swapInstruction);
currentAmount = hopQuote.amountOut.toNumber() / Math.pow(10, hop.swapYtoX ? 6 : 9);
}
// Execute all hops atomically
const transaction = new Transaction().add(...swapInstructions);
const signature = await this.connection.sendTransaction(transaction, [user]);
await this.connection.confirmTransaction(signature, 'confirmed');
return {
signature,
amountIn,
amountOut: currentAmount,
hops: route.length
};
}
/**
* Execute multiple swaps in one transaction
*/
async executeBatchSwaps({
user,
swaps
}: {
user: Keypair;
swaps: {
pool: PublicKey;
amountIn: number;
minAmountOut: number;
swapYtoX: boolean;
}[];
}) {
const swapInstructions = [];
for (const swap of swaps) {
const pool = new DLMM(this.connection, swap.pool);
const instruction = await pool.swap({
user: user.publicKey,
amountIn: new BN(swap.amountIn * Math.pow(10, swap.swapYtoX ? 9 : 6)),
minAmountOut: new BN(swap.minAmountOut * Math.pow(10, swap.swapYtoX ? 6 : 9)),
swapYtoX: swap.swapYtoX
});
swapInstructions.push(instruction);
}
const transaction = new Transaction().add(...swapInstructions);
const signature = await this.connection.sendTransaction(transaction, [user]);
await this.connection.confirmTransaction(signature, 'confirmed');
return { signature, swapsExecuted: swaps.length };
}
// Helper methods
private calculatePriceImpact(amountIn: BN, amountOut: BN, swapYtoX: boolean): number {
// Simplified calculation - in production use DLMM math
const inputValue = amountIn.toNumber();
const outputValue = amountOut.toNumber();
return Math.abs((outputValue - inputValue) / inputValue) * 100;
}
private async parseSwapResult(signature: string) {
// Parse actual transaction results - simplified for example
return { amountOut: 0.95 };
}
}
// Helper methods
private calculatePriceImpact(amountIn: BN, amountOut: BN, swapYtoX: boolean): number {
// Simplified calculation - in production use DLMM math
const inputValue = amountIn.toNumber();
const outputValue = amountOut.toNumber();
return Math.abs((outputValue - inputValue) / inputValue) * 100;
}
private async parseSwapResult(signature: string) {
// Parse actual transaction results - simplified for example
return { amountOut: 0.95 };
}
}
## Execute Your Swap
```typescript
// Set up your swap
const connection = new Connection('https://api.devnet.solana.com');
const poolAddress = new PublicKey('YourPoolAddress');
const user = Keypair.generate(); // Your wallet keypair
const swapExecutor = new OptimalSwapExecutor(connection, poolAddress);
// 1. Get quote for 100 USDC → SOL
const quote = await swapExecutor.getSwapQuote({
fromToken: new PublicKey('USDC_MINT'),
toToken: new PublicKey('SOL_MINT'),
amount: 100,
swapYtoX: false
});
console.log(`Quote: ${quote.amountIn} USDC → ${quote.amountOut.toFixed(4)} SOL`);
console.log(`Price impact: ${quote.priceImpact.toFixed(2)}%`);
console.log(`Bins crossed: ${quote.binsCrossed}`);
// 2. Execute with 1% slippage protection
const result = await swapExecutor.executeSwap({
user,
fromToken: new PublicKey('USDC_MINT'),
toToken: new PublicKey('SOL_MINT'),
amountIn: 100,
minAmountOut: quote.amountOut * 0.99, // 1% slippage tolerance
swapYtoX: false
});
console.log(`✅ Swap complete: ${result.signature}`);
console.log(`Got ${result.amountOut} SOL (${result.priceImpact.toFixed(2)}% impact)`);