nyxora 1.1.8 → 1.2.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.
- package/dist/agent/reasoning.js +46 -5
- package/dist/gateway/server.js +19 -6
- package/dist/gateway/telegram.js +14 -2
- package/dist/web3/skills/bridgeToken.js +199 -0
- package/dist/web3/skills/customTx.js +93 -0
- package/dist/web3/skills/getBalance.js +42 -5
- package/dist/web3/skills/mintNft.js +122 -0
- package/dist/web3/skills/swapToken.js +174 -51
- package/dist/web3/skills/transfer.js +95 -17
- package/dist/web3/utils/tokens.js +109 -0
- package/package.json +1 -1
package/dist/agent/reasoning.js
CHANGED
|
@@ -14,6 +14,9 @@ const getBalance_1 = require("../web3/skills/getBalance");
|
|
|
14
14
|
const transfer_1 = require("../web3/skills/transfer");
|
|
15
15
|
const getPrice_1 = require("../web3/skills/getPrice");
|
|
16
16
|
const swapToken_1 = require("../web3/skills/swapToken");
|
|
17
|
+
const bridgeToken_1 = require("../web3/skills/bridgeToken");
|
|
18
|
+
const mintNft_1 = require("../web3/skills/mintNft");
|
|
19
|
+
const customTx_1 = require("../web3/skills/customTx");
|
|
17
20
|
const paths_1 = require("../config/paths");
|
|
18
21
|
exports.logger = new logger_1.Logger();
|
|
19
22
|
let currentKeyIndex = 0;
|
|
@@ -140,7 +143,15 @@ async function processUserInput(input, role = 'user') {
|
|
|
140
143
|
model: config.llm.model,
|
|
141
144
|
temperature: config.llm.temperature,
|
|
142
145
|
messages: messages,
|
|
143
|
-
tools: [
|
|
146
|
+
tools: [
|
|
147
|
+
getBalance_1.getBalanceToolDefinition,
|
|
148
|
+
transfer_1.transferToolDefinition,
|
|
149
|
+
getPrice_1.getPriceToolDefinition,
|
|
150
|
+
swapToken_1.swapTokenToolDefinition,
|
|
151
|
+
bridgeToken_1.bridgeTokenToolDefinition,
|
|
152
|
+
mintNft_1.mintNftToolDefinition,
|
|
153
|
+
customTx_1.customTxToolDefinition
|
|
154
|
+
],
|
|
144
155
|
tool_choice: "auto",
|
|
145
156
|
});
|
|
146
157
|
const responseMessage = response.choices[0].message;
|
|
@@ -162,7 +173,7 @@ async function processUserInput(input, role = 'user') {
|
|
|
162
173
|
const toolCall = _toolCall;
|
|
163
174
|
if (toolCall.function.name === 'get_balance') {
|
|
164
175
|
const args = JSON.parse(toolCall.function.arguments);
|
|
165
|
-
const balanceResult = await (0, getBalance_1.getBalance)(args.chainName, args.address);
|
|
176
|
+
const balanceResult = await (0, getBalance_1.getBalance)(args.chainName, args.address, args.token);
|
|
166
177
|
exports.logger.addEntry({
|
|
167
178
|
role: 'tool',
|
|
168
179
|
tool_call_id: toolCall.id,
|
|
@@ -170,9 +181,9 @@ async function processUserInput(input, role = 'user') {
|
|
|
170
181
|
content: balanceResult,
|
|
171
182
|
});
|
|
172
183
|
}
|
|
173
|
-
else if (toolCall.function.name === 'transfer_native') {
|
|
184
|
+
else if (toolCall.function.name === 'transfer_token' || toolCall.function.name === 'transfer_native') {
|
|
174
185
|
const args = JSON.parse(toolCall.function.arguments);
|
|
175
|
-
const transferResult = await (0, transfer_1.
|
|
186
|
+
const transferResult = await (0, transfer_1.prepareTransfer)(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
|
|
176
187
|
exports.logger.addEntry({
|
|
177
188
|
role: 'tool',
|
|
178
189
|
tool_call_id: toolCall.id,
|
|
@@ -192,7 +203,7 @@ async function processUserInput(input, role = 'user') {
|
|
|
192
203
|
}
|
|
193
204
|
else if (toolCall.function.name === 'swap_token') {
|
|
194
205
|
const args = JSON.parse(toolCall.function.arguments);
|
|
195
|
-
const swapResult = await (0, swapToken_1.
|
|
206
|
+
const swapResult = await (0, swapToken_1.prepareSwapToken)(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
|
|
196
207
|
exports.logger.addEntry({
|
|
197
208
|
role: 'tool',
|
|
198
209
|
tool_call_id: toolCall.id,
|
|
@@ -200,6 +211,36 @@ async function processUserInput(input, role = 'user') {
|
|
|
200
211
|
content: swapResult,
|
|
201
212
|
});
|
|
202
213
|
}
|
|
214
|
+
else if (toolCall.function.name === 'bridge_token') {
|
|
215
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
216
|
+
const bridgeResult = await (0, bridgeToken_1.prepareBridgeToken)(args.fromChainName, args.toChainName, args.fromToken, args.toToken, args.amountStr, args.mode, args.providerName);
|
|
217
|
+
exports.logger.addEntry({
|
|
218
|
+
role: 'tool',
|
|
219
|
+
tool_call_id: toolCall.id,
|
|
220
|
+
name: toolCall.function.name,
|
|
221
|
+
content: bridgeResult,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
else if (toolCall.function.name === 'mint_nft') {
|
|
225
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
226
|
+
const mintResult = await (0, mintNft_1.prepareMintNft)(args.chainName, args.contractAddress, args.functionSignature, args.argsStr, args.valueEth);
|
|
227
|
+
exports.logger.addEntry({
|
|
228
|
+
role: 'tool',
|
|
229
|
+
tool_call_id: toolCall.id,
|
|
230
|
+
name: toolCall.function.name,
|
|
231
|
+
content: mintResult,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
else if (toolCall.function.name === 'custom_tx') {
|
|
235
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
236
|
+
const customResult = await (0, customTx_1.prepareCustomTx)(args.chainName, args.toAddress, args.dataHex, args.valueEth, args.gasLimitStr);
|
|
237
|
+
exports.logger.addEntry({
|
|
238
|
+
role: 'tool',
|
|
239
|
+
tool_call_id: toolCall.id,
|
|
240
|
+
name: toolCall.function.name,
|
|
241
|
+
content: customResult,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
203
244
|
}
|
|
204
245
|
// Second call to get the final answer after tool execution
|
|
205
246
|
const secondMessages = [
|
package/dist/gateway/server.js
CHANGED
|
@@ -15,9 +15,10 @@ const transactionManager_1 = require("../agent/transactionManager");
|
|
|
15
15
|
const transfer_1 = require("../web3/skills/transfer");
|
|
16
16
|
const swapToken_1 = require("../web3/skills/swapToken");
|
|
17
17
|
const getBalance_1 = require("../web3/skills/getBalance");
|
|
18
|
-
const transfer_2 = require("../web3/skills/transfer");
|
|
19
18
|
const getPrice_1 = require("../web3/skills/getPrice");
|
|
20
|
-
const
|
|
19
|
+
const bridgeToken_1 = require("../web3/skills/bridgeToken");
|
|
20
|
+
const mintNft_1 = require("../web3/skills/mintNft");
|
|
21
|
+
const customTx_1 = require("../web3/skills/customTx");
|
|
21
22
|
const telegram_1 = require("./telegram");
|
|
22
23
|
const formatter_1 = require("../utils/formatter");
|
|
23
24
|
// Intercept console.log and console.error
|
|
@@ -94,9 +95,12 @@ app.get('/api/logs', (req, res) => {
|
|
|
94
95
|
app.get('/api/skills', (req, res) => {
|
|
95
96
|
res.json([
|
|
96
97
|
getBalance_1.getBalanceToolDefinition,
|
|
97
|
-
|
|
98
|
+
transfer_1.transferToolDefinition,
|
|
98
99
|
getPrice_1.getPriceToolDefinition,
|
|
99
|
-
|
|
100
|
+
swapToken_1.swapTokenToolDefinition,
|
|
101
|
+
bridgeToken_1.bridgeTokenToolDefinition,
|
|
102
|
+
mintNft_1.mintNftToolDefinition,
|
|
103
|
+
customTx_1.customTxToolDefinition
|
|
100
104
|
]);
|
|
101
105
|
});
|
|
102
106
|
app.get('/api/transactions', (req, res) => {
|
|
@@ -110,10 +114,19 @@ app.post('/api/transactions/:id/approve', async (req, res) => {
|
|
|
110
114
|
try {
|
|
111
115
|
let result = '';
|
|
112
116
|
if (tx.type === 'transfer') {
|
|
113
|
-
result = await (0, transfer_1.executeTransfer)(tx.chainName, tx.details
|
|
117
|
+
result = await (0, transfer_1.executeTransfer)(tx.chainName, tx.details);
|
|
114
118
|
}
|
|
115
119
|
else if (tx.type === 'swap') {
|
|
116
|
-
result = await (0, swapToken_1.executeSwap)(tx.chainName, tx.details
|
|
120
|
+
result = await (0, swapToken_1.executeSwap)(tx.chainName, tx.details);
|
|
121
|
+
}
|
|
122
|
+
else if (tx.type === 'bridge') {
|
|
123
|
+
result = await (0, bridgeToken_1.executeBridge)(tx.chainName, tx.details);
|
|
124
|
+
}
|
|
125
|
+
else if (tx.type === 'mint') {
|
|
126
|
+
result = await (0, mintNft_1.executeMintNft)(tx.chainName, tx.details);
|
|
127
|
+
}
|
|
128
|
+
else if (tx.type === 'custom') {
|
|
129
|
+
result = await (0, customTx_1.executeCustomTx)(tx.chainName, tx.details);
|
|
117
130
|
}
|
|
118
131
|
transactionManager_1.txManager.updateStatus(id, 'executed', result);
|
|
119
132
|
// Add programmatic beautiful message directly to chat
|
package/dist/gateway/telegram.js
CHANGED
|
@@ -10,6 +10,9 @@ const parser_1 = require("../config/parser");
|
|
|
10
10
|
const transactionManager_1 = require("../agent/transactionManager");
|
|
11
11
|
const transfer_1 = require("../web3/skills/transfer");
|
|
12
12
|
const swapToken_1 = require("../web3/skills/swapToken");
|
|
13
|
+
const bridgeToken_1 = require("../web3/skills/bridgeToken");
|
|
14
|
+
const mintNft_1 = require("../web3/skills/mintNft");
|
|
15
|
+
const customTx_1 = require("../web3/skills/customTx");
|
|
13
16
|
const formatter_1 = require("../utils/formatter");
|
|
14
17
|
function startTelegramBot() {
|
|
15
18
|
const config = (0, parser_1.loadConfig)();
|
|
@@ -73,10 +76,19 @@ function startTelegramBot() {
|
|
|
73
76
|
try {
|
|
74
77
|
let result = '';
|
|
75
78
|
if (tx.type === 'transfer') {
|
|
76
|
-
result = await (0, transfer_1.executeTransfer)(tx.chainName, tx.details
|
|
79
|
+
result = await (0, transfer_1.executeTransfer)(tx.chainName, tx.details);
|
|
77
80
|
}
|
|
78
81
|
else if (tx.type === 'swap') {
|
|
79
|
-
result = await (0, swapToken_1.executeSwap)(tx.chainName, tx.details
|
|
82
|
+
result = await (0, swapToken_1.executeSwap)(tx.chainName, tx.details);
|
|
83
|
+
}
|
|
84
|
+
else if (tx.type === 'bridge') {
|
|
85
|
+
result = await (0, bridgeToken_1.executeBridge)(tx.chainName, tx.details);
|
|
86
|
+
}
|
|
87
|
+
else if (tx.type === 'mint') {
|
|
88
|
+
result = await (0, mintNft_1.executeMintNft)(tx.chainName, tx.details);
|
|
89
|
+
}
|
|
90
|
+
else if (tx.type === 'custom') {
|
|
91
|
+
result = await (0, customTx_1.executeCustomTx)(tx.chainName, tx.details);
|
|
80
92
|
}
|
|
81
93
|
transactionManager_1.txManager.updateStatus(txId, 'executed', result);
|
|
82
94
|
const prettyMsg = (0, formatter_1.formatTransactionSuccess)(tx, result);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bridgeTokenToolDefinition = void 0;
|
|
4
|
+
exports.prepareBridgeToken = prepareBridgeToken;
|
|
5
|
+
exports.executeBridge = executeBridge;
|
|
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 body = {
|
|
36
|
+
user: userAddress,
|
|
37
|
+
originChainId: fromChainId,
|
|
38
|
+
destinationChainId: toChainId,
|
|
39
|
+
originCurrency: fromToken,
|
|
40
|
+
destinationCurrency: toToken,
|
|
41
|
+
amount: amountWei,
|
|
42
|
+
tradeType: "EXACT_INPUT"
|
|
43
|
+
};
|
|
44
|
+
const res = await fetch("https://api.relay.link/quote", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
body: JSON.stringify(body)
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const err = await res.json().catch(() => ({}));
|
|
51
|
+
throw new Error(`Relay API Error: ${err.message || res.statusText}`);
|
|
52
|
+
}
|
|
53
|
+
return await res.json();
|
|
54
|
+
}
|
|
55
|
+
async function prepareBridgeToken(fromChainName, toChainName, fromToken, toToken, amountStr, mode = "auto", providerName = "lifi", slippagePercent = 0.5) {
|
|
56
|
+
try {
|
|
57
|
+
const publicClient = (0, config_1.getPublicClient)(fromChainName);
|
|
58
|
+
const walletClient = (0, config_1.getWalletClient)(fromChainName);
|
|
59
|
+
const account = walletClient.account;
|
|
60
|
+
const fromChainId = CHAIN_IDS[fromChainName];
|
|
61
|
+
const toChainId = CHAIN_IDS[toChainName];
|
|
62
|
+
const fromTokenAddress = (0, tokens_1.resolveToken)(fromToken, fromChainName);
|
|
63
|
+
const toTokenAddress = (0, tokens_1.resolveToken)(toToken, toChainName);
|
|
64
|
+
const isNativeIn = fromTokenAddress === "0x0000000000000000000000000000000000000000";
|
|
65
|
+
// Get decimals
|
|
66
|
+
let decimals = 18;
|
|
67
|
+
if (!isNativeIn) {
|
|
68
|
+
decimals = await publicClient.readContract({
|
|
69
|
+
address: fromTokenAddress,
|
|
70
|
+
abi: tokens_1.ERC20_ABI,
|
|
71
|
+
functionName: 'decimals',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const amountWei = (0, viem_1.parseUnits)(amountStr, decimals).toString();
|
|
75
|
+
let txRequest = null;
|
|
76
|
+
let approvalAddress = null;
|
|
77
|
+
let expectedOutputStr = "";
|
|
78
|
+
const actualProvider = mode === "auto" ? "lifi" : providerName;
|
|
79
|
+
if (actualProvider === "lifi") {
|
|
80
|
+
const quote = await getLifiQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
|
|
81
|
+
txRequest = quote.transactionRequest;
|
|
82
|
+
approvalAddress = quote.estimate.approvalAddress;
|
|
83
|
+
const toDecimals = quote.action.toToken.decimals;
|
|
84
|
+
expectedOutputStr = (0, viem_1.formatUnits)(BigInt(quote.estimate.toAmount), toDecimals);
|
|
85
|
+
}
|
|
86
|
+
else if (actualProvider === "relay") {
|
|
87
|
+
const relayQuote = await getRelayQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, account.address);
|
|
88
|
+
if (!relayQuote.steps || relayQuote.steps.length === 0)
|
|
89
|
+
throw new Error("No route found by Relay.");
|
|
90
|
+
const txStep = relayQuote.steps.find((s) => s.id === "execute");
|
|
91
|
+
if (!txStep || !txStep.items || txStep.items.length === 0)
|
|
92
|
+
throw new Error("Relay steps invalid.");
|
|
93
|
+
const item = txStep.items[0];
|
|
94
|
+
txRequest = item.data;
|
|
95
|
+
if (!isNativeIn && txRequest.to.toLowerCase() !== fromTokenAddress.toLowerCase()) {
|
|
96
|
+
approvalAddress = txRequest.to;
|
|
97
|
+
}
|
|
98
|
+
expectedOutputStr = relayQuote.details?.currencyOut?.amountFormatted || "Unknown";
|
|
99
|
+
}
|
|
100
|
+
let needsApprove = false;
|
|
101
|
+
if (!isNativeIn && approvalAddress && approvalAddress !== "0x0000000000000000000000000000000000000000") {
|
|
102
|
+
const allowance = await publicClient.readContract({
|
|
103
|
+
address: fromTokenAddress,
|
|
104
|
+
abi: tokens_1.ERC20_ABI,
|
|
105
|
+
functionName: 'allowance',
|
|
106
|
+
args: [account.address, approvalAddress],
|
|
107
|
+
});
|
|
108
|
+
if (allowance < BigInt(amountWei)) {
|
|
109
|
+
needsApprove = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('bridge', fromChainName, {
|
|
113
|
+
txRequest,
|
|
114
|
+
needsApprove,
|
|
115
|
+
fromTokenAddress,
|
|
116
|
+
approvalAddress,
|
|
117
|
+
amountWei
|
|
118
|
+
});
|
|
119
|
+
return `TRANSACTION_PENDING: Bridge simulated via ${actualProvider.toUpperCase()}. Expected Output on ${toChainName}: ~${expectedOutputStr} ${toToken.toUpperCase()}. ${needsApprove ? '(Auto-Approve required) ' : ''}Gas est: ${txRequest.gasLimit || 'auto'}. Transaction ID: ${tx.id}. Wait for user to approve.`;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
return `Simulation failed! Cannot prepare bridge. Error: ${error.message}`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function executeBridge(chainName, params) {
|
|
126
|
+
try {
|
|
127
|
+
const client = (0, config_1.getWalletClient)(chainName);
|
|
128
|
+
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
129
|
+
const { txRequest, needsApprove, fromTokenAddress, approvalAddress, amountWei } = params;
|
|
130
|
+
if (needsApprove) {
|
|
131
|
+
const approveHash = await client.writeContract({
|
|
132
|
+
account: client.account,
|
|
133
|
+
chain: client.chain,
|
|
134
|
+
address: fromTokenAddress,
|
|
135
|
+
abi: tokens_1.ERC20_ABI,
|
|
136
|
+
functionName: 'approve',
|
|
137
|
+
args: [approvalAddress, 115792089237316195423570985008687907853269984665640564039457584007913129639935n],
|
|
138
|
+
});
|
|
139
|
+
await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
140
|
+
}
|
|
141
|
+
const txHash = await client.sendTransaction({
|
|
142
|
+
account: client.account,
|
|
143
|
+
chain: client.chain,
|
|
144
|
+
to: txRequest.to,
|
|
145
|
+
data: txRequest.data,
|
|
146
|
+
value: txRequest.value ? BigInt(txRequest.value) : 0n,
|
|
147
|
+
gas: txRequest.gasLimit ? (BigInt(txRequest.gasLimit) * 12n / 10n) : undefined
|
|
148
|
+
});
|
|
149
|
+
return `Bridge successful. Tx Hash: ${txHash}`;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return `Failed to execute bridge: ${error.message}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
exports.bridgeTokenToolDefinition = {
|
|
156
|
+
type: "function",
|
|
157
|
+
function: {
|
|
158
|
+
name: "bridge_token",
|
|
159
|
+
description: "Executes a cross-chain token bridge from one network to another using Li.Fi or Relay. Automatically simulates to fetch quotes.",
|
|
160
|
+
parameters: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
fromChainName: {
|
|
164
|
+
type: "string",
|
|
165
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
166
|
+
description: "The source blockchain network",
|
|
167
|
+
},
|
|
168
|
+
toChainName: {
|
|
169
|
+
type: "string",
|
|
170
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
171
|
+
description: "The destination blockchain network",
|
|
172
|
+
},
|
|
173
|
+
fromToken: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "The token symbol to sell on source chain (e.g., 'ETH', 'USDC')",
|
|
176
|
+
},
|
|
177
|
+
toToken: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "The token symbol to buy on destination chain (e.g., 'USDC', 'UNI')",
|
|
180
|
+
},
|
|
181
|
+
amountStr: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "The amount of fromToken to bridge",
|
|
184
|
+
},
|
|
185
|
+
mode: {
|
|
186
|
+
type: "string",
|
|
187
|
+
enum: ["auto", "manual"],
|
|
188
|
+
description: "auto uses lifi. manual uses the specified provider."
|
|
189
|
+
},
|
|
190
|
+
providerName: {
|
|
191
|
+
type: "string",
|
|
192
|
+
enum: ["lifi", "relay"],
|
|
193
|
+
description: "Used if mode is manual."
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
required: ["fromChainName", "toChainName", "fromToken", "toToken", "amountStr"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.customTxToolDefinition = void 0;
|
|
4
|
+
exports.prepareCustomTx = prepareCustomTx;
|
|
5
|
+
exports.executeCustomTx = executeCustomTx;
|
|
6
|
+
const viem_1 = require("viem");
|
|
7
|
+
const config_1 = require("../config");
|
|
8
|
+
const transactionManager_1 = require("../../agent/transactionManager");
|
|
9
|
+
async function prepareCustomTx(chainName, toAddress, dataHex, valueEth = "0", gasLimitStr) {
|
|
10
|
+
try {
|
|
11
|
+
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
12
|
+
const walletClient = (0, config_1.getWalletClient)(chainName);
|
|
13
|
+
const account = walletClient.account;
|
|
14
|
+
if (!dataHex.startsWith("0x")) {
|
|
15
|
+
throw new Error("Data must start with 0x (Hex format)");
|
|
16
|
+
}
|
|
17
|
+
const valueWei = (0, viem_1.parseEther)(valueEth);
|
|
18
|
+
let gasEstimate = 0n;
|
|
19
|
+
if (gasLimitStr) {
|
|
20
|
+
gasEstimate = BigInt(gasLimitStr);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
gasEstimate = await publicClient.estimateGas({
|
|
24
|
+
account,
|
|
25
|
+
to: toAddress,
|
|
26
|
+
data: dataHex,
|
|
27
|
+
value: valueWei,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('custom', chainName, {
|
|
31
|
+
toAddress,
|
|
32
|
+
dataHex,
|
|
33
|
+
valueWei: valueWei.toString(),
|
|
34
|
+
gasEstimate: gasEstimate.toString()
|
|
35
|
+
});
|
|
36
|
+
return `TRANSACTION_PENDING: Simulated Custom Transaction successfully. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve.`;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return `Simulation failed! Cannot prepare custom tx. Error: ${error.message}`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function executeCustomTx(chainName, params) {
|
|
43
|
+
try {
|
|
44
|
+
const client = (0, config_1.getWalletClient)(chainName);
|
|
45
|
+
const { toAddress, dataHex, valueWei, gasEstimate } = params;
|
|
46
|
+
const hash = await client.sendTransaction({
|
|
47
|
+
account: client.account,
|
|
48
|
+
chain: client.chain,
|
|
49
|
+
to: toAddress,
|
|
50
|
+
data: dataHex,
|
|
51
|
+
value: BigInt(valueWei),
|
|
52
|
+
gas: gasEstimate ? BigInt(gasEstimate) * 12n / 10n : undefined, // 20% buffer
|
|
53
|
+
});
|
|
54
|
+
return `Custom transaction successful. Tx Hash: ${hash}`;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return `Failed to execute custom transaction: ${error.message}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.customTxToolDefinition = {
|
|
61
|
+
type: "function",
|
|
62
|
+
function: {
|
|
63
|
+
name: "custom_tx",
|
|
64
|
+
description: "Executes a raw custom transaction with calldata (hex) on a specific blockchain network. Automatically simulates the execution.",
|
|
65
|
+
parameters: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
chainName: {
|
|
69
|
+
type: "string",
|
|
70
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
71
|
+
description: "The blockchain network",
|
|
72
|
+
},
|
|
73
|
+
toAddress: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "The destination contract or wallet address (0x...)",
|
|
76
|
+
},
|
|
77
|
+
dataHex: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "The raw calldata payload in hex format (starting with 0x)",
|
|
80
|
+
},
|
|
81
|
+
valueEth: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "The amount of native ETH/BNB to attach. Default is '0'.",
|
|
84
|
+
},
|
|
85
|
+
gasLimitStr: {
|
|
86
|
+
type: "string",
|
|
87
|
+
description: "Optional custom gas limit as a string. If omitted, the node will estimate it.",
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
required: ["chainName", "toAddress", "dataHex", "valueEth"],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
};
|
|
@@ -37,7 +37,8 @@ exports.getBalanceToolDefinition = void 0;
|
|
|
37
37
|
exports.getBalance = getBalance;
|
|
38
38
|
const viem_1 = require("viem");
|
|
39
39
|
const config_1 = require("../config");
|
|
40
|
-
|
|
40
|
+
const tokens_1 = require("../utils/tokens");
|
|
41
|
+
async function getBalance(chainName, address, token) {
|
|
41
42
|
try {
|
|
42
43
|
const client = (0, config_1.getPublicClient)(chainName);
|
|
43
44
|
let targetAddress = address;
|
|
@@ -48,9 +49,41 @@ async function getBalance(chainName, address) {
|
|
|
48
49
|
if (!targetAddress) {
|
|
49
50
|
throw new Error('Address is required but could not be resolved from private key.');
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
if (token) {
|
|
53
|
+
const tokenAddress = (0, tokens_1.resolveToken)(token, chainName);
|
|
54
|
+
if (tokenAddress === "0x0000000000000000000000000000000000000000") {
|
|
55
|
+
const balanceWei = await client.getBalance({ address: targetAddress });
|
|
56
|
+
const balanceEth = (0, viem_1.formatEther)(balanceWei);
|
|
57
|
+
return `${balanceEth} on ${chainName}`;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const [balanceWei, decimals, symbol] = await Promise.all([
|
|
61
|
+
client.readContract({
|
|
62
|
+
address: tokenAddress,
|
|
63
|
+
abi: tokens_1.ERC20_ABI,
|
|
64
|
+
functionName: 'balanceOf',
|
|
65
|
+
args: [targetAddress],
|
|
66
|
+
}),
|
|
67
|
+
client.readContract({
|
|
68
|
+
address: tokenAddress,
|
|
69
|
+
abi: tokens_1.ERC20_ABI,
|
|
70
|
+
functionName: 'decimals',
|
|
71
|
+
}),
|
|
72
|
+
client.readContract({
|
|
73
|
+
address: tokenAddress,
|
|
74
|
+
abi: tokens_1.ERC20_ABI,
|
|
75
|
+
functionName: 'symbol',
|
|
76
|
+
}).catch(() => token)
|
|
77
|
+
]);
|
|
78
|
+
const balanceFormatted = (0, viem_1.formatUnits)(balanceWei, decimals);
|
|
79
|
+
return `${balanceFormatted} ${symbol} on ${chainName}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const balanceWei = await client.getBalance({ address: targetAddress });
|
|
84
|
+
const balanceEth = (0, viem_1.formatEther)(balanceWei);
|
|
85
|
+
return `${balanceEth} on ${chainName}`;
|
|
86
|
+
}
|
|
54
87
|
}
|
|
55
88
|
catch (error) {
|
|
56
89
|
return `Failed to get balance: ${error.message}`;
|
|
@@ -60,7 +93,7 @@ exports.getBalanceToolDefinition = {
|
|
|
60
93
|
type: "function",
|
|
61
94
|
function: {
|
|
62
95
|
name: "get_balance",
|
|
63
|
-
description: "Get the native token balance
|
|
96
|
+
description: "Get the native or ERC-20 token balance of a wallet address on a specific chain. If address is omitted, it returns the balance of the agent's own wallet.",
|
|
64
97
|
parameters: {
|
|
65
98
|
type: "object",
|
|
66
99
|
properties: {
|
|
@@ -72,6 +105,10 @@ exports.getBalanceToolDefinition = {
|
|
|
72
105
|
address: {
|
|
73
106
|
type: "string",
|
|
74
107
|
description: "Optional. The 0x... address of the wallet. If not provided, it uses the agent's wallet."
|
|
108
|
+
},
|
|
109
|
+
token: {
|
|
110
|
+
type: "string",
|
|
111
|
+
description: "Optional. The token symbol (e.g. USDC, USDT, WETH) or contract address (0x...) to check. If omitted, checks the native coin (ETH/BNB)."
|
|
75
112
|
}
|
|
76
113
|
},
|
|
77
114
|
required: ["chainName"]
|
|
@@ -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,200 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.swapTokenToolDefinition = void 0;
|
|
4
|
-
exports.
|
|
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 body = {
|
|
36
|
+
user: userAddress,
|
|
37
|
+
originChainId: fromChainId,
|
|
38
|
+
destinationChainId: toChainId,
|
|
39
|
+
originCurrency: fromToken,
|
|
40
|
+
destinationCurrency: toToken,
|
|
41
|
+
amount: amountWei,
|
|
42
|
+
tradeType: "EXACT_INPUT"
|
|
43
|
+
};
|
|
44
|
+
const res = await fetch("https://api.relay.link/quote", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
body: JSON.stringify(body)
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
const err = await res.json().catch(() => ({}));
|
|
51
|
+
throw new Error(`Relay API Error: ${err.message || res.statusText}`);
|
|
52
|
+
}
|
|
53
|
+
return await res.json();
|
|
54
|
+
}
|
|
55
|
+
async function prepareSwapToken(chainName, fromToken, toToken, amountStr, mode = "auto", providerName = "lifi", slippagePercent = 0.5) {
|
|
56
|
+
try {
|
|
57
|
+
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
58
|
+
const walletClient = (0, config_1.getWalletClient)(chainName);
|
|
59
|
+
const account = walletClient.account;
|
|
60
|
+
const chainId = CHAIN_IDS[chainName];
|
|
61
|
+
const fromTokenAddress = (0, tokens_1.resolveToken)(fromToken, chainName);
|
|
62
|
+
const toTokenAddress = (0, tokens_1.resolveToken)(toToken, chainName);
|
|
63
|
+
const isNativeIn = fromTokenAddress === "0x0000000000000000000000000000000000000000";
|
|
64
|
+
// Get decimals
|
|
65
|
+
let decimals = 18;
|
|
66
|
+
if (!isNativeIn) {
|
|
67
|
+
decimals = await publicClient.readContract({
|
|
68
|
+
address: fromTokenAddress,
|
|
69
|
+
abi: tokens_1.ERC20_ABI,
|
|
70
|
+
functionName: 'decimals',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const amountWei = (0, viem_1.parseUnits)(amountStr, decimals).toString();
|
|
74
|
+
let txRequest = null;
|
|
75
|
+
let approvalAddress = null;
|
|
76
|
+
let expectedOutputStr = "";
|
|
77
|
+
const actualProvider = mode === "auto" ? "lifi" : providerName;
|
|
78
|
+
if (actualProvider === "lifi") {
|
|
79
|
+
const quote = await getLifiQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
|
|
80
|
+
txRequest = quote.transactionRequest;
|
|
81
|
+
approvalAddress = quote.estimate.approvalAddress;
|
|
82
|
+
const toDecimals = quote.action.toToken.decimals;
|
|
83
|
+
expectedOutputStr = (0, viem_1.formatUnits)(BigInt(quote.estimate.toAmount), toDecimals);
|
|
84
|
+
}
|
|
85
|
+
else if (actualProvider === "relay") {
|
|
86
|
+
const relayQuote = await getRelayQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, account.address);
|
|
87
|
+
if (!relayQuote.steps || relayQuote.steps.length === 0)
|
|
88
|
+
throw new Error("No route found by Relay.");
|
|
89
|
+
// Relay returns steps. We need to find the main transaction step.
|
|
90
|
+
const txStep = relayQuote.steps.find((s) => s.id === "execute");
|
|
91
|
+
if (!txStep || !txStep.items || txStep.items.length === 0)
|
|
92
|
+
throw new Error("Relay steps invalid.");
|
|
93
|
+
const item = txStep.items[0];
|
|
94
|
+
txRequest = item.data;
|
|
95
|
+
// Usually Relay route approval to `txRequest.to` if it's not native
|
|
96
|
+
if (!isNativeIn && txRequest.to.toLowerCase() !== fromTokenAddress.toLowerCase()) {
|
|
97
|
+
approvalAddress = txRequest.to;
|
|
98
|
+
}
|
|
99
|
+
expectedOutputStr = relayQuote.details?.currencyOut?.amountFormatted || "Unknown";
|
|
100
|
+
}
|
|
101
|
+
// Check allowance early so we know if we need to auto-approve
|
|
102
|
+
let needsApprove = false;
|
|
103
|
+
if (!isNativeIn && approvalAddress && approvalAddress !== "0x0000000000000000000000000000000000000000") {
|
|
104
|
+
const allowance = await publicClient.readContract({
|
|
105
|
+
address: fromTokenAddress,
|
|
106
|
+
abi: tokens_1.ERC20_ABI,
|
|
107
|
+
functionName: 'allowance',
|
|
108
|
+
args: [account.address, approvalAddress],
|
|
109
|
+
});
|
|
110
|
+
if (allowance < BigInt(amountWei)) {
|
|
111
|
+
needsApprove = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('swap', chainName, {
|
|
115
|
+
txRequest,
|
|
116
|
+
needsApprove,
|
|
117
|
+
fromTokenAddress,
|
|
118
|
+
approvalAddress,
|
|
119
|
+
amountWei
|
|
120
|
+
});
|
|
121
|
+
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.`;
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return `Simulation failed! Cannot prepare swap. Error: ${error.message}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function executeSwap(chainName, params) {
|
|
128
|
+
try {
|
|
129
|
+
const client = (0, config_1.getWalletClient)(chainName);
|
|
130
|
+
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
131
|
+
const { txRequest, needsApprove, fromTokenAddress, approvalAddress, amountWei } = params;
|
|
132
|
+
if (needsApprove) {
|
|
133
|
+
// Auto-Approve Transaction
|
|
134
|
+
const approveHash = await client.writeContract({
|
|
135
|
+
account: client.account,
|
|
136
|
+
chain: client.chain,
|
|
137
|
+
address: fromTokenAddress,
|
|
138
|
+
abi: tokens_1.ERC20_ABI,
|
|
139
|
+
functionName: 'approve',
|
|
140
|
+
// Max Uint256
|
|
141
|
+
args: [approvalAddress, 115792089237316195423570985008687907853269984665640564039457584007913129639935n],
|
|
142
|
+
});
|
|
143
|
+
await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
144
|
+
}
|
|
145
|
+
// Execute Swap
|
|
146
|
+
// @ts-ignore
|
|
147
|
+
const txHash = await client.sendTransaction({
|
|
148
|
+
account: client.account,
|
|
149
|
+
chain: client.chain,
|
|
150
|
+
to: txRequest.to,
|
|
151
|
+
data: txRequest.data,
|
|
152
|
+
value: txRequest.value ? BigInt(txRequest.value) : 0n,
|
|
153
|
+
gas: txRequest.gasLimit ? (BigInt(txRequest.gasLimit) * 12n / 10n) : undefined // 20% buffer
|
|
154
|
+
});
|
|
155
|
+
return `Swap successful. Tx Hash: ${txHash}`;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
return `Failed to execute swap: ${error.message}`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
6
161
|
exports.swapTokenToolDefinition = {
|
|
7
162
|
type: "function",
|
|
8
163
|
function: {
|
|
9
164
|
name: "swap_token",
|
|
10
|
-
description: "Executes a decentralized token swap (DEX) to exchange one cryptocurrency for another.",
|
|
165
|
+
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
166
|
parameters: {
|
|
12
167
|
type: "object",
|
|
13
168
|
properties: {
|
|
14
169
|
chainName: {
|
|
15
170
|
type: "string",
|
|
16
|
-
|
|
171
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
172
|
+
description: "The blockchain network",
|
|
17
173
|
},
|
|
18
174
|
fromToken: {
|
|
19
175
|
type: "string",
|
|
20
|
-
description: "The token symbol to sell
|
|
176
|
+
description: "The token symbol to sell (e.g., 'ETH', 'USDC')",
|
|
21
177
|
},
|
|
22
178
|
toToken: {
|
|
23
179
|
type: "string",
|
|
24
|
-
description: "The token symbol to buy
|
|
180
|
+
description: "The token symbol to buy (e.g., 'USDC', 'UNI')",
|
|
25
181
|
},
|
|
26
|
-
|
|
27
|
-
type: "
|
|
182
|
+
amountStr: {
|
|
183
|
+
type: "string",
|
|
28
184
|
description: "The amount of fromToken to swap",
|
|
29
185
|
},
|
|
186
|
+
mode: {
|
|
187
|
+
type: "string",
|
|
188
|
+
enum: ["auto", "manual"],
|
|
189
|
+
description: "auto uses lifi. manual uses the specified provider."
|
|
190
|
+
},
|
|
191
|
+
providerName: {
|
|
192
|
+
type: "string",
|
|
193
|
+
enum: ["lifi", "relay"],
|
|
194
|
+
description: "Used if mode is manual."
|
|
195
|
+
}
|
|
30
196
|
},
|
|
31
|
-
required: ["chainName", "fromToken", "toToken", "
|
|
197
|
+
required: ["chainName", "fromToken", "toToken", "amountStr"],
|
|
32
198
|
},
|
|
33
199
|
},
|
|
34
200
|
};
|
|
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.
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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,
|
|
71
|
+
async function executeTransfer(chainName, params) {
|
|
14
72
|
try {
|
|
15
73
|
const client = (0, config_1.getWalletClient)(chainName);
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
|
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", "
|
|
128
|
+
required: ["chainName", "toAddress", "amountStr"]
|
|
51
129
|
}
|
|
52
130
|
}
|
|
53
131
|
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TOKEN_MAP = exports.ERC20_ABI = void 0;
|
|
4
|
+
exports.resolveToken = resolveToken;
|
|
5
|
+
exports.ERC20_ABI = [
|
|
6
|
+
{
|
|
7
|
+
type: 'function',
|
|
8
|
+
name: 'balanceOf',
|
|
9
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
10
|
+
outputs: [{ type: 'uint256' }],
|
|
11
|
+
stateMutability: 'view',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: 'function',
|
|
15
|
+
name: 'transfer',
|
|
16
|
+
inputs: [
|
|
17
|
+
{ name: 'to', type: 'address' },
|
|
18
|
+
{ name: 'value', type: 'uint256' }
|
|
19
|
+
],
|
|
20
|
+
outputs: [{ type: 'bool' }],
|
|
21
|
+
stateMutability: 'nonpayable',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type: 'function',
|
|
25
|
+
name: 'approve',
|
|
26
|
+
inputs: [
|
|
27
|
+
{ name: 'spender', type: 'address' },
|
|
28
|
+
{ name: 'value', type: 'uint256' }
|
|
29
|
+
],
|
|
30
|
+
outputs: [{ type: 'bool' }],
|
|
31
|
+
stateMutability: 'nonpayable',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'function',
|
|
35
|
+
name: 'allowance',
|
|
36
|
+
inputs: [
|
|
37
|
+
{ name: 'owner', type: 'address' },
|
|
38
|
+
{ name: 'spender', type: 'address' }
|
|
39
|
+
],
|
|
40
|
+
outputs: [{ type: 'uint256' }],
|
|
41
|
+
stateMutability: 'view',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'function',
|
|
45
|
+
name: 'decimals',
|
|
46
|
+
inputs: [],
|
|
47
|
+
outputs: [{ type: 'uint8' }],
|
|
48
|
+
stateMutability: 'view',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'function',
|
|
52
|
+
name: 'symbol',
|
|
53
|
+
inputs: [],
|
|
54
|
+
outputs: [{ type: 'string' }],
|
|
55
|
+
stateMutability: 'view',
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
exports.TOKEN_MAP = {
|
|
59
|
+
ethereum: {
|
|
60
|
+
ETH: "0x0000000000000000000000000000000000000000",
|
|
61
|
+
WETH: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
62
|
+
USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
63
|
+
USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7"
|
|
64
|
+
},
|
|
65
|
+
arbitrum: {
|
|
66
|
+
ETH: "0x0000000000000000000000000000000000000000",
|
|
67
|
+
WETH: "0x82aF49447D8a07e3bd95BD0d56f352415231Cl11",
|
|
68
|
+
USDC: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
69
|
+
"USDC.E": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
|
|
70
|
+
USDT: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"
|
|
71
|
+
},
|
|
72
|
+
base: {
|
|
73
|
+
ETH: "0x0000000000000000000000000000000000000000",
|
|
74
|
+
WETH: "0x4200000000000000000000000000000000000006",
|
|
75
|
+
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
76
|
+
USDT: "0xf55BEC9cbd4732f1F4143f647652e924540d9d64"
|
|
77
|
+
},
|
|
78
|
+
optimism: {
|
|
79
|
+
ETH: "0x0000000000000000000000000000000000000000",
|
|
80
|
+
WETH: "0x4200000000000000000000000000000000000006",
|
|
81
|
+
USDC: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
82
|
+
USDT: "0x94b008aA00579c1307b0EF2c499aD98a8ce58e58"
|
|
83
|
+
},
|
|
84
|
+
bsc: {
|
|
85
|
+
BNB: "0x0000000000000000000000000000000000000000",
|
|
86
|
+
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
|
87
|
+
USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32CD580d",
|
|
88
|
+
USDT: "0x55d398326f99059fF775485246999027B3197955"
|
|
89
|
+
},
|
|
90
|
+
sepolia: {
|
|
91
|
+
ETH: "0x0000000000000000000000000000000000000000",
|
|
92
|
+
WETH: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
|
|
93
|
+
USDC: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" // Circle Faucet Sepolia USDC
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
function resolveToken(tokenSymbolOrAddress, chainName) {
|
|
97
|
+
if (tokenSymbolOrAddress.startsWith("0x") && tokenSymbolOrAddress.length === 42) {
|
|
98
|
+
return tokenSymbolOrAddress;
|
|
99
|
+
}
|
|
100
|
+
const symbolUpper = tokenSymbolOrAddress.toUpperCase().trim();
|
|
101
|
+
if (["ETH", "MATIC", "POL", "BNB", "AVAX", "NATIVE"].includes(symbolUpper)) {
|
|
102
|
+
return "0x0000000000000000000000000000000000000000";
|
|
103
|
+
}
|
|
104
|
+
const chainTokens = exports.TOKEN_MAP[chainName];
|
|
105
|
+
if (chainTokens && chainTokens[symbolUpper]) {
|
|
106
|
+
return chainTokens[symbolUpper];
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Token "${tokenSymbolOrAddress}" pada chain ${chainName} tidak ditemukan. Silakan gunakan alamat kontrak langsung (0x...).`);
|
|
109
|
+
}
|