@tamago-labs/asetta-mcp
Version:
MCP libraries for AI agents on the Asetta.xyz platform, enabling RWA project creation and multi-chain token issuance (currently Avalaunch only)
1 lines • 1.28 MB
Source Map (JSON)
{"version":3,"sources":["../src/config.ts","../src/index.ts","../src/agent/wallet.ts","../src/agent/api.ts","../src/mcp/wallet/get_wallet_info_tool.ts","../src/mcp/wallet/get_account_balances_tool.ts","../src/mcp/wallet/send_eth_tool.ts","../src/mcp/wallet/send_token_tool.ts","../src/mcp/wallet/approve_token_tool.ts","../src/mcp/wallet/check_allowance_tool.ts","../src/mcp/wallet/get_token_info_tool.ts","../src/mcp/wallet/get_transaction_history_tool.ts","../src/mcp/wallet/create_rwa_token_tool.ts","../src/mcp/wallet/get_rwa_project_tool.ts","../src/mcp/wallet/mint_usdc_tool.ts","../src/mcp/wallet/get_usdc_balance_tool.ts","../src/mcp/wallet/configure_ccip_tool.ts","../src/mcp/wallet/mark_ccip_configured_tool.ts","../src/contracts/abis/RWAManager.json","../src/mcp/wallet/register_primary_sales_tool.ts","../src/mcp/wallet/activate_primary_sales_tool.ts","../src/mcp/api/get_profile_tool.ts","../src/mcp/api/create_rwa_project_tool.ts","../src/mcp/api/get_rwa_projects_tool.ts","../src/mcp/api/update_project_status_tool.ts","../src/mcp/ccip/deploy_ccip_pool_tool.ts","../src/contracts/constants/chainlink-networks.ts","../src/contracts/abis/BurnMintTokenPool.json","../src/mcp/ccip/configure_ccip_roles_tool.ts","../src/contracts/abis/RegistryModuleOwnerCustom.json","../src/contracts/abis/TokenAdminRegistry.json","../src/mcp/ccip/connect_ccip_chains_tool.ts","../src/mcp/ccip/validate_ccip_setup_tool.ts","../src/mcp/rwa/mint_rwa_token_tool.ts","../src/contracts/abis/RWAToken.json","../src/mcp/rwa/transfer_rwa_cross_chain_tool.ts","../src/mcp/rwa/get_cross_chain_fee_tool.ts","../src/mcp/rwa/approve_router_tool.ts","../src/mcp/rwa/get_chain_selectors_tool.ts","../src/mcp/index.ts"],"sourcesContent":["import { Chain, createPublicClient, createWalletClient, http, WalletClient } from 'viem';\nimport { privateKeyToAccount, Address, Account, generatePrivateKey } from 'viem/accounts';\nimport { avalancheFuji, sepolia, arbitrumSepolia } from 'viem/chains'\n\ntype NetworkType = 'avalancheFuji' | 'ethereumSepolia' | 'arbitrumSepolia'\n\ntype AgentMode = 'legal' | 'tokenization';\n\ninterface NetworkConfig {\n rpcProviderUrl: string;\n blockExplorer: string;\n chain: Chain;\n chainId: number;\n nativeCurrency: string;\n}\n\nconst getArgs = () =>\n process.argv.reduce((args: any, arg: any) => {\n // long arg\n if (arg.slice(0, 2) === \"--\") {\n const longArg = arg.split(\"=\");\n const longArgFlag = longArg[0].slice(2);\n const longArgValue = longArg.length > 1 ? longArg[1] : true;\n args[longArgFlag] = longArgValue;\n }\n // flags\n else if (arg[0] === \"-\") {\n const flags = arg.slice(1).split(\"\");\n flags.forEach((flag: any) => {\n args[flag] = true;\n });\n }\n return args;\n }, {});\n\n\n// Contract addresses for different networks\nconst CONTRACT_ADDRESSES = {\n avalancheFuji: {\n mockUSDC: \"0x5067e9a9154A2EA674DEf639de5e98F238824039\",\n rwaManager: \"0x6ee904a0Ff97b5682E80660Bf2Aca280D18aB5F3\",\n tokenFactory: \"0xEc5003E8451EC488ea1e1a7142A38e77a5082fCf\",\n primaryDistribution: \"0x9304F30b1AEfeCB43F86fd5841C6ea75BD0F2529\",\n rfq: \"0x307992307C89216b1079C7c5Cbc4F51005b1472D\"\n },\n ethereumSepolia: {\n mockUSDC: \"0xf2260B00250c772CB64606dBb88d9544F709308C\",\n rwaManager: \"0x9682DaBf26831523B21759A50b0a45832f82DBa3\",\n tokenFactory: \"0x6fdB032668F1F856fbC2e9F5Df348938aFBFBE17\",\n primaryDistribution: \"0xf309011fbf013C352849Cd4b5C85E71cC69a1EBF\",\n rfq: \"0x42209A0A2a3D80Ad48B7D25fC6a61ad355901484\"\n },\n arbitrumSepolia: {\n mockUSDC: \"0x16EE94e3C07B24EbA6067eb9394BA70178aAc4c0\",\n rwaManager: \"0x4fd5Ae48A869c5ec0214CB050D2D713433515D8d\",\n tokenFactory: \"0xe5209A4f622C6eD2C158dcCcdDB69B05f9D0E4E0\",\n primaryDistribution: \"0xA657b300009802Be7c88617128545534aCA12dbe\",\n rfq: \"0x61ad3Fe6B44Bfbbcec39c9FaD566538c894b6471\"\n }\n} as const;\n\n// Network configurations\nconst networkConfigs: Record<NetworkType, NetworkConfig> = {\n avalancheFuji: {\n rpcProviderUrl: 'https://avalanche-fuji.drpc.org',\n blockExplorer: 'https://testnet.snowtrace.io',\n chain: avalancheFuji,\n chainId: 43113,\n nativeCurrency: 'AVAX'\n },\n ethereumSepolia: {\n rpcProviderUrl: 'https://sepolia.drpc.org',\n blockExplorer: 'https://sepolia.etherscan.io',\n chain: sepolia,\n chainId: 11155111,\n nativeCurrency: 'ETH'\n },\n arbitrumSepolia: {\n rpcProviderUrl: 'https://arbitrum-sepolia.drpc.org',\n blockExplorer: 'https://sepolia-explorer.arbitrum.io',\n chain: arbitrumSepolia,\n chainId: 421614,\n nativeCurrency: 'ETH'\n }\n} as const;\n\nconst getNetwork = (): NetworkType => {\n const args = getArgs();\n const network = args.network as NetworkType;\n\n if (network && !(network in networkConfigs)) {\n throw new Error(`Invalid network: ${network}. Must be one of: ${Object.keys(networkConfigs).join(', ')}`);\n }\n return network || 'avalancheFuji';\n};\n\nconst getAccount = (): Account => {\n\n const args = getArgs();\n const hasPrivateKey = !!(args?.wallet_private_key);\n\n if (!hasPrivateKey) {\n const privateKey = generatePrivateKey();\n return privateKeyToAccount(privateKey);\n } else {\n return privateKeyToAccount(`0x${(args?.wallet_private_key)}` as Address);\n }\n}\n\n\n// Initialize client configuration\nexport const network = getNetwork();\n\nexport const networkInfo = {\n ...networkConfigs[network],\n rpcProviderUrl: networkConfigs[network].rpcProviderUrl,\n};\n\nexport const account: Account = getAccount()\n\nconst getMode = (): any => {\n const args = getArgs();\n return args.agent_mode\n}\n\nconst getAccessKey = (): string | undefined => {\n const args = getArgs();\n return args.access_key;\n}\n\nexport const agentMode: any = getMode()\nexport const accessKey: string | undefined = getAccessKey()\n\nconst baseConfig = {\n chain: networkInfo.chain,\n transport: http(networkInfo.rpcProviderUrl),\n} as const;\n\nexport const publicClient = createPublicClient(baseConfig);\n\nexport const walletClient = createWalletClient({\n ...baseConfig,\n account,\n}) as WalletClient;\n\n// Multi-chain client factory\nexport function createClientForNetwork(networkType: NetworkType) {\n const config = networkConfigs[networkType];\n const baseConfig = {\n chain: config.chain,\n transport: http(config.rpcProviderUrl),\n };\n\n return {\n publicClient: createPublicClient(baseConfig),\n walletClient: createWalletClient({\n ...baseConfig,\n account,\n }) as WalletClient,\n networkInfo: config\n };\n}\n\n// Get contract addresses for a network\nexport function getContractAddresses(networkType: NetworkType) {\n return CONTRACT_ADDRESSES[networkType];\n}\n\nexport function validateEnvironment(): void {\n try {\n\n const args = getArgs();\n const hasAgentMode = !!(args?.agent_mode)\n\n if (!hasAgentMode) {\n console.error(`AGENT_MODE is not set, default to non-wallet mode`);\n } else {\n console.error(`✅ Asetta mode: ${args.agent_mode}`);\n }\n\n if (args.agent_mode === \"tokenization\") {\n getNetwork()\n console.error(`✅ Asetta MCP environment configuration valid (${network})`);\n console.error(`📍 RPC URL: ${networkInfo.rpcProviderUrl}`);\n console.error(`📍 Chain ID: ${networkInfo.chainId}`);\n console.error(`📍 Native Currency: ${networkInfo.nativeCurrency}`);\n getAccount()\n console.error(`📍 Account: ${account.address}`);\n }\n \n if (args.access_key) {\n console.error(`📍 Access Key: ${args.access_key}`);\n }\n\n } catch (error) {\n console.error('❌ Invalid environment configuration:', error);\n throw error;\n }\n}\n\n// Export network configs for external use\nexport { networkConfigs, CONTRACT_ADDRESSES, type NetworkType };\n","#!/usr/bin/env node\n\nimport { validateEnvironment } from \"./config\"\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { WalletAgent } from \"./agent/wallet\"\nimport { ApiAgent } from \"./agent/api\"\nimport { AsettaWalletTools, AsettaApiTools } from \"./mcp\"\nimport { agentMode } from \"./config\"\n\n/**\n * Creates an MCP server for RWA project creation on Asetta.xyz\n */\nfunction createMcpServer(agent: WalletAgent | ApiAgent) {\n\n // Create MCP server instance\n const server = new McpServer({\n name: \"asetta-mcp\",\n version: \"0.1.0\"\n });\n\n const finalTools = agentMode === \"tokenization\" ? AsettaWalletTools : AsettaApiTools;\n\n // Register all tools\n for (const [_key, tool] of Object.entries(finalTools)) {\n server.tool(tool.name, tool.description, tool.schema, async (params: any): Promise<any> => {\n try {\n // Execute the handler with the params directly\n const result = await tool.handler(agent, params);\n\n // Format the result as MCP tool response\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n } catch (error) {\n console.error(\"Tool execution error:\", error);\n // Handle errors in MCP format\n return {\n isError: true,\n content: [\n {\n type: \"text\",\n text: error instanceof Error\n ? error.message\n : \"Unknown error occurred\",\n },\n ],\n };\n }\n });\n }\n\n return server;\n}\n\nasync function main() {\n try {\n console.error(\"🎨 Starting Asetta MCP Server...\");\n\n // Validate environment before proceeding\n validateEnvironment();\n\n // Create Asetta agent \n const asettaAgent = agentMode === \"tokenization\" ? new WalletAgent() : new ApiAgent()\n\n // Create and start MCP server\n const server = createMcpServer(asettaAgent);\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n console.error(\"✅ Asetta MCP Server is running!\");\n\n } catch (error) {\n console.error('❌ Error starting Asetta MCP server:', error);\n process.exit(1);\n }\n}\n\nmain();","import { publicClient, walletClient, account, network, networkInfo, agentMode, createClientForNetwork, type NetworkType } from '../config';\n\n\nexport class WalletAgent {\n\n public account: typeof account;\n public walletClient: typeof walletClient;\n public publicClient: typeof publicClient;\n public network: NetworkType;\n public networkInfo: typeof networkInfo;\n\n constructor(networkType?: NetworkType) {\n if (networkType && networkType !== network) {\n // Use different network\n const clients = createClientForNetwork(networkType);\n this.publicClient = clients.publicClient;\n this.walletClient = clients.walletClient;\n this.networkInfo = clients.networkInfo;\n this.network = networkType; // Fix: Actually set the network property\n } else {\n // Use default configured clients\n this.walletClient = walletClient;\n this.publicClient = publicClient;\n this.network = network;\n this.networkInfo = networkInfo;\n }\n\n this.account = account;\n\n console.error(`🎨 Asetta Agent initialized on ${this.network}`);\n console.error(`📍 Wallet address: ${this.account.address}`);\n\n }\n\n async connect(): Promise<void> {\n try {\n // Test connection by getting chain ID\n const chainId = await this.publicClient.getChainId();\n console.error(`✅ Connected to the network (Chain ID: ${chainId})`);\n console.error(`🌐 Network: ${this.network}`);\n console.error(`🔗 RPC: ${this.networkInfo.rpcProviderUrl}`);\n } catch (error) {\n console.error('❌ Failed to connect to network:', error);\n throw error;\n }\n }\n\n async disconnect(): Promise<void> {\n console.error('🔌 Disconnected from the network');\n }\n\n async getWalletInfo(): Promise<any> {\n try {\n const balance = await this.publicClient.getBalance({\n address: this.account.address\n });\n\n return {\n address: this.account.address,\n balance: balance.toString(),\n network: this.network,\n chainId: await this.publicClient.getChainId(),\n blockExplorer: this.networkInfo.blockExplorer,\n nativeCurrency: this.networkInfo.nativeCurrency\n };\n } catch (error) {\n console.error('Failed to get wallet info:', error);\n throw error;\n }\n }\n\n}\n","import { publicClient, walletClient, account, network, networkInfo, agentMode } from '../config';\n\nexport class ApiAgent {\n\n constructor() {\n // Use the configured clients from config \n \n console.error(`✅ Asetta Agent runs on Non-Wallet Mode`); \n\n }\n\n}","import { z } from \"zod\";\nimport { WalletAgent } from \"../../agent/wallet\";\nimport { type McpTool } from \"../../types\";\nimport { type NetworkType } from \"../../config\";\n\nexport const GetWalletInfoTool: McpTool = {\n name: \"asetta_get_wallet_info\",\n description: \"Get wallet address and basic account information\",\n schema: {\n network: z.enum(['avalancheFuji', 'ethereumSepolia', 'arbitrumSepolia'])\n .optional()\n .describe(\"Network to check (optional, defaults to configured network)\")\n },\n handler: async (agent: WalletAgent, input: Record<string, any>) => {\n try {\n const networkType = input.network as NetworkType;\n const walletAgent = networkType ? new WalletAgent(networkType) : agent;\n \n await walletAgent.connect();\n \n const balance = await walletAgent.publicClient.getBalance({\n address: walletAgent.account.address\n });\n\n const balanceInNative = Number(balance) / 1e18;\n const nativeCurrency = walletAgent.networkInfo.nativeCurrency;\n\n return {\n status: \"success\",\n message: \"✅ Wallet information retrieved successfully\",\n wallet_details: {\n address: walletAgent.account.address,\n network: walletAgent.network,\n balance: `${balanceInNative.toFixed(6)} ${nativeCurrency}`,\n balance_in_wei: balance.toString(),\n chain_id: await walletAgent.publicClient.getChainId(),\n block_explorer: walletAgent.networkInfo.blockExplorer,\n native_currency: nativeCurrency\n },\n account_status: {\n activated: true,\n minimum_balance_required: `0.01 ${nativeCurrency}`,\n can_register_rwa: balanceInNative >= 0.01,\n ready_for_operations: balanceInNative >= 0.001\n },\n recommendations: balanceInNative < 0.01\n ? [\n `⚠️ Low ${nativeCurrency} balance detected`,\n `Fund wallet with at least 0.01 ${nativeCurrency} for RWA registration`,\n \"Gas fees required for all Asetta Protocol operations\",\n `Current balance: ${balanceInNative.toFixed(6)} ${nativeCurrency}`\n ]\n : [\n \"✅ Wallet has sufficient balance for operations\",\n \"Ready to register RWA assets\",\n \"Ready to create vaults and issue tokens\"\n ]\n };\n } catch (error: any) {\n throw new Error(`Failed to get wallet info: ${error.message}`);\n } finally {\n await agent.disconnect();\n }\n }\n};\n","import { z } from \"zod\";\nimport { WalletAgent } from \"../../agent/wallet\";\nimport { type McpTool } from \"../../types\";\nimport { formatEther, Address } from \"viem\";\nimport { type NetworkType, getContractAddresses } from \"../../config\";\n\nexport const GetAccountBalancesTool: McpTool = {\n name: \"asetta_get_account_balances\",\n description: \"Get all token balances including native tokens and USDC\",\n schema: {\n account_address: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .optional()\n .describe(\"Ethereum address to check (optional, defaults to wallet address)\"),\n network: z.enum(['avalancheFuji', 'ethereumSepolia', 'arbitrumSepolia'])\n .optional()\n .describe(\"Network to check (optional, defaults to configured network)\")\n },\n handler: async (agent: WalletAgent, input: Record<string, any>) => {\n try {\n const networkType = input.network as NetworkType;\n const walletAgent = networkType ? new WalletAgent(networkType) : agent;\n \n await walletAgent.connect();\n\n const targetAddress = (input.account_address || walletAgent.account.address) as Address;\n const nativeCurrency = walletAgent.networkInfo.nativeCurrency;\n const contracts = getContractAddresses(walletAgent.network);\n\n // Get native balance (ETH/AVAX)\n const nativeBalance = await walletAgent.publicClient.getBalance({\n address: targetAddress\n });\n\n // ERC20 ABI for USDC\n const erc20Abi = [\n {\n name: 'balanceOf',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ name: 'account', type: 'address' }],\n outputs: [{ name: '', type: 'uint256' }],\n },\n {\n name: 'decimals',\n type: 'function',\n stateMutability: 'view',\n inputs: [],\n outputs: [{ name: '', type: 'uint8' }],\n }\n ];\n\n // Get USDC balance\n let usdcBalance = BigInt(0);\n let usdcDecimals = 6;\n try {\n [usdcBalance, usdcDecimals] = await Promise.all([\n walletAgent.publicClient.readContract({\n address: contracts.mockUSDC as Address,\n abi: erc20Abi,\n functionName: 'balanceOf',\n args: [targetAddress]\n }) as Promise<bigint>,\n walletAgent.publicClient.readContract({\n address: contracts.mockUSDC as Address,\n abi: erc20Abi,\n functionName: 'decimals'\n }) as Promise<number>\n ]);\n } catch (error) {\n console.error('Failed to get USDC balance:', error);\n }\n\n // Format balances\n const nativeFormatted = formatEther(nativeBalance);\n const usdcFormatted = (Number(usdcBalance) / Math.pow(10, usdcDecimals)).toFixed(6);\n\n return {\n status: \"success\",\n message: `✅ Account balances retrieved for ${targetAddress}`,\n account_info: {\n address: targetAddress,\n network: walletAgent.network,\n chain_id: walletAgent.networkInfo.chainId,\n native_currency: nativeCurrency,\n is_own_wallet: targetAddress.toLowerCase() === walletAgent.account.address.toLowerCase()\n },\n native_balance: {\n symbol: nativeCurrency,\n balance: nativeFormatted,\n balance_wei: nativeBalance.toString(),\n usd_value: \"N/A\"\n },\n usdc_balance: {\n symbol: \"USDC\",\n balance: usdcFormatted,\n balance_raw: usdcBalance.toString(),\n decimals: usdcDecimals,\n contract_address: contracts.mockUSDC,\n usd_value: usdcFormatted // USDC is pegged to USD\n },\n portfolio_summary: {\n total_native_balance: nativeFormatted,\n total_usdc_balance: usdcFormatted,\n can_pay_gas: Number(nativeFormatted) > 0.001,\n ready_for_operations: Number(nativeFormatted) > 0.001,\n has_usdc: Number(usdcFormatted) > 0\n },\n next_steps: Number(nativeFormatted) < 0.001\n ? [\n `🔋 Fund wallet with ${nativeCurrency} for gas fees`,\n \"🎨 Ready for tokenization once funded\",\n Number(usdcFormatted) === 0 ? \"💰 Consider minting USDC for RWA purchases\" : \"✅ USDC balance available for RWA purchases\"\n ]\n : [\n `✅ Sufficient ${nativeCurrency} for gas fees`,\n \"🎫 Ready for RWA tokenization\",\n Number(usdcFormatted) === 0 ? \"💰 Consider minting USDC for RWA purchases\" : `💰 ${usdcFormatted} USDC available for RWA purchases`\n ]\n };\n } catch (error: any) {\n throw new Error(`Failed to get account balances: ${error.message}`);\n } finally {\n await agent.disconnect();\n }\n }\n};\n","import { z } from \"zod\";\nimport { WalletAgent } from \"../../agent/wallet\";\nimport { type McpTool } from \"../../types\";\nimport { parseEther, Address } from \"viem\";\nimport { type NetworkType } from \"../../config\";\n\nexport const SendETHTool: McpTool = {\n name: \"asetta_send_native_ip\",\n description: \"Send native IP token to another address for gas fees or payments\",\n schema: {\n destination: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .describe(\"Recipient's Ethereum address\"),\n amount: z.number()\n .positive()\n .describe(\"Amount of native token to send\"),\n memo: z.string()\n .optional()\n .describe(\"Optional memo for the transaction\"),\n network: z.enum(['avalancheFuji', 'ethereumSepolia', 'arbitrumSepolia'])\n .optional()\n .describe(\"Network to use (optional, defaults to configured network)\")\n },\n handler: async (agent: WalletAgent, input: Record<string, any>) => {\n try {\n const networkType = input.network as NetworkType;\n const walletAgent = networkType ? new WalletAgent(networkType) : agent;\n \n await walletAgent.connect();\n\n const destination = input.destination as Address;\n const amount = parseEther(input.amount.toString());\n const nativeCurrency = walletAgent.networkInfo.nativeCurrency;\n \n // Check sender balance\n const balance = await walletAgent.publicClient.getBalance({\n address: walletAgent.account.address\n });\n\n if (balance < amount) {\n throw new Error(`Insufficient balance. Available: ${Number(balance) / 1e18} ${nativeCurrency}, Required: ${input.amount} ${nativeCurrency}`);\n }\n\n // Simulate transaction first to get accurate gas estimate and catch errors\n let gasEstimate: bigint;\n try {\n // For native transfers, we use estimateGas directly since it's not a contract call\n gasEstimate = await walletAgent.publicClient.estimateGas({\n account: walletAgent.account.address,\n to: destination,\n value: amount\n });\n } catch (error: any) {\n throw new Error(`Transaction simulation failed: ${error.message}. Check recipient address and amount.`);\n }\n\n // Get gas price for cost calculation\n const gasPrice = await walletAgent.publicClient.getGasPrice();\n const gasCost = gasEstimate * gasPrice;\n\n if (balance < amount + gasCost) {\n throw new Error(`Insufficient balance for transaction + gas. Total needed: ${Number(amount + gasCost) / 1e18} ${nativeCurrency}`);\n }\n\n console.error(`✅ Native ${nativeCurrency} transfer simulation successful. Gas estimate: ${gasEstimate.toString()}`);\n\n // Send transaction\n const txHash = await walletAgent.walletClient.sendTransaction({\n account: walletAgent.account,\n to: destination,\n value: amount,\n gas: gasEstimate\n } as any);\n\n // Wait for confirmation\n const receipt = await walletAgent.publicClient.waitForTransactionReceipt({\n hash: txHash,\n confirmations: 1\n });\n\n return {\n status: \"success\",\n message: `✅ Successfully sent ${input.amount} ${nativeCurrency} to ${destination}`,\n transaction_details: {\n transaction_hash: txHash,\n from: walletAgent.account.address,\n to: destination,\n amount: `${input.amount} ${nativeCurrency}`,\n amount_wei: amount.toString(),\n gas_used: receipt.gasUsed.toString(),\n gas_price: gasPrice.toString(),\n total_cost: `${Number(amount + (receipt.gasUsed * gasPrice)) / 1e18} ${nativeCurrency}`,\n block_number: receipt.blockNumber.toString(),\n confirmations: 1,\n memo: input.memo || \"N/A\"\n },\n network_info: {\n network: walletAgent.network,\n chain_id: walletAgent.networkInfo.chainId,\n native_currency: nativeCurrency,\n explorer_url: `${walletAgent.networkInfo.blockExplorer}/tx/${txHash}`\n },\n next_steps: [\n \"✅ Transaction confirmed on blockchain\",\n \"🔍 View transaction details on block explorer\",\n `💰 Recipient can now use ${nativeCurrency} for Asetta operations`\n ]\n };\n } catch (error: any) {\n throw new Error(`Failed to send ${agent.networkInfo?.nativeCurrency || 'native token'}: ${error.message}`);\n } finally {\n await agent.disconnect();\n }\n }\n};\n","import { z } from \"zod\";\nimport { WalletAgent } from \"../../agent/wallet\";\nimport { type McpTool } from \"../../types\";\nimport { parseEther, Address, formatEther } from \"viem\"; \nimport { type NetworkType } from \"../../config\";\n\nexport const SendTokenTool: McpTool = {\n name: \"asetta_send_token\",\n description: \"Send ERC-20 tokens to another address\",\n schema: {\n token_address: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .describe(\"Token contract address\"),\n destination: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .describe(\"Recipient's Ethereum address\"),\n amount: z.number()\n .positive()\n .describe(\"Amount of tokens to send\"),\n memo: z.string()\n .optional()\n .describe(\"Optional memo for the transaction\"),\n network: z.enum(['avalancheFuji', 'ethereumSepolia', 'arbitrumSepolia'])\n .optional()\n .describe(\"Network to use (optional, defaults to configured network)\")\n },\n handler: async (agent: WalletAgent, input: Record<string, any>) => {\n try {\n const networkType = input.network as NetworkType;\n const walletAgent = networkType ? new WalletAgent(networkType) : agent;\n \n await walletAgent.connect();\n\n const destination = input.destination as Address;\n let tokenAddress = input.token_address as Address;\n \n const amount = parseEther(input.amount.toString());\n\n // ERC20 ABI for token operations\n const erc20Abi = [\n {\n name: 'transfer',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'to', type: 'address' },\n { name: 'amount', type: 'uint256' }\n ],\n outputs: [{ name: '', type: 'bool' }],\n },\n {\n name: 'balanceOf',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ name: 'account', type: 'address' }],\n outputs: [{ name: '', type: 'uint256' }],\n },\n {\n name: 'symbol',\n type: 'function',\n stateMutability: 'view',\n inputs: [],\n outputs: [{ name: '', type: 'string' }],\n },\n {\n name: 'decimals',\n type: 'function',\n stateMutability: 'view',\n inputs: [],\n outputs: [{ name: '', type: 'uint8' }],\n }\n ];\n\n // Get token info\n const [balance, symbol, decimals] = await Promise.all([\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'balanceOf',\n args: [walletAgent.account.address]\n }) as Promise<bigint>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'symbol'\n }) as Promise<string>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'decimals'\n }) as Promise<number>\n ]);\n\n if (balance < amount) {\n throw new Error(`Insufficient ${symbol} balance. Available: ${formatEther(balance)}, Required: ${input.amount}`);\n }\n\n // Simulate token transfer first to catch errors and get accurate gas estimate\n const { request, result } = await walletAgent.publicClient.simulateContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'transfer',\n args: [destination, amount],\n account: walletAgent.account.address\n });\n\n console.error(`✅ Transfer simulation successful. Proceeding with transaction...`);\n\n // Send token transfer transaction using the simulated request\n const txHash = await walletAgent.walletClient.writeContract(request);\n\n // Wait for confirmation\n const receipt = await walletAgent.publicClient.waitForTransactionReceipt({\n hash: txHash,\n confirmations: 1\n });\n\n return {\n status: \"success\",\n message: `✅ Successfully sent ${input.amount} ${symbol} to ${destination}`,\n transaction_details: {\n transaction_hash: txHash,\n from: walletAgent.account.address,\n to: destination,\n token_address: tokenAddress,\n token_symbol: symbol,\n amount: `${input.amount} ${symbol}`,\n amount_wei: amount.toString(),\n decimals: decimals,\n gas_used: receipt.gasUsed.toString(),\n block_number: receipt.blockNumber.toString(),\n confirmations: 1,\n memo: input.memo || \"N/A\"\n },\n token_info: {\n contract_address: tokenAddress,\n symbol: symbol,\n decimals: decimals\n },\n network_info: {\n network: walletAgent.network,\n chain_id: walletAgent.networkInfo.chainId,\n native_currency: walletAgent.networkInfo.nativeCurrency,\n explorer_url: `${walletAgent.networkInfo.blockExplorer}/tx/${txHash}`\n },\n next_steps: [\n \"✅ Token transfer confirmed on blockchain\",\n \"🔍 View transaction details on block explorer\",\n `💎 Recipient can now use ${symbol} tokens for on-chain operations`\n ]\n };\n } catch (error: any) {\n throw new Error(`Failed to send tokens: ${error.message}`);\n } finally {\n await agent.disconnect();\n }\n }\n};\n","import { z } from \"zod\";\nimport { WalletAgent } from \"../../agent/wallet\"\nimport { type McpTool } from \"../../types\";\nimport { parseEther, Address, formatEther, maxUint256 } from \"viem\";\nimport { type NetworkType } from \"../../config\";\n\nexport const ApproveTokenTool: McpTool = {\n name: \"asetta_approve_token\",\n description: \"Approve smart contracts to spend your tokens\",\n schema: {\n token_address: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .describe(\"Token contract address\"),\n spender: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .describe(\"Contract address to approve\"),\n amount: z.number()\n .positive()\n .optional()\n .describe(\"Amount to approve (optional, defaults to unlimited)\"),\n unlimited: z.boolean()\n .default(true)\n .describe(\"Set unlimited approval (recommended for convenience)\"),\n network: z.enum(['avalancheFuji', 'ethereumSepolia', 'arbitrumSepolia'])\n .optional()\n .describe(\"Network to use (optional, defaults to configured network)\")\n },\n handler: async (agent: WalletAgent, input: Record<string, any>) => {\n try {\n const networkType = input.network as NetworkType;\n const walletAgent = networkType ? new WalletAgent(networkType) : agent;\n \n await walletAgent.connect();\n\n let tokenAddress = input.token_address as Address;\n const spender = input.spender as Address;\n \n const amount = input.unlimited || !input.amount \n ? maxUint256 \n : parseEther(input.amount.toString());\n\n // ERC20 ABI for approval operations\n const erc20Abi = [\n {\n name: 'approve',\n type: 'function',\n stateMutability: 'nonpayable',\n inputs: [\n { name: 'spender', type: 'address' },\n { name: 'amount', type: 'uint256' }\n ],\n outputs: [{ name: '', type: 'bool' }],\n },\n {\n name: 'allowance',\n type: 'function',\n stateMutability: 'view',\n inputs: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' }\n ],\n outputs: [{ name: '', type: 'uint256' }],\n },\n {\n name: 'symbol',\n type: 'function',\n stateMutability: 'view',\n inputs: [],\n outputs: [{ name: '', type: 'string' }],\n },\n {\n name: 'balanceOf',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ name: 'account', type: 'address' }],\n outputs: [{ name: '', type: 'uint256' }],\n }\n ];\n\n // Get current allowance and token info\n const [currentAllowance, symbol, balance] = await Promise.all([\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [walletAgent.account.address, spender]\n }) as Promise<bigint>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'symbol'\n }) as Promise<string>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'balanceOf',\n args: [walletAgent.account.address]\n }) as Promise<bigint>\n ]);\n\n // Check if approval is needed\n if (currentAllowance >= amount && amount !== maxUint256) {\n return {\n status: \"success\",\n message: `✅ Sufficient approval already exists for ${symbol}`,\n approval_details: {\n token_address: tokenAddress,\n token_symbol: symbol,\n spender: spender,\n current_allowance: input.unlimited ? \"Unlimited\" : formatEther(currentAllowance),\n requested_amount: input.unlimited ? \"Unlimited\" : input.amount?.toString(),\n approval_needed: false\n },\n wallet_info: {\n balance: formatEther(balance),\n address: walletAgent.account.address\n },\n next_steps: [\n \"✅ Approval already sufficient\",\n \"🎨 Ready to proceed with on-chain operations\",\n \"💡 No additional transaction needed\"\n ]\n };\n }\n\n // Simulate approval first to catch errors and get accurate gas estimate\n const { request, result } = await walletAgent.publicClient.simulateContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'approve',\n args: [spender, amount],\n account: walletAgent.account.address\n });\n\n console.error(`✅ Approval simulation successful. Proceeding with transaction...`);\n\n // Send approval transaction using the simulated request\n const txHash = await walletAgent.walletClient.writeContract(request);\n\n // Wait for confirmation\n const receipt = await walletAgent.publicClient.waitForTransactionReceipt({\n hash: txHash,\n confirmations: 1\n });\n\n // Get new allowance\n const newAllowance = await walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [walletAgent.account.address, spender]\n }) as bigint;\n\n return {\n status: \"success\",\n message: `✅ Successfully approved ${symbol} spending for smart contract`,\n transaction_details: {\n transaction_hash: txHash,\n from: walletAgent.account.address,\n token_address: tokenAddress,\n token_symbol: symbol,\n spender: spender,\n approved_amount: input.unlimited ? \"Unlimited\" : input.amount?.toString(),\n gas_used: receipt.gasUsed.toString(),\n block_number: receipt.blockNumber.toString(),\n confirmations: 1\n },\n approval_details: {\n previous_allowance: formatEther(currentAllowance),\n new_allowance: amount === maxUint256 ? \"Unlimited\" : formatEther(newAllowance),\n is_unlimited: amount === maxUint256,\n spender_contract: spender\n },\n wallet_info: {\n balance: formatEther(balance),\n address: walletAgent.account.address\n },\n network_info: {\n network: walletAgent.network,\n chain_id: walletAgent.networkInfo.chainId,\n native_currency: walletAgent.networkInfo.nativeCurrency,\n explorer_url: `${walletAgent.networkInfo.blockExplorer}/tx/${txHash}`\n },\n next_steps: [\n \"✅ Token approval confirmed on blockchain\", \n \"💎 Smart contracts can now spend your tokens\",\n \"🔍 View transaction details on block explorer\"\n ]\n };\n } catch (error: any) {\n throw new Error(`Failed to approve tokens: ${error.message}`);\n } finally {\n await agent.disconnect();\n }\n }\n};","import { z } from \"zod\";\nimport { WalletAgent } from \"../../agent/wallet\";\nimport { type McpTool } from \"../../types\";\nimport { Address, formatEther } from \"viem\";\nimport { type NetworkType } from \"../../config\"; \n\nexport const CheckAllowanceTool: McpTool = {\n name: \"asetta_check_allowance\",\n description: \"Check token allowance for smart contracts and other spenders\",\n schema: {\n token_address: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .describe(\"Token contract address\"),\n owner: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .optional()\n .describe(\"Token owner address (optional, defaults to wallet address)\"),\n spender: z.string()\n .regex(/^0x[0-9a-fA-F]{40}$/)\n .describe(\"Spender contract address to check allowance for\"),\n network: z.enum(['avalancheFuji', 'ethereumSepolia', 'arbitrumSepolia'])\n .optional()\n .describe(\"Network to check (optional, defaults to configured network)\")\n },\n handler: async (agent: WalletAgent, input: Record<string, any>) => {\n try {\n const networkType = input.network as NetworkType;\n const walletAgent = networkType ? new WalletAgent(networkType) : agent;\n \n await walletAgent.connect();\n\n let tokenAddress = input.token_address as Address;\n const owner = (input.owner || walletAgent.account.address) as Address;\n const spender = input.spender as Address;\n \n // ERC20 ABI for allowance operations\n const erc20Abi = [\n {\n name: 'allowance',\n type: 'function',\n stateMutability: 'view',\n inputs: [\n { name: 'owner', type: 'address' },\n { name: 'spender', type: 'address' }\n ],\n outputs: [{ name: '', type: 'uint256' }],\n },\n {\n name: 'balanceOf',\n type: 'function',\n stateMutability: 'view',\n inputs: [{ name: 'account', type: 'address' }],\n outputs: [{ name: '', type: 'uint256' }],\n },\n {\n name: 'symbol',\n type: 'function',\n stateMutability: 'view',\n inputs: [],\n outputs: [{ name: '', type: 'string' }],\n },\n {\n name: 'decimals',\n type: 'function',\n stateMutability: 'view',\n inputs: [],\n outputs: [{ name: '', type: 'uint8' }],\n },\n {\n name: 'name',\n type: 'function',\n stateMutability: 'view',\n inputs: [],\n outputs: [{ name: '', type: 'string' }],\n }\n ];\n\n // Get token info and allowance\n const [allowance, balance, symbol, decimals, name] = await Promise.all([\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'allowance',\n args: [owner, spender]\n }) as Promise<bigint>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'balanceOf',\n args: [owner]\n }) as Promise<bigint>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'symbol'\n }) as Promise<string>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'decimals'\n }) as Promise<number>,\n walletAgent.publicClient.readContract({\n address: tokenAddress,\n abi: erc20Abi,\n functionName: 'name'\n }) as Promise<string>\n ]);\n\n // Check if allowance is unlimited (max uint256)\n const maxUint256 = BigInt(\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\");\n const isUnlimited = allowance >= maxUint256 / BigInt(2); // Close to max value\n\n // Calculate allowance status\n const allowanceFormatted = isUnlimited ? \"Unlimited\" : formatEther(allowance);\n const balanceFormatted = formatEther(balance);\n const canSpendBalance = allowance >= balance;\n const needsApproval = allowance === BigInt(0);\n\n // Determine approval recommendations\n const getRecommendations = () => {\n if (needsApproval) {\n return [\n \"⚠️ No allowance set - approval required\",\n `Use asetta_approve_token to approve ${symbol} spending`,\n \"Recommend setting unlimited approval for convenience\"\n ];\n } else if (!canSpendBalance && !isUnlimited) {\n return [\n \"⚠️ Allowance is less than current balance\",\n `Current allowance: ${allowanceFormatted} ${symbol}`,\n `Current balance: ${balanceFormatted} ${symbol}`,\n \"Consider increasing allowance for full balance access\"\n ];\n } else if (isUnlimited) {\n return [\n \"✅ Unlimited allowance set\",\n \"No further approvals needed for this token\",\n \"Ready for all on-chain operations\"\n ];\n } else {\n return [\n \"✅ Sufficient allowance for current balance\",\n `Can spend up to ${allowanceFormatted} ${symbol}`,\n \"Ready for on-chain operations\"\n ];\n }\n };\n\n return {\n status: \"success\",\n message: `✅ Allowance checked for ${symbol} token`,\n allowance_details: {\n token_address: