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 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 and 24h market movements via CoinGecko integration.
25
- * **DeFi Token Swapping**: The agent can autonomously simulate liquidity routes and execute token swaps with gas fee estimations.
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**: 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. **Use Testnets**: While getting started or testing new skills, ALWAYS use a testnet (e.g., Sepolia) and a wallet containing only testnet funds.
19
- 4. **Do Not Share Your `memory.json`**: The agent's memory may contain sensitive conversational data or addresses you've interacted with. Be cautious before sharing the `memory.json` export.
20
- 5. **API Keys**: Treat your OpenAI, Gemini, and other LLM provider API keys as highly confidential. Rotate them immediately if you suspect a compromise.
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
+ };
@@ -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
- if (toolCall.function.name === 'get_balance') {
175
- const args = JSON.parse(toolCall.function.arguments);
176
- const balanceResult = await (0, getBalance_1.getBalance)(args.chainName, args.address, args.token);
177
- exports.logger.addEntry({
178
- role: 'tool',
179
- tool_call_id: toolCall.id,
180
- name: toolCall.function.name,
181
- content: balanceResult,
182
- });
183
- }
184
- else if (toolCall.function.name === 'transfer_token' || toolCall.function.name === 'transfer_native') {
185
- const args = JSON.parse(toolCall.function.arguments);
186
- const transferResult = await (0, transfer_1.prepareTransfer)(args.chainName, args.toAddress, args.amountStr || args.amountEth, args.token);
187
- exports.logger.addEntry({
188
- role: 'tool',
189
- tool_call_id: toolCall.id,
190
- name: toolCall.function.name,
191
- content: transferResult,
192
- });
193
- }
194
- else if (toolCall.function.name === 'get_price') {
195
- const args = JSON.parse(toolCall.function.arguments);
196
- const priceResult = await (0, getPrice_1.getPrice)(args.coinId);
197
- exports.logger.addEntry({
198
- role: 'tool',
199
- tool_call_id: toolCall.id,
200
- name: toolCall.function.name,
201
- content: priceResult,
202
- });
203
- }
204
- else if (toolCall.function.name === 'swap_token') {
205
- const args = JSON.parse(toolCall.function.arguments);
206
- const swapResult = await (0, swapToken_1.prepareSwapToken)(args.chainName, args.fromToken, args.toToken, args.amountStr || args.amount, args.mode, args.providerName);
207
- exports.logger.addEntry({
208
- role: 'tool',
209
- tool_call_id: toolCall.id,
210
- name: toolCall.function.name,
211
- content: swapResult,
212
- });
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
- });
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 = [
@@ -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}`);
@@ -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("https://api.relay.link/quote", {
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
- const actualProvider = mode === "auto" ? "lifi" : providerName;
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("https://api.relay.link/quote", {
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
- const actualProvider = mode === "auto" ? "lifi" : providerName;
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "",
5
5
  "main": "dist/gateway/cli.js",
6
6
  "files": [