Skip to main content
Glama
dex-service.ts7.42 kB
import { Coin } from '@allthatjazzleo/proto-signing'; import { BaseService } from './base-service.js'; import { TransactionResponse } from '../types.js'; export interface SwapParams { tokenIn: Coin; tokenOutDenom: string; slippage?: string; // percentage (e.g. "1" for 1%) memo?: string; } export interface MantraSwap { pool_identifier: string; token_in_denom: string; token_out_denom: string; } export interface SwapOperation { mantra_swap: MantraSwap; } export interface PoolInfo { pool_info: { pool_identifier: string; asset_denoms: string[]; lp_denom: string; asset_decimals: number[]; assets: { denom: string; amount: string; }[]; pool_type: string | { stable_swap: { amp: number } }; pool_fees: { protocol_fee: { share: string }; swap_fee: { share: string }; burn_fee: { share: string }; extra_fees: any[]; }; }; total_share: { denom: string; amount: string; }; } export class DexService extends BaseService { private getDexContractAddress(): string { // Return appropriate contract address based on the network // throw an error if not found if (!this.network.dexContractAddress) { throw new Error('DEX contract address not found for the current network'); } return this.network.dexContractAddress; } /** * Get all pools from the DEX */ async getPools(): Promise<PoolInfo[]> { try { const contractAddress = this.getDexContractAddress(); const response = await this.wasmClient.queryContractSmart(contractAddress, { pools: {} }); return response.pools; } catch (error) { throw new Error(`Failed to get pools: ${error instanceof Error ? error.message : String(error)}`); } } /** * Find swap routes between two tokens */ async findRoutes(tokenInDenom: string, tokenOutDenom: string): Promise<SwapOperation[][]> { try { const pools = await this.getPools(); // Check for direct routes (single pool swaps) const directPools = pools.filter(pool => { const denoms = pool.pool_info.asset_denoms; return denoms.includes(tokenInDenom) && denoms.includes(tokenOutDenom); }); const directRoutes = directPools.map(pool => [{ mantra_swap: { pool_identifier: pool.pool_info.pool_identifier, token_in_denom: tokenInDenom, token_out_denom: tokenOutDenom } }]); if (directRoutes.length > 0) { return directRoutes; } // Find multi-hop routes (2-hop routes only for simplicity) const multiHopRoutes = []; const poolsWithTokenIn = pools.filter(pool => pool.pool_info.asset_denoms.some(denom => denom === tokenInDenom) ); const poolsWithTokenOut = pools.filter(pool => pool.pool_info.asset_denoms.some(denom => denom === tokenOutDenom) ); for (const inPool of poolsWithTokenIn) { const inPoolDenoms = inPool.pool_info.asset_denoms; for (const intermediateToken of inPoolDenoms) { if (intermediateToken === tokenInDenom) continue; const connectedOutPools = poolsWithTokenOut.filter(outPool => outPool.pool_info.asset_denoms.some(denom => denom === intermediateToken) ); for (const outPool of connectedOutPools) { multiHopRoutes.push([ { mantra_swap: { pool_identifier: inPool.pool_info.pool_identifier, token_in_denom: tokenInDenom, token_out_denom: intermediateToken } }, { mantra_swap: { pool_identifier: outPool.pool_info.pool_identifier, token_in_denom: intermediateToken, token_out_denom: tokenOutDenom } } ]); } } } return [...directRoutes, ...multiHopRoutes]; } catch (error) { throw new Error(`Failed to find swap routes: ${error instanceof Error ? error.message : String(error)}`); } } /** * Simulate a swap to get the expected return amount */ async simulateSwap(params: SwapParams): Promise<{ expectedReturn: string; routes: SwapOperation[]; }> { try { const routes = await this.findRoutes(params.tokenIn.denom, params.tokenOutDenom); if (routes.length === 0) { throw new Error(`No route found for swap from ${params.tokenIn.denom} to ${params.tokenOutDenom}`); } // Simulate each route and find the best one let bestRoute = routes[0]; let bestReturnAmount = '0'; for (const route of routes) { const contractAddress = this.getDexContractAddress(); const response = await this.wasmClient.queryContractSmart(contractAddress, { simulate_swap_operations: { operations: route, offer_amount: params.tokenIn.amount } }); if (BigInt(response.return_amount) > BigInt(bestReturnAmount)) { bestReturnAmount = response.return_amount; bestRoute = route; } } return { expectedReturn: bestReturnAmount, routes: bestRoute }; } catch (error) { throw new Error(`Failed to simulate swap: ${error instanceof Error ? error.message : String(error)}`); } } /** * Execute a swap between two tokens */ async swap(params: SwapParams): Promise<TransactionResponse> { try { // First simulate to get the expected return const { expectedReturn, routes } = await this.simulateSwap(params); // Calculate minimum to receive based on slippage const slippagePercent = params.slippage ? parseFloat(params.slippage) : 1.0; const slippageMultiplier = (100 - slippagePercent) / 100; const minToReceive = (BigInt(Math.floor(Number(expectedReturn) * slippageMultiplier))).toString(); // Execute the swap const contractAddress = this.getDexContractAddress(); let executeMsg; if (routes.length === 1) { // Single swap executeMsg = { swap: { ask_asset_denom: params.tokenOutDenom, belief_price: null, max_spread: `${slippagePercent/100}`, pool_identifier: routes[0].mantra_swap.pool_identifier, receiver: null } }; } else { // Multi-hop swap executeMsg = { execute_swap_operations: { max_spread: `${slippagePercent/100}`, minimum_receive: minToReceive, operations: routes, receiver: null } }; } const result = await this.wasmClient.execute( this.address, contractAddress, executeMsg, 'auto', params.memo, [params.tokenIn] ); return { transactionHash: result.transactionHash, explorerUrl: `${this.network.explorerUrl}/tx/${result.transactionHash}`, success: true, gasUsed: result.gasUsed.toString(), gasWanted: result.gasWanted.toString(), }; } catch (error) { throw new Error(`Failed to execute swap: ${error instanceof Error ? error.message : String(error)}`); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/allthatjazzleo/mantrachain-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server