get_swap_quote
Retrieve a token swap quote on SailFish DEX by specifying input and output token addresses, amount, and fee tier for accurate exchange calculations.
Instructions
Get a quote for swapping tokens on SailFish DEX
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| amountIn | Yes | Amount of input token to swap | |
| fee | No | Fee tier (100=0.01%, 500=0.05%, 3000=0.3%, 10000=1%) | |
| tokenIn | Yes | Address of the input token | |
| tokenOut | Yes | Address of the output token |
Input Schema (JSON Schema)
{
"properties": {
"amountIn": {
"description": "Amount of input token to swap",
"type": "string"
},
"fee": {
"description": "Fee tier (100=0.01%, 500=0.05%, 3000=0.3%, 10000=1%)",
"type": "number"
},
"tokenIn": {
"description": "Address of the input token",
"type": "string"
},
"tokenOut": {
"description": "Address of the output token",
"type": "string"
}
},
"required": [
"tokenIn",
"tokenOut",
"amountIn"
],
"type": "object"
}
Implementation Reference
- src/index.ts:492-515 (registration)Registration of the 'get_swap_quote' tool including its schema definition in the ListToolsRequestSchema handler.name: 'get_swap_quote', description: 'Get a quote for swapping tokens on SailFish DEX', inputSchema: { type: 'object', properties: { tokenIn: { type: 'string', description: 'Address of the input token', }, tokenOut: { type: 'string', description: 'Address of the output token', }, amountIn: { type: 'string', description: 'Amount of input token to swap', }, fee: { type: 'number', description: 'Fee tier (100=0.01%, 500=0.05%, 3000=0.3%, 10000=1%)', }, }, required: ['tokenIn', 'tokenOut', 'amountIn'], },
- src/index.ts:1102-1149 (handler)MCP tool handler for 'get_swap_quote' in the CallToolRequestSchema switch statement. Validates input, calls swap.getSwapQuote, formats and returns the quote.case 'get_swap_quote': { if (!args.tokenIn || typeof args.tokenIn !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'Input token address is required'); } if (!args.tokenOut || typeof args.tokenOut !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'Output token address is required'); } if (!args.amountIn || typeof args.amountIn !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'Input amount is required'); } const slippagePercentage = typeof args.slippagePercentage === 'number' ? args.slippagePercentage : 0.5; const quote = await swap.getSwapQuote(args.tokenIn, args.tokenOut, args.amountIn, slippagePercentage); return { content: [ { type: 'text', text: JSON.stringify({ inputToken: { address: args.tokenIn, symbol: quote.tokenInSymbol, decimals: quote.tokenInDecimals, amount: args.amountIn, rawAmount: ethers.parseUnits(args.amountIn, quote.tokenInDecimals).toString() }, outputToken: { address: args.tokenOut, symbol: quote.tokenOutSymbol, decimals: quote.tokenOutDecimals, amount: quote.formattedAmountOut, minimumAmount: quote.formattedMinimumAmountOut, rawAmount: quote.amountOut, rawMinimumAmount: quote.minimumAmountOut }, exchangeRate: (Number(quote.formattedAmountOut) / Number(args.amountIn)).toString(), priceImpact: quote.priceImpact.toFixed(2), routeType: quote.route.type, slippage: slippagePercentage.toString(), note: "Amounts are formatted using the token's decimal places. Raw amounts are in wei units." }, null, 2), }, ], }; }
- src/swap.ts:622-794 (helper)Core implementation of getSwapQuote helper function: finds best route, gets token info, queries QuoterV2 for amountOut (direct/multi-hop), applies slippage, calculates price impact.export async function getSwapQuote( tokenIn: string, tokenOut: string, amountIn: string, slippagePercentage: number = 0.5 ): Promise<{ amountOut: string; formattedAmountOut: string; minimumAmountOut: string; formattedMinimumAmountOut: string; tokenInSymbol: string; tokenOutSymbol: string; tokenInDecimals: number; tokenOutDecimals: number; route: routes.RouteInfo; priceImpact: number; midPrice: string; }> { try { // Find the best route for the token pair const route = await findBestRoute(tokenIn, tokenOut); const provider = blockchain.getProvider(); // Get token details const tokenInContract = new ethers.Contract(tokenIn, ERC20_ABI, provider); const tokenOutContract = new ethers.Contract(tokenOut, ERC20_ABI, provider); const [tokenInDecimals, tokenOutDecimals, tokenInSymbol, tokenOutSymbol] = await Promise.all([ tokenInContract.decimals(), tokenOutContract.decimals(), tokenInContract.symbol(), tokenOutContract.symbol(), ]); // Convert amount to token units const amountInWei = ethers.parseUnits(amountIn, tokenInDecimals); // Get quote based on route type let amountOutWei: bigint; let midPrice: string; if (route.type === 'direct') { // Direct route (single hop) const pool = route.path[0]; const fee = parseInt(pool.feeTier); // Get quote using the QuoterV2 contract const quoterContract = new ethers.Contract(CONTRACTS.QuoterV2, QUOTER_ABI, provider); const quoteParams = { tokenIn, tokenOut, amountIn: amountInWei, fee, sqrtPriceLimitX96: 0 // No price limit }; // Use a static call to get the quote without sending a transaction const quoterInterface = new ethers.Interface(QUOTER_ABI); const calldata = quoterInterface.encodeFunctionData('quoteExactInputSingle', [quoteParams]); const result = await provider.call({ to: CONTRACTS.QuoterV2, data: calldata, }); const decodedResult = quoterInterface.decodeFunctionResult('quoteExactInputSingle', result); amountOutWei = decodedResult[0]; // Get the first return value (amountOut) // Get mid price from the pool if (pool.token0.address.toLowerCase() === tokenIn.toLowerCase()) { midPrice = pool.token1Price || '0'; } else { midPrice = pool.token0Price || '0'; } } else { // Indirect route (multi-hop) // For simplicity, we'll use the direct route approach for each hop and multiply the results // In a production environment, you would use the exactInput function with a path // First hop const pool1 = route.path[0]; const fee1 = parseInt(pool1.feeTier); const intermediaryToken = route.intermediaryToken!; // Second hop const pool2 = route.path[1]; const fee2 = parseInt(pool2.feeTier); // Get quote for first hop const quoterContract = new ethers.Contract(CONTRACTS.QuoterV2, QUOTER_ABI, provider); // First hop quote const quoteParams1 = { tokenIn, tokenOut: intermediaryToken.address, amountIn: amountInWei, fee: fee1, sqrtPriceLimitX96: 0 }; const quoterInterface = new ethers.Interface(QUOTER_ABI); const calldata1 = quoterInterface.encodeFunctionData('quoteExactInputSingle', [quoteParams1]); const result1 = await provider.call({ to: CONTRACTS.QuoterV2, data: calldata1, }); const decodedResult1 = quoterInterface.decodeFunctionResult('quoteExactInputSingle', result1); const intermediateAmountWei = decodedResult1[0]; // Second hop quote const quoteParams2 = { tokenIn: intermediaryToken.address, tokenOut, amountIn: intermediateAmountWei, fee: fee2, sqrtPriceLimitX96: 0 }; const calldata2 = quoterInterface.encodeFunctionData('quoteExactInputSingle', [quoteParams2]); const result2 = await provider.call({ to: CONTRACTS.QuoterV2, data: calldata2, }); const decodedResult2 = quoterInterface.decodeFunctionResult('quoteExactInputSingle', result2); amountOutWei = decodedResult2[0]; // Calculate mid price for multi-hop (approximate) const midPrice1 = pool1.token0.address.toLowerCase() === tokenIn.toLowerCase() ? pool1.token1Price || '0' : pool1.token0Price || '0'; const midPrice2 = pool2.token0.address.toLowerCase() === intermediaryToken.address.toLowerCase() ? pool2.token1Price || '0' : pool2.token0Price || '0'; midPrice = (parseFloat(midPrice1) * parseFloat(midPrice2)).toString(); } // Format amount out const formattedAmountOut = ethers.formatUnits(amountOutWei, tokenOutDecimals); // Calculate minimum amount out with slippage const minimumAmountOut = applySlippage(amountOutWei.toString(), slippagePercentage); const formattedMinimumAmountOut = ethers.formatUnits(minimumAmountOut, tokenOutDecimals); // Calculate price impact const priceImpact = calculatePriceImpact( amountIn, formattedAmountOut, midPrice, tokenInDecimals, tokenOutDecimals ); return { amountOut: amountOutWei.toString(), formattedAmountOut, minimumAmountOut, formattedMinimumAmountOut, tokenInSymbol, tokenOutSymbol, tokenInDecimals, tokenOutDecimals, route, priceImpact, midPrice }; } catch (error) {
- src/swap.ts:560-579 (helper)Helper function findBestRoute that delegates to routes.getBestRoute to find optimal swap path (direct or indirect).export async function findBestRoute( tokenA: string, tokenB: string ): Promise<routes.RouteInfo> { try { // Get the best route from the routes module const bestRoute = await routes.getBestRoute(tokenA, tokenB); if (!bestRoute) { throw new Error(`No route found for token pair ${tokenA}/${tokenB}`); } // Remove console.log to prevent interference with JSON parsing // console.log(`Found ${bestRoute.type} route for ${tokenA}/${tokenB} with fee: ${bestRoute.totalFee * 100}%`); return bestRoute; } catch (error) { console.error('Error finding best route:', error); throw error; }
- src/index.ts:494-515 (schema)Input schema definition for the get_swap_quote tool.inputSchema: { type: 'object', properties: { tokenIn: { type: 'string', description: 'Address of the input token', }, tokenOut: { type: 'string', description: 'Address of the output token', }, amountIn: { type: 'string', description: 'Amount of input token to swap', }, fee: { type: 'number', description: 'Fee tier (100=0.01%, 500=0.05%, 3000=0.3%, 10000=1%)', }, }, required: ['tokenIn', 'tokenOut', 'amountIn'], },