nyxora 26.6.20 → 26.6.21
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 +18 -1
- package/bin/nyxora.mjs +32 -0
- package/dist/packages/core/src/agent/reasoning.js +10 -0
- package/dist/packages/core/src/config/parser.js +121 -7
- package/dist/packages/core/src/gateway/chat.js +82 -0
- package/dist/packages/core/src/gateway/cli.js +63 -0
- package/dist/packages/core/src/gateway/server.js +100 -56
- package/dist/packages/core/src/gateway/setup.js +39 -22
- package/dist/packages/core/src/utils/formatter.test.js +40 -0
- package/dist/packages/core/src/utils/skillManager.js +91 -0
- package/dist/packages/core/src/utils/userWhitelistManager.js +41 -36
- package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +2 -2
- package/dist/packages/core/src/web3/aggregator/defiRouter.js +3 -0
- package/dist/packages/core/src/web3/skills/bridgeToken.js +4 -0
- package/dist/packages/core/src/web3/skills/checkRegistryStatus.js +13 -0
- package/dist/packages/core/src/web3/skills/customTx.js +2 -0
- package/dist/packages/core/src/web3/skills/defiLending.js +2 -0
- package/dist/packages/core/src/web3/skills/manageCustomTokens.js +18 -32
- package/dist/packages/core/src/web3/skills/marketAnalysis.js +3 -1
- package/dist/packages/core/src/web3/skills/mintNft.js +2 -0
- package/dist/packages/core/src/web3/skills/provideLiquidity.js +2 -0
- package/dist/packages/core/src/web3/skills/revokeApprovals.js +2 -0
- package/dist/packages/core/src/web3/skills/swapToken.js +4 -2
- package/dist/packages/core/src/web3/skills/transfer.js +2 -0
- package/dist/packages/core/src/web3/skills/yieldVault.js +2 -0
- package/dist/packages/core/src/web3/utils/tokens.js +9 -1
- package/package.json +2 -1
- package/packages/core/package.json +1 -1
- package/packages/core/src/agent/reasoning.ts +11 -0
- package/packages/core/src/config/parser.ts +119 -9
- package/packages/core/src/gateway/chat.ts +85 -0
- package/packages/core/src/gateway/cli.ts +63 -0
- package/packages/core/src/gateway/server.ts +115 -60
- package/packages/core/src/gateway/setup.ts +39 -27
- package/packages/core/src/utils/formatter.test.ts +41 -0
- package/packages/core/src/utils/skillManager.ts +98 -0
- package/packages/core/src/utils/userWhitelistManager.ts +48 -39
- package/packages/core/src/web3/aggregator/aggregatorMainnet.ts +2 -2
- package/packages/core/src/web3/aggregator/defiRouter.ts +4 -0
- package/packages/core/src/web3/skills/bridgeToken.ts +3 -0
- package/packages/core/src/web3/skills/checkRegistryStatus.ts +13 -0
- package/packages/core/src/web3/skills/customTx.ts +1 -0
- package/packages/core/src/web3/skills/defiLending.ts +1 -0
- package/packages/core/src/web3/skills/manageCustomTokens.ts +18 -29
- package/packages/core/src/web3/skills/marketAnalysis.ts +2 -1
- package/packages/core/src/web3/skills/mintNft.ts +1 -0
- package/packages/core/src/web3/skills/provideLiquidity.ts +1 -0
- package/packages/core/src/web3/skills/revokeApprovals.ts +1 -0
- package/packages/core/src/web3/skills/swapToken.ts +3 -2
- package/packages/core/src/web3/skills/transfer.ts +1 -0
- package/packages/core/src/web3/skills/yieldVault.ts +1 -0
- package/packages/core/src/web3/utils/tokens.ts +9 -1
- package/packages/dashboard/dist/assets/index-Di9x08yk.js +16 -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/dist/packages/core/src/agent/limitOrderManager.js +0 -124
- package/dist/packages/core/src/system/pluginManager.js +0 -91
- package/dist/packages/core/src/system/skills/installSkill.js +0 -52
- package/dist/packages/core/src/test-all-routers.js +0 -81
- package/dist/packages/core/src/test-router.js +0 -38
- package/dist/packages/core/src/web3/skills/autonomousDefi.js +0 -191
- package/dist/packages/core/src/web3/skills/createWallet.js +0 -34
- package/dist/packages/core/src/web3/skills/limitOrder.js +0 -106
- package/dist/packages/core/src/web3/utils/protocolRegistry.js +0 -46
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/packages/core/src/__tests__/reasoning.test.ts +0 -81
- package/packages/core/src/__tests__/tokens.test.ts +0 -55
- package/packages/core/src/__tests__/web3.test.ts +0 -50
- package/packages/core/src/agent/reasoning.d.ts.map +0 -1
- package/packages/core/src/config/parser.d.ts.map +0 -1
- package/packages/core/src/gateway/cli.d.ts.map +0 -1
- package/packages/core/src/memory/logger.d.ts.map +0 -1
- package/packages/core/src/test-all-routers.ts +0 -59
- package/packages/core/src/test-router.ts +0 -49
- package/packages/core/src/web3/config.d.ts.map +0 -1
- package/packages/core/src/web3/skills/getBalance.d.ts.map +0 -1
- package/packages/dashboard/dist/assets/index-O2m42q4p.js +0 -16
|
@@ -42,7 +42,7 @@ import { checkPortfolioToolDefinition } from '../web3/skills/checkPortfolio';
|
|
|
42
42
|
import { marketAnalysisToolDefinition } from '../web3/skills/marketAnalysis';
|
|
43
43
|
import { executeApprove, executeAaveSupply, executeVaultDeposit, executeUniv3Mint } from '../web3/skills/executeDefi';
|
|
44
44
|
import { executeRevokeApproval } from '../web3/skills/revokeApprovals';
|
|
45
|
-
import { isSkillActive, toggleSkill } from '../utils/skillManager';
|
|
45
|
+
import { isSkillActive, toggleSkill, syncAllSkillsToConfig } from '../utils/skillManager';
|
|
46
46
|
import { executeBridge, bridgeTokenToolDefinition } from '../web3/skills/bridgeToken';
|
|
47
47
|
import { executeMintNft, mintNftToolDefinition } from '../web3/skills/mintNft';
|
|
48
48
|
import { executeCustomTx, customTxToolDefinition } from '../web3/skills/customTx';
|
|
@@ -51,8 +51,11 @@ import { revokeApprovalToolDefinition } from '../web3/skills/revokeApprovals';
|
|
|
51
51
|
import { vaultDepositToolDefinition } from '../web3/skills/yieldVault';
|
|
52
52
|
import { provideLiquidityToolDefinition } from '../web3/skills/provideLiquidity';
|
|
53
53
|
import { getTxHistoryToolDefinition } from '../web3/skills/getTxHistory';
|
|
54
|
-
import { checkRegistryStatus } from '../web3/skills/checkRegistryStatus';
|
|
54
|
+
import { checkRegistryStatus, checkRegistryStatusToolDefinition } from '../web3/skills/checkRegistryStatus';
|
|
55
55
|
import { createLimitOrderToolDefinition } from '../web3/skills/createLimitOrder';
|
|
56
|
+
import { getUserWhitelist, saveTokenToWhitelist, removeTokenFromWhitelist } from '../utils/userWhitelistManager';
|
|
57
|
+
import { getTokenMetadata } from '../web3/utils/tokens';
|
|
58
|
+
import { ChainName } from '../web3/config';
|
|
56
59
|
|
|
57
60
|
// System Skills
|
|
58
61
|
import { browseWebsiteToolDefinition } from '../system/skills/browseWeb';
|
|
@@ -84,6 +87,9 @@ import { ReflectionEngine } from '../memory/reflection';
|
|
|
84
87
|
// Initialize Google Auth
|
|
85
88
|
initGoogleAuth();
|
|
86
89
|
|
|
90
|
+
// Synchronize all active skills to config.yaml on startup
|
|
91
|
+
syncAllSkillsToConfig();
|
|
92
|
+
|
|
87
93
|
import util from 'util';
|
|
88
94
|
|
|
89
95
|
// Intercept console.log and console.error
|
|
@@ -331,8 +337,63 @@ app.post('/api/defi-keys', (req, res) => {
|
|
|
331
337
|
}
|
|
332
338
|
});
|
|
333
339
|
|
|
340
|
+
const allSkills = [
|
|
341
|
+
getBalanceToolDefinition,
|
|
342
|
+
transferToolDefinition,
|
|
343
|
+
getPriceToolDefinition,
|
|
344
|
+
swapTokenToolDefinition,
|
|
345
|
+
bridgeTokenToolDefinition,
|
|
346
|
+
mintNftToolDefinition,
|
|
347
|
+
customTxToolDefinition,
|
|
348
|
+
checkAddressToolDefinition,
|
|
349
|
+
getMyAddressToolDefinition,
|
|
350
|
+
checkSecurityToolDefinition,
|
|
351
|
+
checkPortfolioToolDefinition,
|
|
352
|
+
marketAnalysisToolDefinition,
|
|
353
|
+
manageCustomTokensDefinition,
|
|
354
|
+
aaveSupplyToolDefinition,
|
|
355
|
+
revokeApprovalToolDefinition,
|
|
356
|
+
vaultDepositToolDefinition,
|
|
357
|
+
provideLiquidityToolDefinition,
|
|
358
|
+
getTxHistoryToolDefinition,
|
|
359
|
+
createLimitOrderToolDefinition,
|
|
360
|
+
checkRegistryStatusToolDefinition
|
|
361
|
+
];
|
|
362
|
+
|
|
363
|
+
const systemSkills = [
|
|
364
|
+
runTerminalCommandToolDefinition,
|
|
365
|
+
readLocalFileToolDefinition,
|
|
366
|
+
writeLocalFileToolDefinition,
|
|
367
|
+
generateExcelToolDefinition,
|
|
368
|
+
browseWebsiteToolDefinition,
|
|
369
|
+
updateSecurityPolicyToolDefinition,
|
|
370
|
+
|
|
371
|
+
analyzeDocumentToolDefinition,
|
|
372
|
+
searchWebToolDefinition,
|
|
373
|
+
readGmailInboxToolDefinition,
|
|
374
|
+
listCalendarEventsToolDefinition,
|
|
375
|
+
appendRowToSheetsToolDefinition,
|
|
376
|
+
readGoogleDocsToolDefinition,
|
|
377
|
+
readGoogleFormResponsesToolDefinition,
|
|
378
|
+
editLocalFileToolDefinition,
|
|
379
|
+
gitManagerToolDefinition,
|
|
380
|
+
xManagerToolDefinition,
|
|
381
|
+
notionWorkspaceToolDefinition,
|
|
382
|
+
audioTranscribeToolDefinition,
|
|
383
|
+
summarizeTextToolDefinition
|
|
384
|
+
];
|
|
385
|
+
|
|
334
386
|
app.get('/api/stats', (req, res) => {
|
|
335
|
-
|
|
387
|
+
const stats = Tracker.getStats();
|
|
388
|
+
const dbPath = getPath('memory.db');
|
|
389
|
+
|
|
390
|
+
const activeWeb3 = allSkills.filter(s => isSkillActive(s.function.name)).length;
|
|
391
|
+
const activeSystem = systemSkills.filter(s => isSkillActive(s.function.name)).length;
|
|
392
|
+
|
|
393
|
+
const totalSkills = allSkills.length + systemSkills.length;
|
|
394
|
+
const activeSkills = activeWeb3 + activeSystem;
|
|
395
|
+
|
|
396
|
+
res.json({ ...stats, memoryPath: dbPath, totalSkills, activeSkills });
|
|
336
397
|
});
|
|
337
398
|
|
|
338
399
|
app.get('/api/logs', (req, res) => {
|
|
@@ -340,28 +401,6 @@ app.get('/api/logs', (req, res) => {
|
|
|
340
401
|
});
|
|
341
402
|
|
|
342
403
|
app.get('/api/skills', (req, res) => {
|
|
343
|
-
const allSkills = [
|
|
344
|
-
getBalanceToolDefinition,
|
|
345
|
-
transferToolDefinition,
|
|
346
|
-
getPriceToolDefinition,
|
|
347
|
-
swapTokenToolDefinition,
|
|
348
|
-
bridgeTokenToolDefinition,
|
|
349
|
-
mintNftToolDefinition,
|
|
350
|
-
customTxToolDefinition,
|
|
351
|
-
checkAddressToolDefinition,
|
|
352
|
-
getMyAddressToolDefinition,
|
|
353
|
-
checkSecurityToolDefinition,
|
|
354
|
-
checkPortfolioToolDefinition,
|
|
355
|
-
marketAnalysisToolDefinition,
|
|
356
|
-
manageCustomTokensDefinition,
|
|
357
|
-
aaveSupplyToolDefinition,
|
|
358
|
-
revokeApprovalToolDefinition,
|
|
359
|
-
vaultDepositToolDefinition,
|
|
360
|
-
provideLiquidityToolDefinition,
|
|
361
|
-
getTxHistoryToolDefinition,
|
|
362
|
-
createLimitOrderToolDefinition
|
|
363
|
-
];
|
|
364
|
-
|
|
365
404
|
const skillsWithStatus = allSkills.map(skill => ({
|
|
366
405
|
...skill,
|
|
367
406
|
isActive: isSkillActive(skill.function.name)
|
|
@@ -371,29 +410,6 @@ app.get('/api/skills', (req, res) => {
|
|
|
371
410
|
});
|
|
372
411
|
|
|
373
412
|
app.get('/api/skills/system', (req, res) => {
|
|
374
|
-
const systemSkills = [
|
|
375
|
-
runTerminalCommandToolDefinition,
|
|
376
|
-
readLocalFileToolDefinition,
|
|
377
|
-
writeLocalFileToolDefinition,
|
|
378
|
-
generateExcelToolDefinition,
|
|
379
|
-
browseWebsiteToolDefinition,
|
|
380
|
-
updateSecurityPolicyToolDefinition,
|
|
381
|
-
|
|
382
|
-
analyzeDocumentToolDefinition,
|
|
383
|
-
searchWebToolDefinition,
|
|
384
|
-
readGmailInboxToolDefinition,
|
|
385
|
-
listCalendarEventsToolDefinition,
|
|
386
|
-
appendRowToSheetsToolDefinition,
|
|
387
|
-
readGoogleDocsToolDefinition,
|
|
388
|
-
readGoogleFormResponsesToolDefinition,
|
|
389
|
-
editLocalFileToolDefinition,
|
|
390
|
-
gitManagerToolDefinition,
|
|
391
|
-
xManagerToolDefinition,
|
|
392
|
-
notionWorkspaceToolDefinition,
|
|
393
|
-
audioTranscribeToolDefinition,
|
|
394
|
-
summarizeTextToolDefinition
|
|
395
|
-
];
|
|
396
|
-
|
|
397
413
|
const skillsWithStatus = systemSkills.map(skill => ({
|
|
398
414
|
...skill,
|
|
399
415
|
isActive: isSkillActive(skill.function.name)
|
|
@@ -411,6 +427,44 @@ app.post('/api/skills/toggle', (req, res) => {
|
|
|
411
427
|
res.json({ success: true, skillName, active });
|
|
412
428
|
});
|
|
413
429
|
|
|
430
|
+
// Portfolio Whitelist Routes
|
|
431
|
+
app.get('/api/portfolio/whitelist', async (req, res) => {
|
|
432
|
+
const whitelist = getUserWhitelist();
|
|
433
|
+
res.json(whitelist);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
app.post('/api/portfolio/whitelist', async (req, res) => {
|
|
437
|
+
const { walletAddress, chainName, tokenAddress, symbol, decimals } = req.body;
|
|
438
|
+
if (!walletAddress || !chainName || !tokenAddress) {
|
|
439
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
440
|
+
}
|
|
441
|
+
await saveTokenToWhitelist(walletAddress, chainName, tokenAddress, 'manual', symbol, decimals);
|
|
442
|
+
res.json({ success: true });
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
app.delete('/api/portfolio/whitelist', (req, res) => {
|
|
446
|
+
const { walletAddress, chainName, tokenAddress } = req.body;
|
|
447
|
+
if (!walletAddress || !chainName || !tokenAddress) {
|
|
448
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
449
|
+
}
|
|
450
|
+
removeTokenFromWhitelist(walletAddress, chainName, tokenAddress);
|
|
451
|
+
res.json({ success: true });
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
app.get('/api/portfolio/token-metadata', async (req, res) => {
|
|
455
|
+
const { chain, address } = req.query;
|
|
456
|
+
if (!chain || !address || typeof address !== 'string') {
|
|
457
|
+
return res.status(400).json({ error: 'Missing chain or address' });
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const client = getPublicClient(chain as ChainName);
|
|
461
|
+
const metadata = await getTokenMetadata(client, address as `0x${string}`);
|
|
462
|
+
res.json(metadata);
|
|
463
|
+
} catch (err: any) {
|
|
464
|
+
res.status(500).json({ error: err.message });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
414
468
|
// Google Workspace Auth Routes
|
|
415
469
|
app.get('/api/auth/google/url', (req, res) => {
|
|
416
470
|
const url = getAuthUrl();
|
|
@@ -642,13 +696,8 @@ app.get('/api/wallet', async (req, res) => {
|
|
|
642
696
|
app.get('/api/portfolio', async (req, res) => {
|
|
643
697
|
try {
|
|
644
698
|
const userAddress = await getAddress();
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
if (fs.existsSync(customTokensPath)) {
|
|
648
|
-
try {
|
|
649
|
-
customTokens = JSON.parse(fs.readFileSync(customTokensPath, 'utf8'));
|
|
650
|
-
} catch (e) {}
|
|
651
|
-
}
|
|
699
|
+
const whitelist = getUserWhitelist();
|
|
700
|
+
const userCustomTokens = whitelist[userAddress.toLowerCase()] || [];
|
|
652
701
|
|
|
653
702
|
const portfolio: Record<string, any[]> = {};
|
|
654
703
|
|
|
@@ -669,11 +718,15 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
669
718
|
});
|
|
670
719
|
}
|
|
671
720
|
|
|
672
|
-
// 2. Combine TOKEN_MAP and
|
|
721
|
+
// 2. Combine TOKEN_MAP and YAML whitelist for this chain
|
|
673
722
|
const tokensToQuery = { ...((TOKEN_MAP as any)[chainName] || {}) };
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
723
|
+
|
|
724
|
+
// Inject whitelisted tokens
|
|
725
|
+
userCustomTokens.forEach(t => {
|
|
726
|
+
if (t.chainName === chainName && t.symbol && t.address) {
|
|
727
|
+
tokensToQuery[t.symbol.toUpperCase()] = t.address;
|
|
728
|
+
}
|
|
729
|
+
});
|
|
677
730
|
|
|
678
731
|
// 3. Query all ERC-20 balances in parallel
|
|
679
732
|
await Promise.all(Object.entries(tokensToQuery).map(async ([symbol, address]) => {
|
|
@@ -692,7 +745,9 @@ app.get('/api/portfolio', async (req, res) => {
|
|
|
692
745
|
} as any);
|
|
693
746
|
|
|
694
747
|
const [bal, decimals] = await Promise.all([balPromise, decPromise]) as [bigint, number];
|
|
695
|
-
|
|
748
|
+
const isCustom = userCustomTokens.some(t => t.chainName === chainName && t.address === address);
|
|
749
|
+
|
|
750
|
+
if (bal > 0n || isCustom) {
|
|
696
751
|
portfolio[chainName].push({
|
|
697
752
|
symbol,
|
|
698
753
|
address,
|
|
@@ -7,24 +7,7 @@ import { getAppDir, getPath } from '../config/paths';
|
|
|
7
7
|
import { loadConfig, saveConfig, saveApiKeys, saveRpcConfig } from '../config/parser';
|
|
8
8
|
import crypto from 'crypto';
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const salt = crypto.randomBytes(16);
|
|
12
|
-
const key = crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
|
|
13
|
-
const iv = crypto.randomBytes(12);
|
|
14
|
-
|
|
15
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
16
|
-
let encrypted = cipher.update(privateKey, 'utf8', 'hex');
|
|
17
|
-
encrypted += cipher.final('hex');
|
|
18
|
-
|
|
19
|
-
const authTag = cipher.getAuthTag().toString('hex');
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
salt: salt.toString('hex'),
|
|
23
|
-
iv: iv.toString('hex'),
|
|
24
|
-
authTag,
|
|
25
|
-
encryptedData: encrypted
|
|
26
|
-
};
|
|
27
|
-
}
|
|
10
|
+
|
|
28
11
|
import { generatePrivateKey, privateKeyToAccount, generateMnemonic, mnemonicToAccount, english } from 'viem/accounts';
|
|
29
12
|
|
|
30
13
|
export async function runSetupWizard() {
|
|
@@ -286,9 +269,9 @@ Provider: ${config.llm.provider}`;
|
|
|
286
269
|
privateKey = '0x' + Buffer.from(account.getHdKey().privateKey!).toString('hex');
|
|
287
270
|
log.success('New Wallet Generated!');
|
|
288
271
|
log.info(`Address: ${account.address}`);
|
|
289
|
-
log.info(`Private Key:
|
|
272
|
+
log.info(`Private Key: [REDACTED - Saved securely to vault]`);
|
|
290
273
|
log.info(`Seed Phrase (Mnemonic): ${seedPhrase}`);
|
|
291
|
-
log.warn('IMPORTANT: Write down these 12 words
|
|
274
|
+
log.warn('IMPORTANT: Write down these 12 words NOW! This is your ONLY backup. The credentials have been securely injected into your local OS vault.');
|
|
292
275
|
}
|
|
293
276
|
}
|
|
294
277
|
|
|
@@ -356,19 +339,32 @@ Provider: ${config.llm.provider}`;
|
|
|
356
339
|
})) as string;
|
|
357
340
|
if (isCancel(telegramToken)) return process.exit(0);
|
|
358
341
|
|
|
359
|
-
if (telegramToken &&
|
|
342
|
+
if (telegramToken && telegramToken.trim() !== '') {
|
|
343
|
+
authorizedChatId = undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const activeToken = telegramToken || config.integrations?.telegram?.bot_token;
|
|
347
|
+
|
|
348
|
+
if (activeToken && !authorizedChatId) {
|
|
360
349
|
const s = spinner();
|
|
361
350
|
const pin = Math.floor(100000 + Math.random() * 900000).toString();
|
|
362
351
|
|
|
363
352
|
note(pc.cyan(`1. Open Telegram and search for your Bot.\n2. Send this exact message to your bot:\n\n /auth ${pin}\n\nWaiting for your message...`), 'Telegram Pairing Required');
|
|
364
353
|
s.start(`Waiting for /auth ${pin} on Telegram...`);
|
|
365
354
|
|
|
355
|
+
let bot: any = null;
|
|
366
356
|
try {
|
|
367
357
|
const { Telegraf } = require('telegraf');
|
|
368
|
-
|
|
358
|
+
bot = new Telegraf(activeToken);
|
|
369
359
|
let paired = false;
|
|
370
360
|
|
|
361
|
+
let failedAttempts: Record<string, number> = {};
|
|
371
362
|
bot.command('auth', (ctx: any) => {
|
|
363
|
+
const chatId = ctx.chat.id.toString();
|
|
364
|
+
if (failedAttempts[chatId] >= 5) {
|
|
365
|
+
return ctx.reply('❌ Too many failed attempts. You are locked out.');
|
|
366
|
+
}
|
|
367
|
+
|
|
372
368
|
const text = ctx.message.text.split(' ');
|
|
373
369
|
if (text[1] === pin) {
|
|
374
370
|
authorizedChatId = ctx.chat.id;
|
|
@@ -376,6 +372,7 @@ Provider: ${config.llm.provider}`;
|
|
|
376
372
|
ctx.reply('✅ Bot successfully paired with Nyxora!');
|
|
377
373
|
bot.stop();
|
|
378
374
|
} else {
|
|
375
|
+
failedAttempts[chatId] = (failedAttempts[chatId] || 0) + 1;
|
|
379
376
|
ctx.reply('❌ Invalid PIN.');
|
|
380
377
|
}
|
|
381
378
|
});
|
|
@@ -383,12 +380,25 @@ Provider: ${config.llm.provider}`;
|
|
|
383
380
|
bot.launch();
|
|
384
381
|
|
|
385
382
|
// Wait until paired
|
|
386
|
-
|
|
383
|
+
let attempts = 0;
|
|
384
|
+
while (!paired && attempts < 120) {
|
|
387
385
|
await new Promise(r => setTimeout(r, 1000));
|
|
386
|
+
attempts++;
|
|
387
|
+
}
|
|
388
|
+
if (!paired) {
|
|
389
|
+
s.stop('Timeout waiting for Telegram pairing. Setup will continue, but Telegram integration is disabled.');
|
|
390
|
+
try { bot.stop(); } catch(e) {}
|
|
391
|
+
authorizedChatId = undefined;
|
|
392
|
+
telegramToken = '';
|
|
393
|
+
} else {
|
|
394
|
+
s.stop(`Bot successfully paired with Chat ID: ${authorizedChatId}`);
|
|
388
395
|
}
|
|
389
|
-
s.stop(`Bot successfully paired with Chat ID: ${authorizedChatId}`);
|
|
390
396
|
} catch (err: any) {
|
|
391
397
|
s.stop(`Failed to start bot listener: ${err.message}. You can pair it later.`);
|
|
398
|
+
// Try to stop the bot if it was initialized before the error (L17)
|
|
399
|
+
try {
|
|
400
|
+
if (bot) bot.stop();
|
|
401
|
+
} catch(e) {}
|
|
392
402
|
}
|
|
393
403
|
}
|
|
394
404
|
}
|
|
@@ -403,11 +413,11 @@ Provider: ${config.llm.provider}`;
|
|
|
403
413
|
config.agent.default_chain = defaultChain as string;
|
|
404
414
|
|
|
405
415
|
if (!config.skills) config.skills = { web3: [], os: [] } as any;
|
|
406
|
-
config.skills
|
|
407
|
-
config.skills
|
|
416
|
+
config.skills!.web3 = activeWeb3Skills;
|
|
417
|
+
config.skills!.os = activeOsSkills;
|
|
408
418
|
|
|
409
419
|
if (!config.channels) config.channels = { active: [] } as any;
|
|
410
|
-
config.channels
|
|
420
|
+
config.channels!.active = activeChannels;
|
|
411
421
|
|
|
412
422
|
const newApiKeys: Record<string, string> = {};
|
|
413
423
|
if (apiKey) {
|
|
@@ -442,6 +452,8 @@ Provider: ${config.llm.provider}`;
|
|
|
442
452
|
|
|
443
453
|
if (authorizedChatId) {
|
|
444
454
|
config.integrations.telegram.authorized_chat_id = authorizedChatId;
|
|
455
|
+
} else if (config.integrations.telegram) {
|
|
456
|
+
delete config.integrations.telegram.authorized_chat_id;
|
|
445
457
|
}
|
|
446
458
|
|
|
447
459
|
saveConfig(config);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { formatTransactionSuccess, formatTransactionError } from './formatter';
|
|
3
|
+
import { PendingTransaction } from '../agent/transactionManager';
|
|
4
|
+
|
|
5
|
+
describe('Formatter Utilities', () => {
|
|
6
|
+
it('should format transaction success correctly for swap', () => {
|
|
7
|
+
const tx: PendingTransaction = {
|
|
8
|
+
id: 'test-1',
|
|
9
|
+
type: 'swap',
|
|
10
|
+
chainName: 'ethereum',
|
|
11
|
+
status: 'pending',
|
|
12
|
+
nonce: "0",
|
|
13
|
+
details: {
|
|
14
|
+
fromToken: 'eth',
|
|
15
|
+
toToken: 'usdc',
|
|
16
|
+
amountStr: '1'
|
|
17
|
+
},
|
|
18
|
+
createdAt: Date.now()
|
|
19
|
+
};
|
|
20
|
+
const rawResult = '{"txHash":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"}';
|
|
21
|
+
const output = formatTransactionSuccess(tx, rawResult);
|
|
22
|
+
expect(output).toContain('Network:** Ethereum');
|
|
23
|
+
expect(output).toContain('Action:** Swapped 1 ETH to USDC');
|
|
24
|
+
expect(output).toContain('Tx Hash:** `0x1234...cdef`');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should format transaction error correctly', () => {
|
|
28
|
+
const tx: PendingTransaction = {
|
|
29
|
+
id: 'test-2',
|
|
30
|
+
type: 'bridge',
|
|
31
|
+
chainName: 'base_sepolia',
|
|
32
|
+
status: 'failed',
|
|
33
|
+
nonce: "0",
|
|
34
|
+
details: {},
|
|
35
|
+
createdAt: Date.now()
|
|
36
|
+
};
|
|
37
|
+
const output = formatTransactionError(tx, 'Insufficient funds');
|
|
38
|
+
expect(output).toContain('Transaction Failed (Base Sepolia)');
|
|
39
|
+
expect(output).toContain('Error: Insufficient funds');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -1,8 +1,54 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import { getPath } from '../config/paths';
|
|
3
|
+
import { loadConfig, saveConfig } from '../config/parser';
|
|
3
4
|
|
|
4
5
|
let disabledSkillsCache: string[] | null = null;
|
|
5
6
|
|
|
7
|
+
const reverseSkillMapping: Record<string, { category: 'os' | 'web3', name: string }> = {
|
|
8
|
+
// OS Skills
|
|
9
|
+
'read_local_file': { category: 'os', name: 'readFile' },
|
|
10
|
+
'write_local_file': { category: 'os', name: 'writeFile' },
|
|
11
|
+
'edit_local_file': { category: 'os', name: 'editFile' },
|
|
12
|
+
'generate_excel_file': { category: 'os', name: 'generateExcel' },
|
|
13
|
+
'analyze_document': { category: 'os', name: 'analyzeDocument' },
|
|
14
|
+
'run_terminal_command': { category: 'os', name: 'executeShell' },
|
|
15
|
+
'browse_website': { category: 'os', name: 'browseWeb' },
|
|
16
|
+
'search_web': { category: 'os', name: 'searchWeb' },
|
|
17
|
+
'read_gmail_inbox': { category: 'os', name: 'readGmail' },
|
|
18
|
+
'list_calendar_events': { category: 'os', name: 'listCalendar' },
|
|
19
|
+
'append_row_to_sheets': { category: 'os', name: 'appendSheets' },
|
|
20
|
+
'read_google_docs': { category: 'os', name: 'readDocs' },
|
|
21
|
+
'read_google_form_responses': { category: 'os', name: 'readForms' },
|
|
22
|
+
'execute_git_command': { category: 'os', name: 'gitManager' },
|
|
23
|
+
'manage_twitter': { category: 'os', name: 'xManager' },
|
|
24
|
+
'manage_notion': { category: 'os', name: 'notionWorkspace' },
|
|
25
|
+
'transcribe_audio': { category: 'os', name: 'audioTranscribe' },
|
|
26
|
+
'summarize_text': { category: 'os', name: 'summarizeText' },
|
|
27
|
+
'update_security_policy': { category: 'os', name: 'updateSecurityPolicy' },
|
|
28
|
+
|
|
29
|
+
// Web3 Skills
|
|
30
|
+
'transfer_token': { category: 'web3', name: 'transfer' },
|
|
31
|
+
'transfer_native': { category: 'web3', name: 'transfer' },
|
|
32
|
+
'swap_token': { category: 'web3', name: 'swap' },
|
|
33
|
+
'bridge_token': { category: 'web3', name: 'bridge' },
|
|
34
|
+
'mint_nft': { category: 'web3', name: 'mintNft' },
|
|
35
|
+
'custom_tx': { category: 'web3', name: 'customTx' },
|
|
36
|
+
'check_address': { category: 'web3', name: 'checkAddress' },
|
|
37
|
+
'get_my_address': { category: 'web3', name: 'getMyAddress' },
|
|
38
|
+
'check_token_security': { category: 'web3', name: 'checkSecurity' },
|
|
39
|
+
'check_portfolio': { category: 'web3', name: 'checkPortfolio' },
|
|
40
|
+
'analyze_market': { category: 'web3', name: 'marketAnalysis' },
|
|
41
|
+
'manage_custom_tokens': { category: 'web3', name: 'manageCustomTokens' },
|
|
42
|
+
'get_price': { category: 'web3', name: 'getPrice' },
|
|
43
|
+
'supply_aave': { category: 'web3', name: 'aaveSupply' },
|
|
44
|
+
'revoke_approval': { category: 'web3', name: 'revokeApproval' },
|
|
45
|
+
'deposit_yield_vault': { category: 'web3', name: 'vaultDeposit' },
|
|
46
|
+
'provide_liquidity_v3': { category: 'web3', name: 'provideLiquidity' },
|
|
47
|
+
'get_tx_history': { category: 'web3', name: 'getTxHistory' },
|
|
48
|
+
'check_registry_status': { category: 'web3', name: 'checkRegistryStatus' },
|
|
49
|
+
'create_limit_order': { category: 'web3', name: 'createLimitOrder' }
|
|
50
|
+
};
|
|
51
|
+
|
|
6
52
|
function getDisabledSkillsFile(): string {
|
|
7
53
|
return getPath('disabled_skills.json');
|
|
8
54
|
}
|
|
@@ -36,9 +82,61 @@ export function toggleSkill(skillName: string, active: boolean): void {
|
|
|
36
82
|
}
|
|
37
83
|
disabledSkillsCache = Array.from(current);
|
|
38
84
|
fs.writeFileSync(getDisabledSkillsFile(), JSON.stringify(disabledSkillsCache, null, 2));
|
|
85
|
+
|
|
86
|
+
// Sync to config.yaml
|
|
87
|
+
const mapping = reverseSkillMapping[skillName];
|
|
88
|
+
if (mapping) {
|
|
89
|
+
const config = loadConfig();
|
|
90
|
+
if (!config.skills) config.skills = { web3: [], os: [] } as any;
|
|
91
|
+
|
|
92
|
+
const categoryArray = mapping.category === 'web3' ? config.skills!.web3 : config.skills!.os;
|
|
93
|
+
|
|
94
|
+
if (active && !categoryArray.includes(mapping.name)) {
|
|
95
|
+
categoryArray.push(mapping.name);
|
|
96
|
+
} else if (!active) {
|
|
97
|
+
const index = categoryArray.indexOf(mapping.name);
|
|
98
|
+
if (index !== -1) {
|
|
99
|
+
categoryArray.splice(index, 1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
saveConfig(config);
|
|
104
|
+
}
|
|
39
105
|
}
|
|
40
106
|
|
|
41
107
|
export function isSkillActive(skillName: string): boolean {
|
|
42
108
|
const disabled = getDisabledSkills();
|
|
43
109
|
return !disabled.includes(skillName);
|
|
44
110
|
}
|
|
111
|
+
|
|
112
|
+
export function syncAllSkillsToConfig(): void {
|
|
113
|
+
const config = loadConfig();
|
|
114
|
+
if (!config.skills) config.skills = { web3: [], os: [] } as any;
|
|
115
|
+
|
|
116
|
+
const activeWeb3 = new Set<string>();
|
|
117
|
+
const activeOs = new Set<string>();
|
|
118
|
+
|
|
119
|
+
for (const [skillName, mapping] of Object.entries(reverseSkillMapping)) {
|
|
120
|
+
if (isSkillActive(skillName)) {
|
|
121
|
+
if (mapping.category === 'web3') {
|
|
122
|
+
activeWeb3.add(mapping.name);
|
|
123
|
+
} else {
|
|
124
|
+
activeOs.add(mapping.name);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check if arrays are different to avoid unnecessary saves
|
|
130
|
+
const currentWeb3 = config.skills!.web3 || [];
|
|
131
|
+
const currentOs = config.skills!.os || [];
|
|
132
|
+
|
|
133
|
+
const web3Changed = currentWeb3.length !== activeWeb3.size || !currentWeb3.every(s => activeWeb3.has(s));
|
|
134
|
+
const osChanged = currentOs.length !== activeOs.size || !currentOs.every(s => activeOs.has(s));
|
|
135
|
+
|
|
136
|
+
if (web3Changed || osChanged) {
|
|
137
|
+
config.skills!.web3 = Array.from(activeWeb3);
|
|
138
|
+
config.skills!.os = Array.from(activeOs);
|
|
139
|
+
saveConfig(config);
|
|
140
|
+
console.log('[SkillManager] Automatically synchronized active skills to config.yaml');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
|
+
import yaml from 'yaml';
|
|
5
|
+
import { getPublicClient, ChainName } from '../web3/config';
|
|
6
|
+
import { getTokenMetadata } from '../web3/utils/tokens';
|
|
4
7
|
|
|
5
|
-
const WHITELIST_FILE_PATH = path.join(os.homedir(), '.nyxora', 'user_whitelist.
|
|
8
|
+
const WHITELIST_FILE_PATH = path.join(os.homedir(), '.nyxora', 'user_whitelist.yaml');
|
|
6
9
|
|
|
7
10
|
export interface WhitelistedToken {
|
|
8
11
|
chainName: string;
|
|
9
12
|
address: string;
|
|
13
|
+
symbol?: string;
|
|
14
|
+
decimals?: number;
|
|
10
15
|
source: 'manual' | 'explorer' | 'swap';
|
|
11
16
|
lastSeen: number;
|
|
12
17
|
}
|
|
@@ -18,70 +23,74 @@ export interface UserWhitelist {
|
|
|
18
23
|
export function getUserWhitelist(): UserWhitelist {
|
|
19
24
|
try {
|
|
20
25
|
if (!fs.existsSync(WHITELIST_FILE_PATH)) {
|
|
26
|
+
// Try migrating from JSON if YAML doesn't exist yet
|
|
27
|
+
const oldJsonPath = path.join(os.homedir(), '.nyxora', 'user_whitelist.json');
|
|
28
|
+
if (fs.existsSync(oldJsonPath)) {
|
|
29
|
+
const data = fs.readFileSync(oldJsonPath, 'utf-8');
|
|
30
|
+
const parsed = JSON.parse(data);
|
|
31
|
+
fs.writeFileSync(WHITELIST_FILE_PATH, yaml.stringify(parsed), 'utf-8');
|
|
32
|
+
// Rename old file so it doesn't get used again
|
|
33
|
+
fs.renameSync(oldJsonPath, `${oldJsonPath}.bak`);
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
21
36
|
return {};
|
|
22
37
|
}
|
|
23
38
|
const data = fs.readFileSync(WHITELIST_FILE_PATH, 'utf-8');
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// Auto-migrate legacy format
|
|
27
|
-
let migrated = false;
|
|
28
|
-
for (const addr in parsed) {
|
|
29
|
-
if (parsed[addr] && !Array.isArray(parsed[addr])) {
|
|
30
|
-
const newArray: WhitelistedToken[] = [];
|
|
31
|
-
for (const chain in parsed[addr]) {
|
|
32
|
-
const tokens = parsed[addr][chain];
|
|
33
|
-
if (Array.isArray(tokens)) {
|
|
34
|
-
for (const t of tokens) {
|
|
35
|
-
newArray.push({
|
|
36
|
-
chainName: chain,
|
|
37
|
-
address: typeof t === 'string' ? t.toLowerCase() : (t.address || '').toLowerCase(),
|
|
38
|
-
source: 'manual',
|
|
39
|
-
lastSeen: Date.now()
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
parsed[addr] = newArray;
|
|
45
|
-
migrated = true;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (migrated) {
|
|
50
|
-
fs.writeFileSync(WHITELIST_FILE_PATH, JSON.stringify(parsed, null, 2), 'utf-8');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return parsed;
|
|
39
|
+
return yaml.parse(data) || {};
|
|
54
40
|
} catch (err) {
|
|
55
|
-
console.error('[Whitelist] Error reading user_whitelist.
|
|
41
|
+
console.error('[Whitelist] Error reading user_whitelist.yaml', err);
|
|
56
42
|
return {};
|
|
57
43
|
}
|
|
58
44
|
}
|
|
59
45
|
|
|
60
|
-
export function saveTokenToWhitelist(
|
|
46
|
+
export async function saveTokenToWhitelist(
|
|
47
|
+
walletAddress: string,
|
|
48
|
+
chainName: ChainName,
|
|
49
|
+
tokenAddress: string,
|
|
50
|
+
source: 'manual' | 'explorer' | 'swap' = 'manual',
|
|
51
|
+
symbol?: string,
|
|
52
|
+
decimals?: number
|
|
53
|
+
) {
|
|
61
54
|
try {
|
|
62
55
|
const whitelist = getUserWhitelist();
|
|
63
56
|
const addr = walletAddress.toLowerCase();
|
|
64
57
|
const tokenAddr = tokenAddress.toLowerCase();
|
|
65
58
|
|
|
59
|
+
// Auto-fetch metadata if not provided
|
|
60
|
+
if (!symbol || decimals === undefined) {
|
|
61
|
+
try {
|
|
62
|
+
const client = getPublicClient(chainName);
|
|
63
|
+
const metadata = await getTokenMetadata(client, tokenAddr as `0x${string}`);
|
|
64
|
+
symbol = metadata.symbol;
|
|
65
|
+
decimals = metadata.decimals;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.warn(`[Whitelist] Could not fetch metadata for ${tokenAddr} on ${chainName}`, err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
if (!whitelist[addr]) whitelist[addr] = [];
|
|
67
72
|
|
|
68
73
|
const existingIndex = whitelist[addr].findIndex(t => t.chainName === chainName && t.address === tokenAddr);
|
|
69
74
|
|
|
70
75
|
if (existingIndex >= 0) {
|
|
71
76
|
whitelist[addr][existingIndex].lastSeen = Date.now();
|
|
77
|
+
if (symbol) whitelist[addr][existingIndex].symbol = symbol;
|
|
78
|
+
if (decimals !== undefined) whitelist[addr][existingIndex].decimals = decimals;
|
|
72
79
|
} else {
|
|
73
80
|
whitelist[addr].push({
|
|
74
81
|
chainName,
|
|
75
82
|
address: tokenAddr,
|
|
83
|
+
symbol,
|
|
84
|
+
decimals,
|
|
76
85
|
source,
|
|
77
86
|
lastSeen: Date.now()
|
|
78
87
|
});
|
|
79
|
-
console.log(`[Whitelist] Added ${tokenAddr} to ${chainName} for ${addr} via ${source}`);
|
|
88
|
+
console.log(`[Whitelist] Added ${symbol || tokenAddr} to ${chainName} for ${addr} via ${source}`);
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
fs.writeFileSync(WHITELIST_FILE_PATH,
|
|
91
|
+
fs.writeFileSync(WHITELIST_FILE_PATH, yaml.stringify(whitelist), 'utf-8');
|
|
83
92
|
} catch (err) {
|
|
84
|
-
console.error('[Whitelist] Error saving token to user_whitelist.
|
|
93
|
+
console.error('[Whitelist] Error saving token to user_whitelist.yaml', err);
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
|
|
@@ -97,11 +106,11 @@ export function removeTokenFromWhitelist(walletAddress: string, chainName: strin
|
|
|
97
106
|
whitelist[addr] = whitelist[addr].filter(t => !(t.chainName === chainName && t.address === tokenAddr));
|
|
98
107
|
|
|
99
108
|
if (whitelist[addr].length !== initialLength) {
|
|
100
|
-
fs.writeFileSync(WHITELIST_FILE_PATH,
|
|
101
|
-
console.log(`[Whitelist] Removed
|
|
109
|
+
fs.writeFileSync(WHITELIST_FILE_PATH, yaml.stringify(whitelist), 'utf-8');
|
|
110
|
+
console.log(`[Whitelist] Removed token ${tokenAddr} on ${chainName} for ${addr}`);
|
|
102
111
|
}
|
|
103
112
|
} catch (err) {
|
|
104
|
-
console.error('[Whitelist] Error removing token from user_whitelist.
|
|
113
|
+
console.error('[Whitelist] Error removing token from user_whitelist.yaml', err);
|
|
105
114
|
}
|
|
106
115
|
}
|
|
107
116
|
|