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.
- package/README.md +7 -3
- package/SECURITY.md +6 -4
- package/dist/agent/limitOrderManager.js +177 -0
- package/dist/agent/reasoning.js +93 -40
- package/dist/gateway/server.js +21 -6
- package/dist/gateway/telegram.js +19 -2
- package/dist/web3/skills/bridgeToken.js +216 -0
- package/dist/web3/skills/checkPortfolio.js +154 -0
- package/dist/web3/skills/checkSecurity.js +67 -0
- package/dist/web3/skills/createWallet.js +34 -0
- package/dist/web3/skills/customTx.js +93 -0
- package/dist/web3/skills/getBalance.js +42 -5
- package/dist/web3/skills/marketAnalysis.js +71 -0
- package/dist/web3/skills/mintNft.js +122 -0
- package/dist/web3/skills/swapToken.js +191 -51
- package/dist/web3/skills/transfer.js +95 -17
- package/dist/web3/utils/tokens.js +110 -0
- package/package.json +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
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 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 prepareBridgeToken(fromChainName, toChainName, fromToken, toToken, amountStr, mode = "auto", providerName = "lifi", slippagePercent = 0.5) {
|
|
58
|
+
try {
|
|
59
|
+
const publicClient = (0, config_1.getPublicClient)(fromChainName);
|
|
60
|
+
const walletClient = (0, config_1.getWalletClient)(fromChainName);
|
|
61
|
+
const account = walletClient.account;
|
|
62
|
+
const fromChainId = CHAIN_IDS[fromChainName];
|
|
63
|
+
const toChainId = CHAIN_IDS[toChainName];
|
|
64
|
+
const fromTokenAddress = (0, tokens_1.resolveToken)(fromToken, fromChainName);
|
|
65
|
+
const toTokenAddress = (0, tokens_1.resolveToken)(toToken, toChainName);
|
|
66
|
+
const isNativeIn = fromTokenAddress === "0x0000000000000000000000000000000000000000";
|
|
67
|
+
// Get decimals
|
|
68
|
+
let decimals = 18;
|
|
69
|
+
if (!isNativeIn) {
|
|
70
|
+
decimals = await publicClient.readContract({
|
|
71
|
+
address: fromTokenAddress,
|
|
72
|
+
abi: tokens_1.ERC20_ABI,
|
|
73
|
+
functionName: 'decimals',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const amountWei = (0, viem_1.parseUnits)(amountStr, decimals).toString();
|
|
77
|
+
let txRequest = null;
|
|
78
|
+
let approvalAddress = null;
|
|
79
|
+
let expectedOutputStr = "";
|
|
80
|
+
let actualProvider = mode === "auto" ? "lifi" : providerName;
|
|
81
|
+
const isTestnet = fromChainId === 11155111 || toChainId === 11155111;
|
|
82
|
+
// --- SEPOLIA TESTNET MOCK ---
|
|
83
|
+
if (isTestnet) {
|
|
84
|
+
const mockGasLimit = "150000";
|
|
85
|
+
expectedOutputStr = "MOCK_TEST_AMOUNT";
|
|
86
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('bridge', fromChainName, {
|
|
87
|
+
txRequest: { to: fromTokenAddress, data: "0x", value: amountWei, gasLimit: mockGasLimit },
|
|
88
|
+
needsApprove: false,
|
|
89
|
+
fromTokenAddress,
|
|
90
|
+
approvalAddress: null,
|
|
91
|
+
amountWei
|
|
92
|
+
});
|
|
93
|
+
return `TRANSACTION_PENDING: Bridge simulated via TESTNET_MOCK. Expected Output on ${toChainName}: ~${expectedOutputStr} ${toToken.toUpperCase()}. Gas est: ${mockGasLimit}. Transaction ID: ${tx.id}. Wait for user to approve.`;
|
|
94
|
+
}
|
|
95
|
+
// --- END MOCK ---
|
|
96
|
+
if (actualProvider === "lifi") {
|
|
97
|
+
const quote = await getLifiQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
|
|
98
|
+
txRequest = quote.transactionRequest;
|
|
99
|
+
approvalAddress = quote.estimate.approvalAddress;
|
|
100
|
+
const toDecimals = quote.action.toToken.decimals;
|
|
101
|
+
expectedOutputStr = (0, viem_1.formatUnits)(BigInt(quote.estimate.toAmount), toDecimals);
|
|
102
|
+
}
|
|
103
|
+
else if (actualProvider === "relay") {
|
|
104
|
+
const relayQuote = await getRelayQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, account.address);
|
|
105
|
+
if (!relayQuote.steps || relayQuote.steps.length === 0)
|
|
106
|
+
throw new Error("No route found by Relay.");
|
|
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
|
+
if (!isNativeIn && txRequest.to.toLowerCase() !== fromTokenAddress.toLowerCase()) {
|
|
113
|
+
approvalAddress = txRequest.to;
|
|
114
|
+
}
|
|
115
|
+
expectedOutputStr = relayQuote.details?.currencyOut?.amountFormatted || "Unknown";
|
|
116
|
+
}
|
|
117
|
+
let needsApprove = false;
|
|
118
|
+
if (!isNativeIn && approvalAddress && approvalAddress !== "0x0000000000000000000000000000000000000000") {
|
|
119
|
+
const allowance = await publicClient.readContract({
|
|
120
|
+
address: fromTokenAddress,
|
|
121
|
+
abi: tokens_1.ERC20_ABI,
|
|
122
|
+
functionName: 'allowance',
|
|
123
|
+
args: [account.address, approvalAddress],
|
|
124
|
+
});
|
|
125
|
+
if (allowance < BigInt(amountWei)) {
|
|
126
|
+
needsApprove = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('bridge', fromChainName, {
|
|
130
|
+
txRequest,
|
|
131
|
+
needsApprove,
|
|
132
|
+
fromTokenAddress,
|
|
133
|
+
approvalAddress,
|
|
134
|
+
amountWei
|
|
135
|
+
});
|
|
136
|
+
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.`;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return `Simulation failed! Cannot prepare bridge. Error: ${error.message}`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function executeBridge(chainName, params) {
|
|
143
|
+
try {
|
|
144
|
+
const client = (0, config_1.getWalletClient)(chainName);
|
|
145
|
+
const publicClient = (0, config_1.getPublicClient)(chainName);
|
|
146
|
+
const { txRequest, needsApprove, fromTokenAddress, approvalAddress, amountWei } = params;
|
|
147
|
+
if (needsApprove) {
|
|
148
|
+
const approveHash = await client.writeContract({
|
|
149
|
+
account: client.account,
|
|
150
|
+
chain: client.chain,
|
|
151
|
+
address: fromTokenAddress,
|
|
152
|
+
abi: tokens_1.ERC20_ABI,
|
|
153
|
+
functionName: 'approve',
|
|
154
|
+
args: [approvalAddress, 115792089237316195423570985008687907853269984665640564039457584007913129639935n],
|
|
155
|
+
});
|
|
156
|
+
await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
157
|
+
}
|
|
158
|
+
const txHash = await client.sendTransaction({
|
|
159
|
+
account: client.account,
|
|
160
|
+
chain: client.chain,
|
|
161
|
+
to: txRequest.to,
|
|
162
|
+
data: txRequest.data,
|
|
163
|
+
value: txRequest.value ? BigInt(txRequest.value) : 0n,
|
|
164
|
+
gas: txRequest.gasLimit ? (BigInt(txRequest.gasLimit) * 12n / 10n) : undefined
|
|
165
|
+
});
|
|
166
|
+
return `Bridge successful. Tx Hash: ${txHash}`;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
return `Failed to execute bridge: ${error.message}`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
exports.bridgeTokenToolDefinition = {
|
|
173
|
+
type: "function",
|
|
174
|
+
function: {
|
|
175
|
+
name: "bridge_token",
|
|
176
|
+
description: "Executes a cross-chain token bridge from one network to another using Li.Fi or Relay. Automatically simulates to fetch quotes.",
|
|
177
|
+
parameters: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
fromChainName: {
|
|
181
|
+
type: "string",
|
|
182
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
183
|
+
description: "The source blockchain network",
|
|
184
|
+
},
|
|
185
|
+
toChainName: {
|
|
186
|
+
type: "string",
|
|
187
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
188
|
+
description: "The destination blockchain network",
|
|
189
|
+
},
|
|
190
|
+
fromToken: {
|
|
191
|
+
type: "string",
|
|
192
|
+
description: "The token symbol to sell on source chain (e.g., 'ETH', 'USDC')",
|
|
193
|
+
},
|
|
194
|
+
toToken: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "The token symbol to buy on destination chain (e.g., 'USDC', 'UNI')",
|
|
197
|
+
},
|
|
198
|
+
amountStr: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "The amount of fromToken to bridge",
|
|
201
|
+
},
|
|
202
|
+
mode: {
|
|
203
|
+
type: "string",
|
|
204
|
+
enum: ["auto", "manual"],
|
|
205
|
+
description: "auto uses lifi. manual uses the specified provider."
|
|
206
|
+
},
|
|
207
|
+
providerName: {
|
|
208
|
+
type: "string",
|
|
209
|
+
enum: ["lifi", "relay"],
|
|
210
|
+
description: "Used if mode is manual."
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
required: ["fromChainName", "toChainName", "fromToken", "toToken", "amountStr"],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkPortfolioToolDefinition = void 0;
|
|
37
|
+
exports.checkPortfolio = checkPortfolio;
|
|
38
|
+
const viem_1 = require("viem");
|
|
39
|
+
const config_1 = require("../config");
|
|
40
|
+
const tokens_1 = require("../utils/tokens");
|
|
41
|
+
async function checkPortfolio(chainName, address) {
|
|
42
|
+
try {
|
|
43
|
+
const client = (0, config_1.getPublicClient)(chainName);
|
|
44
|
+
let targetAddress = address;
|
|
45
|
+
if (!targetAddress) {
|
|
46
|
+
const { getAddress } = await Promise.resolve().then(() => __importStar(require('../config')));
|
|
47
|
+
targetAddress = getAddress();
|
|
48
|
+
}
|
|
49
|
+
if (!targetAddress) {
|
|
50
|
+
throw new Error('Address is required but could not be resolved from private key.');
|
|
51
|
+
}
|
|
52
|
+
const tokensToScan = [
|
|
53
|
+
{ symbol: 'Native', address: '0x0000000000000000000000000000000000000000', isNative: true }
|
|
54
|
+
];
|
|
55
|
+
const chainTokens = tokens_1.TOKEN_MAP[chainName];
|
|
56
|
+
if (chainTokens) {
|
|
57
|
+
for (const [sym, addr] of Object.entries(chainTokens)) {
|
|
58
|
+
if (addr !== "0x0000000000000000000000000000000000000000") {
|
|
59
|
+
tokensToScan.push({ symbol: sym, address: addr, isNative: false });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
let report = `📊 **Portfolio for ${targetAddress} on ${chainName.toUpperCase()}**\n\n`;
|
|
64
|
+
let totalUsdValue = 0;
|
|
65
|
+
// We will do Promise.all for balances
|
|
66
|
+
const balancePromises = tokensToScan.map(async (t) => {
|
|
67
|
+
let balanceNum = 0;
|
|
68
|
+
if (t.isNative) {
|
|
69
|
+
const bal = await client.getBalance({ address: targetAddress });
|
|
70
|
+
balanceNum = parseFloat((0, viem_1.formatEther)(bal));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
try {
|
|
74
|
+
const [balWei, dec] = await Promise.all([
|
|
75
|
+
client.readContract({
|
|
76
|
+
address: t.address,
|
|
77
|
+
abi: tokens_1.ERC20_ABI,
|
|
78
|
+
functionName: 'balanceOf',
|
|
79
|
+
args: [targetAddress],
|
|
80
|
+
}),
|
|
81
|
+
client.readContract({
|
|
82
|
+
address: t.address,
|
|
83
|
+
abi: tokens_1.ERC20_ABI,
|
|
84
|
+
functionName: 'decimals',
|
|
85
|
+
})
|
|
86
|
+
]);
|
|
87
|
+
balanceNum = parseFloat((0, viem_1.formatUnits)(balWei, dec));
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
balanceNum = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { ...t, balanceNum };
|
|
94
|
+
});
|
|
95
|
+
const balances = await Promise.all(balancePromises);
|
|
96
|
+
const nonZeroBalances = balances.filter(b => b.balanceNum > 0);
|
|
97
|
+
if (nonZeroBalances.length === 0) {
|
|
98
|
+
return report + `No funds found for standard tokens on this chain. Net Worth: $0.00`;
|
|
99
|
+
}
|
|
100
|
+
// Now fetch prices from Dexscreener
|
|
101
|
+
// Prepare addresses to fetch
|
|
102
|
+
const addressesToFetch = nonZeroBalances.map(b => b.isNative ? (chainTokens?.WETH || chainTokens?.WBNB) : b.address).filter(Boolean);
|
|
103
|
+
const priceMap = {};
|
|
104
|
+
if (addressesToFetch.length > 0) {
|
|
105
|
+
const url = `https://api.dexscreener.com/latest/dex/tokens/${addressesToFetch.join(',')}`;
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(url);
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
if (data.pairs) {
|
|
110
|
+
data.pairs.forEach((p) => {
|
|
111
|
+
if (!priceMap[p.baseToken.address.toLowerCase()]) {
|
|
112
|
+
priceMap[p.baseToken.address.toLowerCase()] = parseFloat(p.priceUsd);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) { }
|
|
118
|
+
}
|
|
119
|
+
for (const b of nonZeroBalances) {
|
|
120
|
+
const lookupAddr = (b.isNative ? (chainTokens?.WETH || chainTokens?.WBNB) : b.address)?.toLowerCase() || "";
|
|
121
|
+
const price = priceMap[lookupAddr] || 0;
|
|
122
|
+
const usdValue = b.balanceNum * price;
|
|
123
|
+
totalUsdValue += usdValue;
|
|
124
|
+
report += `- **${b.symbol}**: ${b.balanceNum.toFixed(4)} (~$${usdValue.toFixed(2)})\n`;
|
|
125
|
+
}
|
|
126
|
+
report += `\n💰 **Estimated Net Worth: $${totalUsdValue.toFixed(2)}**`;
|
|
127
|
+
return report;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return `Failed to check portfolio: ${error.message}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.checkPortfolioToolDefinition = {
|
|
134
|
+
type: "function",
|
|
135
|
+
function: {
|
|
136
|
+
name: "check_portfolio",
|
|
137
|
+
description: "Scans the user's wallet for common tokens on a specific chain and calculates their total USD Net Worth (PNL proxy) using live prices.",
|
|
138
|
+
parameters: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
chainName: {
|
|
142
|
+
type: "string",
|
|
143
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
144
|
+
description: "The blockchain network",
|
|
145
|
+
},
|
|
146
|
+
address: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "Optional wallet address. If omitted, uses the AI agent's own wallet.",
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
required: ["chainName"],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkSecurityToolDefinition = void 0;
|
|
4
|
+
exports.checkTokenSecurity = checkTokenSecurity;
|
|
5
|
+
const CHAIN_IDS = {
|
|
6
|
+
ethereum: 1,
|
|
7
|
+
base: 8453,
|
|
8
|
+
bsc: 56,
|
|
9
|
+
arbitrum: 42161,
|
|
10
|
+
optimism: 10,
|
|
11
|
+
sepolia: 11155111,
|
|
12
|
+
};
|
|
13
|
+
async function checkTokenSecurity(chainName, contractAddress) {
|
|
14
|
+
try {
|
|
15
|
+
const chainId = CHAIN_IDS[chainName];
|
|
16
|
+
if (chainName === 'sepolia') {
|
|
17
|
+
return `Security check API (GoPlus) does not support Sepolia testnet. Try a mainnet token.`;
|
|
18
|
+
}
|
|
19
|
+
const url = `https://api.gopluslabs.io/api/v1/token_security/${chainId}?contract_addresses=${contractAddress}`;
|
|
20
|
+
const res = await fetch(url);
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(`GoPlus API Error: ${res.statusText}`);
|
|
23
|
+
}
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
if (data.code !== 1 || !data.result) {
|
|
26
|
+
throw new Error(`API returned error: ${data.message || 'Unknown error'}`);
|
|
27
|
+
}
|
|
28
|
+
const tokenData = data.result[contractAddress.toLowerCase()];
|
|
29
|
+
if (!tokenData) {
|
|
30
|
+
return `Token security data not found for ${contractAddress} on ${chainName}.`;
|
|
31
|
+
}
|
|
32
|
+
let report = `Security Analysis for ${tokenData.token_name || 'Unknown'} (${tokenData.token_symbol || 'Unknown'}):\n`;
|
|
33
|
+
report += `- Is Honeypot: ${tokenData.is_honeypot === "1" ? "⚠️ YES (DANGER)" : "✅ NO"}\n`;
|
|
34
|
+
report += `- Buy Tax: ${tokenData.buy_tax ? (parseFloat(tokenData.buy_tax) * 100).toFixed(2) + '%' : 'Unknown'}\n`;
|
|
35
|
+
report += `- Sell Tax: ${tokenData.sell_tax ? (parseFloat(tokenData.sell_tax) * 100).toFixed(2) + '%' : 'Unknown'}\n`;
|
|
36
|
+
report += `- Cannot Sell All: ${tokenData.cannot_sell_all === "1" ? "⚠️ YES" : "✅ NO"}\n`;
|
|
37
|
+
report += `- Is Proxy Contract: ${tokenData.is_proxy === "1" ? "⚠️ YES (Can be upgraded)" : "✅ NO"}\n`;
|
|
38
|
+
report += `- Owner Can Change Balance: ${tokenData.owner_change_balance === "1" ? "⚠️ YES" : "✅ NO"}\n`;
|
|
39
|
+
report += `- Is Open Source: ${tokenData.is_open_source === "1" ? "✅ YES" : "⚠️ NO (Code is hidden)"}\n`;
|
|
40
|
+
return report;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return `Failed to check token security: ${error.message}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.checkSecurityToolDefinition = {
|
|
47
|
+
type: "function",
|
|
48
|
+
function: {
|
|
49
|
+
name: "check_token_security",
|
|
50
|
+
description: "Check a token's smart contract for honeypot, rugpull risks, and buy/sell tax using GoPlus Security API.",
|
|
51
|
+
parameters: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
chainName: {
|
|
55
|
+
type: "string",
|
|
56
|
+
enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"],
|
|
57
|
+
description: "The blockchain network",
|
|
58
|
+
},
|
|
59
|
+
contractAddress: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "The token smart contract address (0x...)",
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ["chainName", "contractAddress"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWalletToolDefinition = void 0;
|
|
4
|
+
exports.createWallet = createWallet;
|
|
5
|
+
const accounts_1 = require("viem/accounts");
|
|
6
|
+
async function createWallet() {
|
|
7
|
+
try {
|
|
8
|
+
const mnemonic = (0, accounts_1.generateMnemonic)(accounts_1.english);
|
|
9
|
+
const account = (0, accounts_1.mnemonicToAccount)(mnemonic);
|
|
10
|
+
return `[WALLET_CREATED_SUCCESSFULLY]
|
|
11
|
+
A new EVM wallet has been generated. Please instruct the user to back up these details immediately as they will not be saved anywhere else:
|
|
12
|
+
|
|
13
|
+
Address: ${account.address}
|
|
14
|
+
Private Key: ${account.getHdKey().privateKey ? '0x' + Buffer.from(account.getHdKey().privateKey).toString('hex') : 'Unavailable'}
|
|
15
|
+
Seed Phrase: ${mnemonic}
|
|
16
|
+
|
|
17
|
+
IMPORTANT: Do not store this data in your AI memory. Only display it once.`;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return `Failed to generate wallet: ${error.message}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.createWalletToolDefinition = {
|
|
24
|
+
type: "function",
|
|
25
|
+
function: {
|
|
26
|
+
name: "create_wallet",
|
|
27
|
+
description: "Generates a new EVM wallet address, private key, and 12-word seed phrase.",
|
|
28
|
+
parameters: {
|
|
29
|
+
type: "object",
|
|
30
|
+
properties: {},
|
|
31
|
+
required: [],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -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"]
|