nyxora 1.1.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.marketAnalysisToolDefinition = void 0;
4
+ exports.analyzeMarket = analyzeMarket;
5
+ const tokens_1 = require("../utils/tokens");
6
+ async function analyzeMarket(chainName, tokenAddressOrSymbol) {
7
+ try {
8
+ let tokenAddress = tokenAddressOrSymbol;
9
+ try {
10
+ tokenAddress = (0, tokens_1.resolveToken)(tokenAddressOrSymbol, chainName);
11
+ if (tokenAddress === "0x0000000000000000000000000000000000000000") {
12
+ // For native token, we should pass WETH / wrapped version to Dexscreener usually,
13
+ // because native token itself doesn't have a pair on most DEXes directly.
14
+ tokenAddress = (0, tokens_1.resolveToken)("W" + tokenAddressOrSymbol, chainName); // e.g. WETH, WBNB
15
+ }
16
+ }
17
+ catch (e) {
18
+ // If it fails to resolve, assume it's already an address or let DexScreener handle it (though DexScreener needs exact address)
19
+ }
20
+ const url = `https://api.dexscreener.com/latest/dex/tokens/${tokenAddress}`;
21
+ const res = await fetch(url);
22
+ if (!res.ok) {
23
+ throw new Error(`DexScreener API Error: ${res.statusText}`);
24
+ }
25
+ const data = await res.json();
26
+ if (!data.pairs || data.pairs.length === 0) {
27
+ return `No market data found for token ${tokenAddressOrSymbol} on DexScreener.`;
28
+ }
29
+ // Filter pairs by chain if possible, Dexscreener chain IDs are strings like 'ethereum', 'bsc', 'base', 'arbitrum', 'optimism'
30
+ let pair = data.pairs.find((p) => p.chainId === chainName);
31
+ if (!pair) {
32
+ pair = data.pairs[0]; // Fallback to the most liquid pair anywhere
33
+ }
34
+ let report = `📈 **Market Analysis for ${pair.baseToken.name} (${pair.baseToken.symbol})** on ${pair.chainId.toUpperCase()}\n\n`;
35
+ report += `**Price:** $${pair.priceUsd}\n`;
36
+ report += `**Liquidity (USD):** $${Number(pair.liquidity?.usd || 0).toLocaleString()}\n`;
37
+ report += `**FDV:** $${Number(pair.fdv || 0).toLocaleString()}\n`;
38
+ report += `**24h Volume:** $${Number(pair.volume?.h24 || 0).toLocaleString()}\n\n`;
39
+ report += `**Price Change:**\n`;
40
+ report += `- 5m: ${pair.priceChange?.m5}% \n`;
41
+ report += `- 1h: ${pair.priceChange?.h1}% \n`;
42
+ report += `- 24h: ${pair.priceChange?.h24}% \n\n`;
43
+ report += `**DEX:** ${pair.dexId} (${pair.url})\n`;
44
+ return report;
45
+ }
46
+ catch (error) {
47
+ return `Failed to analyze market: ${error.message}`;
48
+ }
49
+ }
50
+ exports.marketAnalysisToolDefinition = {
51
+ type: "function",
52
+ function: {
53
+ name: "analyze_market",
54
+ description: "Fetches live market data (Price, Liquidity, Volume, FDV, Price Change) for a token using DexScreener.",
55
+ parameters: {
56
+ type: "object",
57
+ properties: {
58
+ chainName: {
59
+ type: "string",
60
+ enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
61
+ description: "The blockchain network",
62
+ },
63
+ tokenAddressOrSymbol: {
64
+ type: "string",
65
+ description: "The token symbol (e.g. USDC, PEPE) or contract address to analyze.",
66
+ }
67
+ },
68
+ required: ["chainName", "tokenAddressOrSymbol"],
69
+ },
70
+ },
71
+ };
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mintNftToolDefinition = void 0;
4
+ exports.prepareMintNft = prepareMintNft;
5
+ exports.executeMintNft = executeMintNft;
6
+ const viem_1 = require("viem");
7
+ const config_1 = require("../config");
8
+ const transactionManager_1 = require("../../agent/transactionManager");
9
+ async function prepareMintNft(chainName, contractAddress, functionSignature, argsStr, valueEth = "0") {
10
+ try {
11
+ const publicClient = (0, config_1.getPublicClient)(chainName);
12
+ const walletClient = (0, config_1.getWalletClient)(chainName);
13
+ const account = walletClient.account;
14
+ // Parse arguments from JSON or comma separated
15
+ let parsedArgs = [];
16
+ try {
17
+ parsedArgs = JSON.parse(argsStr);
18
+ if (!Array.isArray(parsedArgs)) {
19
+ parsedArgs = [parsedArgs];
20
+ }
21
+ }
22
+ catch (e) {
23
+ if (argsStr.includes(",")) {
24
+ parsedArgs = argsStr.split(",").map(i => i.trim());
25
+ }
26
+ else {
27
+ parsedArgs = [argsStr.trim()];
28
+ }
29
+ }
30
+ let cleanSig = functionSignature.trim();
31
+ if (!cleanSig.startsWith("function ")) {
32
+ cleanSig = `function ${cleanSig}`;
33
+ }
34
+ // ensure it's payable or nonpayable properly. Viem parseAbi requires it to be payable if we send value
35
+ if (valueEth !== "0" && !cleanSig.includes("payable")) {
36
+ cleanSig = `${cleanSig} payable`;
37
+ }
38
+ const abi = (0, viem_1.parseAbi)([cleanSig]);
39
+ // Extract function name
40
+ const match = cleanSig.match(/function\s+([a-zA-Z0-9_]+)\s*\(/);
41
+ if (!match)
42
+ throw new Error("Invalid function signature format. Use e.g. 'mint(uint256)'");
43
+ const functionName = match[1];
44
+ const valueWei = (0, viem_1.parseEther)(valueEth);
45
+ const { request } = await publicClient.simulateContract({
46
+ account,
47
+ address: contractAddress,
48
+ abi,
49
+ functionName,
50
+ args: parsedArgs,
51
+ value: valueWei,
52
+ });
53
+ // @ts-ignore
54
+ const gasEstimate = request.gas || 100000n;
55
+ const tx = transactionManager_1.txManager.createPendingTransaction('mint', chainName, {
56
+ contractAddress,
57
+ abi,
58
+ functionName,
59
+ parsedArgs,
60
+ valueWei: valueWei.toString(),
61
+ gasEstimate: gasEstimate.toString()
62
+ });
63
+ return `TRANSACTION_PENDING: Simulated NFT Minting successfully. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve.`;
64
+ }
65
+ catch (error) {
66
+ return `Simulation failed! Cannot prepare mint. Error: ${error.message}`;
67
+ }
68
+ }
69
+ async function executeMintNft(chainName, params) {
70
+ try {
71
+ const client = (0, config_1.getWalletClient)(chainName);
72
+ const { contractAddress, abi, functionName, parsedArgs, valueWei } = params;
73
+ // @ts-ignore
74
+ const hash = await client.writeContract({
75
+ account: client.account,
76
+ chain: client.chain,
77
+ address: contractAddress,
78
+ abi,
79
+ functionName,
80
+ args: parsedArgs,
81
+ value: BigInt(valueWei),
82
+ });
83
+ return `Mint successful. Tx Hash: ${hash}`;
84
+ }
85
+ catch (error) {
86
+ return `Failed to execute mint: ${error.message}`;
87
+ }
88
+ }
89
+ exports.mintNftToolDefinition = {
90
+ type: "function",
91
+ function: {
92
+ name: "mint_nft",
93
+ description: "Mint an NFT by calling a specific smart contract function. Automatically simulates the transaction first.",
94
+ parameters: {
95
+ type: "object",
96
+ properties: {
97
+ chainName: {
98
+ type: "string",
99
+ enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
100
+ description: "The blockchain network",
101
+ },
102
+ contractAddress: {
103
+ type: "string",
104
+ description: "The NFT smart contract address (0x...)",
105
+ },
106
+ functionSignature: {
107
+ type: "string",
108
+ description: "The function signature to call, e.g., 'mint(uint256)' or 'claim(address,uint256)'",
109
+ },
110
+ argsStr: {
111
+ type: "string",
112
+ description: "A JSON array of arguments for the function, e.g., '[1]' or '[\"0x123...\", 2]'",
113
+ },
114
+ valueEth: {
115
+ type: "string",
116
+ description: "The amount of native ETH/BNB to send as payment for the mint. Use '0' if it's a free mint.",
117
+ }
118
+ },
119
+ required: ["chainName", "contractAddress", "functionSignature", "argsStr", "valueEth"],
120
+ },
121
+ },
122
+ };
@@ -1,77 +1,217 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.swapTokenToolDefinition = void 0;
4
- exports.swapToken = swapToken;
4
+ exports.prepareSwapToken = prepareSwapToken;
5
5
  exports.executeSwap = executeSwap;
6
+ const viem_1 = require("viem");
7
+ const config_1 = require("../config");
8
+ const transactionManager_1 = require("../../agent/transactionManager");
9
+ const tokens_1 = require("../utils/tokens");
10
+ const CHAIN_IDS = {
11
+ ethereum: 1,
12
+ base: 8453,
13
+ bsc: 56,
14
+ arbitrum: 42161,
15
+ optimism: 10,
16
+ sepolia: 11155111,
17
+ };
18
+ async function getLifiQuote(fromChainId, toChainId, fromToken, toToken, amountWei, userAddress, slippage) {
19
+ const url = new URL('https://li.quest/v1/quote');
20
+ url.searchParams.append('fromChain', fromChainId.toString());
21
+ url.searchParams.append('toChain', toChainId.toString());
22
+ url.searchParams.append('fromToken', fromToken);
23
+ url.searchParams.append('toToken', toToken);
24
+ url.searchParams.append('fromAmount', amountWei);
25
+ url.searchParams.append('fromAddress', userAddress);
26
+ url.searchParams.append('slippage', slippage.toString());
27
+ const res = await fetch(url.toString());
28
+ if (!res.ok) {
29
+ const err = await res.json().catch(() => ({}));
30
+ throw new Error(`Li.Fi API Error: ${err.message || res.statusText}`);
31
+ }
32
+ return await res.json();
33
+ }
34
+ async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountWei, userAddress) {
35
+ const isTestnet = fromChainId === 11155111 || toChainId === 11155111;
36
+ const baseUrl = isTestnet ? "https://api.testnets.relay.link" : "https://api.relay.link";
37
+ const body = {
38
+ user: userAddress,
39
+ originChainId: fromChainId,
40
+ destinationChainId: toChainId,
41
+ originCurrency: fromToken,
42
+ destinationCurrency: toToken,
43
+ amount: amountWei,
44
+ tradeType: "EXACT_INPUT"
45
+ };
46
+ const res = await fetch(`${baseUrl}/quote`, {
47
+ method: "POST",
48
+ headers: { "Content-Type": "application/json" },
49
+ body: JSON.stringify(body)
50
+ });
51
+ if (!res.ok) {
52
+ const err = await res.json().catch(() => ({}));
53
+ throw new Error(`Relay API Error: ${err.message || res.statusText}`);
54
+ }
55
+ return await res.json();
56
+ }
57
+ async function prepareSwapToken(chainName, fromToken, toToken, amountStr, mode = "auto", providerName = "lifi", slippagePercent = 0.5) {
58
+ try {
59
+ const publicClient = (0, config_1.getPublicClient)(chainName);
60
+ const walletClient = (0, config_1.getWalletClient)(chainName);
61
+ const account = walletClient.account;
62
+ const chainId = CHAIN_IDS[chainName];
63
+ const fromTokenAddress = (0, tokens_1.resolveToken)(fromToken, chainName);
64
+ const toTokenAddress = (0, tokens_1.resolveToken)(toToken, chainName);
65
+ const isNativeIn = fromTokenAddress === "0x0000000000000000000000000000000000000000";
66
+ // Get decimals
67
+ let decimals = 18;
68
+ if (!isNativeIn) {
69
+ decimals = await publicClient.readContract({
70
+ address: fromTokenAddress,
71
+ abi: tokens_1.ERC20_ABI,
72
+ functionName: 'decimals',
73
+ });
74
+ }
75
+ const amountWei = (0, viem_1.parseUnits)(amountStr, decimals).toString();
76
+ let txRequest = null;
77
+ let approvalAddress = null;
78
+ let expectedOutputStr = "";
79
+ let actualProvider = mode === "auto" ? "lifi" : providerName;
80
+ const isTestnet = chainId === 11155111;
81
+ // --- SEPOLIA TESTNET MOCK ---
82
+ if (isTestnet) {
83
+ const mockGasLimit = "150000";
84
+ expectedOutputStr = "MOCK_TEST_AMOUNT";
85
+ const tx = transactionManager_1.txManager.createPendingTransaction('swap', chainName, {
86
+ txRequest: { to: fromTokenAddress, data: "0x", value: amountWei, gasLimit: mockGasLimit },
87
+ needsApprove: false,
88
+ fromTokenAddress,
89
+ approvalAddress: null,
90
+ amountWei
91
+ });
92
+ return `TRANSACTION_PENDING: Swap simulated via TESTNET_MOCK. Expected Output: ~${expectedOutputStr} ${toToken.toUpperCase()}. Gas est: ${mockGasLimit}. Transaction ID: ${tx.id}. Wait for user to approve.`;
93
+ }
94
+ // --- END MOCK ---
95
+ if (actualProvider === "lifi") {
96
+ const quote = await getLifiQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
97
+ txRequest = quote.transactionRequest;
98
+ approvalAddress = quote.estimate.approvalAddress;
99
+ const toDecimals = quote.action.toToken.decimals;
100
+ expectedOutputStr = (0, viem_1.formatUnits)(BigInt(quote.estimate.toAmount), toDecimals);
101
+ }
102
+ else if (actualProvider === "relay") {
103
+ const relayQuote = await getRelayQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, account.address);
104
+ if (!relayQuote.steps || relayQuote.steps.length === 0)
105
+ throw new Error("No route found by Relay.");
106
+ // Relay returns steps. We need to find the main transaction step.
107
+ const txStep = relayQuote.steps.find((s) => s.id === "execute");
108
+ if (!txStep || !txStep.items || txStep.items.length === 0)
109
+ throw new Error("Relay steps invalid.");
110
+ const item = txStep.items[0];
111
+ txRequest = item.data;
112
+ // Usually Relay route approval to `txRequest.to` if it's not native
113
+ if (!isNativeIn && txRequest.to.toLowerCase() !== fromTokenAddress.toLowerCase()) {
114
+ approvalAddress = txRequest.to;
115
+ }
116
+ expectedOutputStr = relayQuote.details?.currencyOut?.amountFormatted || "Unknown";
117
+ }
118
+ // Check allowance early so we know if we need to auto-approve
119
+ let needsApprove = false;
120
+ if (!isNativeIn && approvalAddress && approvalAddress !== "0x0000000000000000000000000000000000000000") {
121
+ const allowance = await publicClient.readContract({
122
+ address: fromTokenAddress,
123
+ abi: tokens_1.ERC20_ABI,
124
+ functionName: 'allowance',
125
+ args: [account.address, approvalAddress],
126
+ });
127
+ if (allowance < BigInt(amountWei)) {
128
+ needsApprove = true;
129
+ }
130
+ }
131
+ const tx = transactionManager_1.txManager.createPendingTransaction('swap', chainName, {
132
+ txRequest,
133
+ needsApprove,
134
+ fromTokenAddress,
135
+ approvalAddress,
136
+ amountWei
137
+ });
138
+ return `TRANSACTION_PENDING: Swap simulated via ${actualProvider.toUpperCase()}. Expected Output: ~${expectedOutputStr} ${toToken.toUpperCase()}. ${needsApprove ? '(Auto-Approve required) ' : ''}Gas est: ${txRequest.gasLimit || 'auto'}. Transaction ID: ${tx.id}. Wait for user to approve.`;
139
+ }
140
+ catch (error) {
141
+ return `Simulation failed! Cannot prepare swap. Error: ${error.message}`;
142
+ }
143
+ }
144
+ async function executeSwap(chainName, params) {
145
+ try {
146
+ const client = (0, config_1.getWalletClient)(chainName);
147
+ const publicClient = (0, config_1.getPublicClient)(chainName);
148
+ const { txRequest, needsApprove, fromTokenAddress, approvalAddress, amountWei } = params;
149
+ if (needsApprove) {
150
+ // Auto-Approve Transaction
151
+ const approveHash = await client.writeContract({
152
+ account: client.account,
153
+ chain: client.chain,
154
+ address: fromTokenAddress,
155
+ abi: tokens_1.ERC20_ABI,
156
+ functionName: 'approve',
157
+ // Max Uint256
158
+ args: [approvalAddress, 115792089237316195423570985008687907853269984665640564039457584007913129639935n],
159
+ });
160
+ await publicClient.waitForTransactionReceipt({ hash: approveHash });
161
+ }
162
+ // Execute Swap
163
+ // @ts-ignore
164
+ const txHash = await client.sendTransaction({
165
+ account: client.account,
166
+ chain: client.chain,
167
+ to: txRequest.to,
168
+ data: txRequest.data,
169
+ value: txRequest.value ? BigInt(txRequest.value) : 0n,
170
+ gas: txRequest.gasLimit ? (BigInt(txRequest.gasLimit) * 12n / 10n) : undefined // 20% buffer
171
+ });
172
+ return `Swap successful. Tx Hash: ${txHash}`;
173
+ }
174
+ catch (error) {
175
+ return `Failed to execute swap: ${error.message}`;
176
+ }
177
+ }
6
178
  exports.swapTokenToolDefinition = {
7
179
  type: "function",
8
180
  function: {
9
181
  name: "swap_token",
10
- description: "Executes a decentralized token swap (DEX) to exchange one cryptocurrency for another.",
182
+ description: "Executes a decentralized token swap (DEX) to exchange one cryptocurrency for another using Li.Fi or Relay. Automatically simulates the swap to fetch quotes.",
11
183
  parameters: {
12
184
  type: "object",
13
185
  properties: {
14
186
  chainName: {
15
187
  type: "string",
16
- description: "The blockchain network (e.g., 'ethereum', 'base', 'arbitrum', 'sepolia')",
188
+ enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
189
+ description: "The blockchain network",
17
190
  },
18
191
  fromToken: {
19
192
  type: "string",
20
- description: "The token symbol to sell/swap from (e.g., 'ETH', 'USDC')",
193
+ description: "The token symbol to sell (e.g., 'ETH', 'USDC')",
21
194
  },
22
195
  toToken: {
23
196
  type: "string",
24
- description: "The token symbol to buy/swap to (e.g., 'USDC', 'UNI')",
197
+ description: "The token symbol to buy (e.g., 'USDC', 'UNI')",
25
198
  },
26
- amount: {
27
- type: "number",
199
+ amountStr: {
200
+ type: "string",
28
201
  description: "The amount of fromToken to swap",
29
202
  },
203
+ mode: {
204
+ type: "string",
205
+ enum: ["auto", "manual"],
206
+ description: "auto uses lifi. manual uses the specified provider."
207
+ },
208
+ providerName: {
209
+ type: "string",
210
+ enum: ["lifi", "relay"],
211
+ description: "Used if mode is manual."
212
+ }
30
213
  },
31
- required: ["chainName", "fromToken", "toToken", "amount"],
214
+ required: ["chainName", "fromToken", "toToken", "amountStr"],
32
215
  },
33
216
  },
34
217
  };
35
- const transactionManager_1 = require("../../agent/transactionManager");
36
- async function swapToken(chainName, fromToken, toToken, amount) {
37
- const tx = transactionManager_1.txManager.createPendingTransaction('swap', chainName, { fromToken, toToken, amount });
38
- return `TRANSACTION_PENDING: I have prepared the token swap. Transaction ID: ${tx.id}. Wait for user to approve.`;
39
- }
40
- async function executeSwap(chainName, fromToken, toToken, amount) {
41
- try {
42
- // Generate simulated exchange rate for testnet/demo purposes
43
- let rate = 1.0;
44
- // Simple mock rates based on common pairs
45
- if (fromToken.toUpperCase() === 'ETH' && toToken.toUpperCase() === 'USDC')
46
- rate = 3200;
47
- if (fromToken.toUpperCase() === 'USDC' && toToken.toUpperCase() === 'ETH')
48
- rate = 1 / 3200;
49
- if (fromToken.toUpperCase() === 'ETH' && toToken.toUpperCase() === 'LINK')
50
- rate = 200;
51
- if (fromToken.toUpperCase() === 'SOL' && toToken.toUpperCase() === 'USDC')
52
- rate = 150;
53
- // Add a slight random variance (±1%) to simulate live market slippage
54
- const variance = 1 + (Math.random() * 0.02 - 0.01);
55
- const finalRate = rate * variance;
56
- const toAmount = amount * finalRate;
57
- // Simulate transaction execution delay
58
- await new Promise(resolve => setTimeout(resolve, 2500));
59
- // Generate mock transaction hash
60
- const txHash = '0x' + Array.from({ length: 64 }, () => Math.floor(Math.random() * 16).toString(16)).join('');
61
- const swapResult = {
62
- chain: chainName,
63
- fromToken: fromToken.toUpperCase(),
64
- toToken: toToken.toUpperCase(),
65
- fromAmount: amount.toFixed(4),
66
- toAmount: toAmount.toFixed(4),
67
- exchangeRate: finalRate.toFixed(6),
68
- gasFee: (Math.random() * 0.005).toFixed(4) + ' ETH',
69
- txHash: txHash,
70
- status: 'SUCCESS'
71
- };
72
- return JSON.stringify(swapResult);
73
- }
74
- catch (error) {
75
- return JSON.stringify({ error: `Failed to execute swap: ${error.message}` });
76
- }
77
- }
@@ -1,35 +1,109 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transferToolDefinition = void 0;
4
- exports.transferNative = transferNative;
4
+ exports.prepareTransfer = prepareTransfer;
5
5
  exports.executeTransfer = executeTransfer;
6
6
  const viem_1 = require("viem");
7
7
  const config_1 = require("../config");
8
8
  const transactionManager_1 = require("../../agent/transactionManager");
9
- async function transferNative(chainName, toAddress, amountEth) {
10
- const tx = transactionManager_1.txManager.createPendingTransaction('transfer', chainName, { toAddress, amountEth });
11
- return `TRANSACTION_PENDING: I have prepared the transfer. Transaction ID: ${tx.id}. Wait for user to approve.`;
9
+ const tokens_1 = require("../utils/tokens");
10
+ async function prepareTransfer(chainName, toAddress, amountStr, token) {
11
+ try {
12
+ const publicClient = (0, config_1.getPublicClient)(chainName);
13
+ const walletClient = (0, config_1.getWalletClient)(chainName);
14
+ const account = walletClient.account;
15
+ let tokenAddress = "0x0000000000000000000000000000000000000000";
16
+ let isNative = true;
17
+ let decimals = 18;
18
+ let symbol = "ETH/BNB"; // Generic fallback for native
19
+ if (token) {
20
+ tokenAddress = (0, tokens_1.resolveToken)(token, chainName);
21
+ isNative = tokenAddress === "0x0000000000000000000000000000000000000000";
22
+ }
23
+ let gasEstimate = 0n;
24
+ if (isNative) {
25
+ // Simulate Native Transfer
26
+ const value = (0, viem_1.parseEther)(amountStr);
27
+ gasEstimate = await publicClient.estimateGas({
28
+ account,
29
+ to: toAddress,
30
+ value,
31
+ });
32
+ }
33
+ else {
34
+ // Simulate ERC-20 Transfer
35
+ decimals = await publicClient.readContract({
36
+ address: tokenAddress,
37
+ abi: tokens_1.ERC20_ABI,
38
+ functionName: 'decimals',
39
+ });
40
+ symbol = await publicClient.readContract({
41
+ address: tokenAddress,
42
+ abi: tokens_1.ERC20_ABI,
43
+ functionName: 'symbol',
44
+ }).catch(() => token || "TOKEN");
45
+ const value = (0, viem_1.parseUnits)(amountStr, decimals);
46
+ const { request } = await publicClient.simulateContract({
47
+ account,
48
+ address: tokenAddress,
49
+ abi: tokens_1.ERC20_ABI,
50
+ functionName: 'transfer',
51
+ args: [toAddress, value],
52
+ });
53
+ // @ts-ignore
54
+ gasEstimate = request.gas || 50000n;
55
+ }
56
+ const tx = transactionManager_1.txManager.createPendingTransaction('transfer', chainName, {
57
+ toAddress,
58
+ amountStr,
59
+ tokenAddress,
60
+ isNative,
61
+ decimals,
62
+ gasEstimate: gasEstimate.toString()
63
+ });
64
+ const tokenName = isNative ? "Native Token" : symbol;
65
+ return `TRANSACTION_PENDING: I have prepared the ${tokenName} transfer and simulated it successfully. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve.`;
66
+ }
67
+ catch (error) {
68
+ return `Simulation failed! Cannot prepare transfer. Error: ${error.message}`;
69
+ }
12
70
  }
13
- async function executeTransfer(chainName, toAddress, amountEth) {
71
+ async function executeTransfer(chainName, params) {
14
72
  try {
15
73
  const client = (0, config_1.getWalletClient)(chainName);
16
- const hash = await client.sendTransaction({
17
- account: client.account,
18
- chain: client.chain,
19
- to: toAddress,
20
- value: (0, viem_1.parseEther)(amountEth),
21
- });
74
+ const { toAddress, amountStr, tokenAddress, isNative, decimals } = params;
75
+ let hash;
76
+ if (isNative) {
77
+ hash = await client.sendTransaction({
78
+ account: client.account,
79
+ chain: client.chain,
80
+ to: toAddress,
81
+ value: (0, viem_1.parseEther)(amountStr),
82
+ });
83
+ }
84
+ else {
85
+ const value = (0, viem_1.parseUnits)(amountStr, decimals);
86
+ // @ts-ignore
87
+ hash = await client.writeContract({
88
+ account: client.account,
89
+ chain: client.chain,
90
+ address: tokenAddress,
91
+ abi: tokens_1.ERC20_ABI,
92
+ functionName: 'transfer',
93
+ args: [toAddress, value],
94
+ });
95
+ }
22
96
  return `Transaction successful. Hash: ${hash}`;
23
97
  }
24
98
  catch (error) {
25
- return `Failed to transfer: ${error.message}`;
99
+ return `Failed to execute transfer: ${error.message}`;
26
100
  }
27
101
  }
28
102
  exports.transferToolDefinition = {
29
103
  type: "function",
30
104
  function: {
31
- name: "transfer_native",
32
- description: "Transfer native tokens (ETH, BNB, etc.) from the agent's wallet to a destination address.",
105
+ name: "transfer_token",
106
+ description: "Transfer native tokens (ETH, BNB, etc.) or ERC-20 tokens from the agent's wallet to a destination address. Automatically performs on-chain simulation before requesting user approval.",
33
107
  parameters: {
34
108
  type: "object",
35
109
  properties: {
@@ -42,12 +116,16 @@ exports.transferToolDefinition = {
42
116
  type: "string",
43
117
  description: "The destination 0x... wallet address."
44
118
  },
45
- amountEth: {
119
+ amountStr: {
120
+ type: "string",
121
+ description: "The amount of tokens to send (e.g. '0.01')."
122
+ },
123
+ token: {
46
124
  type: "string",
47
- description: "The amount of tokens to send in ETH units (e.g. '0.01')."
125
+ description: "Optional. The token symbol (e.g. USDC, USDT) or contract address (0x...). If omitted, sends the native coin."
48
126
  }
49
127
  },
50
- required: ["chainName", "toAddress", "amountEth"]
128
+ required: ["chainName", "toAddress", "amountStr"]
51
129
  }
52
130
  }
53
131
  };