Skip to main content
Glama
swap.tsβ€’9.9 kB
import { McpResponse, SwapIntent, SwapParams, SwapQuoteRequest, SwapQuoteResponse, SwapPrepareRequest, SwapPrepareResponse, SwapExecuteRequest, SwapExecuteResponse, X402PaymentRequired } from '../types/index.js' import { AuraAdapter } from '../core/aura-adapter.js' import { GuardEngine } from '../core/guard-engine.js' import axios from 'axios' export class SwapTools { constructor( private auraAdapter: AuraAdapter, private guardEngine: GuardEngine ) {} async handleTool(name: string, args: any): Promise<McpResponse | X402PaymentRequired> { switch (name) { case 'swap.parse': return await this.parseIntent(args as SwapIntent) case 'swap.quote': return await this.getQuote(args as SwapQuoteRequest) case 'swap.prepare': return await this.prepare(args as SwapPrepareRequest) case 'swap.execute': return await this.execute(args as SwapExecuteRequest) default: throw new Error(`Unknown swap tool: ${name}`) } } /** * Parse natural language swap intent */ private async parseIntent(intent: SwapIntent): Promise<McpResponse<SwapParams>> { try { const params = await this.auraAdapter.parseSwapIntent(intent) return { success: true, data: params, metadata: { timestamp: new Date().toISOString(), requestId: `parse_${Date.now()}` } } } catch (error) { return { success: false, error: { code: 'PARSE_ERROR', message: error instanceof Error ? error.message : String(error) }, metadata: { timestamp: new Date().toISOString() } } } } /** * Get swap quote from AURA API * AURA automatically selects best DEX across 200+ chains */ private async getQuote(request: SwapQuoteRequest): Promise<McpResponse<SwapQuoteResponse>> { try { // Get quote from AURA API (auto-selects best DEX) const quote = await this.auraAdapter.getSwapQuote(request) // Validate with guard engine const guardResult = this.guardEngine.validateSimulation( { ok: true, est: { feeUsd: quote.estimatedGasUsd, slippagePct: quote.priceImpact, avgPrice: parseFloat(quote.price) }, guardsTriggered: [] }, { fromToken: request.fromToken, toToken: request.toToken, amount: request.amount, chain: request.chain } ) if (!guardResult.passed) { return { success: false, error: { code: 'GUARD_VIOLATION', message: `Quote blocked by guards: ${guardResult.triggeredGuards.join(', ')}`, details: { triggeredGuards: guardResult.triggeredGuards, warnings: guardResult.warnings } }, metadata: { timestamp: new Date().toISOString() } } } return { success: true, data: quote, metadata: { timestamp: new Date().toISOString(), requestId: `quote_${Date.now()}` } } } catch (error) { return { success: false, error: { code: 'QUOTE_ERROR', message: error instanceof Error ? error.message : String(error) }, metadata: { timestamp: new Date().toISOString() } } } } /** * Prepare swap transaction (check allowance + build transaction data) */ private async prepare(request: SwapPrepareRequest): Promise<McpResponse<SwapPrepareResponse>> { try { const { quote, userAddress, slippageTolerance } = request // Build swap transaction with proper calldata from AURA API const txData = await this.auraAdapter.buildSwapTransaction(quote, userAddress, slippageTolerance) const response: SwapPrepareResponse = { needsApproval: txData.needsApproval, approvalTx: txData.approvalTx ? { to: txData.approvalTx.to, data: txData.approvalTx.data, value: txData.approvalTx.value || '0', gasLimit: txData.approvalTx.gasLimit } : undefined, swapTx: { to: txData.to, data: txData.data, value: txData.value, gasLimit: txData.gasLimit }, summary: { fromAmount: quote.fromToken.amount, fromSymbol: quote.fromToken.symbol, toAmount: quote.toToken.amount, toSymbol: quote.toToken.symbol, dex: quote.route.dex, estimatedGasUsd: quote.estimatedGasUsd } } return { success: true, data: response, metadata: { timestamp: new Date().toISOString(), requestId: `prepare_${Date.now()}` } } } catch (error) { return { success: false, error: { code: 'PREPARE_ERROR', message: error instanceof Error ? error.message : String(error) }, metadata: { timestamp: new Date().toISOString() } } } } /** * Execute swap with user-signed transaction * Server only broadcasts the transaction, user signs in browser */ private async execute(request: SwapExecuteRequest): Promise<McpResponse<SwapExecuteResponse> | X402PaymentRequired> { try { // Check if payment is required (x402 gate) if (this.shouldRequirePayment(request)) { if (!request.paymentProof) { return this.generatePaymentRequired(request) } // Verify payment proof const paymentValid = await this.verifyPaymentProof(request.paymentProof) if (!paymentValid) { return { success: false, error: { code: 'INVALID_PAYMENT', message: 'Payment proof verification failed' }, metadata: { timestamp: new Date().toISOString() } } } } // Broadcast user-signed transaction const txHash = await this.broadcastTransaction(request.signedTx, request.chain) // Get explorer URL for the chain const explorerUrl = this.getExplorerUrl(request.chain, txHash) return { success: true, data: { txHash, status: 'pending', explorerUrl }, metadata: { timestamp: new Date().toISOString(), requestId: `execute_${Date.now()}` } } } catch (error) { return { success: false, error: { code: 'EXECUTION_ERROR', message: error instanceof Error ? error.message : String(error) }, metadata: { timestamp: new Date().toISOString() } } } } /** * Check if x402 payment is required for this swap */ private shouldRequirePayment(request: SwapExecuteRequest): boolean { // Payment required for premium features or large swaps // This is configurable based on your business model return true // For now, always require payment } /** * Generate x402 payment required response */ private generatePaymentRequired(request: SwapExecuteRequest): X402PaymentRequired { return { invoiceId: `swap_${Date.now()}`, amount: '10', // 10 USDC per swap asset: 'USDC', receiver: process.env.X402_RECEIVER || '0x0000000000000000000000000000000000000000', description: 'Payment required for swap execution' } } /** * Verify x402 payment proof */ private async verifyPaymentProof(proof: any): Promise<boolean> { try { // Verify on-chain payment // This would check the blockchain to confirm payment was made // For now, basic validation return proof.txHash && proof.amount && proof.asset === 'USDC' } catch { return false } } /** * Broadcast signed transaction to blockchain */ private async broadcastTransaction(signedTx: string, chain: string): Promise<string> { try { // Get RPC endpoint for chain const rpcUrl = this.getRpcUrl(chain) // Broadcast transaction const response = await axios.post(rpcUrl, { jsonrpc: '2.0', id: 1, method: 'eth_sendRawTransaction', params: [signedTx] }) if (response.data.error) { throw new Error(response.data.error.message) } return response.data.result } catch (error) { throw new Error(`Broadcast failed: ${error instanceof Error ? error.message : String(error)}`) } } /** * Get RPC URL for chain */ private getRpcUrl(chain: string): string { const rpcMap: Record<string, string> = { ethereum: process.env.RPC_ETHEREUM || 'https://eth-mainnet.g.alchemy.com/v2/demo', base: process.env.RPC_BASE || 'https://mainnet.base.org', arbitrum: process.env.RPC_ARBITRUM || 'https://arb1.arbitrum.io/rpc', polygon: process.env.RPC_POLYGON || 'https://polygon-rpc.com', optimism: process.env.RPC_OPTIMISM || 'https://mainnet.optimism.io' } return rpcMap[chain.toLowerCase()] || rpcMap.ethereum } /** * Get block explorer URL for transaction */ private getExplorerUrl(chain: string, txHash: string): string { const explorerMap: Record<string, string> = { ethereum: 'https://etherscan.io/tx/', base: 'https://basescan.org/tx/', arbitrum: 'https://arbiscan.io/tx/', polygon: 'https://polygonscan.com/tx/', optimism: 'https://optimistic.etherscan.io/tx/' } const baseUrl = explorerMap[chain.toLowerCase()] || explorerMap.ethereum return `${baseUrl}${txHash}` } }

Latest Blog Posts

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/antidump/MCP'

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