nyxora 1.7.1 → 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/.dockerignore ADDED
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ .git
3
+ .github
4
+ .env
5
+ .env.*
6
+ *.log
7
+ .DS_Store
8
+ dist
9
+ build
10
+ .nyxora
11
+ vault.key
12
+ api_vault.key
package/CHANGELOG.md CHANGED
@@ -5,6 +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.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
17
+
18
+ ### UI/UX Enhancements
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.
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
+
8
29
  ## [1.7.1]
9
30
 
10
31
  ### CLI Enhancements
package/DOCKER.md ADDED
@@ -0,0 +1,68 @@
1
+ # 🐳 Nyxora Docker Guide
2
+
3
+ This document provides a comprehensive guide to installing, configuring interactively, and running the Nyxora Agent fully containerized via Docker. This approach ensures Nyxora can run seamlessly across different environments (Linux, macOS, Windows) without requiring a local Node.js installation.
4
+
5
+ ## 🛠 1. Get the Docker Image
6
+
7
+ You have two options to obtain the Nyxora Docker image:
8
+
9
+ ### Option A: Pull Pre-built Image (Recommended & Fastest)
10
+ Nyxora officially publishes Docker images via GitHub Container Registry (GHCR) upon every release.
11
+ ```bash
12
+ docker pull ghcr.io/nyxoraai/nyxora:latest
13
+ ```
14
+
15
+ ### Option B: Build Locally
16
+ If you are developing or prefer to compile it yourself, run this in the root directory:
17
+ ```bash
18
+ docker build -t ghcr.io/nyxoraai/nyxora:latest .
19
+ ```
20
+ > **Note:** The initial build takes time as it compiles C++/Rust system modules (`isolated-vm`, `libsecret`).
21
+
22
+ ---
23
+
24
+ ## ⚙️ 2. Interactive Setup (Secure & Isolated)
25
+ Nyxora requires an initial configuration (API Keys, Web3 Wallet, etc.). Run the command below to launch the **Interactive Setup Wizard** securely inside Docker.
26
+
27
+ This command mounts a volume and saves your configurations safely to a `.nyxora_docker` folder in your computer's Home directory, ensuring your existing local Nyxora installation remains untouched.
28
+ ```bash
29
+ docker run -it --rm -v ~/.nyxora_docker:/root/.nyxora ghcr.io/nyxoraai/nyxora:latest npx ts-node -T packages/core/src/gateway/setup-cli.ts
30
+ ```
31
+ *Complete the setup by answering the prompts in your terminal. Once you see "Setup Successful!", this temporary container will automatically delete itself.*
32
+
33
+ ---
34
+
35
+ ## 🚀 3. Start Nyxora (Background Daemon)
36
+ Now that the setup is complete, it's time to start the main architecture (Core API, Policy Engine, and Signer Vault) as a non-stop background daemon:
37
+ ```bash
38
+ docker run -d --name nyxora-daemon -p 3000:3000 -p 3001:3001 -v ~/.nyxora_docker:/root/.nyxora ghcr.io/nyxoraai/nyxora:latest
39
+ ```
40
+ *(This command will output a long container ID indicating that the daemon is successfully running).*
41
+
42
+ ---
43
+
44
+ ## 🔑 4. Retrieve the Auth Token
45
+ For security reasons, the Web Dashboard is locked behind a randomly generated runtime token upon every boot. Retrieve this token from the Docker logs:
46
+ ```bash
47
+ docker logs nyxora-daemon
48
+ ```
49
+ Look for a line that says: `[Launcher] Generated Internal Auth Token: <LONG_TOKEN_CODE>`. Please copy that token code.
50
+
51
+ ---
52
+
53
+ ## 💻 5. Access the Web Dashboard
54
+ Open your preferred web browser, and access the following URL (replace `<LONG_TOKEN_CODE>` with the token you copied in Step 4):
55
+ ```text
56
+ http://localhost:3000/?token=<LONG_TOKEN_CODE>
57
+ ```
58
+ Congratulations! Your Nyxora Agent is now fully operational.
59
+
60
+ ---
61
+
62
+ ## 🧰 Docker Management Reference
63
+ Here are some useful commands for managing your Nyxora daemon in the future:
64
+
65
+ * **Stop Nyxora:** `docker stop nyxora-daemon`
66
+ * **Start Nyxora again:** `docker start nyxora-daemon`
67
+ * **Monitor real-time AI logs:** `docker logs -f nyxora-daemon` (Press `Ctrl+C` to exit the stream).
68
+ * **Remove the container (e.g., to reset or upgrade):** `docker rm -f nyxora-daemon`
package/Dockerfile ADDED
@@ -0,0 +1,43 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Set Production Environment
7
+ # Install native dependencies required for isolated-vm, keyring, etc.
8
+ RUN apt-get update && apt-get install -y \
9
+ python3 \
10
+ make \
11
+ g++ \
12
+ libsecret-1-dev \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Copy package metadata first to leverage Docker caching
16
+ COPY package*.json ./
17
+ COPY packages/core/package*.json ./packages/core/
18
+ COPY packages/dashboard/package*.json ./packages/dashboard/
19
+ COPY packages/mcp-server/package*.json ./packages/mcp-server/
20
+ COPY packages/policy/package*.json ./packages/policy/
21
+ COPY packages/signer/package*.json ./packages/signer/
22
+
23
+ # Install dependencies
24
+ RUN npm install
25
+
26
+ # Copy the rest of the application code
27
+ COPY . .
28
+
29
+ # Build the dashboard (frontend)
30
+ RUN npm run build --workspace=dashboard
31
+
32
+ # Set Production Environment now that build is done
33
+ ENV NODE_ENV=production
34
+
35
+ # Expose the ports used by Core/Dashboard and Policy Engine
36
+ EXPOSE 3000
37
+ EXPOSE 3001
38
+
39
+ # PERSISTENT STORAGE: Protect Nyxora's memory and configuration
40
+ VOLUME ["/root/.nyxora"]
41
+
42
+ # Start the daemon in the foreground
43
+ CMD ["npx", "ts-node", "-T", "launcher.ts"]
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.1",
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.1",
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',
@@ -158,10 +160,19 @@ export function loadConfig(): NyxoraConfig {
158
160
  system: { allow_shell_execution: false, allow_file_write: false }
159
161
  }
160
162
  } as NyxoraConfig;
161
- } catch (error) {
162
- console.error('Failed to load config.yaml. Using default configuration.', error);
163
+ } catch (error: any) {
164
+ if (error.code === 'ENOENT') {
165
+ console.log('[Config] No config.yaml found. Using default configuration.');
166
+ } else {
167
+ console.error('[Config] Failed to load config.yaml. Using default configuration.', error);
168
+ }
163
169
  return {
164
- 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
+ },
165
176
  llm: {
166
177
  provider: 'openai',
167
178
  model: 'gpt-4o-mini',
@@ -145,6 +145,25 @@ export async function getAccessToken(): Promise<string | null> {
145
145
  }
146
146
  }
147
147
 
148
+ export async function logoutGoogle(): Promise<boolean> {
149
+ try {
150
+ const { Entry } = require('@napi-rs/keyring');
151
+ const entry = new Entry('nyxora', 'google_refresh_token');
152
+ await entry.deletePassword();
153
+ } catch (e) {}
154
+
155
+ try {
156
+ if (fs.existsSync(FALLBACK_TOKEN_PATH)) {
157
+ fs.unlinkSync(FALLBACK_TOKEN_PATH);
158
+ }
159
+ } catch (e) {}
160
+
161
+ accessToken = null;
162
+ tokenExpiry = 0;
163
+ console.log('[Google Auth] Successfully logged out.');
164
+ return true;
165
+ }
166
+
148
167
  // ---- Secure Storage for Refresh Token ----
149
168
 
150
169
  async function saveRefreshToken(token: string) {
@@ -44,7 +44,7 @@ import { readGmailInboxToolDefinition, listCalendarEventsToolDefinition, appendR
44
44
 
45
45
  import { startTelegramBot } from './telegram';
46
46
  import { formatTransactionSuccess, formatTransactionError } from '../utils/formatter';
47
- import { initGoogleAuth, getAuthUrl, processCallback, isAuthenticated } from './googleAuthModule';
47
+ import { initGoogleAuth, getAuthUrl, processCallback, isAuthenticated, logoutGoogle } from './googleAuthModule';
48
48
 
49
49
  // Initialize Google Auth
50
50
  initGoogleAuth();
@@ -291,7 +291,13 @@ app.get('/api/auth/google/callback', async (req, res) => {
291
291
  });
292
292
 
293
293
  app.get('/api/auth/google/status', async (req, res) => {
294
- res.json({ connected: await isAuthenticated() });
294
+ const connected = await isAuthenticated();
295
+ res.json({ connected });
296
+ });
297
+
298
+ app.delete('/api/auth/google', async (req, res) => {
299
+ const success = await logoutGoogle();
300
+ res.json({ success });
295
301
  });
296
302
 
297
303
  app.get('/api/transactions', (req, res) => {
@@ -405,7 +411,7 @@ export function startServer() {
405
411
  limitOrderManager.startMonitor();
406
412
 
407
413
  const PORT = Number(process.env.PORT || 3000);
408
- app.listen(PORT, '127.0.0.1', () => {
414
+ app.listen(PORT, '0.0.0.0', () => {
409
415
  console.log(`🤖 Nyxora API Server running on port ${PORT}`);
410
416
 
411
417
  // Start the Telegram bot listener
@@ -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
+ };