UNPKG

@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,274 lines (1,260 loc) 897 kB
#!/usr/bin/env node "use strict"; // src/config.ts var import_viem = require("viem"); var import_accounts = require("viem/accounts"); var import_chains = require("viem/chains"); var getArgs = () => process.argv.reduce((args, arg) => { if (arg.slice(0, 2) === "--") { const longArg = arg.split("="); const longArgFlag = longArg[0].slice(2); const longArgValue = longArg.length > 1 ? longArg[1] : true; args[longArgFlag] = longArgValue; } else if (arg[0] === "-") { const flags = arg.slice(1).split(""); flags.forEach((flag) => { args[flag] = true; }); } return args; }, {}); var CONTRACT_ADDRESSES = { avalancheFuji: { mockUSDC: "0x5067e9a9154A2EA674DEf639de5e98F238824039", rwaManager: "0x6ee904a0Ff97b5682E80660Bf2Aca280D18aB5F3", tokenFactory: "0xEc5003E8451EC488ea1e1a7142A38e77a5082fCf", primaryDistribution: "0x9304F30b1AEfeCB43F86fd5841C6ea75BD0F2529", rfq: "0x307992307C89216b1079C7c5Cbc4F51005b1472D" }, ethereumSepolia: { mockUSDC: "0xf2260B00250c772CB64606dBb88d9544F709308C", rwaManager: "0x9682DaBf26831523B21759A50b0a45832f82DBa3", tokenFactory: "0x6fdB032668F1F856fbC2e9F5Df348938aFBFBE17", primaryDistribution: "0xf309011fbf013C352849Cd4b5C85E71cC69a1EBF", rfq: "0x42209A0A2a3D80Ad48B7D25fC6a61ad355901484" }, arbitrumSepolia: { mockUSDC: "0x16EE94e3C07B24EbA6067eb9394BA70178aAc4c0", rwaManager: "0x4fd5Ae48A869c5ec0214CB050D2D713433515D8d", tokenFactory: "0xe5209A4f622C6eD2C158dcCcdDB69B05f9D0E4E0", primaryDistribution: "0xA657b300009802Be7c88617128545534aCA12dbe", rfq: "0x61ad3Fe6B44Bfbbcec39c9FaD566538c894b6471" } }; var networkConfigs = { avalancheFuji: { rpcProviderUrl: "https://avalanche-fuji.drpc.org", blockExplorer: "https://testnet.snowtrace.io", chain: import_chains.avalancheFuji, chainId: 43113, nativeCurrency: "AVAX" }, ethereumSepolia: { rpcProviderUrl: "https://sepolia.drpc.org", blockExplorer: "https://sepolia.etherscan.io", chain: import_chains.sepolia, chainId: 11155111, nativeCurrency: "ETH" }, arbitrumSepolia: { rpcProviderUrl: "https://arbitrum-sepolia.drpc.org", blockExplorer: "https://sepolia-explorer.arbitrum.io", chain: import_chains.arbitrumSepolia, chainId: 421614, nativeCurrency: "ETH" } }; var getNetwork = () => { const args = getArgs(); const network2 = args.network; if (network2 && !(network2 in networkConfigs)) { throw new Error(`Invalid network: ${network2}. Must be one of: ${Object.keys(networkConfigs).join(", ")}`); } return network2 || "avalancheFuji"; }; var getAccount = () => { const args = getArgs(); const hasPrivateKey = !!args?.wallet_private_key; if (!hasPrivateKey) { const privateKey = (0, import_accounts.generatePrivateKey)(); return (0, import_accounts.privateKeyToAccount)(privateKey); } else { return (0, import_accounts.privateKeyToAccount)(`0x${args?.wallet_private_key}`); } }; var network = getNetwork(); var networkInfo = { ...networkConfigs[network], rpcProviderUrl: networkConfigs[network].rpcProviderUrl }; var account = getAccount(); var getMode = () => { const args = getArgs(); return args.agent_mode; }; var getAccessKey = () => { const args = getArgs(); return args.access_key; }; var agentMode = getMode(); var accessKey = getAccessKey(); var baseConfig = { chain: networkInfo.chain, transport: (0, import_viem.http)(networkInfo.rpcProviderUrl) }; var publicClient = (0, import_viem.createPublicClient)(baseConfig); var walletClient = (0, import_viem.createWalletClient)({ ...baseConfig, account }); function createClientForNetwork(networkType) { const config = networkConfigs[networkType]; const baseConfig2 = { chain: config.chain, transport: (0, import_viem.http)(config.rpcProviderUrl) }; return { publicClient: (0, import_viem.createPublicClient)(baseConfig2), walletClient: (0, import_viem.createWalletClient)({ ...baseConfig2, account }), networkInfo: config }; } function getContractAddresses(networkType) { return CONTRACT_ADDRESSES[networkType]; } function validateEnvironment() { try { const args = getArgs(); const hasAgentMode = !!args?.agent_mode; if (!hasAgentMode) { console.error(`AGENT_MODE is not set, default to non-wallet mode`); } else { console.error(`\u2705 Asetta mode: ${args.agent_mode}`); } if (args.agent_mode === "tokenization") { getNetwork(); console.error(`\u2705 Asetta MCP environment configuration valid (${network})`); console.error(`\u{1F4CD} RPC URL: ${networkInfo.rpcProviderUrl}`); console.error(`\u{1F4CD} Chain ID: ${networkInfo.chainId}`); console.error(`\u{1F4CD} Native Currency: ${networkInfo.nativeCurrency}`); getAccount(); console.error(`\u{1F4CD} Account: ${account.address}`); } if (args.access_key) { console.error(`\u{1F4CD} Access Key: ${args.access_key}`); } } catch (error) { console.error("\u274C Invalid environment configuration:", error); throw error; } } // src/index.ts var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js"); var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js"); // src/agent/wallet.ts var WalletAgent = class { account; walletClient; publicClient; network; networkInfo; constructor(networkType) { if (networkType && networkType !== network) { const clients = createClientForNetwork(networkType); this.publicClient = clients.publicClient; this.walletClient = clients.walletClient; this.networkInfo = clients.networkInfo; this.network = networkType; } else { this.walletClient = walletClient; this.publicClient = publicClient; this.network = network; this.networkInfo = networkInfo; } this.account = account; console.error(`\u{1F3A8} Asetta Agent initialized on ${this.network}`); console.error(`\u{1F4CD} Wallet address: ${this.account.address}`); } async connect() { try { const chainId = await this.publicClient.getChainId(); console.error(`\u2705 Connected to the network (Chain ID: ${chainId})`); console.error(`\u{1F310} Network: ${this.network}`); console.error(`\u{1F517} RPC: ${this.networkInfo.rpcProviderUrl}`); } catch (error) { console.error("\u274C Failed to connect to network:", error); throw error; } } async disconnect() { console.error("\u{1F50C} Disconnected from the network"); } async getWalletInfo() { try { const balance = await this.publicClient.getBalance({ address: this.account.address }); return { address: this.account.address, balance: balance.toString(), network: this.network, chainId: await this.publicClient.getChainId(), blockExplorer: this.networkInfo.blockExplorer, nativeCurrency: this.networkInfo.nativeCurrency }; } catch (error) { console.error("Failed to get wallet info:", error); throw error; } } }; // src/agent/api.ts var ApiAgent = class { constructor() { console.error(`\u2705 Asetta Agent runs on Non-Wallet Mode`); } }; // src/mcp/wallet/get_wallet_info_tool.ts var import_zod = require("zod"); var GetWalletInfoTool = { name: "asetta_get_wallet_info", description: "Get wallet address and basic account information", schema: { network: import_zod.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to check (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); const balance = await walletAgent.publicClient.getBalance({ address: walletAgent.account.address }); const balanceInNative = Number(balance) / 1e18; const nativeCurrency = walletAgent.networkInfo.nativeCurrency; return { status: "success", message: "\u2705 Wallet information retrieved successfully", wallet_details: { address: walletAgent.account.address, network: walletAgent.network, balance: `${balanceInNative.toFixed(6)} ${nativeCurrency}`, balance_in_wei: balance.toString(), chain_id: await walletAgent.publicClient.getChainId(), block_explorer: walletAgent.networkInfo.blockExplorer, native_currency: nativeCurrency }, account_status: { activated: true, minimum_balance_required: `0.01 ${nativeCurrency}`, can_register_rwa: balanceInNative >= 0.01, ready_for_operations: balanceInNative >= 1e-3 }, recommendations: balanceInNative < 0.01 ? [ `\u26A0\uFE0F Low ${nativeCurrency} balance detected`, `Fund wallet with at least 0.01 ${nativeCurrency} for RWA registration`, "Gas fees required for all Asetta Protocol operations", `Current balance: ${balanceInNative.toFixed(6)} ${nativeCurrency}` ] : [ "\u2705 Wallet has sufficient balance for operations", "Ready to register RWA assets", "Ready to create vaults and issue tokens" ] }; } catch (error) { throw new Error(`Failed to get wallet info: ${error.message}`); } finally { await agent.disconnect(); } } }; // src/mcp/wallet/get_account_balances_tool.ts var import_zod2 = require("zod"); var import_viem2 = require("viem"); var GetAccountBalancesTool = { name: "asetta_get_account_balances", description: "Get all token balances including native tokens and USDC", schema: { account_address: import_zod2.z.string().regex(/^0x[0-9a-fA-F]{40}$/).optional().describe("Ethereum address to check (optional, defaults to wallet address)"), network: import_zod2.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to check (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); const targetAddress = input.account_address || walletAgent.account.address; const nativeCurrency = walletAgent.networkInfo.nativeCurrency; const contracts = getContractAddresses(walletAgent.network); const nativeBalance = await walletAgent.publicClient.getBalance({ address: targetAddress }); const erc20Abi = [ { name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }, { name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint8" }] } ]; let usdcBalance = BigInt(0); let usdcDecimals = 6; try { [usdcBalance, usdcDecimals] = await Promise.all([ walletAgent.publicClient.readContract({ address: contracts.mockUSDC, abi: erc20Abi, functionName: "balanceOf", args: [targetAddress] }), walletAgent.publicClient.readContract({ address: contracts.mockUSDC, abi: erc20Abi, functionName: "decimals" }) ]); } catch (error) { console.error("Failed to get USDC balance:", error); } const nativeFormatted = (0, import_viem2.formatEther)(nativeBalance); const usdcFormatted = (Number(usdcBalance) / Math.pow(10, usdcDecimals)).toFixed(6); return { status: "success", message: `\u2705 Account balances retrieved for ${targetAddress}`, account_info: { address: targetAddress, network: walletAgent.network, chain_id: walletAgent.networkInfo.chainId, native_currency: nativeCurrency, is_own_wallet: targetAddress.toLowerCase() === walletAgent.account.address.toLowerCase() }, native_balance: { symbol: nativeCurrency, balance: nativeFormatted, balance_wei: nativeBalance.toString(), usd_value: "N/A" }, usdc_balance: { symbol: "USDC", balance: usdcFormatted, balance_raw: usdcBalance.toString(), decimals: usdcDecimals, contract_address: contracts.mockUSDC, usd_value: usdcFormatted // USDC is pegged to USD }, portfolio_summary: { total_native_balance: nativeFormatted, total_usdc_balance: usdcFormatted, can_pay_gas: Number(nativeFormatted) > 1e-3, ready_for_operations: Number(nativeFormatted) > 1e-3, has_usdc: Number(usdcFormatted) > 0 }, next_steps: Number(nativeFormatted) < 1e-3 ? [ `\u{1F50B} Fund wallet with ${nativeCurrency} for gas fees`, "\u{1F3A8} Ready for tokenization once funded", Number(usdcFormatted) === 0 ? "\u{1F4B0} Consider minting USDC for RWA purchases" : "\u2705 USDC balance available for RWA purchases" ] : [ `\u2705 Sufficient ${nativeCurrency} for gas fees`, "\u{1F3AB} Ready for RWA tokenization", Number(usdcFormatted) === 0 ? "\u{1F4B0} Consider minting USDC for RWA purchases" : `\u{1F4B0} ${usdcFormatted} USDC available for RWA purchases` ] }; } catch (error) { throw new Error(`Failed to get account balances: ${error.message}`); } finally { await agent.disconnect(); } } }; // src/mcp/wallet/send_eth_tool.ts var import_zod3 = require("zod"); var import_viem3 = require("viem"); var SendETHTool = { name: "asetta_send_native_ip", description: "Send native IP token to another address for gas fees or payments", schema: { destination: import_zod3.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Recipient's Ethereum address"), amount: import_zod3.z.number().positive().describe("Amount of native token to send"), memo: import_zod3.z.string().optional().describe("Optional memo for the transaction"), network: import_zod3.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to use (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); const destination = input.destination; const amount = (0, import_viem3.parseEther)(input.amount.toString()); const nativeCurrency = walletAgent.networkInfo.nativeCurrency; const balance = await walletAgent.publicClient.getBalance({ address: walletAgent.account.address }); if (balance < amount) { throw new Error(`Insufficient balance. Available: ${Number(balance) / 1e18} ${nativeCurrency}, Required: ${input.amount} ${nativeCurrency}`); } let gasEstimate; try { gasEstimate = await walletAgent.publicClient.estimateGas({ account: walletAgent.account.address, to: destination, value: amount }); } catch (error) { throw new Error(`Transaction simulation failed: ${error.message}. Check recipient address and amount.`); } const gasPrice = await walletAgent.publicClient.getGasPrice(); const gasCost = gasEstimate * gasPrice; if (balance < amount + gasCost) { throw new Error(`Insufficient balance for transaction + gas. Total needed: ${Number(amount + gasCost) / 1e18} ${nativeCurrency}`); } console.error(`\u2705 Native ${nativeCurrency} transfer simulation successful. Gas estimate: ${gasEstimate.toString()}`); const txHash = await walletAgent.walletClient.sendTransaction({ account: walletAgent.account, to: destination, value: amount, gas: gasEstimate }); const receipt = await walletAgent.publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 1 }); return { status: "success", message: `\u2705 Successfully sent ${input.amount} ${nativeCurrency} to ${destination}`, transaction_details: { transaction_hash: txHash, from: walletAgent.account.address, to: destination, amount: `${input.amount} ${nativeCurrency}`, amount_wei: amount.toString(), gas_used: receipt.gasUsed.toString(), gas_price: gasPrice.toString(), total_cost: `${Number(amount + receipt.gasUsed * gasPrice) / 1e18} ${nativeCurrency}`, block_number: receipt.blockNumber.toString(), confirmations: 1, memo: input.memo || "N/A" }, network_info: { network: walletAgent.network, chain_id: walletAgent.networkInfo.chainId, native_currency: nativeCurrency, explorer_url: `${walletAgent.networkInfo.blockExplorer}/tx/${txHash}` }, next_steps: [ "\u2705 Transaction confirmed on blockchain", "\u{1F50D} View transaction details on block explorer", `\u{1F4B0} Recipient can now use ${nativeCurrency} for Asetta operations` ] }; } catch (error) { throw new Error(`Failed to send ${agent.networkInfo?.nativeCurrency || "native token"}: ${error.message}`); } finally { await agent.disconnect(); } } }; // src/mcp/wallet/send_token_tool.ts var import_zod4 = require("zod"); var import_viem4 = require("viem"); var SendTokenTool = { name: "asetta_send_token", description: "Send ERC-20 tokens to another address", schema: { token_address: import_zod4.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Token contract address"), destination: import_zod4.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Recipient's Ethereum address"), amount: import_zod4.z.number().positive().describe("Amount of tokens to send"), memo: import_zod4.z.string().optional().describe("Optional memo for the transaction"), network: import_zod4.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to use (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); const destination = input.destination; let tokenAddress = input.token_address; const amount = (0, import_viem4.parseEther)(input.amount.toString()); const erc20Abi = [ { name: "transfer", type: "function", stateMutability: "nonpayable", inputs: [ { name: "to", type: "address" }, { name: "amount", type: "uint256" } ], outputs: [{ name: "", type: "bool" }] }, { name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }, { name: "symbol", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }, { name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint8" }] } ]; const [balance, symbol, decimals] = await Promise.all([ walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [walletAgent.account.address] }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "symbol" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "decimals" }) ]); if (balance < amount) { throw new Error(`Insufficient ${symbol} balance. Available: ${(0, import_viem4.formatEther)(balance)}, Required: ${input.amount}`); } const { request, result } = await walletAgent.publicClient.simulateContract({ address: tokenAddress, abi: erc20Abi, functionName: "transfer", args: [destination, amount], account: walletAgent.account.address }); console.error(`\u2705 Transfer simulation successful. Proceeding with transaction...`); const txHash = await walletAgent.walletClient.writeContract(request); const receipt = await walletAgent.publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 1 }); return { status: "success", message: `\u2705 Successfully sent ${input.amount} ${symbol} to ${destination}`, transaction_details: { transaction_hash: txHash, from: walletAgent.account.address, to: destination, token_address: tokenAddress, token_symbol: symbol, amount: `${input.amount} ${symbol}`, amount_wei: amount.toString(), decimals, gas_used: receipt.gasUsed.toString(), block_number: receipt.blockNumber.toString(), confirmations: 1, memo: input.memo || "N/A" }, token_info: { contract_address: tokenAddress, symbol, decimals }, network_info: { network: walletAgent.network, chain_id: walletAgent.networkInfo.chainId, native_currency: walletAgent.networkInfo.nativeCurrency, explorer_url: `${walletAgent.networkInfo.blockExplorer}/tx/${txHash}` }, next_steps: [ "\u2705 Token transfer confirmed on blockchain", "\u{1F50D} View transaction details on block explorer", `\u{1F48E} Recipient can now use ${symbol} tokens for on-chain operations` ] }; } catch (error) { throw new Error(`Failed to send tokens: ${error.message}`); } finally { await agent.disconnect(); } } }; // src/mcp/wallet/approve_token_tool.ts var import_zod5 = require("zod"); var import_viem5 = require("viem"); var ApproveTokenTool = { name: "asetta_approve_token", description: "Approve smart contracts to spend your tokens", schema: { token_address: import_zod5.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Token contract address"), spender: import_zod5.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Contract address to approve"), amount: import_zod5.z.number().positive().optional().describe("Amount to approve (optional, defaults to unlimited)"), unlimited: import_zod5.z.boolean().default(true).describe("Set unlimited approval (recommended for convenience)"), network: import_zod5.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to use (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); let tokenAddress = input.token_address; const spender = input.spender; const amount = input.unlimited || !input.amount ? import_viem5.maxUint256 : (0, import_viem5.parseEther)(input.amount.toString()); const erc20Abi = [ { name: "approve", type: "function", stateMutability: "nonpayable", inputs: [ { name: "spender", type: "address" }, { name: "amount", type: "uint256" } ], outputs: [{ name: "", type: "bool" }] }, { name: "allowance", type: "function", stateMutability: "view", inputs: [ { name: "owner", type: "address" }, { name: "spender", type: "address" } ], outputs: [{ name: "", type: "uint256" }] }, { name: "symbol", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }, { name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] } ]; const [currentAllowance, symbol, balance] = await Promise.all([ walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "allowance", args: [walletAgent.account.address, spender] }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "symbol" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [walletAgent.account.address] }) ]); if (currentAllowance >= amount && amount !== import_viem5.maxUint256) { return { status: "success", message: `\u2705 Sufficient approval already exists for ${symbol}`, approval_details: { token_address: tokenAddress, token_symbol: symbol, spender, current_allowance: input.unlimited ? "Unlimited" : (0, import_viem5.formatEther)(currentAllowance), requested_amount: input.unlimited ? "Unlimited" : input.amount?.toString(), approval_needed: false }, wallet_info: { balance: (0, import_viem5.formatEther)(balance), address: walletAgent.account.address }, next_steps: [ "\u2705 Approval already sufficient", "\u{1F3A8} Ready to proceed with on-chain operations", "\u{1F4A1} No additional transaction needed" ] }; } const { request, result } = await walletAgent.publicClient.simulateContract({ address: tokenAddress, abi: erc20Abi, functionName: "approve", args: [spender, amount], account: walletAgent.account.address }); console.error(`\u2705 Approval simulation successful. Proceeding with transaction...`); const txHash = await walletAgent.walletClient.writeContract(request); const receipt = await walletAgent.publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 1 }); const newAllowance = await walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "allowance", args: [walletAgent.account.address, spender] }); return { status: "success", message: `\u2705 Successfully approved ${symbol} spending for smart contract`, transaction_details: { transaction_hash: txHash, from: walletAgent.account.address, token_address: tokenAddress, token_symbol: symbol, spender, approved_amount: input.unlimited ? "Unlimited" : input.amount?.toString(), gas_used: receipt.gasUsed.toString(), block_number: receipt.blockNumber.toString(), confirmations: 1 }, approval_details: { previous_allowance: (0, import_viem5.formatEther)(currentAllowance), new_allowance: amount === import_viem5.maxUint256 ? "Unlimited" : (0, import_viem5.formatEther)(newAllowance), is_unlimited: amount === import_viem5.maxUint256, spender_contract: spender }, wallet_info: { balance: (0, import_viem5.formatEther)(balance), address: walletAgent.account.address }, network_info: { network: walletAgent.network, chain_id: walletAgent.networkInfo.chainId, native_currency: walletAgent.networkInfo.nativeCurrency, explorer_url: `${walletAgent.networkInfo.blockExplorer}/tx/${txHash}` }, next_steps: [ "\u2705 Token approval confirmed on blockchain", "\u{1F48E} Smart contracts can now spend your tokens", "\u{1F50D} View transaction details on block explorer" ] }; } catch (error) { throw new Error(`Failed to approve tokens: ${error.message}`); } finally { await agent.disconnect(); } } }; // src/mcp/wallet/check_allowance_tool.ts var import_zod6 = require("zod"); var import_viem6 = require("viem"); var CheckAllowanceTool = { name: "asetta_check_allowance", description: "Check token allowance for smart contracts and other spenders", schema: { token_address: import_zod6.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Token contract address"), owner: import_zod6.z.string().regex(/^0x[0-9a-fA-F]{40}$/).optional().describe("Token owner address (optional, defaults to wallet address)"), spender: import_zod6.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Spender contract address to check allowance for"), network: import_zod6.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to check (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); let tokenAddress = input.token_address; const owner = input.owner || walletAgent.account.address; const spender = input.spender; const erc20Abi = [ { name: "allowance", type: "function", stateMutability: "view", inputs: [ { name: "owner", type: "address" }, { name: "spender", type: "address" } ], outputs: [{ name: "", type: "uint256" }] }, { name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }, { name: "symbol", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }, { name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint8" }] }, { name: "name", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] } ]; const [allowance, balance, symbol, decimals, name] = await Promise.all([ walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "allowance", args: [owner, spender] }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [owner] }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "symbol" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "decimals" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "name" }) ]); const maxUint2562 = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); const isUnlimited = allowance >= maxUint2562 / BigInt(2); const allowanceFormatted = isUnlimited ? "Unlimited" : (0, import_viem6.formatEther)(allowance); const balanceFormatted = (0, import_viem6.formatEther)(balance); const canSpendBalance = allowance >= balance; const needsApproval = allowance === BigInt(0); const getRecommendations = () => { if (needsApproval) { return [ "\u26A0\uFE0F No allowance set - approval required", `Use asetta_approve_token to approve ${symbol} spending`, "Recommend setting unlimited approval for convenience" ]; } else if (!canSpendBalance && !isUnlimited) { return [ "\u26A0\uFE0F Allowance is less than current balance", `Current allowance: ${allowanceFormatted} ${symbol}`, `Current balance: ${balanceFormatted} ${symbol}`, "Consider increasing allowance for full balance access" ]; } else if (isUnlimited) { return [ "\u2705 Unlimited allowance set", "No further approvals needed for this token", "Ready for all on-chain operations" ]; } else { return [ "\u2705 Sufficient allowance for current balance", `Can spend up to ${allowanceFormatted} ${symbol}`, "Ready for on-chain operations" ]; } }; return { status: "success", message: `\u2705 Allowance checked for ${symbol} token`, allowance_details: { token_address: tokenAddress, token_name: name, token_symbol: symbol, token_decimals: decimals, owner, spender, allowance: allowanceFormatted, allowance_wei: allowance.toString(), is_unlimited: isUnlimited, is_zero: allowance === BigInt(0) }, balance_comparison: { owner_balance: balanceFormatted, owner_balance_wei: balance.toString(), can_spend_full_balance: canSpendBalance, allowance_vs_balance: allowance >= balance ? "sufficient" : "insufficient" }, contract_info: { spender_contract: spender, is_own_wallet: owner.toLowerCase() === walletAgent.account.address.toLowerCase() }, operational_status: { needs_approval: needsApproval, ready_for_operations: !needsApproval, can_spend_tokens: allowance > 0, approval_sufficient: canSpendBalance || isUnlimited }, network_info: { network: walletAgent.network, chain_id: walletAgent.networkInfo.chainId, native_currency: walletAgent.networkInfo.nativeCurrency, block_explorer: walletAgent.networkInfo.blockExplorer, token_explorer_url: `${walletAgent.networkInfo.blockExplorer}/token/${tokenAddress}` }, recommendations: getRecommendations(), next_steps: needsApproval ? [ `\u{1F527} Run: asetta_approve_token with token_address=${tokenAddress}`, `\u{1F4C4} Specify spender=${spender}`, "\u{1F4A1} Consider unlimited approval for convenience", "\u{1F3A8} Then proceed with on-chain operations" ] : [ "\u2705 Allowance is properly configured", "\u{1F3A8} Ready to proceed with on-chain operations", `\u{1F48E} Can spend ${canSpendBalance ? "full balance" : "partial balance"} of ${symbol}`, "\u{1F50D} View token details on block explorer" ] }; } catch (error) { throw new Error(`Failed to check allowance: ${error.message}`); } finally { await agent.disconnect(); } } }; // src/mcp/wallet/get_token_info_tool.ts var import_zod7 = require("zod"); var import_viem7 = require("viem"); var GetTokenInfoTool = { name: "asetta_get_token_info", description: "Get comprehensive information about ERC20 tokens including metadata and user balances", schema: { token_address: import_zod7.z.string().regex(/^0x[0-9a-fA-F]{40}$/).describe("Token contract address (use 'WIP' for WIP token shortcut)"), account_address: import_zod7.z.string().regex(/^0x[0-9a-fA-F]{40}$/).optional().describe("Address to check balance for (optional, defaults to wallet address)"), network: import_zod7.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to check (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); let tokenAddress = input.token_address; const accountAddress = input.account_address || walletAgent.account.address; const erc20Abi = [ { name: "name", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }, { name: "symbol", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "string" }] }, { name: "decimals", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint8" }] }, { name: "totalSupply", type: "function", stateMutability: "view", inputs: [], outputs: [{ name: "", type: "uint256" }] }, { name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] } ]; const [name, symbol, decimals, totalSupply, balance] = await Promise.all([ walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "name" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "symbol" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "decimals" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "totalSupply" }), walletAgent.publicClient.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [accountAddress] }) ]); const bytecode = await walletAgent.publicClient.getBytecode({ address: tokenAddress }); const isContract = !!(bytecode && bytecode !== "0x"); const totalSupplyFormatted = (0, import_viem7.formatEther)(totalSupply); const balanceFormatted = (0, import_viem7.formatEther)(balance); const balancePercentage = totalSupply > 0 ? Number(balance) / Number(totalSupply) * 100 : 0; const getTokenInfo = () => { return { type: "erc20_token", category: "custom", purpose: "Custom ERC20 token - verify legitimacy before use" }; }; const tokenInfo = getTokenInfo(); return { status: "success", message: `\u2705 Token information retrieved for ${symbol}`, token_metadata: { contract_address: tokenAddress, name, symbol, decimals, total_supply: totalSupplyFormatted, total_supply_wei: totalSupply.toString(), is_contract: isContract, ...tokenInfo }, account_balance: { address: accountAddress, balance: balanceFormatted, balance_wei: balance.toString(), percentage_of_supply: balancePercentage.toFixed(6) + "%", is_holder: balance > 0, is_own_wallet: accountAddress.toLowerCase() === walletAgent.account.address.toLowerCase() }, supply_analysis: { total_supply_formatted: totalSupplyFormatted, user_balance_formatted: balanceFormatted, supply_concentration: balancePercentage > 1 ? "significant_holder" : balancePercentage > 0.1 ? "moderate_holder" : balancePercentage > 0 ? "small_holder" : "non_holder" }, network_info: { network: walletAgent.network, chain_id: walletAgent.networkInfo.chainId, native_currency: walletAgent.networkInfo.nativeCurrency, block_explorer: walletAgent.networkInfo.blockExplorer, token_explorer_url: `${walletAgent.networkInfo.blockExplorer}/token/${tokenAddress}`, account_explorer_url: `${walletAgent.networkInfo.blockExplorer}/token/${tokenAddress}?a=${accountAddress}` }, operational_status: { has_balance: balance > 0, can_transfer: balance > 0 }, next_steps: balance === BigInt(0) ? [ `\u{1F4B0} Acquire ${symbol} tokens to use for RWA tokenization`, "\u26A0\uFE0F Verify token legitimacy before using", `\u{1F50D} View token details on ${walletAgent.networkInfo.blockExplorer}`, "\u{1F4A1} Use asetta_send_token to receive tokens from others" ] : [ `\u2705 You have ${balanceFormatted} ${symbol} tokens`, "\u26A0\uFE0F Verify token legitimacy before using", "\u{1F48E} Use asetta_approve_token to enable contract spending", "\u{1F504} Use asetta_send_token to transfer to others" ] }; } catch (error) { throw new Error(`Failed to get token info: ${error.message}`); } finally { await agent.disconnect(); } } }; // src/mcp/wallet/get_transaction_history_tool.ts var import_zod8 = require("zod"); var import_viem8 = require("viem"); var GetTransactionHistoryTool = { name: "asetta_get_transaction_history", description: "Get recent transaction history for the wallet or specified address", schema: { account_address: import_zod8.z.string().regex(/^0x[0-9a-fA-F]{40}$/).optional().describe("Address to check (optional, defaults to wallet address)"), limit: import_zod8.z.number().min(1).max(100).default(20).describe("Number of transactions to retrieve (max 100)"), network: import_zod8.z.enum(["avalancheFuji", "ethereumSepolia", "arbitrumSepolia"]).optional().describe("Network to check (optional, defaults to configured network)") }, handler: async (agent, input) => { try { const networkType = input.network; const walletAgent = networkType ? new WalletAgent(networkType) : agent; await walletAgent.connect(); const targetAddress = input.account_address || walletAgent.account.address; const limit = input.limit || 20; const nativeCurrency = walletAgent.networkInfo.nativeCurrency; const currentBlock = await walletAgent.publicClient.getBlockNumber(); const fromBlock = currentBlock - BigInt(1e4); const recentTransactions = []; const blocksToCheck = Math.min(Number(limit * 5), 1e3); for (let i = 0; i < blocksToCheck && recentTransactions.length < limit; i++) { try { const blockNumber = currentBlock - BigInt(i); const block = await walletAgent.publicClient.getBlock({ blockNumber, includeTransactions: true }); for (const tx of block.transactions) { if (typeof tx === "object") { if ((tx.from?.toLowerCase() === targetAddress.toLowerCase() || tx.to?.toLowerCase() === targetAddress.toLowerCase()) && recentTransactions.length < limit) { try { const receipt = await walletAgent.publicClient.getTransactionReceipt({ hash: tx.hash }); recentTransactions.push({ hash: tx.hash, block_number: block.number?.toString(), timestamp: new Date(Number(block.timestamp) * 1e3).toISOString(), from: tx.from, to: tx.to, value: tx.value ? (0, import_viem8.formatEther)(tx.value) : "0", gas_used: receipt.gasUsed.toString(), gas_price: tx.gasPrice?.toString(), status: receipt.status === "success" ? "success" : "failed", type: tx.from?.toLowerCase() === targetAddress.toLowerCase() ? "sent" : "received", is_contract_interaction: tx.to && tx.input !== "0x" }); } catch (receiptError) { recentTransactions.push({ hash: tx.hash, block_number: block.number?.toString(), timestamp: new Date(Number(block.timestamp) * 1e3).toISOString(), from: tx.from, to: tx.to, value: tx.value ? (0, import_viem8.formatEther)(tx.value) : "0", gas_used: "N/A", gas_price: tx.gasPrice?.toString(), status: "unknown", type: tx.from?.toLowerCase() === targetAddress.toLowerCase() ? "sent" : "received", is_contract_interaction: tx.to && tx.input !== "0x" }); } } } } } catch (blockError) { console.error(`Error fetching block ${currentBlock - BigInt(i)}:`, blockError); continue; } } recentTransactions.sort((a, b) => { const blockA = parseInt(a.block_number || "0"); const blockB = parseInt(b.block_number || "0"); return blockB - blockA; }); const sentTransactions = recentTransactions.filter((tx) => tx.type === "sent"); const receivedTransactions = recentTransactions.filter((tx) => tx.type === "received"); const contractInteractions = recentTransactions.filter((tx) => tx.is_contract_interaction); const totalSent = sentTransactions.reduce((sum, tx) => sum + parseFloat(tx.value), 0); const totalReceived = receivedTransactions.reduce((sum, tx) => sum + parseFloat(tx.value), 0); return { status: "success", message: `\u2705 Retrieved ${recentTransactions.length} recent transactions for ${targetAddress}`, account_info: { address: targetAddress, network: walletAgent.network, is_own_wallet: targetAddress.toLowerCase() === walletAgent.account.address.toLowerCase(), blocks_searched: blocksToCheck, from_block: fromBlock.toString(), to_block: currentBlock.toString() }, transaction_summary: { total_transactions: recentTransactions.length, sent_transactions: sentTransactions.length, received_transactions: receivedTransactions.length, contract_interactions: contractInteractions.length, total_eth_sent: `${totalSent.toFixed(6)} ${nativeCurrency}`, total_eth_received: `${totalReceived.toFixed(6)} ${nativeCurrency}`, net_eth_flow: `${(totalReceived - totalSent).toFixed(6)} ${nativeCurrency}` }, transactions: recentTransactions.map((tx) => ({ ...tx, explorer_url: `${walletAgent.networkInfo.blockExplorer}/tx/${tx.hash}`, age: tx.timestamp ? getTimeAgo(new Date(tx.timestamp)) : "Unknown" })), network_info: { network: walletAgent.network, chain_id: walletAgent.networkInfo.chainId, native_currency: nativeCurrency, block_explorer: walletAgent.networkInfo.blockExplorer, current_block: currentBlock.toString() }, next_steps: recentTransactions.length === 0 ? [ "\u{1F50D} No recent transactions found", `\u{1F4A1} Start by funding your wallet with ${nativeCurrency}`, "\u{1F3A8} Begin registering RWA assets on Asetta" ] : [ "\u2705 Transaction history retrieved successfully", "\u{1F50D} Click explorer URLs to view detailed transaction info", `\u{1F4CA} Found ${contractInteractions.length} smart contract interactions`, "\u{1F48E} Ready to analyze Asetta Protocol activity" ] }; } catch (error) { throw new Error(`Failed to get transaction history: ${error.message}`); } finally { await agent.disconnect(); } } }; var getTimeAgo = (date) => { const now = /* @__PURE__ */ new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1e3); if (diffInSeconds < 60) return `${diffInSeconds} seconds ago`; if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`; if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`; return `${Math.floor(diffInSeconds / 86400)} days ago`; }; // src/mcp/wallet/create_rwa_token_tool.ts var import_zod9 = require("zod"); var import_viem9 = require("viem"); var RWA_MANAGER_ABI = [ { "type": "function", "name": "createRWAToken", "inputs": [ { "name": "name", "type": "string", "internalType": "string" }, { "name": "symbol", "type": "string", "internalType": "string" }, { "name": "metadata", "type": "tuple", "internalType": "struct RWAToken.AssetMetadata", "components": [ { "name": "assetType", "type": "string",