nyxora 1.2.0 → 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 +82 -70
- package/dist/gateway/server.js +2 -0
- package/dist/gateway/telegram.js +5 -0
- package/dist/web3/skills/bridgeToken.js +19 -2
- 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/marketAnalysis.js +71 -0
- package/dist/web3/skills/swapToken.js +19 -2
- package/dist/web3/utils/tokens.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,11 +18,15 @@ With a beautiful, real-time dashboard inspired by modern control centers, Nyxora
|
|
|
18
18
|
* **Omnichannel Approvals**: Approve or reject pending transactions directly from the Web Dashboard's UI or via Telegram Inline Keyboard buttons on the go!
|
|
19
19
|
* **Strict API Auth**: The local Express server is protected via ephemeral Session Tokens (`x-nyxora-token`) and Strict CORS, preventing unauthorized local API requests.
|
|
20
20
|
|
|
21
|
-
### ⛓️ Web3 DeFi Skills
|
|
21
|
+
### ⛓️ Web3 DeFi Skills (Pro-Trader AI)
|
|
22
22
|
* **Multi-Chain Support**: Operate across Ethereum, Base, BSC, Arbitrum, Optimism, and Sepolia Testnet.
|
|
23
|
+
* **Wallet Generation**: Instruct the AI to generate new EVM wallets on the fly securely (Keys are never saved).
|
|
23
24
|
* **Native Wallet Operations**: Autonomously check balances and transfer native tokens using securely injected wallets.
|
|
24
|
-
* **Market Intelligence**: Fetch live crypto prices
|
|
25
|
-
* **
|
|
25
|
+
* **Advanced Market Intelligence**: Fetch live crypto prices, 24h market movements, FDV, and liquidity via CoinGecko and DexScreener integrations.
|
|
26
|
+
* **Anti-Rugpull & Security Scanner**: Nyxora can scan smart contracts via GoPlus Labs to detect Honeypots, Hidden Taxes, and malicious proxy upgrades before you buy.
|
|
27
|
+
* **PNL & Portfolio Tracking**: The AI scans your wallets and multiplies balances by live DEX prices to give you real-time Net Worth estimations.
|
|
28
|
+
* **DeFi Token Swapping & Bridging**: The agent can autonomously simulate liquidity routes and execute token swaps or cross-chain bridges with gas fee estimations.
|
|
29
|
+
* **Automated Limit Orders (Take-Profit/Cut-Loss)**: Set rules (e.g., "Sell my PEPE if price drops below $0.001"). Nyxora runs a background cron monitor and automatically executes the swap while you sleep without requiring manual approval!
|
|
26
30
|
|
|
27
31
|
### 💻 The Interface (Live Canvas)
|
|
28
32
|
* **Premium Glassmorphism UI**: A gorgeous, resizable split-pane interface.
|
package/SECURITY.md
CHANGED
|
@@ -14,7 +14,9 @@ Instead, please send an email to the repository owner or reach out privately. We
|
|
|
14
14
|
When using Nyxora, you are configuring an autonomous agent that has direct access to your injected Web3 Wallet's private key.
|
|
15
15
|
|
|
16
16
|
1. **Protect Your Keystore**: Your private key is encrypted and stored in `~/.nyxora/keystore.json`. While it is encrypted using `AES-256-GCM`, you must still treat it and your **Master Password** as highly sensitive. NEVER share your `keystore.json` or your Master Password with anyone.
|
|
17
|
-
2. **Human-in-the-Loop Verification**:
|
|
18
|
-
3. **
|
|
19
|
-
4. **
|
|
20
|
-
5. **
|
|
17
|
+
2. **Human-in-the-Loop Verification**: For standard actions, the agent is restricted from making unilateral transactions. Always review the exact details of the transaction when prompted to "Approve" or "Reject" on the Web Dashboard or Telegram Inline Keyboard before confirming.
|
|
18
|
+
3. **Limit Order Automation Risk**: If you use the AI to create a **Limit Order** (Take-profit or Cut-loss), the system WILL execute the transaction automatically in the background when the price condition is met. This intentionally bypasses the Human-in-the-Loop verification for speed. Use this feature with caution.
|
|
19
|
+
4. **Wallet Generation**: When you ask the AI to create a new wallet, it generates the Private Key and Seed Phrase locally and displays it once. It does NOT save it anywhere. You are responsible for immediately backing up this information.
|
|
20
|
+
5. **Use Testnets**: While getting started or testing new skills, ALWAYS use a testnet (e.g., Sepolia) and a wallet containing only testnet funds.
|
|
21
|
+
6. **Do Not Share Your `memory.json`**: The agent's memory may contain sensitive conversational data, generated seed phrases, or addresses you've interacted with. Be cautious before sharing the `memory.json` export.
|
|
22
|
+
7. **API Keys**: Treat your OpenAI, Gemini, and other LLM provider API keys as highly confidential. Rotate them immediately if you suspect a compromise.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.cancelLimitOrderToolDefinition = exports.listLimitOrdersToolDefinition = exports.createLimitOrderToolDefinition = exports.limitOrderManager = exports.LimitOrderManager = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const parser_1 = require("../config/parser");
|
|
9
|
+
const paths_1 = require("../config/paths");
|
|
10
|
+
const tokens_1 = require("../web3/utils/tokens");
|
|
11
|
+
const swapToken_1 = require("../web3/skills/swapToken");
|
|
12
|
+
const transactionManager_1 = require("./transactionManager");
|
|
13
|
+
const reasoning_1 = require("./reasoning");
|
|
14
|
+
class LimitOrderManager {
|
|
15
|
+
filePath;
|
|
16
|
+
orders = [];
|
|
17
|
+
monitorInterval = null;
|
|
18
|
+
constructor() {
|
|
19
|
+
const config = (0, parser_1.loadConfig)();
|
|
20
|
+
this.filePath = (0, paths_1.getPath)(config.memory?.path ? config.memory.path.replace('memory.json', 'orders.json') : 'orders.json');
|
|
21
|
+
this.loadOrders();
|
|
22
|
+
}
|
|
23
|
+
loadOrders() {
|
|
24
|
+
if (fs_1.default.existsSync(this.filePath)) {
|
|
25
|
+
try {
|
|
26
|
+
const data = fs_1.default.readFileSync(this.filePath, 'utf-8');
|
|
27
|
+
this.orders = JSON.parse(data);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
this.orders = [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
saveOrders() {
|
|
35
|
+
try {
|
|
36
|
+
fs_1.default.writeFileSync(this.filePath, JSON.stringify(this.orders, null, 2));
|
|
37
|
+
}
|
|
38
|
+
catch (error) { }
|
|
39
|
+
}
|
|
40
|
+
createOrder(chainName, fromToken, toToken, amountStr, targetPriceUsd, condition) {
|
|
41
|
+
const id = `order_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
|
|
42
|
+
const order = {
|
|
43
|
+
id, chainName, fromToken, toToken, amountStr, targetPriceUsd, condition, status: 'pending', createdAt: Date.now()
|
|
44
|
+
};
|
|
45
|
+
this.orders.push(order);
|
|
46
|
+
this.saveOrders();
|
|
47
|
+
return `Limit order created successfully. ID: ${id}. The system will monitor ${fromToken} price on ${chainName} and execute the swap to ${toToken} when price is ${condition} $${targetPriceUsd}.`;
|
|
48
|
+
}
|
|
49
|
+
listOrders() {
|
|
50
|
+
const pending = this.orders.filter(o => o.status === 'pending');
|
|
51
|
+
if (pending.length === 0)
|
|
52
|
+
return "No active limit orders.";
|
|
53
|
+
let report = "Active Limit Orders:\n";
|
|
54
|
+
pending.forEach(o => {
|
|
55
|
+
report += `- [${o.id}] Swap ${o.amountStr} ${o.fromToken} -> ${o.toToken} on ${o.chainName} when ${o.fromToken} is ${o.condition} $${o.targetPriceUsd}\n`;
|
|
56
|
+
});
|
|
57
|
+
return report;
|
|
58
|
+
}
|
|
59
|
+
cancelOrder(id) {
|
|
60
|
+
const order = this.orders.find(o => o.id === id);
|
|
61
|
+
if (!order)
|
|
62
|
+
return `Order ${id} not found.`;
|
|
63
|
+
if (order.status !== 'pending')
|
|
64
|
+
return `Order ${id} cannot be cancelled because it is ${order.status}.`;
|
|
65
|
+
order.status = 'cancelled';
|
|
66
|
+
this.saveOrders();
|
|
67
|
+
return `Order ${id} cancelled successfully.`;
|
|
68
|
+
}
|
|
69
|
+
startMonitor() {
|
|
70
|
+
if (this.monitorInterval)
|
|
71
|
+
clearInterval(this.monitorInterval);
|
|
72
|
+
// Monitor every 60 seconds
|
|
73
|
+
this.monitorInterval = setInterval(() => this.checkOrders(), 60000);
|
|
74
|
+
console.log('[LimitOrderManager] Order monitoring started (interval: 60s)');
|
|
75
|
+
}
|
|
76
|
+
async checkOrders() {
|
|
77
|
+
const pending = this.orders.filter(o => o.status === 'pending');
|
|
78
|
+
if (pending.length === 0)
|
|
79
|
+
return;
|
|
80
|
+
for (const order of pending) {
|
|
81
|
+
try {
|
|
82
|
+
let tokenAddress = (0, tokens_1.resolveToken)(order.fromToken, order.chainName);
|
|
83
|
+
if (tokenAddress === "0x0000000000000000000000000000000000000000") {
|
|
84
|
+
tokenAddress = (0, tokens_1.resolveToken)("W" + order.fromToken, order.chainName);
|
|
85
|
+
}
|
|
86
|
+
const res = await fetch(`https://api.dexscreener.com/latest/dex/tokens/${tokenAddress}`);
|
|
87
|
+
if (!res.ok)
|
|
88
|
+
continue;
|
|
89
|
+
const data = await res.json();
|
|
90
|
+
if (!data.pairs || data.pairs.length === 0)
|
|
91
|
+
continue;
|
|
92
|
+
let pair = data.pairs.find((p) => p.chainId === order.chainName) || data.pairs[0];
|
|
93
|
+
const currentPrice = parseFloat(pair.priceUsd);
|
|
94
|
+
let shouldExecute = false;
|
|
95
|
+
if (order.condition === 'above' && currentPrice >= order.targetPriceUsd)
|
|
96
|
+
shouldExecute = true;
|
|
97
|
+
if (order.condition === 'below' && currentPrice <= order.targetPriceUsd)
|
|
98
|
+
shouldExecute = true;
|
|
99
|
+
if (shouldExecute) {
|
|
100
|
+
console.log(`[LimitOrderManager] Condition met for order ${order.id}. Current price $${currentPrice} is ${order.condition} $${order.targetPriceUsd}. Executing...`);
|
|
101
|
+
// 1. Prepare Swap
|
|
102
|
+
const prepareResult = await (0, swapToken_1.prepareSwapToken)(order.chainName, order.fromToken, order.toToken, order.amountStr, 'auto');
|
|
103
|
+
// 2. Extract Tx ID
|
|
104
|
+
const txMatch = prepareResult.match(/Transaction ID: ([\w-]+)\./);
|
|
105
|
+
if (!txMatch) {
|
|
106
|
+
order.status = 'failed';
|
|
107
|
+
this.saveOrders();
|
|
108
|
+
(0, reasoning_1.processUserInput)(`Limit order ${order.id} execution failed during preparation. Output: ${prepareResult}`, 'system').catch(() => { });
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const txId = txMatch[1];
|
|
112
|
+
const tx = transactionManager_1.txManager.getTransaction(txId);
|
|
113
|
+
if (!tx)
|
|
114
|
+
throw new Error("Transaction not found in manager");
|
|
115
|
+
// 3. Execute Swap automatically
|
|
116
|
+
const executeResult = await (0, swapToken_1.executeSwap)(order.chainName, tx.details);
|
|
117
|
+
if (executeResult.includes('successful')) {
|
|
118
|
+
transactionManager_1.txManager.updateStatus(txId, 'executed', executeResult);
|
|
119
|
+
order.status = 'executed';
|
|
120
|
+
this.saveOrders();
|
|
121
|
+
(0, reasoning_1.processUserInput)(`Limit order ${order.id} just EXECUTED automatically! Price hit $${currentPrice}. Swap result: ${executeResult}. Please notify the user immediately!`, 'system').catch(() => { });
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
transactionManager_1.txManager.updateStatus(txId, 'failed', executeResult);
|
|
125
|
+
order.status = 'failed';
|
|
126
|
+
this.saveOrders();
|
|
127
|
+
(0, reasoning_1.processUserInput)(`Limit order ${order.id} FAILED to execute. Price hit $${currentPrice} but execution failed: ${executeResult}. Please notify the user.`, 'system').catch(() => { });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
console.error(`[LimitOrderManager] Error checking order ${order.id}:`, error.message);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.LimitOrderManager = LimitOrderManager;
|
|
138
|
+
exports.limitOrderManager = new LimitOrderManager();
|
|
139
|
+
exports.createLimitOrderToolDefinition = {
|
|
140
|
+
type: "function",
|
|
141
|
+
function: {
|
|
142
|
+
name: "create_limit_order",
|
|
143
|
+
description: "Creates an automatic cut-loss or take-profit limit order. The system will automatically execute the swap when the price condition is met.",
|
|
144
|
+
parameters: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
147
|
+
chainName: { type: "string", enum: ["ethereum", "base", "bsc", "arbitrum", "optimism", "sepolia"] },
|
|
148
|
+
fromToken: { type: "string", description: "Token to sell" },
|
|
149
|
+
toToken: { type: "string", description: "Token to buy" },
|
|
150
|
+
amountStr: { type: "string", description: "Amount to sell" },
|
|
151
|
+
targetPriceUsd: { type: "number", description: "Target price in USD for the fromToken" },
|
|
152
|
+
condition: { type: "string", enum: ["above", "below"], description: "Trigger when price goes above (take-profit) or below (cut-loss) target" }
|
|
153
|
+
},
|
|
154
|
+
required: ["chainName", "fromToken", "toToken", "amountStr", "targetPriceUsd", "condition"],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
exports.listLimitOrdersToolDefinition = {
|
|
159
|
+
type: "function",
|
|
160
|
+
function: {
|
|
161
|
+
name: "list_limit_orders",
|
|
162
|
+
description: "Lists all active automated limit orders.",
|
|
163
|
+
parameters: { type: "object", properties: {}, required: [] },
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
exports.cancelLimitOrderToolDefinition = {
|
|
167
|
+
type: "function",
|
|
168
|
+
function: {
|
|
169
|
+
name: "cancel_limit_order",
|
|
170
|
+
description: "Cancels an active limit order by ID.",
|
|
171
|
+
parameters: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: { id: { type: "string" } },
|
|
174
|
+
required: ["id"],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
};
|
package/dist/agent/reasoning.js
CHANGED
|
@@ -17,6 +17,11 @@ const swapToken_1 = require("../web3/skills/swapToken");
|
|
|
17
17
|
const bridgeToken_1 = require("../web3/skills/bridgeToken");
|
|
18
18
|
const mintNft_1 = require("../web3/skills/mintNft");
|
|
19
19
|
const customTx_1 = require("../web3/skills/customTx");
|
|
20
|
+
const createWallet_1 = require("../web3/skills/createWallet");
|
|
21
|
+
const checkSecurity_1 = require("../web3/skills/checkSecurity");
|
|
22
|
+
const marketAnalysis_1 = require("../web3/skills/marketAnalysis");
|
|
23
|
+
const checkPortfolio_1 = require("../web3/skills/checkPortfolio");
|
|
24
|
+
const limitOrderManager_1 = require("./limitOrderManager");
|
|
20
25
|
const paths_1 = require("../config/paths");
|
|
21
26
|
exports.logger = new logger_1.Logger();
|
|
22
27
|
let currentKeyIndex = 0;
|
|
@@ -150,7 +155,14 @@ async function processUserInput(input, role = 'user') {
|
|
|
150
155
|
swapToken_1.swapTokenToolDefinition,
|
|
151
156
|
bridgeToken_1.bridgeTokenToolDefinition,
|
|
152
157
|
mintNft_1.mintNftToolDefinition,
|
|
153
|
-
customTx_1.customTxToolDefinition
|
|
158
|
+
customTx_1.customTxToolDefinition,
|
|
159
|
+
createWallet_1.createWalletToolDefinition,
|
|
160
|
+
checkSecurity_1.checkSecurityToolDefinition,
|
|
161
|
+
marketAnalysis_1.marketAnalysisToolDefinition,
|
|
162
|
+
checkPortfolio_1.checkPortfolioToolDefinition,
|
|
163
|
+
limitOrderManager_1.createLimitOrderToolDefinition,
|
|
164
|
+
limitOrderManager_1.listLimitOrdersToolDefinition,
|
|
165
|
+
limitOrderManager_1.cancelLimitOrderToolDefinition
|
|
154
166
|
],
|
|
155
167
|
tool_choice: "auto",
|
|
156
168
|
});
|
|
@@ -171,76 +183,76 @@ async function processUserInput(input, role = 'user') {
|
|
|
171
183
|
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
|
|
172
184
|
for (const _toolCall of responseMessage.tool_calls) {
|
|
173
185
|
const toolCall = _toolCall;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
exports.logger.addEntry({
|
|
238
|
-
role: 'tool',
|
|
239
|
-
tool_call_id: toolCall.id,
|
|
240
|
-
name: toolCall.function.name,
|
|
241
|
-
content: customResult,
|
|
242
|
-
});
|
|
186
|
+
let result = "";
|
|
187
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
188
|
+
const toolName = toolCall.function.name;
|
|
189
|
+
switch (toolName) {
|
|
190
|
+
case 'get_balance': {
|
|
191
|
+
result = await (0, getBalance_1.getBalance)(args.chainName, args.address, args.token);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case 'transfer_token':
|
|
195
|
+
case 'transfer_native': {
|
|
196
|
+
result = await (0, transfer_1.prepareTransfer)(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case 'get_price': {
|
|
200
|
+
result = await (0, getPrice_1.getPrice)(args.coinId);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case 'swap_token': {
|
|
204
|
+
result = await (0, swapToken_1.prepareSwapToken)(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case 'bridge_token': {
|
|
208
|
+
result = await (0, bridgeToken_1.prepareBridgeToken)(args.fromChainName, args.toChainName, args.fromToken, args.toToken, args.amountStr, args.mode, args.providerName);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 'mint_nft': {
|
|
212
|
+
result = await (0, mintNft_1.prepareMintNft)(args.chainName, args.contractAddress, args.functionSignature, args.argsStr, args.valueEth);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case 'custom_tx': {
|
|
216
|
+
result = await (0, customTx_1.prepareCustomTx)(args.chainName, args.toAddress, args.dataHex, args.valueEth, args.gasLimitStr);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case 'create_wallet': {
|
|
220
|
+
result = await (0, createWallet_1.createWallet)();
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 'check_token_security': {
|
|
224
|
+
result = await (0, checkSecurity_1.checkTokenSecurity)(args.chainName, args.contractAddress);
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case 'analyze_market': {
|
|
228
|
+
result = await (0, marketAnalysis_1.analyzeMarket)(args.chainName, args.tokenAddressOrSymbol);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case 'check_portfolio': {
|
|
232
|
+
result = await (0, checkPortfolio_1.checkPortfolio)(args.chainName, args.address);
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
case 'create_limit_order': {
|
|
236
|
+
result = limitOrderManager_1.limitOrderManager.createOrder(args.chainName, args.fromToken, args.toToken, args.amountStr, args.targetPriceUsd, args.condition);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 'list_limit_orders': {
|
|
240
|
+
result = limitOrderManager_1.limitOrderManager.listOrders();
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case 'cancel_limit_order': {
|
|
244
|
+
result = limitOrderManager_1.limitOrderManager.cancelOrder(args.id);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
default:
|
|
248
|
+
result = `Error: Tool ${toolName} is not implemented.`;
|
|
243
249
|
}
|
|
250
|
+
exports.logger.addEntry({
|
|
251
|
+
role: 'tool',
|
|
252
|
+
tool_call_id: toolCall.id,
|
|
253
|
+
name: toolName,
|
|
254
|
+
content: result,
|
|
255
|
+
});
|
|
244
256
|
}
|
|
245
257
|
// Second call to get the final answer after tool execution
|
|
246
258
|
const secondMessages = [
|
package/dist/gateway/server.js
CHANGED
|
@@ -12,6 +12,7 @@ const reasoning_1 = require("../agent/reasoning");
|
|
|
12
12
|
const parser_1 = require("../config/parser");
|
|
13
13
|
const tracker_1 = require("./tracker");
|
|
14
14
|
const transactionManager_1 = require("../agent/transactionManager");
|
|
15
|
+
const limitOrderManager_1 = require("../agent/limitOrderManager");
|
|
15
16
|
const transfer_1 = require("../web3/skills/transfer");
|
|
16
17
|
const swapToken_1 = require("../web3/skills/swapToken");
|
|
17
18
|
const getBalance_1 = require("../web3/skills/getBalance");
|
|
@@ -180,6 +181,7 @@ app.use((req, res, next) => {
|
|
|
180
181
|
}
|
|
181
182
|
});
|
|
182
183
|
function startServer() {
|
|
184
|
+
limitOrderManager_1.limitOrderManager.startMonitor();
|
|
183
185
|
const PORT = process.env.PORT || 3000;
|
|
184
186
|
app.listen(PORT, () => {
|
|
185
187
|
console.log(`🤖 Nyxora API Server running on port ${PORT}`);
|
package/dist/gateway/telegram.js
CHANGED
|
@@ -28,6 +28,11 @@ function startTelegramBot() {
|
|
|
28
28
|
const text = msg.text;
|
|
29
29
|
if (!text)
|
|
30
30
|
return;
|
|
31
|
+
if (text === '/clear') {
|
|
32
|
+
reasoning_1.logger.clear();
|
|
33
|
+
bot.sendMessage(chatId, '✅ Memori AI telah dihapus. Mari kita mulai obrolan baru!');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
31
36
|
// Log incoming message
|
|
32
37
|
console.log(`[Telegram] Received from ${msg.from?.first_name}: ${text}`);
|
|
33
38
|
// Send typing action to Telegram
|
|
@@ -32,6 +32,8 @@ async function getLifiQuote(fromChainId, toChainId, fromToken, toToken, amountWe
|
|
|
32
32
|
return await res.json();
|
|
33
33
|
}
|
|
34
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";
|
|
35
37
|
const body = {
|
|
36
38
|
user: userAddress,
|
|
37
39
|
originChainId: fromChainId,
|
|
@@ -41,7 +43,7 @@ async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountW
|
|
|
41
43
|
amount: amountWei,
|
|
42
44
|
tradeType: "EXACT_INPUT"
|
|
43
45
|
};
|
|
44
|
-
const res = await fetch(
|
|
46
|
+
const res = await fetch(`${baseUrl}/quote`, {
|
|
45
47
|
method: "POST",
|
|
46
48
|
headers: { "Content-Type": "application/json" },
|
|
47
49
|
body: JSON.stringify(body)
|
|
@@ -75,7 +77,22 @@ async function prepareBridgeToken(fromChainName, toChainName, fromToken, toToken
|
|
|
75
77
|
let txRequest = null;
|
|
76
78
|
let approvalAddress = null;
|
|
77
79
|
let expectedOutputStr = "";
|
|
78
|
-
|
|
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 ---
|
|
79
96
|
if (actualProvider === "lifi") {
|
|
80
97
|
const quote = await getLifiQuote(fromChainId, toChainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
|
|
81
98
|
txRequest = quote.transactionRequest;
|
|
@@ -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,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
|
+
};
|
|
@@ -32,6 +32,8 @@ async function getLifiQuote(fromChainId, toChainId, fromToken, toToken, amountWe
|
|
|
32
32
|
return await res.json();
|
|
33
33
|
}
|
|
34
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";
|
|
35
37
|
const body = {
|
|
36
38
|
user: userAddress,
|
|
37
39
|
originChainId: fromChainId,
|
|
@@ -41,7 +43,7 @@ async function getRelayQuote(fromChainId, toChainId, fromToken, toToken, amountW
|
|
|
41
43
|
amount: amountWei,
|
|
42
44
|
tradeType: "EXACT_INPUT"
|
|
43
45
|
};
|
|
44
|
-
const res = await fetch(
|
|
46
|
+
const res = await fetch(`${baseUrl}/quote`, {
|
|
45
47
|
method: "POST",
|
|
46
48
|
headers: { "Content-Type": "application/json" },
|
|
47
49
|
body: JSON.stringify(body)
|
|
@@ -74,7 +76,22 @@ async function prepareSwapToken(chainName, fromToken, toToken, amountStr, mode =
|
|
|
74
76
|
let txRequest = null;
|
|
75
77
|
let approvalAddress = null;
|
|
76
78
|
let expectedOutputStr = "";
|
|
77
|
-
|
|
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 ---
|
|
78
95
|
if (actualProvider === "lifi") {
|
|
79
96
|
const quote = await getLifiQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, account.address, slippagePercent / 100);
|
|
80
97
|
txRequest = quote.transactionRequest;
|
|
@@ -90,7 +90,8 @@ exports.TOKEN_MAP = {
|
|
|
90
90
|
sepolia: {
|
|
91
91
|
ETH: "0x0000000000000000000000000000000000000000",
|
|
92
92
|
WETH: "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14",
|
|
93
|
-
USDC: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238" // Circle Faucet Sepolia USDC
|
|
93
|
+
USDC: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // Circle Faucet Sepolia USDC
|
|
94
|
+
USDT: "0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0" // Common Sepolia USDT
|
|
94
95
|
}
|
|
95
96
|
};
|
|
96
97
|
function resolveToken(tokenSymbolOrAddress, chainName) {
|