nyxora 1.7.2 → 1.7.3

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/CHANGELOG.md CHANGED
@@ -5,11 +5,27 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [1.7.2] - Unreleased
8
+ ## [1.7.3] - Unreleased
9
+
10
+ ### Web3 Routing & Integrations
11
+ - **Multi-Router Selection (DeFi)**: Added a dynamic Router dropdown to the Dashboard UI, allowing users to forcefully route transactions through specific protocols natively. Supported routers include `1inch`, `CowSwap (MEV-Protected)`, `Li.Fi`, `Relay`, `Uniswap V2`, `Uniswap V3`, and `PancakeSwap`. This integration heavily relies on deep aggregator proxying (bypassing the need for complex V2/V3 ABI calldata overhead) to ensure 100% smooth, anti-fail execution without requiring additional API keys.
12
+
13
+ ### Security & Polish
14
+ - **Dashboard:** Redacted the sensitive Nyxora Auth Token from appearing in the Gateway Logs component on the frontend to prevent visual leakage during screen sharing or screenshots.
15
+
16
+ ## [1.7.2] - 2026-06-04
9
17
 
10
18
  ### UI/UX Enhancements
11
19
  - **Google Workspace Logout**: Users can now easily disconnect their Google Workspace accounts directly from the Dashboard (OS Skills tab). This triggers a secure token purge from both the OS Keyring and local storage, ensuring privacy and seamless account switching.
12
20
 
21
+ ### Cloud-Native Deployment
22
+ - **Official Docker & GHCR Support**: Added comprehensive Docker containerization support (`Dockerfile`) and automated publishing pipelines to GitHub Container Registry (GHCR).
23
+ - **Docker Documentation**: Added a dedicated `DOCKER.md` guide explaining how to pull, interactively configure, and run the Nyxora daemon via Docker with isolated Volume storage (`/root/.nyxora`).
24
+
25
+ ### Documentation & Compliance
26
+ - **Legal Infrastructure**: Added standard `Privacy Policy` (`privacy.md`) and `Terms of Service` (`terms.md`) to the VitePress documentation to prepare for official Google OAuth App Verification.
27
+ - **Enterprise Roadmap Evolution**: Updated the documentation roadmap to reflect our "Nyxora Next Update" vision, outlining future plans for a Rust-Native Signer, Idempotent Policy Engine, Multi-VM Architecture, and Google App Verification.
28
+
13
29
  ## [1.7.1]
14
30
 
15
31
  ### CLI Enhancements
package/README.md CHANGED
@@ -40,6 +40,7 @@ It operates under an institutional-grade **Cryptographically Bound Human-in-the-
40
40
  * **Zero-Click Multi-Session**: Instantly create isolated chat sessions with smart auto-naming triggered by your first prompt, exactly like ChatGPT.
41
41
  * **Dynamic Trending Tokens**: Live top 5 crypto assets feed directly injected into the dashboard, completely clickable for instant AI market analysis.
42
42
  * **Premium Utility-Centric UI**: A sleek, dark-themed dashboard built for high readability and professional Web3 execution, featuring Pseudo-Generative UI widgets (`<BalanceWidget>`, `<MarketWidget>`, `<SwapWidget>`).
43
+ * **Context Overrides Defaults (NLP Intelligence)**: The Dashboard configuration (default chain & router) acts only as a safety net. If you issue an explicit command via Telegram (e.g., *"Swap 10 USDC to USDT on Arbitrum using Li.Fi"*), the NLP engine dynamically bypasses the default settings and executes exactly what you asked for, ensuring maximum flexibility.
43
44
  * **Deep Personalization**: Feed the agent custom rules via `user.md` and define its core persona via `IDENTITY.md`.
44
45
 
45
46
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "license": "MIT",
5
5
  "workspaces": [
6
6
  "packages/*"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora-agent-core",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -39,7 +39,9 @@ export async function saveApiKeys(newKeys: Record<string, string>): Promise<void
39
39
  export interface NyxoraConfig {
40
40
  agent: {
41
41
  name: string;
42
+ description: string;
42
43
  default_chain: string;
44
+ default_router?: string;
43
45
  };
44
46
  llm: {
45
47
  provider: 'openai' | 'anthropic' | 'ollama' | 'gemini' | 'openrouter';
@@ -136,7 +138,7 @@ export function loadConfig(): NyxoraConfig {
136
138
 
137
139
  // Merge with defaults
138
140
  return {
139
- agent: parsed.agent || { name: 'Nyxora-Default', default_chain: 'base' },
141
+ agent: parsed.agent || { name: 'Nyxora-Default', description: 'An autonomous agent running on your local machine.', default_chain: 'base', default_router: 'auto' },
140
142
  llm: parsed.llm || {
141
143
  provider: 'openai',
142
144
  model: 'gpt-4o-mini',
@@ -165,7 +167,12 @@ export function loadConfig(): NyxoraConfig {
165
167
  console.error('[Config] Failed to load config.yaml. Using default configuration.', error);
166
168
  }
167
169
  return {
168
- agent: { name: 'Nyxora-Default', default_chain: 'base' },
170
+ agent: {
171
+ name: "Nyxora-Default",
172
+ description: "An autonomous agent running on your local machine.",
173
+ default_chain: "ethereum",
174
+ default_router: "auto"
175
+ },
169
176
  llm: {
170
177
  provider: 'openai',
171
178
  model: 'gpt-4o-mini',
@@ -3,7 +3,8 @@ import { getPublicClient, getAddress, ChainName, SUPPORTED_CHAIN_NAMES } from '.
3
3
  import { txManager } from '../../agent/transactionManager';
4
4
  import { resolveToken, ERC20_ABI } from '../utils/tokens';
5
5
  import { saveTokenToWhitelist } from '../../utils/userWhitelistManager';
6
- import crypto from 'crypto';
6
+ import { loadConfig } from '../../config/parser';
7
+ import * as crypto from 'crypto';
7
8
 
8
9
  const CHAIN_IDS: Record<string, number> = {
9
10
  ethereum: 1,
@@ -15,7 +16,7 @@ const CHAIN_IDS: Record<string, number> = {
15
16
  polygon: 137,
16
17
  };
17
18
 
18
- async function getLifiQuote(fromChainId: number, toChainId: number, fromToken: string, toToken: string, amountWei: string, userAddress: string, slippage: number) {
19
+ async function getLifiQuote(fromChainId: number, toChainId: number, fromToken: string, toToken: string, amountWei: string, userAddress: string, slippage: number, providerName?: string) {
19
20
  const url = new URL('https://li.quest/v1/quote');
20
21
  url.searchParams.append('fromChain', fromChainId.toString());
21
22
  url.searchParams.append('toChain', toChainId.toString());
@@ -24,6 +25,21 @@ async function getLifiQuote(fromChainId: number, toChainId: number, fromToken: s
24
25
  url.searchParams.append('fromAmount', amountWei);
25
26
  url.searchParams.append('fromAddress', userAddress);
26
27
  url.searchParams.append('slippage', slippage.toString());
28
+
29
+ // Specific Exchange forcing (Native-feel integration via Aggregator constraint)
30
+ if (providerName && providerName !== 'lifi' && providerName !== 'auto') {
31
+ // Map our internal names to Li.Fi exchange names
32
+ const exchangeMap: Record<string, string> = {
33
+ 'uniswap_v2': 'uniswap_v2',
34
+ 'uniswap_v3': 'uniswap_v3',
35
+ 'pancakeswap': 'pancakeswap',
36
+ '1inch': 'oneinch',
37
+ 'cowswap': 'cowswap'
38
+ };
39
+ if (exchangeMap[providerName]) {
40
+ url.searchParams.append('allowExchanges', exchangeMap[providerName]);
41
+ }
42
+ }
27
43
 
28
44
  const res = await fetch(url.toString());
29
45
  if (!res.ok) {
@@ -65,7 +81,7 @@ export async function prepareSwapToken(
65
81
  toToken: string,
66
82
  amountStr: string,
67
83
  mode: "auto" | "manual" = "auto",
68
- providerName: "lifi" | "relay" = "lifi",
84
+ providerName: "auto" | "lifi" | "relay" | "uniswap_v2" | "uniswap_v3" | "pancakeswap" | "1inch" | "cowswap" = "auto",
69
85
  slippagePercent: number = 0.5
70
86
  ): Promise<string> {
71
87
  try {
@@ -101,7 +117,17 @@ export async function prepareSwapToken(
101
117
  let approvalAddress: string | null = null;
102
118
  let expectedOutputStr = "";
103
119
 
104
- let actualProvider = mode === "auto" ? "lifi" : providerName;
120
+ // If auto, read from global configuration set by Dashboard UI
121
+ let actualProvider = mode === "auto" ? "auto" : providerName;
122
+ if (actualProvider === "auto") {
123
+ try {
124
+ const config = loadConfig();
125
+ actualProvider = (config.agent.default_router as any) || "lifi";
126
+ if (actualProvider === "auto") actualProvider = "lifi"; // strict fallback
127
+ } catch (e) {
128
+ actualProvider = "lifi";
129
+ }
130
+ }
105
131
  const isTestnet = chainId === 11155111;
106
132
 
107
133
  // --- SEPOLIA TESTNET MOCK ---
@@ -121,14 +147,7 @@ export async function prepareSwapToken(
121
147
  }
122
148
  // --- END MOCK ---
123
149
 
124
- if (actualProvider === "lifi") {
125
- const quote = await getLifiQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, userAddress, slippagePercent / 100);
126
- txRequest = quote.transactionRequest;
127
- approvalAddress = quote.estimate.approvalAddress;
128
-
129
- const toDecimals = quote.action.toToken.decimals;
130
- expectedOutputStr = formatUnits(BigInt(quote.estimate.toAmount), toDecimals);
131
- } else if (actualProvider === "relay") {
150
+ if (actualProvider === "relay") {
132
151
  const relayQuote = await getRelayQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, userAddress);
133
152
  if (!relayQuote.steps || relayQuote.steps.length === 0) throw new Error("No route found by Relay.");
134
153
 
@@ -144,6 +163,15 @@ export async function prepareSwapToken(
144
163
  }
145
164
 
146
165
  expectedOutputStr = relayQuote.details?.currencyOut?.amountFormatted || "Unknown";
166
+ } else {
167
+ // Use Li.Fi for lifi, 1inch, cowswap, uniswap_v2, uniswap_v3, pancakeswap
168
+ // We mapped the allowExchanges inside getLifiQuote
169
+ const quote = await getLifiQuote(chainId, chainId, fromTokenAddress, toTokenAddress, amountWei, userAddress, slippagePercent / 100, actualProvider);
170
+ txRequest = quote.transactionRequest;
171
+ approvalAddress = quote.estimate.approvalAddress;
172
+
173
+ const toDecimals = quote.action.toToken.decimals;
174
+ expectedOutputStr = formatUnits(BigInt(quote.estimate.toAmount), toDecimals);
147
175
  }
148
176
 
149
177
  // Check allowance early so we know if we need to auto-approve
@@ -250,11 +278,11 @@ export const swapTokenToolDefinition = {
250
278
  mode: {
251
279
  type: "string",
252
280
  enum: ["auto", "manual"],
253
- description: "auto uses lifi. manual uses the specified provider."
281
+ description: "auto uses default router. manual uses the specified provider."
254
282
  },
255
283
  providerName: {
256
284
  type: "string",
257
- enum: ["lifi", "relay"],
285
+ enum: ["auto", "lifi", "relay", "uniswap_v2", "uniswap_v3", "pancakeswap", "1inch", "cowswap"],
258
286
  description: "Used if mode is manual."
259
287
  }
260
288
  },
@@ -0,0 +1,102 @@
1
+ export const ROUTER_ADDRESSES: Record<string, Record<string, string>> = {
2
+ uniswap_v2: {
3
+ ethereum: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
4
+ },
5
+ uniswap_v3: {
6
+ ethereum: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',
7
+ base: '0x2626664c2603336E57B271c5C0b26F421741e481',
8
+ arbitrum: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',
9
+ optimism: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',
10
+ polygon: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45'
11
+ },
12
+ pancakeswap: {
13
+ bsc: '0x10ED43C718714eb63d5aA57B78B54704E256024E'
14
+ }
15
+ };
16
+
17
+ export const UNISWAP_V2_ROUTER_ABI = [
18
+ {
19
+ "inputs": [
20
+ { "internalType": "uint256", "name": "amountIn", "type": "uint256" },
21
+ { "internalType": "uint256", "name": "amountOutMin", "type": "uint256" },
22
+ { "internalType": "address[]", "name": "path", "type": "address[]" },
23
+ { "internalType": "address", "name": "to", "type": "address" },
24
+ { "internalType": "uint256", "name": "deadline", "type": "uint256" }
25
+ ],
26
+ "name": "swapExactTokensForTokens",
27
+ "outputs": [{ "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" }],
28
+ "stateMutability": "nonpayable",
29
+ "type": "function"
30
+ },
31
+ {
32
+ "inputs": [
33
+ { "internalType": "uint256", "name": "amountIn", "type": "uint256" },
34
+ { "internalType": "address[]", "name": "path", "type": "address[]" }
35
+ ],
36
+ "name": "getAmountsOut",
37
+ "outputs": [{ "internalType": "uint256[]", "name": "amounts", "type": "uint256[]" }],
38
+ "stateMutability": "view",
39
+ "type": "function"
40
+ }
41
+ ];
42
+
43
+ export const UNISWAP_V3_ROUTER_ABI = [
44
+ {
45
+ "inputs": [
46
+ {
47
+ "components": [
48
+ { "internalType": "address", "name": "tokenIn", "type": "address" },
49
+ { "internalType": "address", "name": "tokenOut", "type": "address" },
50
+ { "internalType": "uint24", "name": "fee", "type": "uint24" },
51
+ { "internalType": "address", "name": "recipient", "type": "address" },
52
+ { "internalType": "uint256", "name": "amountIn", "type": "uint256" },
53
+ { "internalType": "uint256", "name": "amountOutMinimum", "type": "uint256" },
54
+ { "internalType": "uint160", "name": "sqrtPriceLimitX96", "type": "uint160" }
55
+ ],
56
+ "internalType": "struct IV3SwapRouter.ExactInputSingleParams",
57
+ "name": "params",
58
+ "type": "tuple"
59
+ }
60
+ ],
61
+ "name": "exactInputSingle",
62
+ "outputs": [{ "internalType": "uint256", "name": "amountOut", "type": "uint256" }],
63
+ "stateMutability": "payable",
64
+ "type": "function"
65
+ }
66
+ ];
67
+
68
+ export const UNISWAP_V3_QUOTER_ABI = [
69
+ {
70
+ "inputs": [
71
+ {
72
+ "components": [
73
+ { "internalType": "address", "name": "tokenIn", "type": "address" },
74
+ { "internalType": "address", "name": "tokenOut", "type": "address" },
75
+ { "internalType": "uint256", "name": "amountIn", "type": "uint256" },
76
+ { "internalType": "uint24", "name": "fee", "type": "uint24" },
77
+ { "internalType": "uint160", "name": "sqrtPriceLimitX96", "type": "uint160" }
78
+ ],
79
+ "internalType": "struct IQuoterV2.QuoteExactInputSingleParams",
80
+ "name": "params",
81
+ "type": "tuple"
82
+ }
83
+ ],
84
+ "name": "quoteExactInputSingle",
85
+ "outputs": [
86
+ { "internalType": "uint256", "name": "amountOut", "type": "uint256" },
87
+ { "internalType": "uint160", "name": "sqrtPriceX96After", "type": "uint160" },
88
+ { "internalType": "uint32", "name": "initializedTicksCrossed", "type": "uint32" },
89
+ { "internalType": "uint256", "name": "gasEstimate", "type": "uint256" }
90
+ ],
91
+ "stateMutability": "nonpayable",
92
+ "type": "function"
93
+ }
94
+ ];
95
+
96
+ export const QUOTER_ADDRESSES: Record<string, string> = {
97
+ ethereum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
98
+ base: '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a',
99
+ arbitrum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
100
+ optimism: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
101
+ polygon: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e'
102
+ };