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.
- package/README.md +3 -3
- package/dist/packages/core/src/agent/bridgeWatcher.js +34 -0
- package/dist/packages/core/src/agent/reasoning.js +6 -2
- package/dist/packages/core/src/agent/transactionManager.js +47 -0
- package/dist/packages/core/src/gateway/server.js +3 -0
- package/dist/packages/core/src/gateway/telegram.js +25 -1
- package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +3 -1
- package/dist/packages/core/src/web3/aggregator/aggregatorTestnet.js +19 -10
- package/dist/packages/core/src/web3/skills/bridgeToken.js +1 -1
- package/dist/packages/core/src/web3/skills/customTx.js +1 -1
- package/dist/packages/core/src/web3/skills/defiLending.js +2 -2
- package/dist/packages/core/src/web3/skills/mintNft.js +1 -1
- package/dist/packages/core/src/web3/skills/nativeOpBridge.js +70 -0
- package/dist/packages/core/src/web3/skills/provideLiquidity.js +3 -3
- package/dist/packages/core/src/web3/skills/revokeApprovals.js +1 -1
- package/dist/packages/core/src/web3/skills/swapToken.js +1 -1
- package/dist/packages/core/src/web3/skills/transfer.js +1 -1
- package/dist/packages/core/src/web3/skills/yieldVault.js +2 -2
- package/package.json +4 -1
- package/packages/core/package.json +1 -1
- package/packages/core/src/agent/bridgeWatcher.ts +39 -0
- package/packages/core/src/agent/reasoning.ts +6 -2
- package/packages/core/src/agent/transactionManager.ts +66 -0
- package/packages/core/src/gateway/server.ts +4 -0
- package/packages/core/src/gateway/telegram.ts +24 -1
- package/packages/core/src/web3/aggregator/aggregatorMainnet.ts +4 -1
- package/packages/core/src/web3/aggregator/aggregatorTestnet.ts +28 -12
- package/packages/core/src/web3/skills/bridgeToken.ts +1 -1
- package/packages/core/src/web3/skills/customTx.ts +1 -1
- package/packages/core/src/web3/skills/defiLending.ts +2 -2
- package/packages/core/src/web3/skills/mintNft.ts +1 -1
- package/packages/core/src/web3/skills/nativeOpBridge.ts +83 -0
- package/packages/core/src/web3/skills/provideLiquidity.ts +3 -3
- package/packages/core/src/web3/skills/revokeApprovals.ts +1 -1
- package/packages/core/src/web3/skills/swapToken.ts +1 -1
- package/packages/core/src/web3/skills/transfer.ts +1 -1
- package/packages/core/src/web3/skills/yieldVault.ts +2 -2
- package/packages/dashboard/package.json +1 -1
- package/packages/mcp-server/package.json +1 -1
- package/packages/policy/package.json +1 -1
- 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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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(/<thought>[\s\S]*?<\/thought>\n?/g, '');
|
|
37
39
|
html = html.replace(/<thought>[\s\S]*?<\/thought>\n?/g, '');
|
|
40
|
+
html = html.replace(/<think>[\s\S]*?<\/think>\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:
|
|
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
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
promises.push(
|
|
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 (
|
|
17
|
-
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
}
|
|
@@ -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
|
-
//
|
|
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(/<thought>[\s\S]*?<\/thought>\n?/g, '');
|
|
34
36
|
html = html.replace(/<thought>[\s\S]*?<\/thought>\n?/g, '');
|
|
37
|
+
html = html.replace(/<think>[\s\S]*?<\/think>\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:
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
promises.push(
|
|
28
|
-
|
|
29
|
-
|
|
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}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|