@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
JavaScript
#!/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",