nyxora 26.6.11-2 → 26.6.12
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/dist/packages/core/src/agent/reasoning.js +42 -13
- package/dist/packages/core/src/gateway/server.js +25 -1
- package/dist/packages/core/src/gateway/telegram.js +9 -0
- package/dist/packages/core/src/memory/logger.js +82 -0
- package/dist/packages/core/src/system/skills/analyzeDocument.js +15 -12
- package/dist/packages/core/src/system/skills/generateExcel.js +6 -8
- package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +7 -2
- package/dist/packages/core/src/web3/aggregator/aggregatorTestnet.js +6 -1
- package/dist/packages/core/src/web3/eventListener.js +102 -0
- package/dist/packages/core/src/web3/skills/createLimitOrder.js +48 -0
- package/package.json +5 -3
- package/packages/core/package.json +4 -2
- package/packages/core/src/agent/reasoning.ts +51 -14
- package/packages/core/src/gateway/server.ts +26 -1
- package/packages/core/src/gateway/telegram.ts +7 -0
- package/packages/core/src/memory/logger.ts +113 -0
- package/packages/core/src/system/skills/analyzeDocument.ts +15 -14
- package/packages/core/src/system/skills/generateExcel.ts +6 -10
- package/packages/core/src/web3/aggregator/aggregatorMainnet.ts +8 -2
- package/packages/core/src/web3/aggregator/aggregatorTestnet.ts +7 -1
- package/packages/core/src/web3/eventListener.ts +103 -0
- package/packages/core/src/web3/skills/createLimitOrder.ts +56 -0
- package/packages/dashboard/dist/assets/index-BhKhEfi_.js +13 -0
- package/packages/dashboard/dist/index.html +1 -1
- 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/packages/dashboard/dist/assets/index-CCgm_N9M.js +0 -13
|
@@ -31,6 +31,7 @@ const defiLending_1 = require("../web3/skills/defiLending");
|
|
|
31
31
|
const yieldVault_1 = require("../web3/skills/yieldVault");
|
|
32
32
|
const provideLiquidity_1 = require("../web3/skills/provideLiquidity");
|
|
33
33
|
const getTxHistory_1 = require("../web3/skills/getTxHistory");
|
|
34
|
+
const createLimitOrder_1 = require("../web3/skills/createLimitOrder");
|
|
34
35
|
const updateProfile_1 = require("./updateProfile");
|
|
35
36
|
const updateSecurityPolicy_1 = require("../system/skills/updateSecurityPolicy");
|
|
36
37
|
const analyzeDocument_1 = require("../system/skills/analyzeDocument");
|
|
@@ -122,26 +123,35 @@ async function executeWithRetry(requestBuilder, maxRetries = 3) {
|
|
|
122
123
|
function getSystemPrompt() {
|
|
123
124
|
const config = (0, parser_1.loadConfig)();
|
|
124
125
|
const currentDateTime = new Date().toLocaleString('en-US', { timeZoneName: 'short' });
|
|
125
|
-
let basePrompt = `
|
|
126
|
+
let basePrompt = `[CORE DIRECTIVES]
|
|
127
|
+
You are an autonomous Web3 agent operating on EVM chains.
|
|
126
128
|
You are equipped with a native wallet.
|
|
127
129
|
The current real-world date and time is: ${currentDateTime}. Use this for any time-related questions.
|
|
130
|
+
Default Chain: ${config.agent.default_chain}
|
|
128
131
|
|
|
132
|
+
CRITICAL: You MUST use a Chain of Thought approach for every response. You must enclose your internal reasoning steps within <think>...</think> XML tags BEFORE taking any action or providing a final response. This allows you to plan your tool usage, recall the rules, and avoid hallucinations.
|
|
133
|
+
IMPORTANT: The <think> block is strictly for your internal hidden monologue. NEVER put your final answer, conversational text, or questions to the user inside the <think> block. The actual response that the user will see MUST be written OUTSIDE and AFTER the </think> tag.
|
|
134
|
+
|
|
135
|
+
[EXECUTION WORKFLOW]
|
|
129
136
|
CRITICAL RULE 1: NEVER expose internal JSON tool calls to the user. Always parse them and explain the outcome naturally.
|
|
130
137
|
CRITICAL RULE 2: STRICT LANGUAGE MATCHING. You MUST strictly reply in the exact same language as the user's LATEST prompt.
|
|
131
|
-
CRITICAL RULE 3: FORMATTING & CONCISENESS.
|
|
138
|
+
CRITICAL RULE 3: FORMATTING & CONCISENESS. Provide concise analytical summaries of data rather than just dumping raw markdown tables. Be analytical but brief. Use commas for thousands.
|
|
132
139
|
CRITICAL RULE 4: TOOL PRIORITIZATION. Web3 tasks must use Web3 Skills exclusively. OS Skills (search, browse) are fallbacks only. Use get_my_address to show wallet address, and check_portfolio to show balances.
|
|
133
140
|
CRITICAL RULE 5: DEFAULT CHAIN HANDLING. Default to: ${config.agent.default_chain} unless specified. If overridden, confirm the chain politely. For 2-chain txs (bridge), default source to ${config.agent.default_chain}.
|
|
134
|
-
CRITICAL RULE 6: NETWORK SAFETY VALIDATION. If a request implies cross-chain or mainnet/testnet mixing, or the token symbol is ambiguous (USDC vs USDC.e), YOU MUST NOT GUESS. Ask for confirmation.
|
|
135
|
-
CRITICAL RULE 7: TOOL CONFIDENCE & HALUCINATION PREVENTION. NEVER fabricate blockchain data. If a tool fails or data is missing, state it explicitly. Do not estimate balances, prices, APY, or gas.
|
|
136
141
|
CRITICAL RULE 8: CONDITIONAL PARALLEL EXECUTION. Parallel tool execution is ONLY allowed if there are zero data dependencies between them.
|
|
137
|
-
CRITICAL RULE 9: DEFI CONFIGURATION FALLBACK. If a tool fails due to Rate Limits, Unauthorized, or Missing API Keys, instruct the user to visit the "DeFi Configuration 🔑" menu in the dashboard.
|
|
138
142
|
CRITICAL RULE 10: PLANNING & RISK DISCLOSURE. For high-level instructions (e.g. "Get yield"), formulate a plan and briefly disclose major risks (smart contract risk, impermanent loss) before asking for approval.
|
|
139
|
-
CRITICAL RULE 11:
|
|
140
|
-
CRITICAL RULE 12: SMART SLIPPAGE AWARENESS. For low-liquidity assets, warn the user that default slippage might not be enough. NEVER invent specific slippage percentage numbers.
|
|
143
|
+
CRITICAL RULE 11: ADAPTIVE RESPONSE RULE. You must process Web3 data (portfolio, price) and provide a concise, to-the-point analysis based on the context. Do not use useless filler greetings/closings. Provide deep analysis only if the data requires it to protect the user's portfolio.
|
|
141
144
|
CRITICAL RULE 13: WALLET CONTEXT CACHING. Portfolio data in chat history is potentially stale. Do not use cached data for transactional planning; refresh the balance via tools first.
|
|
142
145
|
CRITICAL RULE 14: TRANSACTION EXECUTION. For ALL state-changing transactions (swap, bridge, transfer, stake), do NOT ask for verbal confirmation. Execute the tool IMMEDIATELY. The tool itself will trigger a secure popup in the user's dashboard UI for final approval.
|
|
146
|
+
CRITICAL RULE 17: MINIMIZE UNNECESSARY TOOL CALLS. Do not call tools if the answer exists in recent verified context and freshness is not strictly required. Use history to save latency.
|
|
147
|
+
|
|
148
|
+
[ANTI-HALLUCINATION PROTOCOL]
|
|
149
|
+
CRITICAL RULE 6: NETWORK SAFETY VALIDATION. If a request implies cross-chain or mainnet/testnet mixing, or the token symbol is ambiguous (USDC vs USDC.e), YOU MUST NOT GUESS. Ask for confirmation.
|
|
150
|
+
CRITICAL RULE 7: TOOL CONFIDENCE & HALUCINATION PREVENTION. NEVER fabricate blockchain data. If a tool fails or data is missing, state it explicitly. Do not estimate balances, prices, APY, or gas.
|
|
151
|
+
CRITICAL RULE 9: DEFI CONFIGURATION FALLBACK. If a tool fails due to Rate Limits, Unauthorized, or Missing API Keys, instruct the user to visit the "DeFi Configuration 🔑" menu in the dashboard.
|
|
152
|
+
CRITICAL RULE 12: SMART SLIPPAGE AWARENESS. For low-liquidity assets, warn the user that default slippage might not be enough. NEVER invent specific slippage percentage numbers.
|
|
143
153
|
CRITICAL RULE 16: CAPABILITY HONESTY. NEVER claim a capability not available through installed tools. If asked for an unsupported action, state honestly that the skill is missing.
|
|
144
|
-
CRITICAL RULE
|
|
154
|
+
CRITICAL RULE 19: MARKET CONFIDENCE SCORE. When analyzing market data, token security, or preparing trades, you MUST explicitly declare a 'Confidence Score (0-100%)' INSIDE your <think> block. If your score is below 40%, you must firmly WARN the user and advise against the trade in your final response.`;
|
|
145
155
|
// Read IDENTITY.md for core AI persona
|
|
146
156
|
try {
|
|
147
157
|
const identityMdPath = (0, paths_1.getPath)('IDENTITY.md');
|
|
@@ -188,6 +198,23 @@ CRITICAL RULE 17: MINIMIZE UNNECESSARY TOOL CALLS. Do not call tools if the answ
|
|
|
188
198
|
catch (error) {
|
|
189
199
|
// Ignore db errors if not initialized
|
|
190
200
|
}
|
|
201
|
+
// V3: Inject Personalized Risk Profile
|
|
202
|
+
try {
|
|
203
|
+
const profile = exports.logger.getUserProfile();
|
|
204
|
+
if (profile) {
|
|
205
|
+
basePrompt += `\n\n--- [USER_PERSONA] RISK PROFILE & PREFERENCES ---\n`;
|
|
206
|
+
basePrompt += `Risk Level: ${profile.risk_level}\n`;
|
|
207
|
+
basePrompt += `Max Slippage Tolerance: ${profile.max_slippage}%\n`;
|
|
208
|
+
basePrompt += `Avoid Memecoins: ${profile.avoid_memecoins ? 'YES' : 'NO'}\n`;
|
|
209
|
+
if (profile.custom_rules) {
|
|
210
|
+
basePrompt += `Custom Rules: ${profile.custom_rules}\n`;
|
|
211
|
+
}
|
|
212
|
+
basePrompt += `CRITICAL: You MUST adhere to these risk parameters when advising the user or executing tools. If a requested action violates these parameters (e.g., buying a high-risk memecoin when 'Avoid Memecoins' is YES), you MUST warn the user and refuse execution unless they explicitly override.\n`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
// Ignore if db not ready
|
|
217
|
+
}
|
|
191
218
|
return basePrompt;
|
|
192
219
|
}
|
|
193
220
|
async function processUserInput(input, role = 'user', onProgress, sessionId) {
|
|
@@ -223,7 +250,7 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
|
|
|
223
250
|
const hasGoogleKeyword = /email|gmail|calendar|sheet|doc|form|event/i.test(lowerInput);
|
|
224
251
|
let tools = [];
|
|
225
252
|
if ((0, skillManager_1.isSkillActive)('web3')) {
|
|
226
|
-
tools.push(getBalance_1.getBalanceToolDefinition, transfer_1.transferToolDefinition, getPrice_1.getPriceToolDefinition, swapToken_1.swapTokenToolDefinition, bridgeToken_1.bridgeTokenToolDefinition, mintNft_1.mintNftToolDefinition, customTx_1.customTxToolDefinition, checkSecurity_1.checkSecurityToolDefinition, marketAnalysis_1.marketAnalysisToolDefinition, checkPortfolio_1.checkPortfolioToolDefinition, checkAddress_1.checkAddressToolDefinition, getMyAddress_1.getMyAddressToolDefinition, manageCustomTokens_1.manageCustomTokensDefinition, revokeApprovals_1.revokeApprovalToolDefinition, defiLending_1.aaveSupplyToolDefinition, yieldVault_1.vaultDepositToolDefinition, provideLiquidity_1.provideLiquidityToolDefinition, getTxHistory_1.getTxHistoryToolDefinition);
|
|
253
|
+
tools.push(getBalance_1.getBalanceToolDefinition, transfer_1.transferToolDefinition, getPrice_1.getPriceToolDefinition, swapToken_1.swapTokenToolDefinition, bridgeToken_1.bridgeTokenToolDefinition, mintNft_1.mintNftToolDefinition, customTx_1.customTxToolDefinition, checkSecurity_1.checkSecurityToolDefinition, marketAnalysis_1.marketAnalysisToolDefinition, checkPortfolio_1.checkPortfolioToolDefinition, checkAddress_1.checkAddressToolDefinition, getMyAddress_1.getMyAddressToolDefinition, manageCustomTokens_1.manageCustomTokensDefinition, revokeApprovals_1.revokeApprovalToolDefinition, defiLending_1.aaveSupplyToolDefinition, yieldVault_1.vaultDepositToolDefinition, provideLiquidity_1.provideLiquidityToolDefinition, getTxHistory_1.getTxHistoryToolDefinition, createLimitOrder_1.createLimitOrderToolDefinition);
|
|
227
254
|
}
|
|
228
255
|
const SYSTEM_TOOLS = [updateProfile_1.updateProfileToolDefinition, updateSecurityPolicy_1.updateSecurityPolicyToolDefinition, analyzeDocument_1.analyzeDocumentToolDefinition, readFile_1.readLocalFileToolDefinition, writeFile_1.writeLocalFileToolDefinition, generateExcel_1.generateExcelToolDefinition, executeShell_1.runTerminalCommandToolDefinition, browseWeb_1.browseWebsiteToolDefinition, searchWeb_1.searchWebToolDefinition, installSkill_1.installExternalSkillToolDefinition, editFile_1.editLocalFileToolDefinition, gitManager_1.gitManagerToolDefinition, xManager_1.xManagerToolDefinition, notionWorkspace_1.notionWorkspaceToolDefinition, audioTranscribe_1.audioTranscribeToolDefinition, summarizeText_1.summarizeTextToolDefinition];
|
|
229
256
|
const GOOGLE_TOOLS = [googleWorkspace_1.readGmailInboxToolDefinition, googleWorkspace_1.listCalendarEventsToolDefinition, googleWorkspace_1.appendRowToSheetsToolDefinition, googleWorkspace_1.readGoogleDocsToolDefinition, googleWorkspace_1.readGoogleFormResponsesToolDefinition];
|
|
@@ -261,10 +288,8 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
|
|
|
261
288
|
if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) {
|
|
262
289
|
let canFastReturnAll = true;
|
|
263
290
|
let accumulatedResults = [];
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
'analyze_market', 'check_token_security', 'search_web', 'read_gmail_inbox', 'list_calendar_events'
|
|
267
|
-
];
|
|
291
|
+
// Disabled fastReturnTools to enforce Web3 Reasoning (V3 feature)
|
|
292
|
+
const fastReturnTools = [];
|
|
268
293
|
for (const _toolCall of responseMessage.tool_calls) {
|
|
269
294
|
const toolCall = _toolCall;
|
|
270
295
|
let result = "";
|
|
@@ -377,6 +402,10 @@ async function processUserInput(input, role = 'user', onProgress, sessionId) {
|
|
|
377
402
|
result = await (0, getTxHistory_1.getTxHistory)(args.chainName, args.address, args.days);
|
|
378
403
|
break;
|
|
379
404
|
}
|
|
405
|
+
case 'create_limit_order': {
|
|
406
|
+
result = await (0, createLimitOrder_1.createLimitOrder)(args.tokenSymbol, args.tokenAddress, args.triggerCondition, args.triggerPriceUsd, args.action, args.amountUsd, args.slippageTolerance);
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
380
409
|
case 'update_profile': {
|
|
381
410
|
result = (0, updateProfile_1.updateProfile)(args.content, args.mode);
|
|
382
411
|
break;
|
|
@@ -51,6 +51,7 @@ const yieldVault_1 = require("../web3/skills/yieldVault");
|
|
|
51
51
|
const provideLiquidity_1 = require("../web3/skills/provideLiquidity");
|
|
52
52
|
const getTxHistory_1 = require("../web3/skills/getTxHistory");
|
|
53
53
|
const checkRegistryStatus_1 = require("../web3/skills/checkRegistryStatus");
|
|
54
|
+
const createLimitOrder_1 = require("../web3/skills/createLimitOrder");
|
|
54
55
|
// System Skills
|
|
55
56
|
const browseWeb_1 = require("../system/skills/browseWeb");
|
|
56
57
|
const executeShell_1 = require("../system/skills/executeShell");
|
|
@@ -69,6 +70,7 @@ const analyzeDocument_1 = require("../system/skills/analyzeDocument");
|
|
|
69
70
|
const searchWeb_1 = require("../system/skills/searchWeb");
|
|
70
71
|
const googleWorkspace_1 = require("../system/skills/googleWorkspace");
|
|
71
72
|
const telegram_1 = require("./telegram");
|
|
73
|
+
const eventListener_1 = require("../web3/eventListener");
|
|
72
74
|
const googleAuthModule_1 = require("./googleAuthModule");
|
|
73
75
|
const legalGenerator_1 = require("./legalGenerator");
|
|
74
76
|
const episodic_1 = require("../memory/episodic");
|
|
@@ -309,7 +311,8 @@ app.get('/api/skills', (req, res) => {
|
|
|
309
311
|
revokeApprovals_2.revokeApprovalToolDefinition,
|
|
310
312
|
yieldVault_1.vaultDepositToolDefinition,
|
|
311
313
|
provideLiquidity_1.provideLiquidityToolDefinition,
|
|
312
|
-
getTxHistory_1.getTxHistoryToolDefinition
|
|
314
|
+
getTxHistory_1.getTxHistoryToolDefinition,
|
|
315
|
+
createLimitOrder_1.createLimitOrderToolDefinition
|
|
313
316
|
];
|
|
314
317
|
const skillsWithStatus = allSkills.map(skill => ({
|
|
315
318
|
...skill,
|
|
@@ -778,6 +781,25 @@ app.delete('/api/memory/:id', (req, res) => {
|
|
|
778
781
|
res.status(500).json({ error: error.message });
|
|
779
782
|
}
|
|
780
783
|
});
|
|
784
|
+
// --- User Persona / Risk Profile Endpoints (V3) ---
|
|
785
|
+
app.get('/api/profile', (req, res) => {
|
|
786
|
+
try {
|
|
787
|
+
const profile = reasoning_1.logger.getUserProfile();
|
|
788
|
+
res.json(profile || { risk_level: 'Moderate', max_slippage: 1.0, avoid_memecoins: false, custom_rules: '' });
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
res.status(500).json({ error: error.message });
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
app.post('/api/profile', (req, res) => {
|
|
795
|
+
try {
|
|
796
|
+
reasoning_1.logger.updateUserProfile(req.body);
|
|
797
|
+
res.json({ success: true });
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
res.status(500).json({ error: error.message });
|
|
801
|
+
}
|
|
802
|
+
});
|
|
781
803
|
// Fallback for React Router (Single Page Application)
|
|
782
804
|
app.use((req, res, next) => {
|
|
783
805
|
if (req.method === 'GET' && !req.path.startsWith('/api')) {
|
|
@@ -827,6 +849,8 @@ function startServer() {
|
|
|
827
849
|
console.log(`🤖 Nyxora API Server running on port ${PORT}`);
|
|
828
850
|
// Start the Telegram bot listener
|
|
829
851
|
(0, telegram_1.startTelegramBot)();
|
|
852
|
+
// Start Event Listener for Limit Orders (V3)
|
|
853
|
+
eventListener_1.eventListener.start();
|
|
830
854
|
});
|
|
831
855
|
server.on('error', (e) => {
|
|
832
856
|
if (e.code === 'EADDRINUSE') {
|
|
@@ -201,6 +201,15 @@ function startTelegramBot() {
|
|
|
201
201
|
else if (tx.type === 'revokeApproval') {
|
|
202
202
|
result = await (0, revokeApprovals_1.executeRevokeApproval)(tx.chainName, tx.details, true);
|
|
203
203
|
}
|
|
204
|
+
else if (tx.type === 'limit_order') {
|
|
205
|
+
const success = reasoning_1.logger.activateLimitOrder(tx.details.orderId);
|
|
206
|
+
if (success) {
|
|
207
|
+
result = `Limit Order ${tx.details.orderId} is now ACTIVE. The Event-Driven Engine is monitoring the market.`;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
throw new Error(`Failed to activate Limit Order. ID not found in database.`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
204
213
|
transactionManager_1.txManager.updateStatus(txId, 'executed', result);
|
|
205
214
|
// Pass session history to formatTransactionSuccess to detect language
|
|
206
215
|
const sessionId = ctx.chat?.id.toString() || 'default';
|
|
@@ -53,6 +53,34 @@ class Logger {
|
|
|
53
53
|
// Phase 1: SQLite Index Optimization
|
|
54
54
|
this.db.exec(`
|
|
55
55
|
CREATE INDEX IF NOT EXISTS idx_session_id ON messages(session_id);
|
|
56
|
+
`);
|
|
57
|
+
// V3: Limit Orders & Event-Driven Engine
|
|
58
|
+
this.db.exec(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS limit_orders (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
token_address TEXT NOT NULL,
|
|
62
|
+
token_symbol TEXT NOT NULL,
|
|
63
|
+
trigger_condition TEXT NOT NULL,
|
|
64
|
+
trigger_price_usd REAL NOT NULL,
|
|
65
|
+
action TEXT NOT NULL,
|
|
66
|
+
amount_usd REAL NOT NULL,
|
|
67
|
+
slippage_tolerance REAL DEFAULT 5.0,
|
|
68
|
+
status TEXT DEFAULT 'ACTIVE',
|
|
69
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
70
|
+
expires_at DATETIME,
|
|
71
|
+
tx_hash TEXT
|
|
72
|
+
)
|
|
73
|
+
`);
|
|
74
|
+
// V3: Personalized Risk Profile
|
|
75
|
+
this.db.exec(`
|
|
76
|
+
CREATE TABLE IF NOT EXISTS user_profiles (
|
|
77
|
+
id TEXT PRIMARY KEY,
|
|
78
|
+
risk_level TEXT DEFAULT 'Moderate',
|
|
79
|
+
max_slippage REAL DEFAULT 1.0,
|
|
80
|
+
avoid_memecoins BOOLEAN DEFAULT 0,
|
|
81
|
+
custom_rules TEXT,
|
|
82
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
83
|
+
)
|
|
56
84
|
`);
|
|
57
85
|
// Ensure session_id exists for older DBs
|
|
58
86
|
try {
|
|
@@ -193,6 +221,60 @@ class Logger {
|
|
|
193
221
|
console.error('[Nyxora Memory] Error closing database:', e);
|
|
194
222
|
}
|
|
195
223
|
}
|
|
224
|
+
// V3: User Persona & Risk Profile
|
|
225
|
+
getUserProfile() {
|
|
226
|
+
try {
|
|
227
|
+
const row = this.db.prepare('SELECT * FROM user_profiles WHERE id = ?').get('default');
|
|
228
|
+
if (row) {
|
|
229
|
+
return {
|
|
230
|
+
id: row.id,
|
|
231
|
+
risk_level: row.risk_level,
|
|
232
|
+
max_slippage: row.max_slippage,
|
|
233
|
+
avoid_memecoins: Boolean(row.avoid_memecoins),
|
|
234
|
+
custom_rules: row.custom_rules
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
updateUserProfile(profile) {
|
|
244
|
+
const existing = this.getUserProfile() || {
|
|
245
|
+
id: 'default',
|
|
246
|
+
risk_level: 'Moderate',
|
|
247
|
+
max_slippage: 1.0,
|
|
248
|
+
avoid_memecoins: false,
|
|
249
|
+
custom_rules: null
|
|
250
|
+
};
|
|
251
|
+
const updated = { ...existing, ...profile };
|
|
252
|
+
this.db.prepare(`
|
|
253
|
+
INSERT INTO user_profiles (id, risk_level, max_slippage, avoid_memecoins, custom_rules, updated_at)
|
|
254
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
255
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
256
|
+
risk_level = excluded.risk_level,
|
|
257
|
+
max_slippage = excluded.max_slippage,
|
|
258
|
+
avoid_memecoins = excluded.avoid_memecoins,
|
|
259
|
+
custom_rules = excluded.custom_rules,
|
|
260
|
+
updated_at = excluded.updated_at
|
|
261
|
+
`).run('default', updated.risk_level, updated.max_slippage, updated.avoid_memecoins ? 1 : 0, updated.custom_rules);
|
|
262
|
+
}
|
|
263
|
+
// V3: Limit Orders
|
|
264
|
+
createLimitOrder(order) {
|
|
265
|
+
const id = crypto_1.default.randomUUID();
|
|
266
|
+
this.db.prepare(`
|
|
267
|
+
INSERT INTO limit_orders (
|
|
268
|
+
id, token_address, token_symbol, trigger_condition, trigger_price_usd, action, amount_usd, slippage_tolerance, status
|
|
269
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
270
|
+
`).run(id, order.token_address, order.token_symbol, order.trigger_condition, order.trigger_price_usd, order.action, order.amount_usd, order.slippage_tolerance || 5.0, 'PENDING_APPROVAL' // Requires user approval in Dashboard/Telegram
|
|
271
|
+
);
|
|
272
|
+
return id;
|
|
273
|
+
}
|
|
274
|
+
activateLimitOrder(orderId) {
|
|
275
|
+
const result = this.db.prepare(`UPDATE limit_orders SET status = 'ACTIVE' WHERE id = ?`).run(orderId);
|
|
276
|
+
return result.changes > 0;
|
|
277
|
+
}
|
|
196
278
|
}
|
|
197
279
|
exports.Logger = Logger;
|
|
198
280
|
exports.logger = new Logger();
|
|
@@ -91,22 +91,25 @@ async function analyzeDocument(filePath) {
|
|
|
91
91
|
return text;
|
|
92
92
|
}
|
|
93
93
|
if (ext === '.xlsx' || ext === '.csv') {
|
|
94
|
-
|
|
95
|
-
const workbook = new ExcelJS.Workbook();
|
|
94
|
+
let text = `--- Spreadsheet Data from ${path_1.default.basename(absolutePath)} ---\n`;
|
|
96
95
|
if (ext === '.csv') {
|
|
97
|
-
await
|
|
96
|
+
const { parse } = await Promise.resolve().then(() => __importStar(require('csv-parse/sync')));
|
|
97
|
+
const content = fs_1.default.readFileSync(absolutePath, 'utf8');
|
|
98
|
+
const records = parse(content, { skip_empty_lines: true });
|
|
99
|
+
records.forEach((row) => {
|
|
100
|
+
text += row.join(',') + '\n';
|
|
101
|
+
});
|
|
98
102
|
}
|
|
99
103
|
else {
|
|
100
|
-
await
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
text += values.join(',') + '\n';
|
|
104
|
+
const readXlsxFile = (await Promise.resolve().then(() => __importStar(require('read-excel-file/node')))).default;
|
|
105
|
+
const sheets = await readXlsxFile(absolutePath);
|
|
106
|
+
sheets.forEach((s) => {
|
|
107
|
+
text += `\n[Sheet: ${s.sheet}]\n`;
|
|
108
|
+
s.data.forEach((row) => {
|
|
109
|
+
text += row.map(cell => cell === null ? '' : String(cell)).join(',') + '\n';
|
|
110
|
+
});
|
|
108
111
|
});
|
|
109
|
-
}
|
|
112
|
+
}
|
|
110
113
|
if (text.length > 20000) {
|
|
111
114
|
text = text.substring(0, 20000) + "... [Content Truncated]";
|
|
112
115
|
}
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.generateExcelToolDefinition = void 0;
|
|
7
7
|
exports.generateExcelFile = generateExcelFile;
|
|
8
|
-
const
|
|
8
|
+
const node_1 = __importDefault(require("write-excel-file/node"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
async function generateExcelFile(data, filePath) {
|
|
@@ -19,14 +19,12 @@ async function generateExcelFile(data, filePath) {
|
|
|
19
19
|
if (!reportData || reportData.length === 0) {
|
|
20
20
|
reportData = [{ Message: 'No data available' }];
|
|
21
21
|
}
|
|
22
|
-
const workbook = new exceljs_1.default.Workbook();
|
|
23
|
-
const worksheet = workbook.addWorksheet('Report');
|
|
24
22
|
const headers = Object.keys(reportData[0]);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
await
|
|
23
|
+
const excelData = [
|
|
24
|
+
headers.map(h => ({ value: h, fontWeight: 'bold' })),
|
|
25
|
+
...reportData.map(row => headers.map(h => ({ value: String(row[h] || '') })))
|
|
26
|
+
];
|
|
27
|
+
await (0, node_1.default)(excelData).toFile(absolutePath);
|
|
30
28
|
return `Success: Excel file generated at ${absolutePath}`;
|
|
31
29
|
}
|
|
32
30
|
catch (error) {
|
|
@@ -167,12 +167,17 @@ async function fetchLifi(fromChain, toChain, fromToken, toToken, amount, address
|
|
|
167
167
|
}
|
|
168
168
|
async function fetchRelay(fromChain, toChain, fromToken, toToken, amount, address, slippage, key) {
|
|
169
169
|
try {
|
|
170
|
+
// Relay API strictly requires the zero address for Native ETH instead of 0xeeee...
|
|
171
|
+
const relayOriginCurrency = fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
|
|
172
|
+
? '0x0000000000000000000000000000000000000000' : fromToken;
|
|
173
|
+
const relayDestCurrency = toToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
|
|
174
|
+
? '0x0000000000000000000000000000000000000000' : toToken;
|
|
170
175
|
const payload = {
|
|
171
176
|
user: address,
|
|
172
177
|
originChainId: CHAIN_IDS[fromChain].toString(),
|
|
173
178
|
destinationChainId: CHAIN_IDS[toChain].toString(),
|
|
174
|
-
originCurrency:
|
|
175
|
-
destinationCurrency:
|
|
179
|
+
originCurrency: relayOriginCurrency,
|
|
180
|
+
destinationCurrency: relayDestCurrency,
|
|
176
181
|
recipient: address,
|
|
177
182
|
tradeType: 'EXACT_INPUT',
|
|
178
183
|
amount: amount,
|
|
@@ -43,9 +43,14 @@ async function fetchRelayTestnet(fromChain, toChain, fromToken, toToken, amount,
|
|
|
43
43
|
const destChainId = RELAY_CHAIN_MAP[toChain];
|
|
44
44
|
if (!originChainId || !destChainId)
|
|
45
45
|
return null;
|
|
46
|
+
// Relay API strictly requires the zero address for Native ETH instead of 0xeeee...
|
|
47
|
+
const relayOriginCurrency = fromToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
|
|
48
|
+
? '0x0000000000000000000000000000000000000000' : fromToken;
|
|
49
|
+
const relayDestCurrency = toToken.toLowerCase() === '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
|
|
50
|
+
? '0x0000000000000000000000000000000000000000' : toToken;
|
|
46
51
|
const payload = {
|
|
47
52
|
user: address, originChainId, destinationChainId: destChainId,
|
|
48
|
-
originCurrency:
|
|
53
|
+
originCurrency: relayOriginCurrency, destinationCurrency: relayDestCurrency,
|
|
49
54
|
recipient: address, tradeType: 'EXACT_INPUT', amount,
|
|
50
55
|
referrer: 'nyxora', useExternalLiquidity: false
|
|
51
56
|
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.eventListener = void 0;
|
|
7
|
+
const logger_1 = require("../memory/logger");
|
|
8
|
+
const getPrice_1 = require("./skills/getPrice");
|
|
9
|
+
const swapToken_1 = require("./skills/swapToken");
|
|
10
|
+
const transactionManager_1 = require("../agent/transactionManager");
|
|
11
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
12
|
+
class EventListener {
|
|
13
|
+
timer = null;
|
|
14
|
+
isRunning = false;
|
|
15
|
+
start() {
|
|
16
|
+
if (this.isRunning)
|
|
17
|
+
return;
|
|
18
|
+
this.isRunning = true;
|
|
19
|
+
console.log(picocolors_1.default.green('[Event Listener] Real-Time Multi-Source Radar started. Monitoring DexScreener & Oracles...'));
|
|
20
|
+
this.poll();
|
|
21
|
+
}
|
|
22
|
+
stop() {
|
|
23
|
+
this.isRunning = false;
|
|
24
|
+
if (this.timer)
|
|
25
|
+
clearTimeout(this.timer);
|
|
26
|
+
console.log(picocolors_1.default.yellow('[Event Listener] Radar stopped.'));
|
|
27
|
+
}
|
|
28
|
+
async poll() {
|
|
29
|
+
if (!this.isRunning)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
// 1. Fetch active limit orders from database
|
|
33
|
+
const rows = logger_1.logger['db'].prepare(`SELECT * FROM limit_orders WHERE status = 'ACTIVE'`).all();
|
|
34
|
+
for (const order of rows) {
|
|
35
|
+
try {
|
|
36
|
+
// 2. Fetch current price via DexScreener / Aggregator (getPrice skill)
|
|
37
|
+
const priceResult = await (0, getPrice_1.getPrice)(order.token_address);
|
|
38
|
+
// Parse price from result string (rough parsing for MVP)
|
|
39
|
+
const priceMatch = priceResult.match(/Current Price:\s*\$([\d.]+)/i);
|
|
40
|
+
if (!priceMatch)
|
|
41
|
+
continue;
|
|
42
|
+
const currentPrice = parseFloat(priceMatch[1]);
|
|
43
|
+
const targetPrice = order.trigger_price_usd;
|
|
44
|
+
let triggered = false;
|
|
45
|
+
if (order.trigger_condition === 'PRICE_DROPS_BELOW' && currentPrice <= targetPrice)
|
|
46
|
+
triggered = true;
|
|
47
|
+
if (order.trigger_condition === 'PRICE_RISES_ABOVE' && currentPrice >= targetPrice)
|
|
48
|
+
triggered = true;
|
|
49
|
+
if (triggered) {
|
|
50
|
+
console.log(picocolors_1.default.bgRed(picocolors_1.default.white(`\n[🚨 LIMIT ORDER TRIGGERED] ${order.token_symbol} hit target price $${targetPrice}! Executing ${order.action}...`)));
|
|
51
|
+
// 3. Mark as EXECUTING to prevent double-execution
|
|
52
|
+
logger_1.logger['db'].prepare(`UPDATE limit_orders SET status = 'EXECUTING' WHERE id = ?`).run(order.id);
|
|
53
|
+
// 4. Policy Engine & Executor: Prepare and Execute Swap with Slippage Tolerance
|
|
54
|
+
// Note: In V3, this delegates to our existing swap engine which inherently uses 1inch/0x aggregators that support slippage param
|
|
55
|
+
if (order.action === 'BUY') {
|
|
56
|
+
// Buying token using USDC as base for example.
|
|
57
|
+
// In full implementation, we'd look up the user's base asset (USDC/ETH).
|
|
58
|
+
const amountStr = order.amount_usd.toString(); // assuming base is USD stablecoin
|
|
59
|
+
const txMsg = await (0, swapToken_1.prepareSwapToken)('base', '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', order.token_address, amountStr, 'auto', 'auto', order.slippage_tolerance);
|
|
60
|
+
// Extract txId
|
|
61
|
+
const txMatch = txMsg.match(/Transaction ID: ([\w-]+)/);
|
|
62
|
+
if (txMatch) {
|
|
63
|
+
const txId = txMatch[1];
|
|
64
|
+
const pendingTx = transactionManager_1.txManager.getTransaction(txId);
|
|
65
|
+
if (pendingTx) {
|
|
66
|
+
const result = await (0, swapToken_1.executeSwap)(pendingTx.chainName, pendingTx.details, true);
|
|
67
|
+
logger_1.logger['db'].prepare(`UPDATE limit_orders SET status = 'COMPLETED', tx_hash = ? WHERE id = ?`).run('Executed via router', order.id);
|
|
68
|
+
console.log(picocolors_1.default.green(`[Event Listener] Order ${order.id} executed successfully. Result: ${result}`));
|
|
69
|
+
// Notify user (would need a callback to Telegram in full implementation)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (order.action === 'SELL') {
|
|
74
|
+
// Selling token for USDC
|
|
75
|
+
const amountStr = order.amount_usd.toString(); // Ideally convert USD to Token Amount via oracle
|
|
76
|
+
const txMsg = await (0, swapToken_1.prepareSwapToken)('base', order.token_address, '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', amountStr, 'auto', 'auto', order.slippage_tolerance);
|
|
77
|
+
// Same extraction logic...
|
|
78
|
+
const txMatch = txMsg.match(/Transaction ID: ([\w-]+)/);
|
|
79
|
+
if (txMatch) {
|
|
80
|
+
const txId = txMatch[1];
|
|
81
|
+
const pendingTx = transactionManager_1.txManager.getTransaction(txId);
|
|
82
|
+
if (pendingTx) {
|
|
83
|
+
const result = await (0, swapToken_1.executeSwap)(pendingTx.chainName, pendingTx.details, true);
|
|
84
|
+
logger_1.logger['db'].prepare(`UPDATE limit_orders SET status = 'COMPLETED', tx_hash = ? WHERE id = ?`).run('Executed via router', order.id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error(`[Event Listener] Error processing order ${order.id}:`, err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
console.error('[Event Listener] Critical Poll Error:', e);
|
|
97
|
+
}
|
|
98
|
+
// Schedule next poll (Graceful Degradation to HTTP Polling if WSS isn't used)
|
|
99
|
+
this.timer = setTimeout(() => this.poll(), 10000); // Poll every 10s for prototype
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.eventListener = new EventListener();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLimitOrderToolDefinition = void 0;
|
|
4
|
+
exports.createLimitOrder = createLimitOrder;
|
|
5
|
+
const logger_1 = require("../../memory/logger");
|
|
6
|
+
const transactionManager_1 = require("../../agent/transactionManager");
|
|
7
|
+
async function createLimitOrder(tokenSymbol, tokenAddress, triggerCondition, triggerPriceUsd, action, amountUsd, slippageTolerance) {
|
|
8
|
+
try {
|
|
9
|
+
const orderData = {
|
|
10
|
+
token_symbol: tokenSymbol,
|
|
11
|
+
token_address: tokenAddress,
|
|
12
|
+
trigger_condition: triggerCondition,
|
|
13
|
+
trigger_price_usd: triggerPriceUsd,
|
|
14
|
+
action,
|
|
15
|
+
amount_usd: amountUsd,
|
|
16
|
+
slippage_tolerance: slippageTolerance || 5.0
|
|
17
|
+
};
|
|
18
|
+
const orderId = logger_1.logger.createLimitOrder(orderData);
|
|
19
|
+
const tx = transactionManager_1.txManager.createPendingTransaction('limit_order', 'any', {
|
|
20
|
+
orderId,
|
|
21
|
+
...orderData
|
|
22
|
+
});
|
|
23
|
+
return `[UNSAFE/HIGH RISK ALERT] Limit Order drafted successfully.\nTrigger: If ${tokenSymbol} ${triggerCondition === 'PRICE_DROPS_BELOW' ? 'drops below' : 'rises above'} $${triggerPriceUsd}, ${action} $${amountUsd} worth of ${tokenSymbol}.\nTransaction ID: ${tx.id}\nStatus: PENDING_APPROVAL. Waiting for your explicit confirmation via UI/Telegram before the Event-Driven Engine activates.`;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return `Failed to create Limit Order: ${error.message}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.createLimitOrderToolDefinition = {
|
|
30
|
+
type: "function",
|
|
31
|
+
function: {
|
|
32
|
+
name: "create_limit_order",
|
|
33
|
+
description: "[HIGH RISK] Create a decentralized limit order (trigger) that will automatically execute a trade when a token's price hits a specific target. Use this when the user asks to buy or sell a token if the price goes up or down to a certain level.",
|
|
34
|
+
parameters: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
tokenSymbol: { type: "string", description: "Symbol of the token (e.g. PEPE, ETH)" },
|
|
38
|
+
tokenAddress: { type: "string", description: "Contract address of the token" },
|
|
39
|
+
triggerCondition: { type: "string", enum: ["PRICE_DROPS_BELOW", "PRICE_RISES_ABOVE"], description: "The condition to trigger the order" },
|
|
40
|
+
triggerPriceUsd: { type: "number", description: "The target price in USD" },
|
|
41
|
+
action: { type: "string", enum: ["BUY", "SELL"], description: "The action to take when triggered" },
|
|
42
|
+
amountUsd: { type: "number", description: "The amount in USD to buy or sell" },
|
|
43
|
+
slippageTolerance: { type: "number", description: "Maximum slippage tolerance in percentage (e.g. 5.0)" }
|
|
44
|
+
},
|
|
45
|
+
required: ["tokenSymbol", "tokenAddress", "triggerCondition", "triggerPriceUsd", "action", "amountUsd"],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyxora",
|
|
3
|
-
"version": "26.6.
|
|
3
|
+
"version": "26.6.12",
|
|
4
4
|
"description": "Your Personal Web3 Assistant",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web3",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"@napi-rs/keyring": "^1.3.0",
|
|
55
55
|
"@notionhq/client": "^5.22.0",
|
|
56
56
|
"cors": "^2.8.6",
|
|
57
|
+
"csv-parse": "^6.2.1",
|
|
57
58
|
"dotenv": "^17.4.2",
|
|
58
59
|
"duck-duck-scrape": "^2.2.7",
|
|
59
|
-
"exceljs": "^4.4.0",
|
|
60
60
|
"express": "^5.2.1",
|
|
61
61
|
"express-rate-limit": "^7.5.0",
|
|
62
62
|
"helmet": "^8.0.0",
|
|
@@ -68,11 +68,13 @@
|
|
|
68
68
|
"pdf-parse": "^2.4.5",
|
|
69
69
|
"picocolors": "^1.1.1",
|
|
70
70
|
"playwright": "^1.60.0",
|
|
71
|
+
"read-excel-file": "^9.2.0",
|
|
71
72
|
"telegraf": "^4.16.3",
|
|
72
73
|
"ts-node": "^10.9.2",
|
|
73
|
-
"typescript": "^6.0.3",
|
|
74
74
|
"twitter-api-v2": "^1.29.0",
|
|
75
|
+
"typescript": "^6.0.3",
|
|
75
76
|
"viem": "^2.51.0",
|
|
77
|
+
"write-excel-file": "^4.1.1",
|
|
76
78
|
"yaml": "^2.9.0",
|
|
77
79
|
"zod": "^3.23.8"
|
|
78
80
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nyxora-agent-core",
|
|
3
|
-
"version": "26.6.
|
|
3
|
+
"version": "26.6.12",
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "src/gateway/server.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"@inquirer/search": "^4.2.1",
|
|
9
9
|
"@notionhq/client": "^5.22.0",
|
|
10
10
|
"cors": "^2.8.6",
|
|
11
|
+
"csv-parse": "^6.2.1",
|
|
11
12
|
"duck-duck-scrape": "^2.2.7",
|
|
12
|
-
"exceljs": "^4.4.0",
|
|
13
13
|
"express": "^5.2.1",
|
|
14
14
|
"express-rate-limit": "^7.5.0",
|
|
15
15
|
"helmet": "^8.0.0",
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
"openai": "^6.39.0",
|
|
20
20
|
"pdf-parse": "^2.4.5",
|
|
21
21
|
"playwright": "^1.60.0",
|
|
22
|
+
"read-excel-file": "^9.2.0",
|
|
22
23
|
"telegraf": "^4.16.3",
|
|
23
24
|
"twitter-api-v2": "^1.29.0",
|
|
25
|
+
"write-excel-file": "^4.1.1",
|
|
24
26
|
"yaml": "^2.9.0",
|
|
25
27
|
"zod": "^3.25.76"
|
|
26
28
|
},
|