@darksol/terminal 0.2.1 → 0.2.2

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 CHANGED
@@ -9,7 +9,11 @@
9
9
 
10
10
  **All DARKSOL services. One terminal. Zero trust required.**
11
11
 
12
- A unified CLI for market intel, trading, on-chain oracle, casino, prepaid cards, builder indexing, and more. Encrypted wallet management. Agent-native. OpenClaw-controllable.
12
+ A unified CLI for market intel, trading, AI-powered analysis, on-chain oracle, casino, prepaid cards, builder indexing, secure agent signing, and more. Encrypted wallet management. Agent-native. OpenClaw-controllable.
13
+
14
+ [![npm](https://img.shields.io/npm/v/@darksol/terminal)](https://www.npmjs.com/package/@darksol/terminal)
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-gold.svg)](https://opensource.org/licenses/MIT)
16
+ [![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-green.svg)](https://nodejs.org/)
13
17
 
14
18
  ## Install
15
19
 
@@ -29,81 +33,198 @@ darksol wallet create main
29
33
  # Check balance
30
34
  darksol wallet balance
31
35
 
32
- # Set chain
33
- darksol config set chain base
34
-
35
- # Add custom RPC
36
- darksol config rpc base https://your-rpc-endpoint.com
37
-
38
36
  # Swap tokens
39
37
  darksol trade swap -i ETH -o USDC -a 0.1
40
38
 
41
- # Snipe a token
42
- darksol trade snipe 0xTOKEN_ADDRESS -a 0.05
43
-
44
- # Create DCA order
45
- darksol dca create
46
-
47
- # Market data
48
- darksol market top
49
- darksol market token VIRTUAL
50
- darksol market compare ETH AERO VIRTUAL
51
-
52
- # Oracle
53
- darksol oracle flip
54
- darksol oracle dice 20
55
-
56
- # Casino
57
- darksol casino bet coin-flip heads
58
- darksol casino tables
59
-
60
- # Execution scripts (automated trading)
61
- darksol script templates # See available templates
62
- darksol script create # Create from template (interactive)
63
- darksol script list # List your scripts
64
- darksol script run my-buy-script # Execute (requires wallet password)
65
- darksol script show my-script # View details + code
66
- darksol script edit my-script # Edit params/wallet/chain
67
- darksol script clone my-script new-script
68
- darksol script delete old-script
69
-
70
- # Prepaid cards
71
- darksol cards catalog
72
-
73
- # Builder index
74
- darksol builders leaderboard
39
+ # AI trading assistant
40
+ darksol ai chat
75
41
 
76
- # Facilitator
77
- darksol facilitator health
42
+ # Start agent signer for OpenClaw
43
+ darksol agent start main
78
44
  ```
79
45
 
80
- ## Wallet Security
81
-
82
- - Private keys are **never stored in plaintext**
83
- - AES-256-GCM encryption with scrypt key derivation
84
- - Password required for every transaction
85
- - Keys stored in `~/.darksol/wallets/` (encrypted JSON)
86
- - No recovery without password — back it up
87
-
88
46
  ## Modules
89
47
 
90
48
  | Module | Description | Pricing |
91
49
  |--------|-------------|---------|
92
- | `wallet` | Create, import, manage wallets | Free |
93
- | `trade` | Swap, snipe, token trading | Gas only |
50
+ | `wallet` | Create, import, manage encrypted wallets | Free |
51
+ | `trade` | Swap (Uniswap V3), snipe (V2), token trading | Gas only |
94
52
  | `dca` | Dollar-cost averaging engine | Gas only |
95
- | `script` | Execution scripts & strategies | Free |
96
- | `market` | Market intel, top movers, analysis | x402 micropayments |
53
+ | `ai` | LLM-powered trading assistant & analysis | Provider dependent |
54
+ | `agent` | Secure agent signer (PK-isolated proxy) | Free |
55
+ | `keys` | API key vault (LLMs, data, RPCs) | Free |
56
+ | `script` | Execution scripts & automated strategies | Free |
57
+ | `skills` | Agent skill directory & installer | Free |
58
+ | `market` | Market intel, top movers, token analysis | x402 micropayments |
97
59
  | `oracle` | On-chain random number oracle | $0.05–$0.25 |
98
60
  | `casino` | The Clawsino — on-chain betting | $1 flat bets |
99
- | `cards` | Crypto → prepaid Visa/MC | 3% markup |
61
+ | `cards` | Crypto → prepaid Visa/MC (no KYC) | 3% markup |
100
62
  | `builders` | ERC-8021 builder leaderboard | Free |
101
- | `facilitator` | x402 payment verification | Free |
63
+ | `facilitator` | x402 payment verification & settlement | Free |
102
64
  | `config` | Terminal configuration | Free |
103
65
 
104
- ## Execution Scripts
66
+ ---
67
+
68
+ ## 🔐 Secure Agent Signer
69
+
70
+ **The killer feature.** A PK-isolated signing proxy for AI agents (OpenClaw, etc.).
71
+
72
+ ```bash
73
+ # Start the signing proxy
74
+ darksol agent start my-wallet
75
+
76
+ # With spending limits
77
+ darksol agent start my-wallet --max-value 0.5 --daily-limit 2.0
78
+
79
+ # With contract allowlist
80
+ darksol agent start my-wallet --allowlist 0xContract1,0xContract2
81
+
82
+ # View security documentation
83
+ darksol agent docs
84
+ ```
85
+
86
+ **Why it exists:** AI agents need to sign transactions, but exposing private keys to LLMs is dangerous — prompt injection could leak the key. Existing wallets (Bankr, Phantom MCP) can't do x402 payments or real contract signing.
87
+
88
+ **How it works:**
89
+ 1. You unlock your wallet ONCE with your password
90
+ 2. The key decrypts into memory (never to disk/API)
91
+ 3. A local HTTP server at `127.0.0.1:18790` exposes signing endpoints
92
+ 4. AI agents call `/send`, `/sign` — never see the key
93
+ 5. Every TX is validated against your security policy
94
+
95
+ **Endpoints:**
96
+
97
+ | Endpoint | Method | Description |
98
+ |----------|--------|-------------|
99
+ | `/address` | GET | Wallet address (safe) |
100
+ | `/balance` | GET | ETH balance (safe) |
101
+ | `/chain` | GET | Active chain info |
102
+ | `/send` | POST | Sign + broadcast transaction |
103
+ | `/sign` | POST | Sign transaction (return raw) |
104
+ | `/sign-message` | POST | Sign EIP-191 message |
105
+ | `/sign-typed-data` | POST | Sign EIP-712 typed data (x402) |
106
+ | `/policy` | GET | View spending policy |
107
+ | `/audit` | GET | Operation audit log |
108
+ | `/health` | GET | Health check |
109
+
110
+ **Security guarantees:**
111
+ - ✅ No `/private-key` endpoint exists — literally impossible to extract
112
+ - ✅ Loopback-only (127.0.0.1) — not accessible from network
113
+ - ✅ One-time bearer token auth (shown only in terminal)
114
+ - ✅ Per-TX value limits + daily spending cap
115
+ - ✅ Contract allowlist support
116
+ - ✅ Dangerous selectors blocked (transferOwnership, selfdestruct)
117
+ - ✅ Full audit log of all operations
118
+ - ✅ Prompt injection proof — the LLM cannot access what doesn't exist in any API response
119
+
120
+ ---
121
+
122
+ ## 🧠 AI Trading Assistant
123
+
124
+ Natural language trading powered by multi-provider LLM support.
125
+
126
+ ```bash
127
+ # Interactive chat with live market data
128
+ darksol ai chat
105
129
 
106
- Automated trading strategies with full PK access. Scripts unlock your wallet at runtime and execute on-chain transactions.
130
+ # One-shot intent parsing
131
+ darksol ai ask "buy 0.5 ETH worth of AERO on Base"
132
+
133
+ # DCA strategy recommendation
134
+ darksol ai strategy VIRTUAL --budget 500 --timeframe "30 days"
135
+
136
+ # AI-powered token analysis
137
+ darksol ai analyze AERO
138
+
139
+ # Use specific provider
140
+ darksol ai chat --provider ollama --model llama3
141
+ ```
142
+
143
+ **Supported providers:** OpenAI, Anthropic, OpenRouter, Ollama (local = free)
144
+
145
+ The AI gets live market context (prices from DexScreener), knows your config (chain, slippage, wallet), and returns structured intents with confidence scores and risk warnings.
146
+
147
+ ---
148
+
149
+ ## 🔑 API Key Vault
150
+
151
+ Encrypted storage for all your API keys.
152
+
153
+ ```bash
154
+ # List all services and status
155
+ darksol keys list
156
+
157
+ # Add keys (encrypted with AES-256-GCM)
158
+ darksol keys add openai
159
+ darksol keys add coingecko
160
+ darksol keys add alchemy
161
+
162
+ # Remove a key
163
+ darksol keys remove openai
164
+ ```
165
+
166
+ **Supported services:**
167
+ | Category | Services |
168
+ |----------|----------|
169
+ | LLM | OpenAI, Anthropic, OpenRouter, Ollama |
170
+ | Data | CoinGecko Pro, DexScreener, DefiLlama |
171
+ | RPC | Alchemy, Infura, QuickNode |
172
+ | Trading | 1inch, ParaSwap |
173
+
174
+ Keys can also come from environment variables (e.g., `OPENAI_API_KEY`).
175
+
176
+ ---
177
+
178
+ ## 💰 Trading
179
+
180
+ ```bash
181
+ # Swap via Uniswap V3
182
+ darksol trade swap -i ETH -o USDC -a 0.1
183
+
184
+ # Snipe a token (Uniswap V2, fast buy)
185
+ darksol trade snipe 0xTOKEN -a 0.05
186
+
187
+ # Snipe with gas boost
188
+ darksol trade snipe 0xTOKEN -a 0.05 -g 2.0
189
+
190
+ # Watch for new pairs
191
+ darksol trade watch
192
+ ```
193
+
194
+ ## 📊 DCA Engine
195
+
196
+ ```bash
197
+ darksol dca create # Interactive order creation
198
+ darksol dca list # List active orders
199
+ darksol dca run # Execute pending orders
200
+ darksol dca cancel <id>
201
+ ```
202
+
203
+ ## 📈 Market Intel
204
+
205
+ ```bash
206
+ darksol market top # Top movers on Base
207
+ darksol market top -c ethereum # Top movers on Ethereum
208
+ darksol market token VIRTUAL # Full token detail
209
+ darksol market compare ETH AERO VIRTUAL # Side-by-side comparison
210
+ ```
211
+
212
+ ---
213
+
214
+ ## ⚡ Execution Scripts
215
+
216
+ Automated trading strategies with full wallet access.
217
+
218
+ ```bash
219
+ darksol script templates # Available templates
220
+ darksol script create # Create from template (interactive)
221
+ darksol script list # List scripts
222
+ darksol script run my-buy # Execute (password required)
223
+ darksol script show my-buy # View code + params
224
+ darksol script edit my-buy # Edit params
225
+ darksol script clone my-buy new-buy
226
+ darksol script delete old
227
+ ```
107
228
 
108
229
  ### Templates
109
230
 
@@ -117,171 +238,123 @@ Automated trading strategies with full PK access. Scripts unlock your wallet at
117
238
  | `transfer` | Transfer ETH or tokens to an address |
118
239
  | `empty` | Custom script — full ethers.js context |
119
240
 
120
- ### Script Context
121
-
122
- Every script gets:
123
- ```javascript
124
- module.exports = async function({ signer, provider, ethers, config, params }) {
125
- // signer — ethers.Wallet (unlocked, connected to provider)
126
- // provider — ethers.JsonRpcProvider for active chain
127
- // ethers — the ethers library
128
- // config — { chain, slippage, gasMultiplier, rpcs }
129
- // params — your custom parameters
130
- };
131
- ```
132
-
133
- ### Automation (OpenClaw / cron)
241
+ ### Automation
134
242
 
135
243
  ```bash
136
- # Run without prompts (password via flag)
244
+ # Non-interactive execution (for cron/OpenClaw)
137
245
  darksol script run my-dca --password "mypass" --yes
138
-
139
- # JSON output for programmatic use
140
- darksol config set output json
141
246
  ```
142
247
 
143
- ## Configuration
248
+ ---
249
+
250
+ ## 🧩 Skills Directory
144
251
 
145
- Config stored at `~/.config/darksol-terminal/config.json`
252
+ Install DARKSOL skills for OpenClaw agents.
146
253
 
147
254
  ```bash
148
- # View all settings
149
- darksol config show
255
+ darksol skills list # Browse available skills
256
+ darksol skills install darksol-terminal # Install to OpenClaw
257
+ darksol skills info darksol-terminal # Skill details
258
+ darksol skills uninstall darksol-terminal
259
+ ```
150
260
 
151
- # Set active chain
152
- darksol config set chain base
261
+ **Available skills:** darksol-terminal, darksol-facilitator, darksol-prepaid-cards, random-oracle, the-clawsino
153
262
 
154
- # Set slippage tolerance
155
- darksol config set slippage 1.0
263
+ ---
156
264
 
157
- # Custom RPC
158
- darksol config rpc base https://mainnet.base.org
159
- darksol config rpc ethereum https://eth.llamarpc.com
160
- darksol config rpc arbitrum https://arb1.arbitrum.io/rpc
161
- ```
265
+ ## 🎲 Services
162
266
 
163
- ## Supported Chains
267
+ ```bash
268
+ # Oracle — on-chain randomness
269
+ darksol oracle flip
270
+ darksol oracle dice 20
271
+ darksol oracle number 1 100
164
272
 
165
- - **Base** (default)
166
- - Ethereum
167
- - Polygon
168
- - Arbitrum
169
- - Optimism
273
+ # Casino — on-chain betting
274
+ darksol casino bet coin-flip heads
275
+ darksol casino tables
276
+ darksol casino stats
170
277
 
171
- ## OpenClaw Integration
278
+ # Prepaid Cards — crypto to Visa/MC
279
+ darksol cards catalog
280
+ darksol cards order -p swype -a 50
281
+ darksol cards status <id>
172
282
 
173
- DARKSOL Terminal is designed to be controlled by AI agents via OpenClaw:
283
+ # Builder Index ERC-8021 rankings
284
+ darksol builders leaderboard
285
+ darksol builders lookup <code>
174
286
 
175
- ```bash
176
- # Agents can run any command non-interactively
177
- darksol market top --output json
178
- darksol wallet balance main
179
- darksol oracle flip
287
+ # Facilitator — x402 payments
288
+ darksol facilitator health
289
+ darksol facilitator verify <payment>
180
290
  ```
181
291
 
182
- JSON output mode for programmatic use:
292
+ ---
293
+
294
+ ## 🔒 Wallet Security
295
+
296
+ - Private keys **never stored in plaintext**
297
+ - AES-256-GCM encryption with scrypt key derivation (N=2^18)
298
+ - Password required for every transaction
299
+ - Keys stored in `~/.darksol/wallets/` (encrypted JSON)
300
+ - No recovery without password — back it up
301
+
183
302
  ```bash
184
- darksol config set output json
303
+ darksol wallet create <name> # Create new (generates keypair)
304
+ darksol wallet import <name> # Import from private key
305
+ darksol wallet list # List all wallets
306
+ darksol wallet balance [name] # ETH + USDC balance
307
+ darksol wallet use <name> # Set active wallet
308
+ darksol wallet export [name] # Export (password required for PK)
185
309
  ```
186
310
 
187
- ## Helper Functions
188
-
189
- When writing custom execution scripts, you have access to powerful helper utilities:
190
-
191
- ```javascript
192
- import {
193
- // Providers & Chain
194
- getProvider, // Get ethers provider for any chain
195
- CHAIN_IDS, // { base: 8453, ethereum: 1, ... }
196
- EXPLORERS, // Block explorer URLs per chain
197
- txUrl, addressUrl, // Generate explorer links
198
-
199
- // Tokens
200
- getERC20, // Get ERC20 contract instance
201
- getFullTokenInfo, // Name, symbol, decimals, totalSupply
202
- getTokenBalance, // Formatted balance for any token
203
- ensureApproval, // Check & approve token spending
204
- TOKENS, // All known token addresses per chain
205
- getUSDC, getWETH, // Quick chain-specific lookups
206
-
207
- // Gas
208
- estimateGasCost, // Estimate gas in ETH
209
- getBoostedGas, // Priority gas settings for snipes
210
-
211
- // Formatting
212
- formatCompact, // 1234567 → "1.23M"
213
- formatUSD, // Format as $1,234.56
214
- formatETH, // Format wei to ETH string
215
- formatTokenAmount, // Format with symbol
216
- shortAddress, // 0x1234...5678
217
- formatDuration, // Seconds → "2h 30m"
218
-
219
- // Validation
220
- isValidAddress, // Check Ethereum address
221
- isValidPrivateKey, // Check private key format
222
- isValidAmount, // Check positive number
223
- parseTokenAmount, // String → bigint with decimals
224
-
225
- // Async
226
- sleep, // await sleep(1000)
227
- retry, // Retry with exponential backoff
228
- waitForTx, // Wait for tx with timeout
229
-
230
- // Price
231
- quickPrice, // DexScreener price lookup
232
- hasLiquidity, // Check minimum liquidity
233
- } from './utils/helpers.js';
234
- ```
311
+ ---
235
312
 
236
- ### Example: Custom Script Using Helpers
313
+ ## ⚙️ Configuration
237
314
 
238
- ```javascript
239
- module.exports = async function({ signer, provider, ethers, config, params }) {
240
- // Import helpers (available in script context)
241
- const helpers = await import('@darksol/terminal/src/utils/helpers.js');
315
+ ```bash
316
+ darksol config show # View all settings
317
+ darksol config set chain base # Set active chain
318
+ darksol config set slippage 1.0 # Slippage tolerance (%)
319
+ darksol config rpc base https://your-rpc.com
320
+ ```
242
321
 
243
- // Check if token has enough liquidity
244
- const liquid = await helpers.hasLiquidity(params.token, 5000);
245
- if (!liquid) throw new Error('Insufficient liquidity');
322
+ ### Supported Chains
246
323
 
247
- // Get price
248
- const price = await helpers.quickPrice(params.token);
249
- console.log(`Price: ${helpers.formatUSD(price.price)}`);
324
+ | Chain | ID | Default RPC |
325
+ |-------|---:|-------------|
326
+ | Base | 8453 | mainnet.base.org |
327
+ | Ethereum | 1 | eth.llamarpc.com |
328
+ | Polygon | 137 | polygon-rpc.com |
329
+ | Arbitrum | 42161 | arb1.arbitrum.io/rpc |
330
+ | Optimism | 10 | mainnet.optimism.io |
250
331
 
251
- // Get boosted gas for priority
252
- const gas = await helpers.getBoostedGas(provider, 1.5);
332
+ ---
253
333
 
254
- // Execute trade with retry
255
- const result = await helpers.retry(async () => {
256
- const tx = await signer.sendTransaction({ ...txParams, ...gas });
257
- return helpers.waitForTx(tx, 60000);
258
- }, 3, 2000);
334
+ ## 📚 Reference
259
335
 
260
- return { txHash: result.hash, price: price.price };
261
- };
336
+ ```bash
337
+ darksol tips # Trading + scripting tips
338
+ darksol tips --trading # Trading tips only
339
+ darksol networks # Chain reference table
340
+ darksol quickstart # Getting started guide
341
+ darksol lookup 0x... # Look up any address
262
342
  ```
263
343
 
264
- ## Tips & Reference
265
-
266
- ```bash
267
- # Trading tips (slippage, MEV protection, etc.)
268
- darksol tips --trading
344
+ ---
269
345
 
270
- # Script writing tips
271
- darksol tips --scripts
346
+ ## 🤖 OpenClaw Integration
272
347
 
273
- # Both
274
- darksol tips
348
+ DARKSOL Terminal is agent-native:
275
349
 
276
- # Network reference (chains, IDs, explorers, USDC addresses)
277
- darksol networks
350
+ 1. **Install the skill:** `darksol skills install darksol-terminal`
351
+ 2. **Start the agent signer:** `darksol agent start my-wallet`
352
+ 3. **Run commands non-interactively** with flags (`-p`, `-y`, `--key`)
353
+ 4. **JSON output:** `darksol config set output json`
278
354
 
279
- # Getting started guide
280
- darksol quickstart
355
+ All commands work without prompts when flags are provided.
281
356
 
282
- # Look up any address (auto-detects token vs wallet)
283
- darksol lookup 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
284
- ```
357
+ ---
285
358
 
286
359
  ## Development
287
360
 
@@ -289,6 +362,7 @@ darksol lookup 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
289
362
  git clone https://gitlab.com/darks0l/darksol-terminal
290
363
  cd darksol-terminal
291
364
  npm install
365
+ npm test # Run test suite (node:test)
292
366
  node bin/darksol.js
293
367
  ```
294
368
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "DARKSOL Terminal — unified CLI for all DARKSOL services. Market intel, trading, oracle, casino, and more.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -16,7 +16,7 @@ import { buildersLeaderboard, buildersLookup, buildersFeed } from './services/bu
16
16
  import { createScript, listScripts, runScript, showScript, editScript, deleteScript, cloneScript, listTemplates } from './scripts/engine.js';
17
17
  import { showTradingTips, showScriptTips, showNetworkReference, showQuickStart, showWalletSummary, showTokenInfo, showTxResult } from './utils/helpers.js';
18
18
  import { addKey, removeKey, listKeys } from './config/keys.js';
19
- import { parseIntent, startChat, adviseStrategy, analyzeToken } from './llm/intent.js';
19
+ import { parseIntent, startChat, adviseStrategy, analyzeToken, executeIntent } from './llm/intent.js';
20
20
  import { startAgentSigner, showAgentDocs } from './wallet/agent-signer.js';
21
21
  import { listSkills, installSkill, skillInfo, uninstallSkill } from './services/skills.js';
22
22
 
@@ -328,6 +328,21 @@ export function cli(argv) {
328
328
  }
329
329
  });
330
330
 
331
+ ai
332
+ .command('execute <prompt...>')
333
+ .description('Parse intent AND execute the trade')
334
+ .option('-p, --provider <name>', 'LLM provider')
335
+ .option('-m, --model <model>', 'Model name')
336
+ .option('--password <pw>', 'Wallet password (for non-interactive)')
337
+ .option('-y, --yes', 'Skip confirmation')
338
+ .action(async (promptParts, opts) => {
339
+ const prompt = promptParts.join(' ');
340
+ const intent = await parseIntent(prompt, opts);
341
+ if (intent.action !== 'error' && intent.action !== 'unknown') {
342
+ await executeIntent(intent, { password: opts.password, yes: opts.yes });
343
+ }
344
+ });
345
+
331
346
  ai
332
347
  .command('strategy <token>')
333
348
  .description('Get DCA strategy recommendation')
package/src/llm/intent.js CHANGED
@@ -307,4 +307,130 @@ Provide: sentiment, liquidity assessment, risk level (1-10), key considerations.
307
307
  }
308
308
  }
309
309
 
310
+ // ──────────────────────────────────────────────────
311
+ // INTENT EXECUTOR — Wire AI intents into real trades
312
+ // ──────────────────────────────────────────────────
313
+
314
+ /**
315
+ * Execute a parsed intent (from parseIntent or ai ask)
316
+ * Bridges natural language → actual swap/snipe/dca/transfer
317
+ *
318
+ * @param {object} intent - Parsed intent object from parseIntent
319
+ * @param {object} opts - { password, confirm, yes }
320
+ * @returns {Promise<object>} Execution result
321
+ */
322
+ export async function executeIntent(intent, opts = {}) {
323
+ if (!intent || intent.action === 'unknown' || intent.action === 'error') {
324
+ error('Cannot execute: intent not recognized');
325
+ return { success: false, reason: 'unknown intent' };
326
+ }
327
+
328
+ if (intent.confidence !== undefined && intent.confidence < 0.6) {
329
+ warn(`Low confidence (${(intent.confidence * 100).toFixed(0)}%) — review the intent before executing`);
330
+ if (!opts.yes) {
331
+ const inquirer = (await import('inquirer')).default;
332
+ const { proceed } = await inquirer.prompt([{
333
+ type: 'confirm',
334
+ name: 'proceed',
335
+ message: theme.accent('Execute anyway?'),
336
+ default: false,
337
+ }]);
338
+ if (!proceed) return { success: false, reason: 'user cancelled' };
339
+ }
340
+ }
341
+
342
+ // Show intent summary for confirmation
343
+ if (!opts.yes) {
344
+ showSection('EXECUTE INTENT');
345
+ kvDisplay([
346
+ ['Action', intent.action],
347
+ ['Token In', intent.tokenIn || '-'],
348
+ ['Token Out', intent.tokenOut || '-'],
349
+ ['Amount', intent.amount || '-'],
350
+ ['Chain', intent.chain || getConfig('chain') || 'base'],
351
+ ['Confidence', intent.confidence ? `${(intent.confidence * 100).toFixed(0)}%` : '-'],
352
+ ]);
353
+
354
+ if (intent.warnings?.length > 0) {
355
+ console.log('');
356
+ intent.warnings.forEach(w => warn(w));
357
+ }
358
+
359
+ console.log('');
360
+ const inquirer = (await import('inquirer')).default;
361
+ const { confirm } = await inquirer.prompt([{
362
+ type: 'confirm',
363
+ name: 'confirm',
364
+ message: theme.gold('Execute this trade?'),
365
+ default: false,
366
+ }]);
367
+ if (!confirm) return { success: false, reason: 'user cancelled' };
368
+ }
369
+
370
+ try {
371
+ switch (intent.action) {
372
+ case 'swap': {
373
+ const { executeSwap } = await import('../trading/swap.js');
374
+ return await executeSwap({
375
+ tokenIn: intent.tokenIn,
376
+ tokenOut: intent.tokenOut,
377
+ amount: intent.amount,
378
+ chain: intent.chain,
379
+ password: opts.password,
380
+ });
381
+ }
382
+
383
+ case 'snipe': {
384
+ const { executeSnipe } = await import('../trading/snipe.js');
385
+ return await executeSnipe({
386
+ token: intent.tokenOut || intent.tokenIn,
387
+ amount: intent.amount,
388
+ chain: intent.chain,
389
+ gasMultiplier: intent.gasMultiplier,
390
+ password: opts.password,
391
+ });
392
+ }
393
+
394
+ case 'dca': {
395
+ const { createDCAOrder } = await import('../trading/dca.js');
396
+ return await createDCAOrder({
397
+ token: intent.tokenOut || intent.tokenIn,
398
+ amount: intent.amount,
399
+ interval: intent.interval || '1h',
400
+ orders: intent.orders || 10,
401
+ chain: intent.chain,
402
+ });
403
+ }
404
+
405
+ case 'transfer': {
406
+ // Transfer intent — use execution script engine
407
+ info(`Transfer intent detected. Use: darksol script create (template: transfer)`);
408
+ info(`To: ${intent.to || 'specify address'}, Amount: ${intent.amount || 'specify amount'}`);
409
+ return { success: true, action: 'transfer', note: 'Use script system for transfers' };
410
+ }
411
+
412
+ case 'info':
413
+ case 'analyze': {
414
+ const token = intent.tokenOut || intent.tokenIn || intent.token;
415
+ if (token) {
416
+ await analyzeToken(token, opts);
417
+ return { success: true, action: 'analyze' };
418
+ }
419
+ info('No token specified for analysis');
420
+ return { success: false, reason: 'no token specified' };
421
+ }
422
+
423
+ default:
424
+ warn(`Action "${intent.action}" not yet wired for execution`);
425
+ if (intent.command) {
426
+ info(`Suggested command: ${theme.gold(intent.command)}`);
427
+ }
428
+ return { success: false, reason: `unhandled action: ${intent.action}` };
429
+ }
430
+ } catch (err) {
431
+ error(`Execution failed: ${err.message}`);
432
+ return { success: false, error: err.message };
433
+ }
434
+ }
435
+
310
436
  export { INTENT_SYSTEM_PROMPT };
package/src/ui/banner.js CHANGED
@@ -26,7 +26,7 @@ export function showBanner(opts = {}) {
26
26
  );
27
27
  console.log(
28
28
  theme.dim(' ║ ') +
29
- theme.subtle(' v0.2.1') +
29
+ theme.subtle(' v0.2.2') +
30
30
  theme.dim(' ') +
31
31
  theme.gold('🌑') +
32
32
  theme.dim(' ║')
@@ -44,7 +44,7 @@ export function showBanner(opts = {}) {
44
44
 
45
45
  export function showMiniBanner() {
46
46
  console.log('');
47
- console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.2.1'));
47
+ console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.2.2'));
48
48
  console.log(theme.dim(' ─────────────────────────────'));
49
49
  console.log('');
50
50
  }
@@ -61,3 +61,4 @@ export function showDivider() {
61
61
 
62
62
 
63
63
 
64
+
package/tests/cli.test.js DELETED
@@ -1,72 +0,0 @@
1
- import test, { before, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { spawnSync } from 'node:child_process';
4
- import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
5
- import { tmpdir } from 'node:os';
6
- import { join } from 'node:path';
7
-
8
- function runCli(args, env) {
9
- return spawnSync(process.execPath, ['bin/darksol.js', ...args], {
10
- cwd: process.cwd(),
11
- encoding: 'utf8',
12
- env,
13
- });
14
- }
15
-
16
- let tempRoot;
17
- let env;
18
-
19
- before(() => {
20
- tempRoot = mkdtempSync(join(tmpdir(), 'darksol-cli-'));
21
- const home = join(tempRoot, 'home');
22
- const appData = join(tempRoot, 'appdata');
23
- const localAppData = join(tempRoot, 'localappdata');
24
- mkdirSync(home, { recursive: true });
25
- mkdirSync(appData, { recursive: true });
26
- mkdirSync(localAppData, { recursive: true });
27
-
28
- env = {
29
- ...process.env,
30
- HOME: home,
31
- USERPROFILE: home,
32
- APPDATA: appData,
33
- LOCALAPPDATA: localAppData,
34
- FORCE_COLOR: '0',
35
- };
36
- });
37
-
38
- after(() => {
39
- rmSync(tempRoot, { recursive: true, force: true });
40
- });
41
-
42
- test('CLI help output renders', (t) => {
43
- const res = runCli(['--help'], env);
44
- if (res.error && res.error.code === 'EPERM') {
45
- t.skip('Child process spawn not permitted in this environment');
46
- return;
47
- }
48
- assert.equal(res.status, 0, res.stderr);
49
- assert.match(res.stdout, /Usage:/);
50
- assert.match(res.stdout, /\bwallet\b/);
51
- assert.match(res.stdout, /\btrade\b/);
52
- assert.match(res.stdout, /\bscript\b/);
53
- assert.match(res.stdout, /\bconfig\b/);
54
- });
55
-
56
- test('command registration includes trade and script subcommands', (t) => {
57
- const tradeHelp = runCli(['trade', '--help'], env);
58
- if (tradeHelp.error && tradeHelp.error.code === 'EPERM') {
59
- t.skip('Child process spawn not permitted in this environment');
60
- return;
61
- }
62
- assert.equal(tradeHelp.status, 0, tradeHelp.stderr);
63
- assert.match(tradeHelp.stdout, /\bswap\b/);
64
- assert.match(tradeHelp.stdout, /\bsnipe\b/);
65
- assert.match(tradeHelp.stdout, /\bwatch\b/);
66
-
67
- const scriptHelp = runCli(['script', '--help'], env);
68
- assert.equal(scriptHelp.status, 0, scriptHelp.stderr);
69
- assert.match(scriptHelp.stdout, /\bcreate\b/);
70
- assert.match(scriptHelp.stdout, /\brun\b/);
71
- assert.match(scriptHelp.stdout, /\btemplates\b/);
72
- });
@@ -1,75 +0,0 @@
1
- import test, { before, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
-
7
- function importFresh(relativePath) {
8
- const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
9
- return import(url);
10
- }
11
-
12
- function setTempEnv(tempRoot) {
13
- const home = join(tempRoot, 'home');
14
- const appData = join(tempRoot, 'appdata');
15
- const localAppData = join(tempRoot, 'localappdata');
16
- mkdirSync(home, { recursive: true });
17
- mkdirSync(appData, { recursive: true });
18
- mkdirSync(localAppData, { recursive: true });
19
-
20
- const prev = {
21
- HOME: process.env.HOME,
22
- USERPROFILE: process.env.USERPROFILE,
23
- APPDATA: process.env.APPDATA,
24
- LOCALAPPDATA: process.env.LOCALAPPDATA,
25
- };
26
-
27
- process.env.HOME = home;
28
- process.env.USERPROFILE = home;
29
- process.env.APPDATA = appData;
30
- process.env.LOCALAPPDATA = localAppData;
31
-
32
- return prev;
33
- }
34
-
35
- function restoreEnv(prev) {
36
- for (const key of Object.keys(prev)) {
37
- if (prev[key] === undefined) delete process.env[key];
38
- else process.env[key] = prev[key];
39
- }
40
- }
41
-
42
- let tempRoot;
43
- let prevEnv;
44
- let store;
45
-
46
- before(async () => {
47
- tempRoot = mkdtempSync(join(tmpdir(), 'darksol-config-'));
48
- prevEnv = setTempEnv(tempRoot);
49
- store = await importFresh('../src/config/store.js');
50
- });
51
-
52
- after(() => {
53
- restoreEnv(prevEnv);
54
- rmSync(tempRoot, { recursive: true, force: true });
55
- });
56
-
57
- test('get/set config values', () => {
58
- assert.equal(store.getConfig('chain'), 'base');
59
- store.setConfig('chain', 'ethereum');
60
- assert.equal(store.getConfig('chain'), 'ethereum');
61
- const all = store.getAllConfig();
62
- assert.equal(all.chain, 'ethereum');
63
- });
64
-
65
- test('RPC management works for getRPC/setRPC', () => {
66
- const defaultBaseRpc = store.getRPC('base');
67
- assert.ok(typeof defaultBaseRpc === 'string' && defaultBaseRpc.startsWith('http'));
68
-
69
- const custom = 'https://rpc.example.invalid';
70
- store.setRPC('base', custom);
71
- assert.equal(store.getRPC('base'), custom);
72
-
73
- store.setConfig('chain', 'base');
74
- assert.equal(store.getRPC(), custom);
75
- });
package/tests/dca.test.js DELETED
@@ -1,141 +0,0 @@
1
- import test, { before, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, rmSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import inquirer from 'inquirer';
7
-
8
- function importFresh(relativePath) {
9
- const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
10
- return import(url);
11
- }
12
-
13
- function setTempEnv(tempRoot) {
14
- const home = join(tempRoot, 'home');
15
- const appData = join(tempRoot, 'appdata');
16
- const localAppData = join(tempRoot, 'localappdata');
17
- mkdirSync(home, { recursive: true });
18
- mkdirSync(appData, { recursive: true });
19
- mkdirSync(localAppData, { recursive: true });
20
-
21
- const prev = {
22
- HOME: process.env.HOME,
23
- USERPROFILE: process.env.USERPROFILE,
24
- APPDATA: process.env.APPDATA,
25
- LOCALAPPDATA: process.env.LOCALAPPDATA,
26
- };
27
-
28
- process.env.HOME = home;
29
- process.env.USERPROFILE = home;
30
- process.env.APPDATA = appData;
31
- process.env.LOCALAPPDATA = localAppData;
32
-
33
- return { prev, home };
34
- }
35
-
36
- function restoreEnv(prev) {
37
- for (const key of Object.keys(prev)) {
38
- if (prev[key] === undefined) delete process.env[key];
39
- else process.env[key] = prev[key];
40
- }
41
- }
42
-
43
- async function withPromptQueue(queue, fn) {
44
- const original = inquirer.prompt;
45
- let idx = 0;
46
- inquirer.prompt = async () => {
47
- if (idx >= queue.length) {
48
- throw new Error('Prompt queue exhausted');
49
- }
50
- return queue[idx++];
51
- };
52
- try {
53
- await fn();
54
- } finally {
55
- inquirer.prompt = original;
56
- }
57
- }
58
-
59
- let tempRoot;
60
- let prevEnv;
61
- let tempHome;
62
- let dca;
63
- let store;
64
-
65
- before(async () => {
66
- tempRoot = mkdtempSync(join(tmpdir(), 'darksol-dca-'));
67
- const env = setTempEnv(tempRoot);
68
- prevEnv = env.prev;
69
- tempHome = env.home;
70
- store = await importFresh('../src/config/store.js');
71
- dca = await importFresh('../src/trading/dca.js');
72
- store.setConfig('chain', 'base');
73
- });
74
-
75
- after(() => {
76
- restoreEnv(prevEnv);
77
- rmSync(tempRoot, { recursive: true, force: true });
78
- });
79
-
80
- test('DCA create/cancel/run CRUD in temp directory', async () => {
81
- await withPromptQueue(
82
- [
83
- {
84
- tokenIn: 'ETH',
85
- tokenOut: 'USDC',
86
- amountPerOrder: '0.1',
87
- interval: 3600,
88
- totalOrders: '2',
89
- },
90
- { confirm: true },
91
- ],
92
- async () => {
93
- await dca.createDCA();
94
- },
95
- );
96
-
97
- const candidates = [
98
- join(tempHome, '.darksol', 'dca', 'orders.json'),
99
- join(process.env.HOME || '', '.darksol', 'dca', 'orders.json'),
100
- join(process.env.USERPROFILE || '', '.darksol', 'dca', 'orders.json'),
101
- ];
102
- const ordersPath = candidates.find((p) => p && existsSync(p));
103
- assert.ok(ordersPath, 'orders.json should exist');
104
- let orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
105
- assert.equal(orders.length, 1);
106
- assert.equal(orders[0].status, 'active');
107
-
108
- await dca.cancelDCA(orders[0].id);
109
- orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
110
- assert.equal(orders[0].status, 'cancelled');
111
-
112
- await withPromptQueue(
113
- [
114
- {
115
- tokenIn: 'ETH',
116
- tokenOut: 'USDC',
117
- amountPerOrder: '0.2',
118
- interval: 3600,
119
- totalOrders: '1',
120
- },
121
- { confirm: true },
122
- ],
123
- async () => {
124
- await dca.createDCA();
125
- },
126
- );
127
-
128
- orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
129
- const active = orders.find((o) => o.status === 'active');
130
- assert.ok(active);
131
- active.nextExecution = new Date(Date.now() - 1000).toISOString();
132
- writeFileSync(ordersPath, JSON.stringify(orders, null, 2));
133
-
134
- await dca.runDCA({ password: 'unused-in-simulated-run' });
135
- orders = JSON.parse(readFileSync(ordersPath, 'utf8'));
136
- const ran = orders.find((o) => o.id === active.id);
137
- assert.equal(ran.executedOrders, 1);
138
- assert.equal(ran.status, 'completed');
139
- assert.equal(ran.history.length, 1);
140
- assert.equal(ran.history[0].status, 'simulated');
141
- });
@@ -1,94 +0,0 @@
1
- import test, { before, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
-
7
- function importFresh(relativePath) {
8
- const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
9
- return import(url);
10
- }
11
-
12
- function setTempEnv(tempRoot) {
13
- const home = join(tempRoot, 'home');
14
- const appData = join(tempRoot, 'appdata');
15
- const localAppData = join(tempRoot, 'localappdata');
16
- mkdirSync(home, { recursive: true });
17
- mkdirSync(appData, { recursive: true });
18
- mkdirSync(localAppData, { recursive: true });
19
-
20
- const prev = {
21
- HOME: process.env.HOME,
22
- USERPROFILE: process.env.USERPROFILE,
23
- APPDATA: process.env.APPDATA,
24
- LOCALAPPDATA: process.env.LOCALAPPDATA,
25
- };
26
-
27
- process.env.HOME = home;
28
- process.env.USERPROFILE = home;
29
- process.env.APPDATA = appData;
30
- process.env.LOCALAPPDATA = localAppData;
31
-
32
- return { prev, home };
33
- }
34
-
35
- function restoreEnv(prev) {
36
- for (const key of Object.keys(prev)) {
37
- if (prev[key] === undefined) delete process.env[key];
38
- else process.env[key] = prev[key];
39
- }
40
- }
41
-
42
- let tempRoot;
43
- let prevEnv;
44
- let keystore;
45
-
46
- before(async () => {
47
- tempRoot = mkdtempSync(join(tmpdir(), 'darksol-keystore-'));
48
- const env = setTempEnv(tempRoot);
49
- prevEnv = env.prev;
50
- keystore = await importFresh('../src/wallet/keystore.js');
51
- });
52
-
53
- after(() => {
54
- restoreEnv(prevEnv);
55
- rmSync(tempRoot, { recursive: true, force: true });
56
- });
57
-
58
- test('encrypt/decrypt roundtrip', () => {
59
- const privateKey = '0x' + '11'.repeat(32);
60
- const password = 'strong-password-123';
61
- const encrypted = keystore.encryptKey(privateKey, password);
62
- const decrypted = keystore.decryptKey(encrypted, password);
63
- assert.equal(decrypted, privateKey);
64
- });
65
-
66
- test('decrypt with wrong password throws', () => {
67
- const encrypted = keystore.encryptKey('0x' + '22'.repeat(32), 'correct-password');
68
- assert.throws(() => keystore.decryptKey(encrypted, 'wrong-password'));
69
- });
70
-
71
- test('wallet CRUD in temp home directory', () => {
72
- const name = 'alice';
73
- const address = '0x1234567890abcdef1234567890abcdef12345678';
74
- const encrypted = keystore.encryptKey('0x' + '33'.repeat(32), 'wallet-pass');
75
-
76
- const walletFile = keystore.saveWallet(name, address, encrypted, { chain: 'base' });
77
- assert.ok(walletFile.startsWith(keystore.WALLET_DIR));
78
- assert.equal(keystore.walletExists(name), true);
79
-
80
- const loaded = keystore.loadWallet(name);
81
- assert.equal(loaded.name, name);
82
- assert.equal(loaded.address, address);
83
- assert.equal(loaded.chain, 'base');
84
- assert.ok(loaded.createdAt);
85
-
86
- const wallets = keystore.listWallets();
87
- assert.equal(wallets.length, 1);
88
- assert.equal(wallets[0].name, name);
89
- assert.equal(wallets[0].address, address);
90
-
91
- keystore.deleteWallet(name);
92
- assert.equal(keystore.walletExists(name), false);
93
- assert.throws(() => keystore.loadWallet(name), /not found/i);
94
- });
@@ -1,136 +0,0 @@
1
- import test, { before, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtempSync, mkdirSync, rmSync, readFileSync, existsSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import inquirer from 'inquirer';
7
-
8
- function importFresh(relativePath) {
9
- const url = new URL(`${relativePath}?t=${Date.now()}-${Math.random()}`, import.meta.url);
10
- return import(url);
11
- }
12
-
13
- function setTempEnv(tempRoot) {
14
- const home = join(tempRoot, 'home');
15
- const appData = join(tempRoot, 'appdata');
16
- const localAppData = join(tempRoot, 'localappdata');
17
- mkdirSync(home, { recursive: true });
18
- mkdirSync(appData, { recursive: true });
19
- mkdirSync(localAppData, { recursive: true });
20
-
21
- const prev = {
22
- HOME: process.env.HOME,
23
- USERPROFILE: process.env.USERPROFILE,
24
- APPDATA: process.env.APPDATA,
25
- LOCALAPPDATA: process.env.LOCALAPPDATA,
26
- };
27
-
28
- process.env.HOME = home;
29
- process.env.USERPROFILE = home;
30
- process.env.APPDATA = appData;
31
- process.env.LOCALAPPDATA = localAppData;
32
-
33
- return { prev, home };
34
- }
35
-
36
- function restoreEnv(prev) {
37
- for (const key of Object.keys(prev)) {
38
- if (prev[key] === undefined) delete process.env[key];
39
- else process.env[key] = prev[key];
40
- }
41
- }
42
-
43
- async function withPromptQueue(queue, fn) {
44
- const original = inquirer.prompt;
45
- let idx = 0;
46
- inquirer.prompt = async () => {
47
- if (idx >= queue.length) {
48
- throw new Error('Prompt queue exhausted');
49
- }
50
- return queue[idx++];
51
- };
52
- try {
53
- await fn();
54
- } finally {
55
- inquirer.prompt = original;
56
- }
57
- }
58
-
59
- let tempRoot;
60
- let prevEnv;
61
- let tempHome;
62
- let engine;
63
- let store;
64
-
65
- before(async () => {
66
- tempRoot = mkdtempSync(join(tmpdir(), 'darksol-scripts-'));
67
- const env = setTempEnv(tempRoot);
68
- prevEnv = env.prev;
69
- tempHome = env.home;
70
- store = await importFresh('../src/config/store.js');
71
- engine = await importFresh('../src/scripts/engine.js');
72
- store.setConfig('activeWallet', 'test-wallet');
73
- store.setConfig('chain', 'base');
74
- });
75
-
76
- after(() => {
77
- restoreEnv(prevEnv);
78
- rmSync(tempRoot, { recursive: true, force: true });
79
- });
80
-
81
- test('script templates have expected structure', () => {
82
- for (const [key, tmpl] of Object.entries(engine.TEMPLATES)) {
83
- assert.ok(key.length > 0);
84
- assert.equal(typeof tmpl.name, 'string');
85
- assert.equal(typeof tmpl.description, 'string');
86
- assert.ok(Array.isArray(tmpl.params));
87
- assert.equal(typeof tmpl.template, 'string');
88
- assert.match(tmpl.template, /module\.exports\s*=\s*async function/);
89
- }
90
- });
91
-
92
- test('script CRUD works in temp directory', async () => {
93
- await withPromptQueue(
94
- [
95
- { templateKey: 'empty' },
96
- { scriptName: 'alpha' },
97
- { walletName: 'test-wallet' },
98
- ],
99
- async () => {
100
- await engine.createScript();
101
- },
102
- );
103
-
104
- const scriptsDir = join(tempHome, '.darksol', 'scripts');
105
- const alphaPath = join(scriptsDir, 'alpha.json');
106
- assert.equal(existsSync(alphaPath), true);
107
-
108
- const alpha = JSON.parse(readFileSync(alphaPath, 'utf8'));
109
- assert.equal(alpha.name, 'alpha');
110
- assert.equal(alpha.template, 'empty');
111
- assert.equal(alpha.wallet, 'test-wallet');
112
- assert.equal(alpha.chain, 'base');
113
- assert.deepEqual(alpha.params, {});
114
-
115
- await engine.cloneScript('alpha', 'beta');
116
- const betaPath = join(scriptsDir, 'beta.json');
117
- assert.equal(existsSync(betaPath), true);
118
-
119
- await withPromptQueue(
120
- [
121
- { what: 'description' },
122
- { desc: 'updated description' },
123
- ],
124
- async () => {
125
- await engine.editScript('alpha');
126
- },
127
- );
128
-
129
- const edited = JSON.parse(readFileSync(alphaPath, 'utf8'));
130
- assert.equal(edited.description, 'updated description');
131
-
132
- await withPromptQueue([{ confirm: true }], async () => {
133
- await engine.deleteScript('beta');
134
- });
135
- assert.equal(existsSync(betaPath), false);
136
- });
@@ -1,21 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { ethers } from 'ethers';
4
- import { resolveToken } from '../src/trading/swap.js';
5
-
6
- test('resolveToken resolves known symbols', () => {
7
- assert.equal(resolveToken('ETH', 'base'), ethers.ZeroAddress);
8
- assert.equal(resolveToken('usdc', 'base'), '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
9
- assert.equal(resolveToken('WETH', 'ethereum'), '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2');
10
- });
11
-
12
- test('resolveToken accepts direct token addresses', () => {
13
- const address = '0x1234567890abcdef1234567890abcdef12345678';
14
- assert.equal(resolveToken(address, 'base'), address);
15
- });
16
-
17
- test('resolveToken handles unknown symbols and chain fallback', () => {
18
- assert.equal(resolveToken('NOT_A_TOKEN', 'base'), null);
19
- assert.equal(resolveToken('USDC', 'unknown-chain'), '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913');
20
- assert.equal(resolveToken('0xabc', 'base'), null);
21
- });
package/tests/ui.test.js DELETED
@@ -1,27 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { formatPrice, formatChange, formatAddress } from '../src/ui/components.js';
4
-
5
- function stripAnsi(value) {
6
- return String(value).replace(/\x1B\[[0-9;]*m/g, '');
7
- }
8
-
9
- test('formatPrice formats values and handles invalid inputs', () => {
10
- assert.equal(formatPrice(12.3456), '$12.35');
11
- assert.equal(formatPrice(0.001234), '$0.001234');
12
- assert.equal(stripAnsi(formatPrice('invalid')), 'N/A');
13
- });
14
-
15
- test('formatChange formats positive/negative/zero values', () => {
16
- assert.equal(stripAnsi(formatChange(2.3456)), '+2.35%');
17
- assert.equal(stripAnsi(formatChange(-2.3456)), '-2.35%');
18
- assert.equal(stripAnsi(formatChange(0)), '+0.00%');
19
- assert.equal(stripAnsi(formatChange('x')), 'N/A');
20
- });
21
-
22
- test('formatAddress shortens addresses predictably', () => {
23
- const addr = '0x1234567890abcdef1234567890abcdef12345678';
24
- assert.equal(formatAddress(addr), '0x1234...5678');
25
- assert.equal(formatAddress(addr, 8), '0x123456...5678');
26
- assert.equal(stripAnsi(formatAddress('')), 'N/A');
27
- });