nyxora 26.6.19 → 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.
Files changed (83) hide show
  1. package/README.md +18 -1
  2. package/bin/nyxora.mjs +32 -0
  3. package/dist/packages/core/src/agent/reasoning.js +11 -1
  4. package/dist/packages/core/src/config/parser.js +121 -7
  5. package/dist/packages/core/src/gateway/chat.js +82 -0
  6. package/dist/packages/core/src/gateway/cli.js +63 -0
  7. package/dist/packages/core/src/gateway/server.js +117 -56
  8. package/dist/packages/core/src/gateway/setup.js +39 -22
  9. package/dist/packages/core/src/utils/formatter.test.js +40 -0
  10. package/dist/packages/core/src/utils/skillManager.js +91 -0
  11. package/dist/packages/core/src/utils/userWhitelistManager.js +41 -36
  12. package/dist/packages/core/src/web3/aggregator/aggregatorMainnet.js +2 -2
  13. package/dist/packages/core/src/web3/aggregator/defiRouter.js +3 -0
  14. package/dist/packages/core/src/web3/skills/bridgeToken.js +4 -0
  15. package/dist/packages/core/src/web3/skills/checkRegistryStatus.js +13 -0
  16. package/dist/packages/core/src/web3/skills/customTx.js +2 -0
  17. package/dist/packages/core/src/web3/skills/defiLending.js +2 -0
  18. package/dist/packages/core/src/web3/skills/getPrice.js +11 -6
  19. package/dist/packages/core/src/web3/skills/manageCustomTokens.js +18 -32
  20. package/dist/packages/core/src/web3/skills/marketAnalysis.js +3 -1
  21. package/dist/packages/core/src/web3/skills/mintNft.js +2 -0
  22. package/dist/packages/core/src/web3/skills/provideLiquidity.js +2 -0
  23. package/dist/packages/core/src/web3/skills/revokeApprovals.js +2 -0
  24. package/dist/packages/core/src/web3/skills/swapToken.js +4 -2
  25. package/dist/packages/core/src/web3/skills/transfer.js +2 -0
  26. package/dist/packages/core/src/web3/skills/yieldVault.js +2 -0
  27. package/dist/packages/core/src/web3/utils/tokens.js +9 -1
  28. package/package.json +2 -1
  29. package/packages/core/package.json +1 -1
  30. package/packages/core/src/agent/reasoning.ts +12 -1
  31. package/packages/core/src/config/parser.ts +119 -9
  32. package/packages/core/src/gateway/chat.ts +85 -0
  33. package/packages/core/src/gateway/cli.ts +63 -0
  34. package/packages/core/src/gateway/server.ts +132 -60
  35. package/packages/core/src/gateway/setup.ts +39 -27
  36. package/packages/core/src/utils/formatter.test.ts +41 -0
  37. package/packages/core/src/utils/skillManager.ts +98 -0
  38. package/packages/core/src/utils/userWhitelistManager.ts +48 -39
  39. package/packages/core/src/web3/aggregator/aggregatorMainnet.ts +2 -2
  40. package/packages/core/src/web3/aggregator/defiRouter.ts +4 -0
  41. package/packages/core/src/web3/skills/bridgeToken.ts +3 -0
  42. package/packages/core/src/web3/skills/checkRegistryStatus.ts +13 -0
  43. package/packages/core/src/web3/skills/customTx.ts +1 -0
  44. package/packages/core/src/web3/skills/defiLending.ts +1 -0
  45. package/packages/core/src/web3/skills/getPrice.ts +11 -6
  46. package/packages/core/src/web3/skills/manageCustomTokens.ts +18 -29
  47. package/packages/core/src/web3/skills/marketAnalysis.ts +2 -1
  48. package/packages/core/src/web3/skills/mintNft.ts +1 -0
  49. package/packages/core/src/web3/skills/provideLiquidity.ts +1 -0
  50. package/packages/core/src/web3/skills/revokeApprovals.ts +1 -0
  51. package/packages/core/src/web3/skills/swapToken.ts +3 -2
  52. package/packages/core/src/web3/skills/transfer.ts +1 -0
  53. package/packages/core/src/web3/skills/yieldVault.ts +1 -0
  54. package/packages/core/src/web3/utils/tokens.ts +9 -1
  55. package/packages/dashboard/dist/assets/{index-DnQrbB4c.css → index-CQNHWZtN.css} +1 -1
  56. package/packages/dashboard/dist/assets/index-Di9x08yk.js +16 -0
  57. package/packages/dashboard/dist/index.html +2 -2
  58. package/packages/dashboard/package.json +1 -1
  59. package/packages/mcp-server/package.json +1 -1
  60. package/packages/policy/package.json +1 -1
  61. package/packages/signer/package.json +1 -1
  62. package/dist/packages/core/src/agent/limitOrderManager.js +0 -124
  63. package/dist/packages/core/src/system/pluginManager.js +0 -91
  64. package/dist/packages/core/src/system/skills/installSkill.js +0 -52
  65. package/dist/packages/core/src/test-all-routers.js +0 -81
  66. package/dist/packages/core/src/test-router.js +0 -38
  67. package/dist/packages/core/src/web3/skills/autonomousDefi.js +0 -191
  68. package/dist/packages/core/src/web3/skills/createWallet.js +0 -34
  69. package/dist/packages/core/src/web3/skills/limitOrder.js +0 -106
  70. package/dist/packages/core/src/web3/utils/protocolRegistry.js +0 -46
  71. package/dist/tsconfig.tsbuildinfo +0 -1
  72. package/packages/core/src/__tests__/reasoning.test.ts +0 -81
  73. package/packages/core/src/__tests__/tokens.test.ts +0 -55
  74. package/packages/core/src/__tests__/web3.test.ts +0 -50
  75. package/packages/core/src/agent/reasoning.d.ts.map +0 -1
  76. package/packages/core/src/config/parser.d.ts.map +0 -1
  77. package/packages/core/src/gateway/cli.d.ts.map +0 -1
  78. package/packages/core/src/memory/logger.d.ts.map +0 -1
  79. package/packages/core/src/test-all-routers.ts +0 -59
  80. package/packages/core/src/test-router.ts +0 -49
  81. package/packages/core/src/web3/config.d.ts.map +0 -1
  82. package/packages/core/src/web3/skills/getBalance.d.ts.map +0 -1
  83. package/packages/dashboard/dist/assets/index-BAXifdMN.js +0 -16
@@ -50,8 +50,8 @@ async function fetchMainnetBestRoute(fromChain, toChain, fromToken, toToken, amo
50
50
  if (isProviderHealthy('openocean')) {
51
51
  promises.push(fetchOpenOcean(fromChain, toChain, fromToken, toToken, amountInWei, userAddress, slippageTolerance, keys.openocean_key));
52
52
  }
53
- // Provider 6: KyberSwap (Cross-chain & Same-chain)
54
- if (isProviderHealthy('kyberswap')) {
53
+ // Provider 6: KyberSwap (Same-chain ONLY)
54
+ if (!isCrossChain && isProviderHealthy('kyberswap')) {
55
55
  promises.push(fetchKyberSwap(fromChain, fromToken, toToken, amountInWei, userAddress, slippageTolerance));
56
56
  }
57
57
  // Execute all healthy providers concurrently with a 4s timeout
@@ -4,6 +4,9 @@ exports.routeTransaction = routeTransaction;
4
4
  const aggregatorMainnet_1 = require("./aggregatorMainnet");
5
5
  const aggregatorTestnet_1 = require("./aggregatorTestnet");
6
6
  async function routeTransaction(fromChain, toChain, fromToken, toToken, amountInWei, userAddress, slippageTolerance = "auto") {
7
+ if (!fromChain || !toChain) {
8
+ throw new Error("Missing source or destination chain in routing.");
9
+ }
7
10
  // Auto-correct: If one is testnet and the other is mainnet, assume they meant testnet
8
11
  if (fromChain.includes('sepolia') && !toChain.includes('sepolia')) {
9
12
  if (toChain === 'base')
@@ -11,6 +11,10 @@ const tokens_1 = require("../utils/tokens");
11
11
  const defiRouter_1 = require("../aggregator/defiRouter");
12
12
  async function prepareBridgeToken(fromChain, toChain, tokenSymbol, amountStr, mode = "auto", providerName = "auto", slippagePercent) {
13
13
  try {
14
+ if (!fromChain || !toChain)
15
+ throw new Error("Source or destination chain not provided by AI.");
16
+ if (!amountStr)
17
+ throw new Error("Bridge amount not provided by AI.");
14
18
  const userAddress = await (0, vaultClient_1.getAddress)();
15
19
  // Auto-correct: If one is testnet and the other is mainnet, assume they meant testnet
16
20
  if (fromChain.includes('sepolia') && !toChain.includes('sepolia')) {
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkRegistryStatusToolDefinition = void 0;
3
4
  exports.checkRegistryStatus = checkRegistryStatus;
4
5
  const config_1 = require("../config");
5
6
  const config_2 = require("../config");
@@ -77,3 +78,15 @@ async function checkRegistryStatus() {
77
78
  return { isActive: true, reason: 'Registry check skipped due to network error.' };
78
79
  }
79
80
  }
81
+ exports.checkRegistryStatusToolDefinition = {
82
+ type: "function",
83
+ function: {
84
+ name: "check_registry_status",
85
+ description: "Internal Security Middleware: Checks the On-Chain Kill Switch Registry before executing any transaction. If the agent is deactivated, the transaction will be strictly blocked for safety.",
86
+ parameters: {
87
+ type: "object",
88
+ properties: {},
89
+ required: []
90
+ }
91
+ }
92
+ };
@@ -8,6 +8,8 @@ const transactionManager_1 = require("../../agent/transactionManager");
8
8
  const vaultClient_1 = require("../utils/vaultClient");
9
9
  async function prepareCustomTx(chainName, toAddress, data, valueWei = "0", description = "Custom transaction") {
10
10
  try {
11
+ if (!chainName || !toAddress || !data)
12
+ throw new Error("Missing required parameters for custom transaction.");
11
13
  const tx = transactionManager_1.txManager.createPendingTransaction('custom', chainName, {
12
14
  toAddress,
13
15
  data,
@@ -32,6 +32,8 @@ const AAVE_V3_POOLS = {
32
32
  };
33
33
  async function prepareAaveSupply(chainName, tokenAddressOrSymbol, amountStr) {
34
34
  try {
35
+ if (!chainName || !tokenAddressOrSymbol || !amountStr)
36
+ throw new Error("Missing protocol/chain/token parameters for DeFi operation.");
35
37
  const publicClient = (0, config_1.getPublicClient)(chainName);
36
38
  const userAddress = await (0, config_1.getAddress)();
37
39
  const account = userAddress;
@@ -14,27 +14,32 @@ exports.getPriceToolDefinition = {
14
14
  coinId: {
15
15
  type: "string",
16
16
  description: "The CoinGecko ID of the coin (e.g., 'ethereum', 'bitcoin', 'solana')."
17
+ },
18
+ currency: {
19
+ type: "string",
20
+ description: "The fiat currency to convert to (e.g., 'usd', 'idr', 'eur', 'jpy'). Defaults to 'usd'."
17
21
  }
18
22
  },
19
23
  required: ["coinId"]
20
24
  }
21
25
  }
22
26
  };
23
- async function getPrice(coinId) {
27
+ async function getPrice(coinId, currency = 'usd') {
24
28
  try {
25
- const url = `https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=usd&include_24hr_change=true`;
29
+ const cur = currency.toLowerCase();
30
+ const url = `https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=${cur}&include_24hr_change=true`;
26
31
  const data = await (0, httpClient_1.safeFetchJson)(url);
27
32
  if (!data[coinId]) {
28
33
  return `❌ Could not find price data for **${coinId.toUpperCase()}**. Please verify the coin name.`;
29
34
  }
30
- const price = data[coinId].usd;
31
- const change24h = data[coinId].usd_24h_change;
35
+ const price = data[coinId][cur];
36
+ const change24h = data[coinId][`${cur}_24h_change`];
32
37
  const isPositive = change24h >= 0;
33
38
  const arrow = isPositive ? '📈 🟩 +' : '📉 🟥 ';
34
- const priceFormatted = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price);
39
+ const priceFormatted = new Intl.NumberFormat(cur === 'idr' ? 'id-ID' : 'en-US', { style: 'currency', currency: cur.toUpperCase() }).format(price);
35
40
  const changeFormatted = change24h ? change24h.toFixed(2) : '0.00';
36
41
  return `💰 **Price Update for ${coinId.toUpperCase()}**\n\n` +
37
- `- **Price (USD)**: \`${priceFormatted}\`\n` +
42
+ `- **Price (${cur.toUpperCase()})**: \`${priceFormatted}\`\n` +
38
43
  `- **24h Change**: ${arrow}${changeFormatted}%`;
39
44
  }
40
45
  catch (error) {
@@ -1,18 +1,15 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.manageCustomTokensDefinition = void 0;
7
4
  exports.executeManageCustomTokens = executeManageCustomTokens;
8
- const fs_1 = __importDefault(require("fs"));
9
- const paths_1 = require("../../config/paths");
10
5
  const config_1 = require("../config");
6
+ const userWhitelistManager_1 = require("../../utils/userWhitelistManager");
7
+ const vaultClient_1 = require("../utils/vaultClient");
11
8
  exports.manageCustomTokensDefinition = {
12
9
  type: 'function',
13
10
  function: {
14
11
  name: 'manage_custom_tokens',
15
- description: 'Add or remove a custom ERC-20 token (like a memecoin or specific token) from the user\'s local portfolio watcher.',
12
+ description: 'Add or remove a custom ERC-20 token (like a memecoin or specific token) from the user\'s local portfolio watcher and swap whitelist.',
16
13
  parameters: {
17
14
  type: 'object',
18
15
  properties: {
@@ -32,50 +29,39 @@ exports.manageCustomTokensDefinition = {
32
29
  },
33
30
  contract_address: {
34
31
  type: 'string',
35
- description: 'The smart contract address of the token. Only required for "add" action.'
32
+ description: 'The smart contract address of the token. Required for "add" action or "remove" action.'
36
33
  }
37
34
  },
38
- required: ['action', 'chain_name', 'symbol']
35
+ required: ['action', 'chain_name', 'symbol', 'contract_address']
39
36
  }
40
37
  }
41
38
  };
42
39
  async function executeManageCustomTokens(args) {
43
40
  const { action, chain_name, symbol, contract_address } = args;
44
- if (!config_1.SUPPORTED_CHAIN_NAMES.includes(chain_name)) {
45
- return `Error: Unsupported chain ${chain_name}.`;
41
+ if (!chain_name) {
42
+ throw new Error("Chain name is required.");
46
43
  }
47
- const customTokensPath = (0, paths_1.getPath)('custom_tokens.json');
48
- let customTokens = {};
49
- if (fs_1.default.existsSync(customTokensPath)) {
50
- try {
51
- const data = fs_1.default.readFileSync(customTokensPath, 'utf8');
52
- customTokens = JSON.parse(data);
53
- }
54
- catch (e) {
55
- console.error('Error parsing custom_tokens.json', e);
56
- }
44
+ if (!symbol) {
45
+ throw new Error("Token symbol is required.");
57
46
  }
58
- if (!customTokens[chain_name]) {
59
- customTokens[chain_name] = {};
47
+ if (!config_1.SUPPORTED_CHAIN_NAMES.includes(chain_name)) {
48
+ return `Error: Unsupported chain ${chain_name}.`;
60
49
  }
61
50
  const upperSymbol = symbol.toUpperCase();
51
+ const userAddress = await (0, vaultClient_1.getAddress)();
62
52
  if (action === 'add') {
63
53
  if (!contract_address || !contract_address.startsWith('0x')) {
64
54
  return `Error: Invalid or missing contract_address.`;
65
55
  }
66
- customTokens[chain_name][upperSymbol] = contract_address;
67
- fs_1.default.writeFileSync(customTokensPath, JSON.stringify(customTokens, null, 2));
68
- return `Successfully added custom token ${upperSymbol} to the ${chain_name} portfolio tracker.`;
56
+ await (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chain_name, contract_address, 'manual', upperSymbol);
57
+ return `Successfully added custom token ${upperSymbol} to the ${chain_name} portfolio tracker and swap whitelist.`;
69
58
  }
70
59
  else if (action === 'remove') {
71
- if (customTokens[chain_name][upperSymbol]) {
72
- delete customTokens[chain_name][upperSymbol];
73
- fs_1.default.writeFileSync(customTokensPath, JSON.stringify(customTokens, null, 2));
74
- return `Successfully removed custom token ${upperSymbol} from the ${chain_name} portfolio tracker.`;
75
- }
76
- else {
77
- return `Warning: Token ${upperSymbol} was not found in the custom portfolio tracker for ${chain_name}.`;
60
+ if (!contract_address || !contract_address.startsWith('0x')) {
61
+ return `Error: Invalid or missing contract_address for removal.`;
78
62
  }
63
+ (0, userWhitelistManager_1.removeTokenFromWhitelist)(userAddress, chain_name, contract_address);
64
+ return `Successfully removed custom token ${upperSymbol} from the ${chain_name} whitelist.`;
79
65
  }
80
66
  return `Error: Invalid action.`;
81
67
  }
@@ -56,7 +56,7 @@ async function fetchDexData(query, isCa, chainName) {
56
56
  if (data && data.pairs && data.pairs.length > 0) {
57
57
  let pairs = data.pairs;
58
58
  if (chainName) {
59
- pairs = pairs.filter((p) => p.chainId.toLowerCase() === chainName.toLowerCase());
59
+ pairs = pairs.filter((p) => p.chainId?.toLowerCase() === chainName?.toLowerCase());
60
60
  if (pairs.length === 0)
61
61
  pairs = data.pairs;
62
62
  }
@@ -95,6 +95,8 @@ async function fetchCexMomentum(symbol, currentP) {
95
95
  }
96
96
  async function analyzeMarket(chainName, tokenAddressOrSymbol) {
97
97
  try {
98
+ if (!tokenAddressOrSymbol)
99
+ throw new Error("Token symbol is invalid.");
98
100
  const cleanInput = tokenAddressOrSymbol.replace('$', '').toLowerCase();
99
101
  const isAddress = cleanInput.startsWith('0x') && cleanInput.length === 42;
100
102
  let officialSymbol = cleanInput.toUpperCase();
@@ -8,6 +8,8 @@ const config_1 = require("../config");
8
8
  const transactionManager_1 = require("../../agent/transactionManager");
9
9
  async function prepareMintNft(chainName, contractAddress, functionSignature, argsStr, valueEth = "0") {
10
10
  try {
11
+ if (!chainName || !contractAddress || !functionSignature)
12
+ throw new Error("Missing required parameters to mint NFT.");
11
13
  const publicClient = (0, config_1.getPublicClient)(chainName);
12
14
  const userAddress = await (0, config_1.getAddress)();
13
15
  const account = userAddress;
@@ -52,6 +52,8 @@ const POSITION_MANAGERS = {
52
52
  const parser_1 = require("../../config/parser");
53
53
  async function prepareProvideLiquidity(chainName, token0AddressOrSymbol, token1AddressOrSymbol, amount0Str, amount1Str, feeTier, tickLower, tickUpper, slippagePercent) {
54
54
  try {
55
+ if (!chainName || !token0AddressOrSymbol || !token1AddressOrSymbol || !amount0Str || !amount1Str)
56
+ throw new Error("Missing protocol/chain/token parameters for DeFi operation.");
55
57
  let actualSlippage = slippagePercent;
56
58
  if (actualSlippage === undefined || actualSlippage === null || actualSlippage === "auto") {
57
59
  try {
@@ -8,6 +8,8 @@ const transactionManager_1 = require("../../agent/transactionManager");
8
8
  const tokens_1 = require("../utils/tokens");
9
9
  async function prepareRevokeApproval(chainName, tokenAddressOrSymbol, spenderAddress) {
10
10
  try {
11
+ if (!chainName || !tokenAddressOrSymbol || !spenderAddress)
12
+ throw new Error("Missing required parameters for revoking approval.");
11
13
  const publicClient = (0, config_1.getPublicClient)(chainName);
12
14
  const userAddress = await (0, config_1.getAddress)();
13
15
  const account = userAddress;
@@ -12,15 +12,17 @@ const userWhitelistManager_1 = require("../../utils/userWhitelistManager");
12
12
  const defiRouter_1 = require("../aggregator/defiRouter");
13
13
  async function prepareSwapToken(chainName, fromToken, toToken, amountStr, mode = "auto", providerName = "auto", slippagePercent) {
14
14
  try {
15
+ if (!chainName || !fromToken || !toToken || !amountStr)
16
+ throw new Error("Missing required parameters for swap (chain, tokens, or amount).");
15
17
  const userAddress = await (0, vaultClient_1.getAddress)();
16
18
  const fromTokenAddress = (0, tokens_1.resolveToken)(fromToken, chainName);
17
19
  const toTokenAddress = (0, tokens_1.resolveToken)(toToken, chainName);
18
20
  const isNativeIn = fromTokenAddress === "0x0000000000000000000000000000000000000000";
19
21
  // Auto-save to Degen Whitelist
20
22
  if (!isNativeIn)
21
- (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, fromTokenAddress, 'swap');
23
+ await (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, fromTokenAddress, 'swap');
22
24
  if (toTokenAddress !== "0x0000000000000000000000000000000000000000") {
23
- (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, toTokenAddress, 'swap');
25
+ await (0, userWhitelistManager_1.saveTokenToWhitelist)(userAddress, chainName, toTokenAddress, 'swap');
24
26
  }
25
27
  // Default to 18 decimals for formatting input, though Aggregator handles this if we pass raw
26
28
  const amountWei = (0, viem_1.parseUnits)(amountStr, 18).toString();
@@ -10,6 +10,8 @@ const tokens_1 = require("../utils/tokens");
10
10
  const vaultClient_1 = require("../utils/vaultClient");
11
11
  async function prepareTransfer(chainName, toAddress, amountStr, token) {
12
12
  try {
13
+ if (!chainName || !toAddress || !amountStr)
14
+ throw new Error("Missing required parameters for transfer (chain, recipient, or amount).");
13
15
  const publicClient = (0, config_1.getPublicClient)(chainName);
14
16
  const userAddress = await (0, config_1.getAddress)();
15
17
  const account = userAddress;
@@ -25,6 +25,8 @@ const VAULT_ABI = [
25
25
  ];
26
26
  async function prepareVaultDeposit(chainName, protocol, vaultAddress, tokenAddressOrSymbol, amountStr) {
27
27
  try {
28
+ if (!chainName || !vaultAddress || !tokenAddressOrSymbol || !amountStr)
29
+ throw new Error("Missing protocol/chain/token parameters for DeFi operation.");
28
30
  const publicClient = (0, config_1.getPublicClient)(chainName);
29
31
  const userAddress = await (0, config_1.getAddress)();
30
32
  const account = userAddress;
@@ -5,6 +5,7 @@ exports.resolveToken = resolveToken;
5
5
  exports.getTokenMetadata = getTokenMetadata;
6
6
  exports.invalidateTokenCache = invalidateTokenCache;
7
7
  exports.preloadTokenMetadataAndAllowance = preloadTokenMetadataAndAllowance;
8
+ const userWhitelistManager_1 = require("../../utils/userWhitelistManager");
8
9
  exports.ERC20_ABI = [
9
10
  {
10
11
  type: 'function',
@@ -76,7 +77,7 @@ exports.TOKEN_MAP = {
76
77
  ETH: "0x0000000000000000000000000000000000000000",
77
78
  WETH: "0x4200000000000000000000000000000000000006",
78
79
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
79
- USDT: "0xf55BEC9cbd4732f1F4143f647652e924540d9d64"
80
+ USDT: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2"
80
81
  },
81
82
  optimism: {
82
83
  ETH: "0x0000000000000000000000000000000000000000",
@@ -126,6 +127,13 @@ function resolveToken(tokenSymbolOrAddress, chainName) {
126
127
  if (chainTokens && chainTokens[symbolUpper]) {
127
128
  return chainTokens[symbolUpper];
128
129
  }
130
+ const whitelistData = (0, userWhitelistManager_1.getUserWhitelist)();
131
+ for (const addr in whitelistData) {
132
+ const tokens = whitelistData[addr];
133
+ const match = tokens.find(t => t.chainName === chainName && t.symbol === symbolUpper);
134
+ if (match)
135
+ return match.address;
136
+ }
129
137
  throw new Error(`Token "${tokenSymbolOrAddress}" pada chain ${chainName} tidak ditemukan. Silakan gunakan alamat kontrak langsung (0x...).`);
130
138
  }
131
139
  // Bounded LRU Cache to prevent RAM bloat from fake tokens (OOM protection)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "26.6.19",
3
+ "version": "26.6.21",
4
4
  "description": "Your Personal Web3 Assistant",
5
5
  "keywords": [
6
6
  "web3",
@@ -42,6 +42,7 @@
42
42
  "stop": "node ./bin/nyxora.mjs stop",
43
43
  "restart": "node ./bin/nyxora.mjs restart",
44
44
  "dashboard": "node ./bin/nyxora.mjs dashboard",
45
+ "chat": "node ./bin/nyxora.mjs chat",
45
46
  "setup": "node ./bin/nyxora.mjs setup",
46
47
  "set-key": "node ./bin/nyxora.mjs set-key",
47
48
  "doctor": "node ./bin/nyxora.mjs doctor",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "26.6.19",
3
+ "version": "26.6.21",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -370,6 +370,17 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
370
370
  continue;
371
371
  }
372
372
 
373
+ if (!isSkillActive(toolName)) {
374
+ console.warn(pc.red(`[Security] Blocked illegal execution of disabled skill: ${toolName}`));
375
+ result = `[System Error] Access denied: Skill '${toolName}' is currently disabled by the user.`;
376
+ logger.addEntry({
377
+ role: "tool",
378
+ tool_call_id: toolCall.id,
379
+ content: result
380
+ }, sessionId);
381
+ continue;
382
+ }
383
+
373
384
  try {
374
385
  switch (toolName) {
375
386
  case 'get_balance': {
@@ -382,7 +393,7 @@ export async function processUserInput(input: string, role: 'user' | 'system' =
382
393
  break;
383
394
  }
384
395
  case 'get_price': {
385
- result = await getPrice(args.coinId);
396
+ result = await getPrice(args.coinId, args.currency);
386
397
  break;
387
398
  }
388
399
  case 'swap_token': {
@@ -1,7 +1,70 @@
1
1
  import fs from 'fs';
2
2
  import yaml from 'yaml';
3
3
  import path from 'path';
4
+ import os from 'os';
5
+ import crypto from 'crypto';
4
6
  import { getPath } from './paths';
7
+ let cachedEncryptionKey: Buffer | null = null;
8
+ function getEncryptionKeySync(): Buffer {
9
+ if (cachedEncryptionKey) return cachedEncryptionKey;
10
+ let masterKeyRaw = process.env.NYXORA_MASTER_KEY;
11
+ if (!masterKeyRaw) {
12
+ try {
13
+ const { execSync } = require('child_process');
14
+ const output = execSync(`node -e "require('@napi-rs/keyring').Entry.prototype.getPassword.call(new (require('@napi-rs/keyring').Entry)('nyxora', 'config_master')).then(console.log).catch(()=>console.log(''))"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
15
+ const pk = output?.trim();
16
+ if (pk) masterKeyRaw = pk;
17
+ } catch (e) {
18
+ // Ignore
19
+ }
20
+ }
21
+ if (!masterKeyRaw) {
22
+ try {
23
+ const masterKeyPath = path.join(os.homedir(), '.nyxora', 'auth', 'master.key');
24
+ if (fs.existsSync(masterKeyPath)) {
25
+ masterKeyRaw = fs.readFileSync(masterKeyPath, 'utf8').trim();
26
+ } else {
27
+ masterKeyRaw = crypto.randomBytes(32).toString('hex');
28
+ try { fs.writeFileSync(masterKeyPath, masterKeyRaw, { mode: 0o600 }); } catch (e) {}
29
+ }
30
+ } catch (e) {
31
+ masterKeyRaw = 'default_fallback_nyxora_key';
32
+ }
33
+ }
34
+ cachedEncryptionKey = crypto.createHash('sha256').update(masterKeyRaw).digest();
35
+ return cachedEncryptionKey;
36
+ }
37
+
38
+ export function encryptDataSync(text: string): string {
39
+ if (!text || text.startsWith('ENC:')) return text;
40
+ const iv = crypto.randomBytes(12);
41
+ const cipher = crypto.createCipheriv('aes-256-gcm', getEncryptionKeySync(), iv);
42
+ let encrypted = cipher.update(text, 'utf8', 'hex');
43
+ encrypted += cipher.final('hex');
44
+ const authTag = cipher.getAuthTag().toString('hex');
45
+ return `ENC:${iv.toString('hex')}:${authTag}:${encrypted}`;
46
+ }
47
+
48
+ export function decryptDataSync(encryptedText: string): string {
49
+ if (!encryptedText) return encryptedText;
50
+ if (!encryptedText.startsWith('ENC:')) {
51
+ return encryptedText;
52
+ }
53
+ try {
54
+ const parts = encryptedText.split(':');
55
+ if (parts.length < 4) return encryptedText;
56
+ const iv = Buffer.from(parts[1], 'hex');
57
+ const authTag = Buffer.from(parts[2], 'hex');
58
+ const encrypted = parts.slice(3).join(':');
59
+ const decipher = crypto.createDecipheriv('aes-256-gcm', getEncryptionKeySync(), iv);
60
+ decipher.setAuthTag(authTag);
61
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
62
+ decrypted += decipher.final('utf8');
63
+ return decrypted;
64
+ } catch (e) {
65
+ return encryptedText; // return raw if decryption fails
66
+ }
67
+ }
5
68
 
6
69
  export function loadRpcConfig(): Record<string, string | string[]> {
7
70
  const rpcPath = getPath('rpc_key.yaml');
@@ -26,7 +89,7 @@ export function saveRpcConfig(rpcUrls: Record<string, string | string[]>): void
26
89
 
27
90
  export async function loadApiKeys(): Promise<Record<string, string>> {
28
91
  const config = loadConfig();
29
- return config.credentials || {};
92
+ return (config.credentials as Record<string, string>) || {};
30
93
  }
31
94
 
32
95
  export async function saveApiKeys(newKeys: Record<string, string>): Promise<void> {
@@ -96,8 +159,27 @@ export function loadConfig(): NyxoraConfig {
96
159
  const file = fs.readFileSync(configPath, 'utf8');
97
160
  const parsed = yaml.parse(file) as Partial<NyxoraConfig>;
98
161
 
99
- // Auto-migration logic: move llm.credentials to root credentials
100
162
  let needsSave = false;
163
+
164
+ // Decrypt credentials
165
+ if (parsed.credentials) {
166
+ for (const key in parsed.credentials) {
167
+ if (parsed.credentials[key]) {
168
+ if (parsed.credentials[key]!.startsWith('ENC:')) needsSave = true;
169
+ parsed.credentials[key] = decryptDataSync(parsed.credentials[key]!);
170
+ }
171
+ }
172
+ }
173
+ if (parsed.integrations?.telegram?.bot_token) {
174
+ if (parsed.integrations.telegram.bot_token.startsWith('ENC:')) needsSave = true;
175
+ parsed.integrations.telegram.bot_token = decryptDataSync(parsed.integrations.telegram.bot_token);
176
+ }
177
+ if (parsed.web3?.explorer_api_key) {
178
+ if (parsed.web3.explorer_api_key.startsWith('ENC:')) needsSave = true;
179
+ parsed.web3.explorer_api_key = decryptDataSync(parsed.web3.explorer_api_key);
180
+ }
181
+
182
+ // Auto-migration logic: move llm.credentials to root credentials
101
183
  if (parsed.llm && (parsed.llm as any).credentials) {
102
184
  if (!parsed.credentials) {
103
185
  parsed.credentials = {};
@@ -121,7 +203,7 @@ export function loadConfig(): NyxoraConfig {
121
203
  // Auto-migration logic: move permissions to policy.yaml
122
204
  const policyPath = getPath('policy.yaml');
123
205
  if (!fs.existsSync(policyPath)) {
124
- const defaultPolicy = `max_usd_per_tx: ${(parsed as any).permissions?.web3?.max_usd_per_tx || 999999999}\nwhitelist_only: false\nrequire_approval: true\n`;
206
+ const defaultPolicy = `auto_approve_limit_usd: 0\ncustom_llm_rules: []\n`;
125
207
  fs.writeFileSync(policyPath, defaultPolicy, 'utf8');
126
208
  console.log('[Config] Created default policy.yaml.');
127
209
  }
@@ -132,9 +214,8 @@ export function loadConfig(): NyxoraConfig {
132
214
 
133
215
  if (needsSave) {
134
216
  try {
135
- const yamlStr = yaml.stringify(parsed);
136
- fs.writeFileSync(configPath, yamlStr, 'utf8');
137
- console.log('[Config] Auto-migrated llm.credentials to root credentials.');
217
+ saveConfig(parsed as NyxoraConfig);
218
+ console.log('[Config] Auto-migrated config file safely.');
138
219
  } catch (e) {
139
220
  console.error('[Config] Failed to auto-migrate config file', e);
140
221
  }
@@ -142,7 +223,8 @@ export function loadConfig(): NyxoraConfig {
142
223
 
143
224
 
144
225
 
145
- return {
226
+
227
+ const validatedConfig: NyxoraConfig = {
146
228
  agent: parsed.agent || { name: 'Nyxora-Default', description: 'Your Personal Web3 Assistant.', default_chain: 'base', default_router: 'auto', default_slippage: 'auto' },
147
229
  llm: parsed.llm || {
148
230
  provider: 'openai',
@@ -159,11 +241,16 @@ export function loadConfig(): NyxoraConfig {
159
241
  web3: { ...parsed.web3, rpc_urls: rpcUrls },
160
242
  integrations: parsed.integrations || {
161
243
  telegram: { enabled: false }
162
- }
163
- } as NyxoraConfig;
244
+ },
245
+ skills: parsed.skills
246
+ };
247
+
248
+ return validatedConfig;
164
249
  } catch (error: any) {
165
250
  if (error.code === 'ENOENT') {
166
251
  console.log('[Config] No config.yaml found. Using default configuration.');
252
+ } else if (error.name === 'YAMLError' || error.message?.includes('YAML')) {
253
+ console.warn('[Parser] YAML Parse Error:', error.message);
167
254
  } else {
168
255
  console.error('[Config] Failed to load config.yaml. Using default configuration.', error);
169
256
  }
@@ -202,9 +289,32 @@ export function saveConfig(newConfig: NyxoraConfig): void {
202
289
  if (configToSave.web3 && configToSave.web3.rpc_urls) {
203
290
  delete configToSave.web3.rpc_urls;
204
291
  }
292
+
293
+ // Keys are no longer encrypted before saving. They are stored in plain text.
294
+
205
295
  const yamlStr = yaml.stringify(configToSave);
206
296
  fs.writeFileSync(configPath, yamlStr, 'utf8');
207
297
  } catch (error) {
208
298
  console.error('Failed to save config.yaml', error);
209
299
  }
210
300
  }
301
+
302
+ export interface PolicyConfig {
303
+ max_usd_per_tx?: number;
304
+ whitelist_only?: boolean;
305
+ require_approval?: boolean;
306
+ auto_approve_limit_usd?: number;
307
+ custom_llm_rules?: string[];
308
+ }
309
+
310
+ export function loadPolicyConfig(): PolicyConfig {
311
+ const policyPath = getPath('policy.yaml');
312
+ if (fs.existsSync(policyPath)) {
313
+ try {
314
+ return yaml.parse(fs.readFileSync(policyPath, 'utf8')) || {};
315
+ } catch (e) {
316
+ console.error('[Config] Failed to parse policy.yaml', e);
317
+ }
318
+ }
319
+ return {};
320
+ }