nyxora 26.6.12 → 26.6.13

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.
Files changed (41) hide show
  1. package/README.md +3 -3
  2. package/dist/packages/core/src/agent/bridgeWatcher.js +34 -0
  3. package/dist/packages/core/src/agent/reasoning.js +6 -2
  4. package/dist/packages/core/src/agent/transactionManager.js +47 -0
  5. package/dist/packages/core/src/gateway/server.js +3 -0
  6. package/dist/packages/core/src/gateway/telegram.js +25 -1
  7. package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +3 -1
  8. package/dist/packages/core/src/web3/aggregator/aggregatorTestnet.js +19 -10
  9. package/dist/packages/core/src/web3/skills/bridgeToken.js +1 -1
  10. package/dist/packages/core/src/web3/skills/customTx.js +1 -1
  11. package/dist/packages/core/src/web3/skills/defiLending.js +2 -2
  12. package/dist/packages/core/src/web3/skills/mintNft.js +1 -1
  13. package/dist/packages/core/src/web3/skills/nativeOpBridge.js +70 -0
  14. package/dist/packages/core/src/web3/skills/provideLiquidity.js +3 -3
  15. package/dist/packages/core/src/web3/skills/revokeApprovals.js +1 -1
  16. package/dist/packages/core/src/web3/skills/swapToken.js +1 -1
  17. package/dist/packages/core/src/web3/skills/transfer.js +1 -1
  18. package/dist/packages/core/src/web3/skills/yieldVault.js +2 -2
  19. package/package.json +4 -1
  20. package/packages/core/package.json +1 -1
  21. package/packages/core/src/agent/bridgeWatcher.ts +39 -0
  22. package/packages/core/src/agent/reasoning.ts +6 -2
  23. package/packages/core/src/agent/transactionManager.ts +66 -0
  24. package/packages/core/src/gateway/server.ts +4 -0
  25. package/packages/core/src/gateway/telegram.ts +24 -1
  26. package/packages/core/src/web3/aggregator/aggregatorMainnet.ts +4 -1
  27. package/packages/core/src/web3/aggregator/aggregatorTestnet.ts +28 -12
  28. package/packages/core/src/web3/skills/bridgeToken.ts +1 -1
  29. package/packages/core/src/web3/skills/customTx.ts +1 -1
  30. package/packages/core/src/web3/skills/defiLending.ts +2 -2
  31. package/packages/core/src/web3/skills/mintNft.ts +1 -1
  32. package/packages/core/src/web3/skills/nativeOpBridge.ts +83 -0
  33. package/packages/core/src/web3/skills/provideLiquidity.ts +3 -3
  34. package/packages/core/src/web3/skills/revokeApprovals.ts +1 -1
  35. package/packages/core/src/web3/skills/swapToken.ts +1 -1
  36. package/packages/core/src/web3/skills/transfer.ts +1 -1
  37. package/packages/core/src/web3/skills/yieldVault.ts +2 -2
  38. package/packages/dashboard/package.json +1 -1
  39. package/packages/mcp-server/package.json +1 -1
  40. package/packages/policy/package.json +1 -1
  41. package/packages/signer/package.json +1 -1
package/README.md CHANGED
@@ -13,14 +13,14 @@ Nyxora is a **secure, non-custodial runtime infrastructure for autonomous onchai
13
13
 
14
14
  **Nyxora now natively supports the Model Context Protocol (MCP)**. You can transform your external AI agents (like Claude Desktop and Cursor) into secure Web3 actors that execute swaps and fetch balances using Nyxora's secure signer vault. [View the MCP Integration Guide](https://nyxoraai.github.io/Nyxora/guide/mcp-integration)
15
15
 
16
- It operates under an institutional-grade **Cryptographically Bound Human-in-the-Loop** execution model, ensuring that Remote AIs (LLMs) never have unilateral access to your funds.
16
+ It operates under a **Zero-Trust, Defense-in-Depth Cryptographically Bound Human-in-the-Loop** execution model, ensuring that Remote AIs (LLMs) never have unilateral access to your funds.
17
17
 
18
18
  ---
19
19
 
20
20
  ## 🔥 Key Features
21
21
 
22
22
  ### Advanced Security Architecture
23
- * **🛡️ On-Chain AI Kill-Switch**: Nyxora is governed by an Arbitrum Smart Contract (`NyxoraAgentRegistry`). Users have absolute cryptographic power to instantly paralyze the AI's on-chain execution if compromised, solving the Web3 AI safety dilemma. [Read more about our Arbitrum Architecture ->](https://nyxoraai.github.io/Nyxora/security/smart-contract)
23
+ * **🛡️ On-Chain AI Kill-Switch**: Nyxora is governed by an Arbitrum Smart Contract (`NyxoraAgentRegistry`). Users have absolute cryptographic power to instantly paralyze the AI's on-chain execution if compromised, solving the Web3 AI safety dilemma. [Read more about our Arbitrum Architecture](https://nyxoraai.github.io/Nyxora/security/smart-contract)
24
24
  * **3-Tier IPC Architecture**: Nyxora is split into isolated processes: **Core** (LLM Runtime), **Policy Engine** (Guardrails on port 3001), and **Signer Vault** (Isolated Key Manager on Unix Sockets).
25
25
  * **DeFi Configuration BYOK & UI Masking**: All aggregator and provider API keys are strictly isolated via a Bring Your Own Keys (BYOK) architecture into a heavily guarded `~/.nyxora/defi_keys.yaml` file. The local web Dashboard masks these injected secrets using `***********` and `IS_SET` censorship, completely neutralizing malicious browser extensions from exfiltrating your keys.
26
26
  * **Approval Replay Protection (Nonce Guard)**: Transactions requested by the AI are drafted as hashes and signed with a randomized 16-byte Nonce. The `/api/transactions/:id/approve` endpoint strictly enforces Nonce matching to completely eliminate double-spending and Replay Attacks.
@@ -32,7 +32,7 @@ It operates under an institutional-grade **Cryptographically Bound Human-in-the-
32
32
  * **Security Scanner**: Nyxora can scan smart contracts via GoPlus Labs to detect Honeypots, Hidden Taxes, and malicious proxy upgrades before you buy.
33
33
  * **Advanced DeFi Optimization**: Autonomously supply assets to Aave V3, deposit into Beefy/Yearn Auto-Compounder Vaults, manage Uniswap V3 Liquidity (LP), and instantly revoke infinite approvals to secure your wallet. Features intelligent Transaction Chaining to auto-approve allowances prior to execution.
34
34
  * **6-Engine Meta-Aggregator & Anti-MEV**: The core engine interfaces with a powerful 6-Engine Meta-Aggregator (**1inch, 0x, LI.FI, Relay, OpenOcean, and KyberSwap**) to route tokens cross-chain, ensuring absolute maximum liquidity depth.
35
- * **Adaptive Auto Slippage Protection**: Nyxora enforces a dynamic and adaptive **'auto' slippage** by default to leverage dynamic MEV-protection from these enterprise aggregators. However, the user retains absolute control to override this dynamically—either globally via the Dashboard Settings or on a per-transaction basis through NLP chat commands (e.g., *"Swap 1 ETH to PEPE with 10% slippage"*).
35
+ * **Adaptive Auto Slippage Protection**: Nyxora enforces a dynamic and adaptive **'auto' slippage** by default to leverage dynamic MEV-protection from these industry-standard aggregators. However, the user retains absolute control to override this dynamically—either globally via the Dashboard Settings or on a per-transaction basis through NLP chat commands (e.g., *"Swap 1 ETH to PEPE with 10% slippage"*).
36
36
 
37
37
  * **Cross-Chain Hybrid Market Scanner**: Real-time asset tracking combining CoinGecko global data with DexScreener on-chain metrics across Ethereum, Base, Solana, BSC, and more.
38
38
  * **"Lean Degen" Auto-Whitelist**: Automatically intercepts Contract Addresses (CAs) whenever you check balances or swap tokens, saving them to your localized `user_whitelist.json` for future tracking.
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startBridgeWatcher = startBridgeWatcher;
4
+ const transactionManager_1 = require("./transactionManager");
5
+ const telegram_1 = require("../gateway/telegram");
6
+ const parser_1 = require("../config/parser");
7
+ // In a real production environment, this would use the @eth-optimism/sdk
8
+ // to fetch the Merkle Proof and call proveWithdrawalTransaction on L1.
9
+ // For the scope of this architecture prototype, we simulate the Challenge Period
10
+ // watcher by using a time-delay, representing the exact asynchronous behavior.
11
+ const CHALLENGE_PERIOD_MS = 2 * 60 * 1000; // Simulating a 2-minute challenge period for testnet demo
12
+ function startBridgeWatcher() {
13
+ console.log('[Bridge Watcher] Started background daemon for asynchronous L2 withdrawals');
14
+ setInterval(async () => {
15
+ const config = (0, parser_1.loadConfig)();
16
+ const authId = config.integrations?.telegram?.authorized_chat_id;
17
+ if (!authId)
18
+ return;
19
+ const pending = transactionManager_1.txManager.getPendingWithdrawals();
20
+ const now = Date.now();
21
+ for (const w of pending) {
22
+ if (w.status === 'WAITING_FOR_CHALLENGE') {
23
+ if (now - w.createdAt > CHALLENGE_PERIOD_MS) {
24
+ // The simulated challenge period is over. State root is "published".
25
+ console.log(`[Bridge Watcher] Withdrawal ${w.id} is ready for L1 claim!`);
26
+ transactionManager_1.txManager.updateWithdrawalStatus(w.id, 'READY_FOR_CLAIM');
27
+ const amountDisplay = Number(w.amount) / 1e18; // assuming 18 decimals
28
+ const message = `🔔 **Bridge Ready to Claim!**\n\nYour withdrawal of ${amountDisplay} ETH from ${w.l2Chain} to ${w.l1Chain} has completed its challenge period.\n\nShall I execute the Prove & Claim transaction on L1 now?`;
29
+ await (0, telegram_1.sendPushNotification)(authId, message, w.id);
30
+ }
31
+ }
32
+ }
33
+ }, 30000); // Check every 30 seconds
34
+ }
@@ -288,8 +288,12 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
288
288
  if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
289
289
  let canFastReturnAll = true;
290
290
  let accumulatedResults = [];
291
- // Disabled fastReturnTools to enforce Web3 Reasoning (V3 feature)
292
- const fastReturnTools = [];
291
+ // Enabled fastReturnTools to eliminate 2nd LLM latency for transaction popups
292
+ const fastReturnTools = [
293
+ 'transfer_token', 'transfer_native', 'swap_token', 'bridge_token',
294
+ 'mint_nft', 'custom_tx', 'revoke_approval', 'supply_aave',
295
+ 'deposit_yield_vault', 'provide_liquidity_v3'
296
+ ];
293
297
  for (const _toolCall of responseMessage.tool_calls) {
294
298
  const toolCall = _toolCall;
295
299
  let result = "";
@@ -5,8 +5,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.txManager = void 0;
7
7
  const crypto_1 = __importDefault(require("crypto"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
8
10
  class TransactionManager {
9
11
  transactions = new Map();
12
+ withdrawals = new Map();
13
+ dbPath;
14
+ constructor() {
15
+ this.dbPath = path_1.default.join(process.cwd(), '.nyxora_withdrawals.json');
16
+ this.loadWithdrawals();
17
+ }
18
+ loadWithdrawals() {
19
+ if (fs_1.default.existsSync(this.dbPath)) {
20
+ try {
21
+ const data = fs_1.default.readFileSync(this.dbPath, 'utf8');
22
+ const parsed = JSON.parse(data);
23
+ parsed.forEach(w => this.withdrawals.set(w.id, w));
24
+ }
25
+ catch (e) {
26
+ console.error("Failed to load withdrawals DB:", e);
27
+ }
28
+ }
29
+ }
30
+ saveWithdrawals() {
31
+ const data = Array.from(this.withdrawals.values());
32
+ fs_1.default.writeFileSync(this.dbPath, JSON.stringify(data, null, 2));
33
+ }
10
34
  createPendingTransaction(type, chainName, details) {
11
35
  const id = crypto_1.default.randomUUID();
12
36
  const nonce = crypto_1.default.randomBytes(16).toString('hex');
@@ -36,5 +60,28 @@ class TransactionManager {
36
60
  tx.result = result;
37
61
  }
38
62
  }
63
+ // --- WITHDRAWAL LOGIC ---
64
+ createPendingWithdrawal(data) {
65
+ const id = crypto_1.default.randomUUID();
66
+ const withdrawal = {
67
+ ...data,
68
+ id,
69
+ status: 'WAITING_FOR_CHALLENGE',
70
+ createdAt: Date.now()
71
+ };
72
+ this.withdrawals.set(id, withdrawal);
73
+ this.saveWithdrawals();
74
+ return withdrawal;
75
+ }
76
+ getPendingWithdrawals() {
77
+ return Array.from(this.withdrawals.values()).filter(w => w.status !== 'COMPLETED');
78
+ }
79
+ updateWithdrawalStatus(id, status) {
80
+ const w = this.withdrawals.get(id);
81
+ if (w) {
82
+ w.status = status;
83
+ this.saveWithdrawals();
84
+ }
85
+ }
39
86
  }
40
87
  exports.txManager = new TransactionManager();
@@ -70,6 +70,7 @@ const analyzeDocument_1 = require("../system/skills/analyzeDocument");
70
70
  const searchWeb_1 = require("../system/skills/searchWeb");
71
71
  const googleWorkspace_1 = require("../system/skills/googleWorkspace");
72
72
  const telegram_1 = require("./telegram");
73
+ const bridgeWatcher_1 = require("../agent/bridgeWatcher");
73
74
  const eventListener_1 = require("../web3/eventListener");
74
75
  const googleAuthModule_1 = require("./googleAuthModule");
75
76
  const legalGenerator_1 = require("./legalGenerator");
@@ -849,6 +850,8 @@ function startServer() {
849
850
  console.log(`🤖 Nyxora API Server running on port ${PORT}`);
850
851
  // Start the Telegram bot listener
851
852
  (0, telegram_1.startTelegramBot)();
853
+ // Start Asynchronous Bridge Watcher
854
+ (0, bridgeWatcher_1.startBridgeWatcher)();
852
855
  // Start Event Listener for Limit Orders (V3)
853
856
  eventListener_1.eventListener.start();
854
857
  });
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.formatToTelegramHTML = formatToTelegramHTML;
7
7
  exports.startTelegramBot = startTelegramBot;
8
+ exports.sendPushNotification = sendPushNotification;
8
9
  const telegraf_1 = require("telegraf");
9
10
  const reasoning_1 = require("../agent/reasoning");
10
11
  const parser_1 = require("../config/parser");
@@ -18,6 +19,7 @@ const executeDefi_1 = require("../web3/skills/executeDefi");
18
19
  const revokeApprovals_1 = require("../web3/skills/revokeApprovals");
19
20
  const formatter_1 = require("../utils/formatter");
20
21
  const picocolors_1 = __importDefault(require("picocolors"));
22
+ let globalBotInstance = null;
21
23
  function formatToTelegramHTML(text) {
22
24
  if (!text)
23
25
  return "";
@@ -32,9 +34,11 @@ function formatToTelegramHTML(text) {
32
34
  // Convert code blocks and inline code
33
35
  html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
34
36
  html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
35
- // Strip <thought> blocks completely for user-friendly output
37
+ // Strip <thought> and <think> blocks completely for user-friendly output
36
38
  html = html.replace(/&lt;thought&gt;[\s\S]*?&lt;\/thought&gt;\n?/g, '');
37
39
  html = html.replace(/<thought>[\s\S]*?<\/thought>\n?/g, '');
40
+ html = html.replace(/&lt;think&gt;[\s\S]*?&lt;\/think&gt;\n?/g, '');
41
+ html = html.replace(/<think>[\s\S]*?<\/think>\n?/g, '');
38
42
  // Transform Markdown Tables to <pre> monospaced blocks so they don't break on mobile
39
43
  const tableRegex = /(?:\|.*\|(?:\n|$))+/g;
40
44
  html = html.replace(tableRegex, (match) => {
@@ -51,6 +55,7 @@ function startTelegramBot() {
51
55
  }
52
56
  try {
53
57
  const bot = new telegraf_1.Telegraf(token);
58
+ globalBotInstance = bot;
54
59
  // Pairing state variables
55
60
  const isPaired = !!config.integrations?.telegram?.authorized_chat_id;
56
61
  let generatedPin = '';
@@ -257,3 +262,22 @@ function startTelegramBot() {
257
262
  console.error('[Telegram] Failed to initialize bot:', error);
258
263
  }
259
264
  }
265
+ async function sendPushNotification(chatId, message, withdrawalId) {
266
+ if (!globalBotInstance)
267
+ return;
268
+ try {
269
+ let extraParams = { parse_mode: 'HTML' };
270
+ if (withdrawalId) {
271
+ extraParams = {
272
+ ...extraParams,
273
+ ...telegraf_1.Markup.inlineKeyboard([
274
+ [telegraf_1.Markup.button.callback(`✅ Approve Claim`, `claim_${withdrawalId}`)]
275
+ ])
276
+ };
277
+ }
278
+ await globalBotInstance.telegram.sendMessage(chatId, formatToTelegramHTML(message), extraParams);
279
+ }
280
+ catch (error) {
281
+ console.error('[Telegram] Failed to send push notification:', error);
282
+ }
283
+ }
@@ -238,6 +238,8 @@ async function fetchKyberSwap(fromChain, fromToken, toToken, amount, address, sl
238
238
  if (!buildRes.ok)
239
239
  throw new Error(await buildRes.text());
240
240
  const buildData = await buildRes.json();
241
+ const isNative = fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ||
242
+ fromToken === '0x0000000000000000000000000000000000000000';
241
243
  return {
242
244
  provider: 'KyberSwap',
243
245
  expectedOutput: (Number(routeData.data.routeSummary.amountOut) / 1e18).toString(),
@@ -246,7 +248,7 @@ async function fetchKyberSwap(fromChain, fromToken, toToken, amount, address, sl
246
248
  txPayload: {
247
249
  to: buildData.data.routerAddress,
248
250
  data: buildData.data.data,
249
- value: routeData.data.routeSummary.amountInUsd // simplified value mapping
251
+ value: isNative ? amount : "0" // FIXED: Mengirim jumlah asli dalam WEI jika Native ETH, atau 0 jika ERC20
250
252
  },
251
253
  rawQuote: buildData.data
252
254
  };
@@ -3,21 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fetchTestnetBestRoute = fetchTestnetBestRoute;
4
4
  const httpClient_1 = require("../../utils/httpClient");
5
5
  const viem_1 = require("viem");
6
+ const nativeOpBridge_1 = require("../skills/nativeOpBridge");
6
7
  async function fetchTestnetBestRoute(fromChain, toChain, fromToken, toToken, amountInWei, userAddress, slippageTolerance = "auto") {
7
8
  const promises = [];
8
- // Routing Logic
9
- const isRelayRoute = (fromChain === 'sepolia' && toChain === 'base_sepolia') ||
10
- (fromChain === 'base_sepolia' && toChain === 'sepolia');
11
- const isArbitrumRoute = (fromChain === 'sepolia' && toChain === 'arbitrum_sepolia') ||
12
- (fromChain === 'arbitrum_sepolia' && toChain === 'sepolia');
13
- if (isRelayRoute) {
14
- promises.push(fetchRelayTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress));
9
+ // Routing Logic Hierarchy
10
+ const isOpStack = toChain === 'optimism_sepolia' || toChain === 'base_sepolia' ||
11
+ fromChain === 'optimism_sepolia' || fromChain === 'base_sepolia';
12
+ const isArbitrum = toChain === 'arbitrum_sepolia' || fromChain === 'arbitrum_sepolia';
13
+ if (isOpStack) {
14
+ // Primary: Universal OP Stack
15
+ promises.push((0, nativeOpBridge_1.fetchNativeOpBridgeTestnet)(fromChain, toChain, fromToken, toToken, amountInWei, userAddress)
16
+ .catch(e => { console.warn('Native OP failed:', e.message); return null; }));
17
+ // Fallback 1: Relay Testnet (Only supports Base Sepolia natively via Relay endpoint)
18
+ if (toChain === 'base_sepolia' || fromChain === 'base_sepolia') {
19
+ promises.push(fetchRelayTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress)
20
+ .catch(e => { console.warn('Relay Fallback failed:', e.message); return null; }));
21
+ }
15
22
  }
16
- else if (isArbitrumRoute) {
17
- promises.push(fetchArbitrumBridgeTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress));
23
+ else if (isArbitrum) {
24
+ // Fallback 2: Official Arbitrum Bridge
25
+ promises.push(fetchArbitrumBridgeTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress)
26
+ .catch(e => { console.warn('Arbitrum Bridge failed:', e.message); return null; }));
18
27
  }
19
28
  else {
20
- throw new Error(`[Testnet Meta-Aggregator] Unsupported testnet route from ${fromChain} to ${toChain}. Only Sepolia <-> Base Sepolia or Sepolia <-> Arbitrum Sepolia are supported.`);
29
+ throw new Error(`[Testnet Meta-Aggregator] Unsupported testnet route from ${fromChain} to ${toChain}.`);
21
30
  }
22
31
  const results = await Promise.allSettled(promises);
23
32
  let bestQuote = null;
@@ -52,7 +52,7 @@ async function prepareBridgeToken(fromChain, toChain, tokenSymbol, amountStr, mo
52
52
  txData: route.txPayload,
53
53
  rawQuote: route.rawQuote
54
54
  });
55
- return `TRANSACTION_PENDING\nBridge transaction of ${amountStr} ${tokenSymbol} from ${fromChain.toUpperCase()} to ${toChain.toUpperCase()} via ${route.provider} has been queued.\nTransaction ID: ${tx.id}\nPlease review and approve this transaction in your dashboard UI.`;
55
+ return `⏳ **Bridge queued:** ${amountStr} ${tokenSymbol} | ${fromChain.toUpperCase()} ➡️ ${toChain.toUpperCase()} | Via ${route.provider} | Approve below.`;
56
56
  }
57
57
  catch (error) {
58
58
  console.error("BRIDGE TOKEN ERROR:", error);
@@ -14,7 +14,7 @@ async function prepareCustomTx(chainName, toAddress, data, valueWei = "0", descr
14
14
  valueWei,
15
15
  description
16
16
  });
17
- return `TRANSACTION_PENDING\nCustom transaction (${description}) on ${chainName.toUpperCase()} has been queued.\nTransaction ID: ${tx.id}\nPlease review and approve this transaction in your dashboard UI.`;
17
+ return `⏳ **Custom Tx queued:** ${description} | ${chainName.toUpperCase()} | Approve below.`;
18
18
  }
19
19
  catch (error) {
20
20
  return `Failed to prepare custom tx: ${error.message}`;
@@ -62,7 +62,7 @@ async function prepareAaveSupply(chainName, tokenAddressOrSymbol, amountStr) {
62
62
  symbol: metadata.symbol,
63
63
  gasEstimate: "60000"
64
64
  });
65
- return `TRANSACTION_PENDING: You need to approve Aave V3 to spend your ${metadata.symbol} before supplying. I have prepared the Approval transaction. ID: ${tx.id}. Please approve it on the Dashboard first.`;
65
+ return `⏳ **Approve queued:** ${metadata.symbol} | For: Aave V3 | ${chainName.toUpperCase()} | Approve below.`;
66
66
  }
67
67
  // 2. Simulate Supply
68
68
  let gasEstimate = 0n;
@@ -87,7 +87,7 @@ async function prepareAaveSupply(chainName, tokenAddressOrSymbol, amountStr) {
87
87
  symbol: metadata.symbol,
88
88
  gasEstimate: gasEstimate.toString()
89
89
  });
90
- return `TRANSACTION_PENDING: I have prepared the Aave V3 Supply transaction for ${amountStr} ${metadata.symbol} on ${chainName}. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve on the Dashboard.`;
90
+ return `⏳ **Aave Supply queued:** ${amountStr} ${metadata.symbol} | ${chainName.toUpperCase()} | Approve below.`;
91
91
  }
92
92
  catch (error) {
93
93
  return `Failed to prepare Aave supply: ${error.message}`;
@@ -60,7 +60,7 @@ async function prepareMintNft(chainName, contractAddress, functionSignature, arg
60
60
  valueWei: valueWei.toString(),
61
61
  gasEstimate: gasEstimate.toString()
62
62
  });
63
- return `TRANSACTION_PENDING: Simulated NFT Minting successfully. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}.\n\n⚠️ **SECURITY WARNING**: You are about to mint an NFT from an unverified contract address (${contractAddress}). Please ensure this is the official contract and not a honeypot or malicious proxy before approving on the Dashboard.`;
63
+ return `⏳ **Mint NFT queued:** ${contractAddress} | ${chainName.toUpperCase()} | ⚠️ Verify Contract | Approve below.`;
64
64
  }
65
65
  catch (error) {
66
66
  return `Simulation failed! Cannot prepare mint. Error: ${error.message}`;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchNativeOpBridgeTestnet = fetchNativeOpBridgeTestnet;
4
+ const viem_1 = require("viem");
5
+ const OP_L1_PORTAL_MAP = {
6
+ 'base_sepolia': '0xfd0bf71f60660e2f608ed56e1659c450eb113120',
7
+ 'optimism_sepolia': '0xfbb0621e0b23b5478b630bd55a5f21f67730b0f1'
8
+ };
9
+ const L2_STANDARD_BRIDGE = '0x4200000000000000000000000000000000000010';
10
+ async function fetchNativeOpBridgeTestnet(fromChain, toChain, fromToken, toToken, amount, address) {
11
+ const isL1toL2 = fromChain === 'sepolia' && OP_L1_PORTAL_MAP[toChain];
12
+ const isL2toL1 = toChain === 'sepolia' && OP_L1_PORTAL_MAP[fromChain];
13
+ if (!isL1toL2 && !isL2toL1) {
14
+ throw new Error(`[Native OP Bridge] Unsupported route from ${fromChain} to ${toChain}`);
15
+ }
16
+ // Ensure it's Native ETH for simplicity
17
+ const isNative = fromToken.toLowerCase() === 'eth' ||
18
+ fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ||
19
+ fromToken === '0x0000000000000000000000000000000000000000';
20
+ if (!isNative) {
21
+ throw new Error(`[Native OP Bridge] Only Native ETH bridging is supported natively in this version.`);
22
+ }
23
+ if (isL1toL2) {
24
+ // Deposit (L1 -> L2)
25
+ const portalAddress = OP_L1_PORTAL_MAP[toChain];
26
+ const bridgeEthAbi = (0, viem_1.parseAbi)(['function bridgeETHTo(address _to, uint32 _minGasLimit, bytes _extraData) payable']);
27
+ const callData = (0, viem_1.encodeFunctionData)({
28
+ abi: bridgeEthAbi,
29
+ functionName: 'bridgeETHTo',
30
+ args: [address, 200000, '0x']
31
+ });
32
+ return {
33
+ provider: 'Native OP Stack Bridge (L1->L2)',
34
+ txPayload: {
35
+ to: portalAddress,
36
+ data: callData,
37
+ value: amount
38
+ },
39
+ expectedOutput: amount,
40
+ expectedOutputRaw: amount,
41
+ gasCostUsd: 0,
42
+ rawQuote: { note: 'Direct L1->L2 Deposit via OP Portal. Funds will arrive instantly on L2.' }
43
+ };
44
+ }
45
+ else {
46
+ // Withdrawal (L2 -> L1)
47
+ const withdrawEthAbi = (0, viem_1.parseAbi)(['function bridgeETHTo(address _to, uint32 _minGasLimit, bytes _extraData) payable']);
48
+ const callData = (0, viem_1.encodeFunctionData)({
49
+ abi: withdrawEthAbi,
50
+ functionName: 'bridgeETHTo',
51
+ args: [address, 200000, '0x']
52
+ });
53
+ return {
54
+ provider: 'Native OP Stack Bridge (L2->L1)',
55
+ txPayload: {
56
+ to: L2_STANDARD_BRIDGE,
57
+ data: callData,
58
+ value: amount
59
+ },
60
+ expectedOutput: amount,
61
+ expectedOutputRaw: amount,
62
+ gasCostUsd: 0,
63
+ rawQuote: {
64
+ note: 'Direct L2->L1 Withdrawal. WARNING: Requires 7-day challenge period.',
65
+ isAsyncWithdrawal: true,
66
+ l1PortalAddress: OP_L1_PORTAL_MAP[fromChain]
67
+ }
68
+ };
69
+ }
70
+ }
@@ -111,11 +111,11 @@ async function prepareProvideLiquidity(chainName, token0AddressOrSymbol, token1A
111
111
  });
112
112
  if (allowance0 < amount0Wei) {
113
113
  const tx = transactionManager_1.txManager.createPendingTransaction('approve', chainName, { spenderAddress: positionManagerAddress, tokenAddress: token0, amountStr: amount0, symbol: meta0.symbol, gasEstimate: "60000" });
114
- return `TRANSACTION_PENDING: You need to approve Uniswap V3 to spend your ${meta0.symbol}. ID: ${tx.id}. Please approve on Dashboard first.`;
114
+ return `⏳ **Approve queued:** ${meta0.symbol} | For: Uniswap V3 | ${chainName.toUpperCase()} | Approve below.`;
115
115
  }
116
116
  if (allowance1 < amount1Wei) {
117
117
  const tx = transactionManager_1.txManager.createPendingTransaction('approve', chainName, { spenderAddress: positionManagerAddress, tokenAddress: token1, amountStr: amount1, symbol: meta1.symbol, gasEstimate: "60000" });
118
- return `TRANSACTION_PENDING: You need to approve Uniswap V3 to spend your ${meta1.symbol}. ID: ${tx.id}. Please approve on Dashboard first.`;
118
+ return `⏳ **Approve queued:** ${meta1.symbol} | For: Uniswap V3 | ${chainName.toUpperCase()} | Approve below.`;
119
119
  }
120
120
  // 2. Simulate Mint
121
121
  const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200); // 20 mins
@@ -148,7 +148,7 @@ async function prepareProvideLiquidity(chainName, token0AddressOrSymbol, token1A
148
148
  positionManagerAddress, token0, token1, amount0, amount1, tickLower, tickUpper,
149
149
  gasEstimate: gasEstimate.toString()
150
150
  });
151
- return `TRANSACTION_PENDING: Prepared Univ3 Liquidity deposit for ${amount0} ${meta0.symbol} & ${amount1} ${meta1.symbol}. Estimated gas: ${gasEstimate}. ID: ${tx.id}. Wait for user approval.`;
151
+ return `⏳ **Add Liquidity queued:** ${amount0} ${meta0.symbol} & ${amount1} ${meta1.symbol} | ${chainName.toUpperCase()} | Approve below.`;
152
152
  }
153
153
  catch (error) {
154
154
  return `Failed to prepare liquidity provision: ${error.message}`;
@@ -39,7 +39,7 @@ async function prepareRevokeApproval(chainName, tokenAddressOrSymbol, spenderAdd
39
39
  symbol,
40
40
  gasEstimate: gasEstimate.toString()
41
41
  });
42
- return `TRANSACTION_PENDING: I have prepared the Revoke Approval transaction for ${symbol} to block spender ${spenderAddress}. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve on the Dashboard.`;
42
+ return `⏳ **Revoke queued:** ${symbol} | Spender: ${spenderAddress} | ${chainName.toUpperCase()} | Approve below.`;
43
43
  }
44
44
  catch (error) {
45
45
  return `Failed to prepare revoke approval: ${error.message}`;
@@ -38,7 +38,7 @@ async function prepareSwapToken(chainName, fromToken, toToken, amountStr, mode =
38
38
  txData: route.txPayload,
39
39
  rawQuote: route.rawQuote
40
40
  });
41
- return `TRANSACTION_PENDING\nSwap transaction of ${amountStr} ${fromToken} to ${toToken} on ${chainName.toUpperCase()} via ${route.provider} has been queued. Expected output: ${route.expectedOutput} ${toToken}.\nTransaction ID: ${tx.id}\nPlease review and approve this transaction in your dashboard UI.`;
41
+ return `⏳ **Swap queued:** ${amountStr} ${fromToken} ➡️ ${route.expectedOutput} ${toToken} | ${chainName.toUpperCase()} | Via ${route.provider} | Approve below.`;
42
42
  }
43
43
  catch (error) {
44
44
  return `Failed to prepare swap: ${error.message}`;
@@ -66,7 +66,7 @@ async function prepareTransfer(chainName, toAddress, amountStr, token) {
66
66
  gasEstimate: gasEstimate.toString()
67
67
  });
68
68
  const tokenName = isNative ? "Native Token" : symbol;
69
- return `TRANSACTION_PENDING: I have prepared the ${tokenName} transfer and simulated it successfully. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve.`;
69
+ return `⏳ **Transfer queued:** ${amountStr} ${tokenName} | ${chainName.toUpperCase()} ➡️ ${toAddress} | Approve below.`;
70
70
  }
71
71
  catch (error) {
72
72
  return `Simulation failed! Cannot prepare transfer. Error: ${error.message}`;
@@ -50,7 +50,7 @@ async function prepareVaultDeposit(chainName, protocol, vaultAddress, tokenAddre
50
50
  symbol: metadata.symbol,
51
51
  gasEstimate: "60000"
52
52
  });
53
- return `TRANSACTION_PENDING: You need to approve the ${protocol.toUpperCase()} Vault to spend your ${metadata.symbol}. I have prepared the Approval transaction. ID: ${tx.id}. Please approve it on the Dashboard first.`;
53
+ return `⏳ **Approve queued:** ${metadata.symbol} | For: ${protocol.toUpperCase()} Vault | ${chainName.toUpperCase()} | Approve below.`;
54
54
  }
55
55
  // 2. Simulate Deposit
56
56
  let gasEstimate = 0n;
@@ -76,7 +76,7 @@ async function prepareVaultDeposit(chainName, protocol, vaultAddress, tokenAddre
76
76
  protocol,
77
77
  gasEstimate: gasEstimate.toString()
78
78
  });
79
- return `TRANSACTION_PENDING: I have prepared the deposit of ${amountStr} ${metadata.symbol} into the ${protocol.toUpperCase()} Auto-Compounder Vault. Estimated gas: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user approval on Dashboard.`;
79
+ return `⏳ **Vault Deposit queued:** ${amountStr} ${metadata.symbol} | ${protocol.toUpperCase()} | ${chainName.toUpperCase()} | Approve below.`;
80
80
  }
81
81
  catch (error) {
82
82
  return `Failed to prepare Vault deposit: ${error.message}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "26.6.12",
3
+ "version": "26.6.13",
4
4
  "description": "Your Personal Web3 Assistant",
5
5
  "keywords": [
6
6
  "web3",
@@ -98,5 +98,8 @@
98
98
  "concurrently": "^10.0.3",
99
99
  "oxc-minify": "^0.135.0",
100
100
  "vitepress": "^2.0.0-alpha.17"
101
+ },
102
+ "allowScripts": {
103
+ "isolated-vm": "true"
101
104
  }
102
105
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "26.6.12",
3
+ "version": "26.6.13",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -0,0 +1,39 @@
1
+ import { txManager } from './transactionManager';
2
+ import { sendPushNotification } from '../gateway/telegram';
3
+ import { loadConfig } from '../config/parser';
4
+
5
+ // In a real production environment, this would use the @eth-optimism/sdk
6
+ // to fetch the Merkle Proof and call proveWithdrawalTransaction on L1.
7
+ // For the scope of this architecture prototype, we simulate the Challenge Period
8
+ // watcher by using a time-delay, representing the exact asynchronous behavior.
9
+
10
+ const CHALLENGE_PERIOD_MS = 2 * 60 * 1000; // Simulating a 2-minute challenge period for testnet demo
11
+
12
+ export function startBridgeWatcher() {
13
+ console.log('[Bridge Watcher] Started background daemon for asynchronous L2 withdrawals');
14
+
15
+ setInterval(async () => {
16
+ const config = loadConfig();
17
+ const authId = config.integrations?.telegram?.authorized_chat_id;
18
+ if (!authId) return;
19
+
20
+ const pending = txManager.getPendingWithdrawals();
21
+ const now = Date.now();
22
+
23
+ for (const w of pending) {
24
+ if (w.status === 'WAITING_FOR_CHALLENGE') {
25
+ if (now - w.createdAt > CHALLENGE_PERIOD_MS) {
26
+ // The simulated challenge period is over. State root is "published".
27
+ console.log(`[Bridge Watcher] Withdrawal ${w.id} is ready for L1 claim!`);
28
+
29
+ txManager.updateWithdrawalStatus(w.id, 'READY_FOR_CLAIM');
30
+
31
+ const amountDisplay = Number(w.amount) / 1e18; // assuming 18 decimals
32
+ const message = `🔔 **Bridge Ready to Claim!**\n\nYour withdrawal of ${amountDisplay} ETH from ${w.l2Chain} to ${w.l1Chain} has completed its challenge period.\n\nShall I execute the Prove & Claim transaction on L1 now?`;
33
+
34
+ await sendPushNotification(authId, message, w.id);
35
+ }
36
+ }
37
+ }
38
+ }, 30000); // Check every 30 seconds
39
+ }
@@ -337,8 +337,12 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
337
337
  if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
338
338
  let canFastReturnAll = true;
339
339
  let accumulatedResults: string[] = [];
340
- // Disabled fastReturnTools to enforce Web3 Reasoning (V3 feature)
341
- const fastReturnTools: string[] = [];
340
+ // Enabled fastReturnTools to eliminate 2nd LLM latency for transaction popups
341
+ const fastReturnTools: string[] = [
342
+ 'transfer_token', 'transfer_native', 'swap_token', 'bridge_token',
343
+ 'mint_nft', 'custom_tx', 'revoke_approval', 'supply_aave',
344
+ 'deposit_yield_vault', 'provide_liquidity_v3'
345
+ ];
342
346
 
343
347
  for (const _toolCall of responseMessage.tool_calls) {
344
348
  const toolCall = _toolCall as any;
@@ -1,4 +1,6 @@
1
1
  import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import path from 'path';
2
4
 
3
5
  export type TransactionType = 'transfer' | 'swap' | 'bridge' | 'mint' | 'custom' | 'approve' | 'revokeApproval' | 'aaveSupply' | 'vaultDeposit' | 'univ3Mint' | 'limit_order';
4
6
 
@@ -13,8 +15,46 @@ export interface PendingTransaction {
13
15
  nonce: string;
14
16
  }
15
17
 
18
+ export type WithdrawalStatus = 'WAITING_FOR_CHALLENGE' | 'READY_FOR_PROVE' | 'WAITING_FOR_FINALIZATION' | 'READY_FOR_CLAIM' | 'COMPLETED';
19
+
20
+ export interface PendingWithdrawal {
21
+ id: string; // Internal UUID
22
+ l2TxHash: string;
23
+ l1Chain: string;
24
+ l2Chain: string;
25
+ portalAddress: string;
26
+ userAddress: string;
27
+ amount: string;
28
+ status: WithdrawalStatus;
29
+ createdAt: number;
30
+ }
31
+
16
32
  class TransactionManager {
17
33
  private transactions: Map<string, PendingTransaction> = new Map();
34
+ private withdrawals: Map<string, PendingWithdrawal> = new Map();
35
+ private dbPath: string;
36
+
37
+ constructor() {
38
+ this.dbPath = path.join(process.cwd(), '.nyxora_withdrawals.json');
39
+ this.loadWithdrawals();
40
+ }
41
+
42
+ private loadWithdrawals() {
43
+ if (fs.existsSync(this.dbPath)) {
44
+ try {
45
+ const data = fs.readFileSync(this.dbPath, 'utf8');
46
+ const parsed = JSON.parse(data) as PendingWithdrawal[];
47
+ parsed.forEach(w => this.withdrawals.set(w.id, w));
48
+ } catch (e) {
49
+ console.error("Failed to load withdrawals DB:", e);
50
+ }
51
+ }
52
+ }
53
+
54
+ private saveWithdrawals() {
55
+ const data = Array.from(this.withdrawals.values());
56
+ fs.writeFileSync(this.dbPath, JSON.stringify(data, null, 2));
57
+ }
18
58
 
19
59
  createPendingTransaction(type: TransactionType, chainName: string, details: any): PendingTransaction {
20
60
  const id = crypto.randomUUID();
@@ -47,6 +87,32 @@ class TransactionManager {
47
87
  if (result) tx.result = result;
48
88
  }
49
89
  }
90
+
91
+ // --- WITHDRAWAL LOGIC ---
92
+ createPendingWithdrawal(data: Omit<PendingWithdrawal, 'id' | 'status' | 'createdAt'>): PendingWithdrawal {
93
+ const id = crypto.randomUUID();
94
+ const withdrawal: PendingWithdrawal = {
95
+ ...data,
96
+ id,
97
+ status: 'WAITING_FOR_CHALLENGE',
98
+ createdAt: Date.now()
99
+ };
100
+ this.withdrawals.set(id, withdrawal);
101
+ this.saveWithdrawals();
102
+ return withdrawal;
103
+ }
104
+
105
+ getPendingWithdrawals(): PendingWithdrawal[] {
106
+ return Array.from(this.withdrawals.values()).filter(w => w.status !== 'COMPLETED');
107
+ }
108
+
109
+ updateWithdrawalStatus(id: string, status: WithdrawalStatus) {
110
+ const w = this.withdrawals.get(id);
111
+ if (w) {
112
+ w.status = status;
113
+ this.saveWithdrawals();
114
+ }
115
+ }
50
116
  }
51
117
 
52
118
  export const txManager = new TransactionManager();
@@ -72,6 +72,7 @@ import { searchWebToolDefinition } from '../system/skills/searchWeb';
72
72
  import { readGmailInboxToolDefinition, listCalendarEventsToolDefinition, appendRowToSheetsToolDefinition, readGoogleDocsToolDefinition, readGoogleFormResponsesToolDefinition } from '../system/skills/googleWorkspace';
73
73
 
74
74
  import { startTelegramBot } from './telegram';
75
+ import { startBridgeWatcher } from '../agent/bridgeWatcher';
75
76
  import { eventListener } from '../web3/eventListener';
76
77
  import { formatTransactionSuccess, formatTransactionError } from '../utils/formatter';
77
78
  import { initGoogleAuth, getAuthUrl, processCallback, isAuthenticated, logoutGoogle } from './googleAuthModule';
@@ -903,6 +904,9 @@ export function startServer() {
903
904
  // Start the Telegram bot listener
904
905
  startTelegramBot();
905
906
 
907
+ // Start Asynchronous Bridge Watcher
908
+ startBridgeWatcher();
909
+
906
910
  // Start Event Listener for Limit Orders (V3)
907
911
  eventListener.start();
908
912
  });
@@ -13,6 +13,8 @@ import { executeRevokeApproval } from '../web3/skills/revokeApprovals';
13
13
  import { formatTransactionSuccess, formatTransactionError } from '../utils/formatter';
14
14
  import pc from 'picocolors';
15
15
 
16
+ let globalBotInstance: Telegraf | null = null;
17
+
16
18
  export function formatToTelegramHTML(text: string): string {
17
19
  if (!text) return "";
18
20
  let html = text
@@ -29,9 +31,11 @@ export function formatToTelegramHTML(text: string): string {
29
31
  html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
30
32
  html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
31
33
 
32
- // Strip <thought> blocks completely for user-friendly output
34
+ // Strip <thought> and <think> blocks completely for user-friendly output
33
35
  html = html.replace(/&lt;thought&gt;[\s\S]*?&lt;\/thought&gt;\n?/g, '');
34
36
  html = html.replace(/<thought>[\s\S]*?<\/thought>\n?/g, '');
37
+ html = html.replace(/&lt;think&gt;[\s\S]*?&lt;\/think&gt;\n?/g, '');
38
+ html = html.replace(/<think>[\s\S]*?<\/think>\n?/g, '');
35
39
 
36
40
  // Transform Markdown Tables to <pre> monospaced blocks so they don't break on mobile
37
41
  const tableRegex = /(?:\|.*\|(?:\n|$))+/g;
@@ -53,6 +57,7 @@ export function startTelegramBot() {
53
57
 
54
58
  try {
55
59
  const bot = new Telegraf(token);
60
+ globalBotInstance = bot;
56
61
 
57
62
  // Pairing state variables
58
63
  const isPaired = !!config.integrations?.telegram?.authorized_chat_id;
@@ -274,3 +279,21 @@ export function startTelegramBot() {
274
279
  console.error('[Telegram] Failed to initialize bot:', error);
275
280
  }
276
281
  }
282
+
283
+ export async function sendPushNotification(chatId: string | number, message: string, withdrawalId?: string) {
284
+ if (!globalBotInstance) return;
285
+ try {
286
+ let extraParams: any = { parse_mode: 'HTML' };
287
+ if (withdrawalId) {
288
+ extraParams = {
289
+ ...extraParams,
290
+ ...Markup.inlineKeyboard([
291
+ [Markup.button.callback(`✅ Approve Claim`, `claim_${withdrawalId}`)]
292
+ ])
293
+ };
294
+ }
295
+ await globalBotInstance.telegram.sendMessage(chatId, formatToTelegramHTML(message), extraParams);
296
+ } catch (error) {
297
+ console.error('[Telegram] Failed to send push notification:', error);
298
+ }
299
+ }
@@ -271,6 +271,9 @@ async function fetchKyberSwap(fromChain: string, fromToken: string, toToken: str
271
271
  if (!buildRes.ok) throw new Error(await buildRes.text());
272
272
  const buildData = await buildRes.json();
273
273
 
274
+ const isNative = fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ||
275
+ fromToken === '0x0000000000000000000000000000000000000000';
276
+
274
277
  return {
275
278
  provider: 'KyberSwap',
276
279
  expectedOutput: (Number(routeData.data.routeSummary.amountOut) / 1e18).toString(),
@@ -279,7 +282,7 @@ async function fetchKyberSwap(fromChain: string, fromToken: string, toToken: str
279
282
  txPayload: {
280
283
  to: buildData.data.routerAddress,
281
284
  data: buildData.data.data,
282
- value: routeData.data.routeSummary.amountInUsd // simplified value mapping
285
+ value: isNative ? amount : "0" // FIXED: Mengirim jumlah asli dalam WEI jika Native ETH, atau 0 jika ERC20
283
286
  },
284
287
  rawQuote: buildData.data
285
288
  };
@@ -4,6 +4,7 @@ import { ChainName } from '../config';
4
4
  import { loadDefiKeys } from '../../config/defiConfigManager';
5
5
  import { safeFetch } from '../../utils/httpClient';
6
6
  import { encodeFunctionData, parseAbi } from 'viem';
7
+ import { fetchNativeOpBridgeTestnet } from '../skills/nativeOpBridge';
7
8
 
8
9
  export async function fetchTestnetBestRoute(
9
10
  fromChain: ChainName,
@@ -16,19 +17,34 @@ export async function fetchTestnetBestRoute(
16
17
  ): Promise<RouteQuote> {
17
18
  const promises: Promise<RouteQuote | null>[] = [];
18
19
 
19
- // Routing Logic
20
- const isRelayRoute = (fromChain === 'sepolia' && toChain === 'base_sepolia') ||
21
- (fromChain === 'base_sepolia' && toChain === 'sepolia');
22
-
23
- const isArbitrumRoute = (fromChain === 'sepolia' && toChain === 'arbitrum_sepolia') ||
24
- (fromChain === 'arbitrum_sepolia' && toChain === 'sepolia');
25
-
26
- if (isRelayRoute) {
27
- promises.push(fetchRelayTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress));
28
- } else if (isArbitrumRoute) {
29
- promises.push(fetchArbitrumBridgeTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress));
20
+ // Routing Logic Hierarchy
21
+ const isOpStack = toChain === 'optimism_sepolia' || toChain === 'base_sepolia' ||
22
+ fromChain === 'optimism_sepolia' || fromChain === 'base_sepolia';
23
+
24
+ const isArbitrum = toChain === 'arbitrum_sepolia' || fromChain === 'arbitrum_sepolia';
25
+
26
+ if (isOpStack) {
27
+ // Primary: Universal OP Stack
28
+ promises.push(
29
+ fetchNativeOpBridgeTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress)
30
+ .catch(e => { console.warn('Native OP failed:', e.message); return null; })
31
+ );
32
+
33
+ // Fallback 1: Relay Testnet (Only supports Base Sepolia natively via Relay endpoint)
34
+ if (toChain === 'base_sepolia' || fromChain === 'base_sepolia') {
35
+ promises.push(
36
+ fetchRelayTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress)
37
+ .catch(e => { console.warn('Relay Fallback failed:', e.message); return null; })
38
+ );
39
+ }
40
+ } else if (isArbitrum) {
41
+ // Fallback 2: Official Arbitrum Bridge
42
+ promises.push(
43
+ fetchArbitrumBridgeTestnet(fromChain, toChain, fromToken, toToken, amountInWei, userAddress)
44
+ .catch(e => { console.warn('Arbitrum Bridge failed:', e.message); return null; })
45
+ );
30
46
  } else {
31
- throw new Error(`[Testnet Meta-Aggregator] Unsupported testnet route from ${fromChain} to ${toChain}. Only Sepolia <-> Base Sepolia or Sepolia <-> Arbitrum Sepolia are supported.`);
47
+ throw new Error(`[Testnet Meta-Aggregator] Unsupported testnet route from ${fromChain} to ${toChain}.`);
32
48
  }
33
49
 
34
50
  const results = await Promise.allSettled(promises);
@@ -61,7 +61,7 @@ export async function prepareBridgeToken(
61
61
  rawQuote: route.rawQuote
62
62
  });
63
63
 
64
- return `TRANSACTION_PENDING\nBridge transaction of ${amountStr} ${tokenSymbol} from ${fromChain.toUpperCase()} to ${toChain.toUpperCase()} via ${route.provider} has been queued.\nTransaction ID: ${tx.id}\nPlease review and approve this transaction in your dashboard UI.`;
64
+ return `⏳ **Bridge queued:** ${amountStr} ${tokenSymbol} | ${fromChain.toUpperCase()} ➡️ ${toChain.toUpperCase()} | Via ${route.provider} | Approve below.`;
65
65
  } catch (error: any) {
66
66
  console.error("BRIDGE TOKEN ERROR:", error);
67
67
  return `Failed to prepare bridge: ${error.message}`;
@@ -17,7 +17,7 @@ export async function prepareCustomTx(
17
17
  description
18
18
  });
19
19
 
20
- return `TRANSACTION_PENDING\nCustom transaction (${description}) on ${chainName.toUpperCase()} has been queued.\nTransaction ID: ${tx.id}\nPlease review and approve this transaction in your dashboard UI.`;
20
+ return `⏳ **Custom Tx queued:** ${description} | ${chainName.toUpperCase()} | Approve below.`;
21
21
  } catch (error: any) {
22
22
  return `Failed to prepare custom tx: ${error.message}`;
23
23
  }
@@ -67,7 +67,7 @@ export async function prepareAaveSupply(chainName: ChainName, tokenAddressOrSymb
67
67
  symbol: metadata.symbol,
68
68
  gasEstimate: "60000"
69
69
  });
70
- return `TRANSACTION_PENDING: You need to approve Aave V3 to spend your ${metadata.symbol} before supplying. I have prepared the Approval transaction. ID: ${tx.id}. Please approve it on the Dashboard first.`;
70
+ return `⏳ **Approve queued:** ${metadata.symbol} | For: Aave V3 | ${chainName.toUpperCase()} | Approve below.`;
71
71
  }
72
72
 
73
73
  // 2. Simulate Supply
@@ -94,7 +94,7 @@ export async function prepareAaveSupply(chainName: ChainName, tokenAddressOrSymb
94
94
  gasEstimate: gasEstimate.toString()
95
95
  });
96
96
 
97
- return `TRANSACTION_PENDING: I have prepared the Aave V3 Supply transaction for ${amountStr} ${metadata.symbol} on ${chainName}. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve on the Dashboard.`;
97
+ return `⏳ **Aave Supply queued:** ${amountStr} ${metadata.symbol} | ${chainName.toUpperCase()} | Approve below.`;
98
98
  } catch (error: any) {
99
99
  return `Failed to prepare Aave supply: ${error.message}`;
100
100
  }
@@ -68,7 +68,7 @@ export async function prepareMintNft(
68
68
  gasEstimate: gasEstimate.toString()
69
69
  });
70
70
 
71
- return `TRANSACTION_PENDING: Simulated NFT Minting successfully. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}.\n\n⚠️ **SECURITY WARNING**: You are about to mint an NFT from an unverified contract address (${contractAddress}). Please ensure this is the official contract and not a honeypot or malicious proxy before approving on the Dashboard.`;
71
+ return `⏳ **Mint NFT queued:** ${contractAddress} | ${chainName.toUpperCase()} | ⚠️ Verify Contract | Approve below.`;
72
72
  } catch (error: any) {
73
73
  return `Simulation failed! Cannot prepare mint. Error: ${error.message}`;
74
74
  }
@@ -0,0 +1,83 @@
1
+ import { encodeFunctionData, parseAbi } from 'viem';
2
+ import { RouteQuote } from '../aggregator/aggregatorMainnet';
3
+
4
+ const OP_L1_PORTAL_MAP: Record<string, string> = {
5
+ 'base_sepolia': '0xfd0bf71f60660e2f608ed56e1659c450eb113120',
6
+ 'optimism_sepolia': '0xfbb0621e0b23b5478b630bd55a5f21f67730b0f1'
7
+ };
8
+
9
+ const L2_STANDARD_BRIDGE = '0x4200000000000000000000000000000000000010';
10
+
11
+ export async function fetchNativeOpBridgeTestnet(
12
+ fromChain: string,
13
+ toChain: string,
14
+ fromToken: string,
15
+ toToken: string,
16
+ amount: string,
17
+ address: string
18
+ ): Promise<RouteQuote | null> {
19
+ const isL1toL2 = fromChain === 'sepolia' && OP_L1_PORTAL_MAP[toChain];
20
+ const isL2toL1 = toChain === 'sepolia' && OP_L1_PORTAL_MAP[fromChain];
21
+
22
+ if (!isL1toL2 && !isL2toL1) {
23
+ throw new Error(`[Native OP Bridge] Unsupported route from ${fromChain} to ${toChain}`);
24
+ }
25
+
26
+ // Ensure it's Native ETH for simplicity
27
+ const isNative = fromToken.toLowerCase() === 'eth' ||
28
+ fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' ||
29
+ fromToken === '0x0000000000000000000000000000000000000000';
30
+
31
+ if (!isNative) {
32
+ throw new Error(`[Native OP Bridge] Only Native ETH bridging is supported natively in this version.`);
33
+ }
34
+
35
+ if (isL1toL2) {
36
+ // Deposit (L1 -> L2)
37
+ const portalAddress = OP_L1_PORTAL_MAP[toChain];
38
+ const bridgeEthAbi = parseAbi(['function bridgeETHTo(address _to, uint32 _minGasLimit, bytes _extraData) payable']);
39
+ const callData = encodeFunctionData({
40
+ abi: bridgeEthAbi,
41
+ functionName: 'bridgeETHTo',
42
+ args: [address as `0x${string}`, 200000, '0x']
43
+ });
44
+
45
+ return {
46
+ provider: 'Native OP Stack Bridge (L1->L2)',
47
+ txPayload: {
48
+ to: portalAddress,
49
+ data: callData,
50
+ value: amount
51
+ },
52
+ expectedOutput: amount,
53
+ expectedOutputRaw: amount,
54
+ gasCostUsd: 0,
55
+ rawQuote: { note: 'Direct L1->L2 Deposit via OP Portal. Funds will arrive instantly on L2.' }
56
+ };
57
+ } else {
58
+ // Withdrawal (L2 -> L1)
59
+ const withdrawEthAbi = parseAbi(['function bridgeETHTo(address _to, uint32 _minGasLimit, bytes _extraData) payable']);
60
+ const callData = encodeFunctionData({
61
+ abi: withdrawEthAbi,
62
+ functionName: 'bridgeETHTo',
63
+ args: [address as `0x${string}`, 200000, '0x']
64
+ });
65
+
66
+ return {
67
+ provider: 'Native OP Stack Bridge (L2->L1)',
68
+ txPayload: {
69
+ to: L2_STANDARD_BRIDGE,
70
+ data: callData,
71
+ value: amount
72
+ },
73
+ expectedOutput: amount,
74
+ expectedOutputRaw: amount,
75
+ gasCostUsd: 0,
76
+ rawQuote: {
77
+ note: 'Direct L2->L1 Withdrawal. WARNING: Requires 7-day challenge period.',
78
+ isAsyncWithdrawal: true,
79
+ l1PortalAddress: OP_L1_PORTAL_MAP[fromChain]
80
+ }
81
+ };
82
+ }
83
+ }
@@ -130,12 +130,12 @@ export async function prepareProvideLiquidity(
130
130
 
131
131
  if (allowance0 < amount0Wei) {
132
132
  const tx = txManager.createPendingTransaction('approve', chainName, { spenderAddress: positionManagerAddress, tokenAddress: token0, amountStr: amount0, symbol: meta0.symbol, gasEstimate: "60000" });
133
- return `TRANSACTION_PENDING: You need to approve Uniswap V3 to spend your ${meta0.symbol}. ID: ${tx.id}. Please approve on Dashboard first.`;
133
+ return `⏳ **Approve queued:** ${meta0.symbol} | For: Uniswap V3 | ${chainName.toUpperCase()} | Approve below.`;
134
134
  }
135
135
 
136
136
  if (allowance1 < amount1Wei) {
137
137
  const tx = txManager.createPendingTransaction('approve', chainName, { spenderAddress: positionManagerAddress, tokenAddress: token1, amountStr: amount1, symbol: meta1.symbol, gasEstimate: "60000" });
138
- return `TRANSACTION_PENDING: You need to approve Uniswap V3 to spend your ${meta1.symbol}. ID: ${tx.id}. Please approve on Dashboard first.`;
138
+ return `⏳ **Approve queued:** ${meta1.symbol} | For: Uniswap V3 | ${chainName.toUpperCase()} | Approve below.`;
139
139
  }
140
140
 
141
141
  // 2. Simulate Mint
@@ -173,7 +173,7 @@ export async function prepareProvideLiquidity(
173
173
  gasEstimate: gasEstimate.toString()
174
174
  });
175
175
 
176
- return `TRANSACTION_PENDING: Prepared Univ3 Liquidity deposit for ${amount0} ${meta0.symbol} & ${amount1} ${meta1.symbol}. Estimated gas: ${gasEstimate}. ID: ${tx.id}. Wait for user approval.`;
176
+ return `⏳ **Add Liquidity queued:** ${amount0} ${meta0.symbol} & ${amount1} ${meta1.symbol} | ${chainName.toUpperCase()} | Approve below.`;
177
177
  } catch (error: any) {
178
178
  return `Failed to prepare liquidity provision: ${error.message}`;
179
179
  }
@@ -42,7 +42,7 @@ export async function prepareRevokeApproval(chainName: ChainName, tokenAddressOr
42
42
  gasEstimate: gasEstimate.toString()
43
43
  });
44
44
 
45
- return `TRANSACTION_PENDING: I have prepared the Revoke Approval transaction for ${symbol} to block spender ${spenderAddress}. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve on the Dashboard.`;
45
+ return `⏳ **Revoke queued:** ${symbol} | Spender: ${spenderAddress} | ${chainName.toUpperCase()} | Approve below.`;
46
46
  } catch (error: any) {
47
47
  return `Failed to prepare revoke approval: ${error.message}`;
48
48
  }
@@ -56,7 +56,7 @@ export async function prepareSwapToken(
56
56
  rawQuote: route.rawQuote
57
57
  });
58
58
 
59
- return `TRANSACTION_PENDING\nSwap transaction of ${amountStr} ${fromToken} to ${toToken} on ${chainName.toUpperCase()} via ${route.provider} has been queued. Expected output: ${route.expectedOutput} ${toToken}.\nTransaction ID: ${tx.id}\nPlease review and approve this transaction in your dashboard UI.`;
59
+ return `⏳ **Swap queued:** ${amountStr} ${fromToken} ➡️ ${route.expectedOutput} ${toToken} | ${chainName.toUpperCase()} | Via ${route.provider} | Approve below.`;
60
60
  } catch (error: any) {
61
61
  return `Failed to prepare swap: ${error.message}`;
62
62
  }
@@ -69,7 +69,7 @@ export async function prepareTransfer(chainName: ChainName, toAddress: `0x${stri
69
69
  });
70
70
 
71
71
  const tokenName = isNative ? "Native Token" : symbol;
72
- return `TRANSACTION_PENDING: I have prepared the ${tokenName} transfer and simulated it successfully. Estimated gas units: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user to approve.`;
72
+ return `⏳ **Transfer queued:** ${amountStr} ${tokenName} | ${chainName.toUpperCase()} ➡️ ${toAddress} | Approve below.`;
73
73
  } catch (error: any) {
74
74
  return `Simulation failed! Cannot prepare transfer. Error: ${error.message}`;
75
75
  }
@@ -53,7 +53,7 @@ export async function prepareVaultDeposit(chainName: ChainName, protocol: string
53
53
  symbol: metadata.symbol,
54
54
  gasEstimate: "60000"
55
55
  });
56
- return `TRANSACTION_PENDING: You need to approve the ${protocol.toUpperCase()} Vault to spend your ${metadata.symbol}. I have prepared the Approval transaction. ID: ${tx.id}. Please approve it on the Dashboard first.`;
56
+ return `⏳ **Approve queued:** ${metadata.symbol} | For: ${protocol.toUpperCase()} Vault | ${chainName.toUpperCase()} | Approve below.`;
57
57
  }
58
58
 
59
59
  // 2. Simulate Deposit
@@ -81,7 +81,7 @@ export async function prepareVaultDeposit(chainName: ChainName, protocol: string
81
81
  gasEstimate: gasEstimate.toString()
82
82
  });
83
83
 
84
- return `TRANSACTION_PENDING: I have prepared the deposit of ${amountStr} ${metadata.symbol} into the ${protocol.toUpperCase()} Auto-Compounder Vault. Estimated gas: ${gasEstimate}. Transaction ID: ${tx.id}. Wait for user approval on Dashboard.`;
84
+ return `⏳ **Vault Deposit queued:** ${amountStr} ${metadata.symbol} | ${protocol.toUpperCase()} | ${chainName.toUpperCase()} | Approve below.`;
85
85
  } catch (error: any) {
86
86
  return `Failed to prepare Vault deposit: ${error.message}`;
87
87
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nyxora-dashboard",
3
3
  "private": true,
4
- "version": "26.6.12",
4
+ "version": "26.6.13",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-mcp-server",
3
- "version": "26.6.12",
3
+ "version": "26.6.13",
4
4
  "description": "Nyxora MCP Subserver, for external AI clients",
5
5
  "main": "dist/server.js",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-policy-engine",
3
- "version": "26.6.12",
3
+ "version": "26.6.13",
4
4
  "private": true,
5
5
  "main": "src/server.ts",
6
6
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-signer",
3
- "version": "26.6.12",
3
+ "version": "26.6.13",
4
4
  "private": true,
5
5
  "main": "src/server.ts",
6
6
  "dependencies": {