@darksol/terminal 0.6.0 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.6.0",
3
+ "version": "0.6.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/skill/SKILL.md CHANGED
@@ -1,177 +1,356 @@
1
1
  ---
2
2
  name: darksol-terminal
3
- description: "DARKSOL Terminal — unified CLI for trading, wallets, execution scripts, AI-powered market analysis, secure agent signing, and all DARKSOL services. Use when: (1) swapping/sniping tokens on Base/Ethereum/L2s, (2) managing encrypted wallets, (3) running automated trading scripts, (4) DCA strategies, (5) market intel lookups, (6) on-chain oracle/casino/cards, (7) natural language trading via LLM, (8) signing transactions securely for x402/contracts without exposing private keys."
3
+ description: "DARKSOL Terminal — unified CLI + x402 platform for trading, wallets, AI-powered market analysis, on-chain casino (4 games), random oracle (x402 auto-pay), prepaid crypto cards, secure agent signing, execution scripts, and all DARKSOL services. Use when: (1) swapping/sniping tokens on Base/Ethereum/L2s, (2) managing encrypted wallets, (3) running automated trading scripts, (4) DCA strategies, (5) market intel lookups, (6) playing on-chain casino games (coinflip, dice, hilo, slots), (7) using the random oracle (x402-gated, auto-pays via agent signer), (8) ordering prepaid Visa/Mastercard cards with crypto, (9) natural language trading via LLM, (10) signing transactions securely for x402/contracts without exposing private keys."
4
4
  ---
5
5
 
6
- # DARKSOL Terminal
6
+ # DARKSOL Terminal — OpenClaw Agent Skill
7
7
 
8
8
  **All DARKSOL services. One terminal. Zero trust required. 🌑**
9
9
 
10
- ## Install
10
+ `@darksol/terminal` v0.6.x | npm: `npm install -g @darksol/terminal`
11
11
 
12
+ ---
13
+
14
+ ## For OpenClaw Agents — How To Use This
15
+
16
+ You're an AI agent. This skill gives you a full crypto terminal. Here's what matters:
17
+
18
+ ### Setup (one-time)
12
19
  ```bash
13
20
  npm install -g @darksol/terminal
21
+ darksol setup # Interactive first-run wizard
22
+ darksol config set output json # JSON output for programmatic parsing
14
23
  ```
15
24
 
16
- ## Quick Reference
25
+ ### Agent Signer (REQUIRED for trading + x402)
26
+ The agent signer is your secure wallet interface. It runs as a local HTTP server and signs transactions without ever exposing the private key.
17
27
 
18
- ### Wallet Management
19
28
  ```bash
20
- darksol wallet create <name> # Create new wallet (AES-256-GCM encrypted)
29
+ # Start the signer with a wallet
30
+ darksol signer start <wallet-name>
31
+ darksol signer start <wallet-name> --max-value 0.5 --daily-limit 2.0
32
+
33
+ # Or set env vars for non-interactive use
34
+ export DARKSOL_WALLET_PASSWORD=<password>
35
+ export DARKSOL_SIGNER_TOKEN=<token> # Set after first start, reuse for API calls
36
+ ```
37
+
38
+ **Signer API (127.0.0.1:18790):**
39
+ | Endpoint | Method | What |
40
+ |---|---|---|
41
+ | `/health` | GET | Check signer status |
42
+ | `/address` | GET | Get wallet address |
43
+ | `/balance` | GET | ETH balance |
44
+ | `/send` | POST | Sign + broadcast transaction |
45
+ | `/sign-message` | POST | Sign EIP-191 message |
46
+ | `/sign-typed-data` | POST | Sign EIP-712 typed data (x402) |
47
+ | `/policy` | GET | Spending limits + daily remaining |
48
+ | `/audit` | GET | Last 50 operations log |
49
+
50
+ **Security:** PK never leaves the signer process. Bearer token auth. Blocked selectors (transferOwnership, selfdestruct). Spending limits enforced. Full audit log.
51
+
52
+ ---
53
+
54
+ ## Complete Command Reference
55
+
56
+ ### 💰 Wallet Management
57
+ ```bash
58
+ darksol wallet create <name> # Create new wallet (AES-256-GCM + scrypt)
21
59
  darksol wallet import <name> # Import from private key
22
60
  darksol wallet list # List all wallets
23
- darksol wallet balance [name] # Check ETH + USDC balance
61
+ darksol wallet balance [name] # ETH + USDC balance
24
62
  darksol wallet use <name> # Set active wallet
25
- darksol wallet export [name] # Export details (password required for PK)
63
+ darksol wallet export [name] # Export (password required for PK)
26
64
  ```
27
65
 
28
- ### Trading
66
+ ### 📊 Trading (5 chains)
29
67
  ```bash
30
- darksol trade swap -i ETH -o USDC -a 0.1 # Swap via Uniswap V3
68
+ darksol trade swap -i ETH -o USDC -a 0.1 # Uniswap V3 swap with slippage protection
69
+ darksol trade swap -i USDC -o ETH -a 100 -c polygon # Swap on Polygon
31
70
  darksol trade snipe <token> -a 0.05 # Fast buy with gas boost
32
71
  darksol trade snipe <token> -a 0.05 -g 2.0 # Snipe with 2x gas priority
33
- darksol trade watch # Monitor new pairs
72
+ darksol trade watch # Monitor new pairs (experimental)
73
+ darksol send # Interactive ETH/ERC-20 transfer
74
+ darksol receive # Show your address for receiving
34
75
  ```
35
76
 
36
- ### DCA (Dollar-Cost Averaging)
77
+ **Supported chains:** Base (default), Ethereum, Polygon, Arbitrum, Optimism
78
+ **Swap routers:** Base uses SwapRouter02 (V2), others use V1 SwapRouter. Handled automatically.
79
+
80
+ ### 📈 DCA (Dollar-Cost Averaging)
37
81
  ```bash
38
- darksol dca create # Interactive DCA order creation
39
- darksol dca list # List active DCA orders
82
+ darksol dca create # Interactive DCA setup
83
+ darksol dca list # Active orders
40
84
  darksol dca run # Execute pending orders
41
- darksol dca cancel <id> # Cancel an order
85
+ darksol dca cancel <id> # Cancel
42
86
  ```
43
87
 
44
- ### AI Trading Assistant
88
+ ### 🤖 AI Trading Assistant
45
89
  ```bash
46
- darksol ai chat # Interactive AI trading chat
47
- darksol ai ask "buy 0.5 ETH of AERO" # Parse natural language trade intent
90
+ darksol ai chat # Interactive AI chat (supports swap/send/price/casino/cards)
91
+ darksol ai ask "buy 0.5 ETH of AERO" # Parse natural language trade intent
92
+ darksol ai ask "flip a coin" -x # Auto-execute if confidence ≥ 60%
48
93
  darksol ai strategy VIRTUAL -b 500 # DCA strategy recommendation
49
- darksol ai analyze AERO # AI-powered token analysis
94
+ darksol ai analyze AERO # Token analysis
50
95
  ```
51
96
 
52
- ### Execution Scripts
97
+ **AI Intent Actions:** swap, send, snipe, dca, price, balance, info, analyze, gas, cards, casino, unknown
98
+
99
+ The AI understands natural language and maps it to executable commands:
100
+ - "swap 100 USDC to ETH" → `darksol trade swap -i USDC -o ETH -a 100`
101
+ - "bet on heads" → `darksol casino bet coinflip -c heads`
102
+ - "order a $50 prepaid card" → `darksol cards order -a 50`
103
+ - "what's the price of AERO" → `darksol market token AERO`
104
+
105
+ ### 📝 Execution Scripts
53
106
  ```bash
54
- darksol script templates # List available templates
55
- darksol script create # Create from template (buy, sell, limit-buy, stop-loss, etc.)
56
- darksol script list # List saved scripts
57
- darksol script run <name> # Execute (requires wallet password)
58
- darksol script run <name> -p "pw" -y # Non-interactive (for automation/cron)
107
+ darksol script templates # 7 templates: buy, sell, limit-buy, stop-loss, multi-buy, transfer, empty
108
+ darksol script create # Interactive template builder
109
+ darksol script list # Saved scripts
110
+ darksol script run <name> # Execute (password required)
111
+ darksol script run <name> -p "pw" -y # Non-interactive (for cron/automation)
59
112
  darksol script show <name> # View code + params
60
- darksol script edit <name> # Edit params/wallet/chain
61
- darksol script clone <name> <new> # Clone a script
62
- darksol script delete <name> # Delete a script
113
+ darksol script edit <name> # Edit
114
+ darksol script clone <name> <new> # Clone
115
+ darksol script delete <name> # Delete
63
116
  ```
64
117
 
65
- Script templates: `buy-token`, `sell-token`, `limit-buy`, `stop-loss`, `multi-buy`, `transfer`, `empty` (custom)
66
-
67
- ### Market Intel
118
+ ### 📈 Market Intel
68
119
  ```bash
69
- darksol market top # Top movers on Base
70
- darksol market top -c ethereum # Top movers on Ethereum
71
- darksol market token VIRTUAL # Full token detail
120
+ darksol market top # Top movers on active chain
121
+ darksol market top -c ethereum # Top movers on specific chain
122
+ darksol market token VIRTUAL # Full token detail (price, volume, liquidity, chain, DEX)
72
123
  darksol market compare ETH AERO VIRTUAL # Side-by-side comparison
124
+ darksol price ETH AERO USDC # Quick multi-token price check
125
+ darksol watch ETH # Live streaming price updates
73
126
  ```
74
127
 
75
- ### Secure Agent Signer (for OpenClaw / AI agents)
128
+ ### 🎰 Casino (The Clawsino)
129
+ All bets are $1 USDC. House edge: 5%. Results verified on-chain.
130
+
76
131
  ```bash
77
- darksol agent start <wallet> # Start signing proxy
78
- darksol agent start <wallet> --max-value 0.5 --daily-limit 2.0
79
- darksol agent docs # Full security documentation
132
+ darksol casino status # House stats, balance, game list
133
+ darksol casino bet # Interactive (picks game params → wallet → confirm)
134
+ darksol casino bet coinflip -c heads # Coin flip — 1.90x payout
135
+ darksol casino bet dice -d over -t 3 # Dice over 3 — variable payout
136
+ darksol casino bet hilo -c higher # Hi-Lo — ~2.06x payout
137
+ darksol casino bet slots # Slots — 1.50-5.00x payout
138
+ darksol casino tables # Recent bets
139
+ darksol casino receipt <id> # Bet receipt
140
+ darksol casino verify <id> # On-chain verification (Basescan links)
141
+ ```
142
+
143
+ **API:** `POST https://casino.darksol.net/api/bet`
144
+ ```json
145
+ {
146
+ "gameType": "coinflip",
147
+ "betParams": { "choice": "heads" },
148
+ "agentWallet": "0x..."
149
+ }
80
150
  ```
81
151
 
82
- The agent signer creates a local HTTP server at `127.0.0.1:18790` that signs transactions without exposing the private key. AI agents authenticate with a one-time bearer token.
152
+ **Games:**
153
+ | Game | Params | Payout |
154
+ |---|---|---|
155
+ | `coinflip` | `{ "choice": "heads"\|"tails" }` | 1.90x |
156
+ | `dice` | `{ "direction": "over"\|"under", "threshold": 2-5 }` | variable |
157
+ | `hilo` | `{ "choice": "higher"\|"lower" }` | ~2.06x |
158
+ | `slots` | `{}` | 1.50-5.00x |
83
159
 
84
- **Endpoints:**
85
- - `GET /address` wallet address
86
- - `GET /balance` — ETH balance
87
- - `POST /send` — sign + broadcast transaction
88
- - `POST /sign-message` — sign EIP-191 message (x402)
89
- - `POST /sign-typed-data` — sign EIP-712 typed data (x402)
90
- - `GET /policy` — spending limits
91
- - `GET /audit` — operation log
160
+ ### 🎲 Random Oracle (x402-gated)
161
+ On-chain verifiable randomness. Each call costs $0.05 USDC on Base via x402 protocol.
92
162
 
93
- ### Oracle
94
163
  ```bash
164
+ darksol oracle health # Status (free)
95
165
  darksol oracle flip # Coin flip
96
166
  darksol oracle dice 20 # Roll d20
97
167
  darksol oracle number 1 100 # Random 1-100
98
168
  darksol oracle shuffle a b c d # Shuffle list
99
169
  ```
100
170
 
101
- ### Casino
171
+ **x402 Auto-Pay:** If the agent signer is running, oracle requests auto-pay via EIP-3009 (transferWithAuthorization). No manual payment needed.
172
+
173
+ **API:** `https://acp.darksol.net/api/oracle/`
174
+ - `GET /health` — free, returns status + contract address
175
+ - `GET /coin` — 402 → x402 payment → result
176
+ - `GET /dice?sides=N` — 402 → x402 payment → result
177
+ - `GET /number?min=N&max=M` — 402 → x402 payment → result
178
+ - `POST /shuffle` — 402 → x402 payment → result
179
+
180
+ ### 💳 Prepaid Cards (Crypto → Visa/Mastercard)
102
181
  ```bash
103
- darksol casino bet coin-flip heads # Place a bet
104
- darksol casino tables # View games
105
- darksol casino stats # House stats
106
- darksol casino receipt <id> # Verify on-chain
182
+ darksol cards catalog # Available providers + amounts
183
+ darksol cards order # Interactive (prompts for everything)
184
+ darksol cards order -p swype -a 50 -e me@email.com -t usdc # Full flags
185
+ darksol cards status <tradeId> # Check order status
107
186
  ```
108
187
 
109
- ### Prepaid Cards
188
+ **Providers:** swype (Mastercard, Global), mpc (Mastercard, US), reward (Visa, US)
189
+ **Amounts:** $10, $25, $50, $100, $250, $500, $1000
190
+ **Crypto payments:** usdc/base, usdc/ERC20, usdt/trc20, btc/Mainnet, eth/ERC20, sol/Mainnet, xmr/Mainnet
191
+
192
+ Invalid inputs re-prompt instead of failing — fully guided flow.
193
+
194
+ ### 🔗 x402 Facilitator
195
+ Free on-chain payment settlement. Zero fees — DARKSOL covers gas.
196
+
110
197
  ```bash
111
- darksol cards catalog # Available providers
112
- darksol cards order -p swype -a 50 # Order a card
113
- darksol cards status <id> # Check order
198
+ darksol facilitator health # Status, chains, contracts, settlement stats
199
+ darksol facilitator verify <payment> # Verify payment off-chain
200
+ darksol facilitator settle <payment> # Settle on-chain (free)
114
201
  ```
115
202
 
116
- ### Builder Index
203
+ **Chains:** Base + Polygon
204
+ **API:** `https://facilitator.darksol.net/`
205
+ - `GET /` — service info + chain status
206
+ - `POST /verify` — verify payment
207
+ - `POST /settle` — settle on-chain
208
+
209
+ ### 🏗️ Builder Index
117
210
  ```bash
118
211
  darksol builders leaderboard # ERC-8021 builder rankings
119
212
  darksol builders lookup <code> # Builder profile
120
213
  darksol builders feed # Recent transactions
121
214
  ```
122
215
 
123
- ### Facilitator
216
+ ### 📧 AgentMail
124
217
  ```bash
125
- darksol facilitator health # Status
126
- darksol facilitator verify <payment> # Verify off-chain
127
- darksol facilitator settle <payment> # Settle on-chain (free)
218
+ darksol mail setup # Set up email inbox
219
+ darksol mail inbox # View messages
220
+ darksol mail send <to> -s "Subject" # Send email
221
+ darksol mail read <id> # Read message
222
+ darksol mail reply <id> # Reply
128
223
  ```
129
224
 
130
- ### API Keys
225
+ ### Gas & Network
131
226
  ```bash
132
- darksol keys list # Show all services + status
133
- darksol keys add openai # Add OpenAI key
134
- darksol keys add coingecko # Add CoinGecko Pro key
135
- darksol keys add alchemy # Add Alchemy RPC key
136
- darksol keys remove <service> # Remove a key
227
+ darksol gas # Gas prices on active chain
228
+ darksol gas --all # Gas across all 5 chains
229
+ darksol networks # Chain reference table
137
230
  ```
138
231
 
139
- Supported: `openai`, `anthropic`, `openrouter`, `ollama`, `coingecko`, `dexscreener`, `alchemy`, `infura`, `quicknode`, `oneinch`, `paraswap`
140
-
141
- ### Configuration
232
+ ### 🔧 Configuration
142
233
  ```bash
143
234
  darksol config show # View all settings
144
235
  darksol config set chain base # Set active chain
145
- darksol config set slippage 1.0 # Set slippage %
146
- darksol config rpc base https://... # Custom RPC endpoint
236
+ darksol config set slippage 1.0 # Slippage %
237
+ darksol config rpc base https://... # Custom RPC
147
238
  ```
148
239
 
149
- ### Reference
240
+ ### 🔑 API Keys (Encrypted Vault)
241
+ ```bash
242
+ darksol keys list # All services + status
243
+ darksol keys add openai # Add key (encrypted AES-256-GCM)
244
+ darksol keys add anthropic # Supported: openai, anthropic, openrouter, ollama,
245
+ darksol keys add email # coingecko, dexscreener, alchemy, infura, email
246
+ darksol keys remove <service> # Remove key
247
+ ```
248
+
249
+ ### 🌐 Web Shell (GUI)
250
+ ```bash
251
+ darksol serve # Launch web terminal at localhost:18791
252
+ darksol serve -p 3000 # Custom port
253
+ ```
254
+
255
+ Web shell includes: full CLI, AI chat, interactive menus, casino, cards ordering, wallet management, agent signer controls.
256
+
257
+ ### 📚 Reference
150
258
  ```bash
151
259
  darksol tips # Trading + scripting tips
152
- darksol tips --trading # Trading tips only
153
- darksol networks # Chain reference table
154
260
  darksol quickstart # Getting started guide
155
- darksol lookup 0x... # Look up address on-chain
261
+ darksol lookup 0x... # On-chain address lookup
262
+ darksol setup # Re-run setup wizard
156
263
  ```
157
264
 
158
- ## Supported Chains
159
- - **Base** (default) — chain ID 8453
160
- - **Ethereum** chain ID 1
161
- - **Polygon** — chain ID 137
162
- - **Arbitrum** chain ID 42161
163
- - **Optimism** — chain ID 10
265
+ ---
266
+
267
+ ## x402 Payment Flow (for agents)
268
+
269
+ The terminal includes a built-in x402 client (`src/utils/x402.js`). When a service returns HTTP 402:
270
+
271
+ 1. Parse `payment-required` header (base64 JSON)
272
+ 2. Sign EIP-3009 `transferWithAuthorization` via agent signer
273
+ 3. Retry request with `X-PAYMENT` header containing the signed authorization
274
+ 4. Facilitator settles on-chain (free, DARKSOL covers gas)
275
+
276
+ **For agents:** Just start the signer and make requests. x402 auto-pay handles the rest.
277
+
278
+ ```javascript
279
+ import { fetchWithX402 } from '@darksol/terminal/src/utils/x402.js';
280
+
281
+ const result = await fetchWithX402(
282
+ 'https://acp.darksol.net/api/oracle/coin',
283
+ {},
284
+ { signerToken: process.env.DARKSOL_SIGNER_TOKEN }
285
+ );
286
+ // result.data = { result: "heads", proof: "0x..." }
287
+ // result.paid = true
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Agent Integration Patterns
293
+
294
+ ### Non-interactive mode (for cron / automation)
295
+ ```bash
296
+ # All trading commands accept flags for non-interactive use
297
+ darksol trade swap -i ETH -o USDC -a 0.1 -y
298
+ darksol script run my-dca -p "password" -y
299
+ darksol casino bet coinflip -c heads -w 0x1234...
300
+
301
+ # Set JSON output for parsing
302
+ darksol config set output json
303
+ ```
304
+
305
+ ### Environment variables
306
+ ```bash
307
+ DARKSOL_WALLET_PASSWORD # Skip password prompts
308
+ DARKSOL_SIGNER_TOKEN # Reuse signer auth token
309
+ ```
310
+
311
+ ### Programmatic imports
312
+ ```javascript
313
+ // Direct service access from Node.js
314
+ import { casinoBet, casinoHealth, GAMES } from '@darksol/terminal/src/services/casino.js';
315
+ import { oracleFlip, oracleDice } from '@darksol/terminal/src/services/oracle.js';
316
+ import { cardsCatalog, cardsOrder } from '@darksol/terminal/src/services/cards.js';
317
+ import { facilitatorHealth } from '@darksol/terminal/src/services/facilitator.js';
318
+ import { fetchWithX402 } from '@darksol/terminal/src/utils/x402.js';
319
+ import { parseIntent, executeIntent } from '@darksol/terminal/src/llm/intent.js';
320
+ import { topMovers, tokenDetail } from '@darksol/terminal/src/services/market.js';
321
+ ```
322
+
323
+ ### OpenClaw cron example
324
+ ```bash
325
+ # Run a DCA script every 4 hours
326
+ darksol script run eth-dca -p "$DARKSOL_WALLET_PASSWORD" -y
327
+ ```
328
+
329
+ ---
330
+
331
+ ## Service Endpoints
332
+
333
+ | Service | URL | Auth |
334
+ |---|---|---|
335
+ | Casino | `https://casino.darksol.net/api/` | None (wallet for payouts) |
336
+ | Oracle | `https://acp.darksol.net/api/oracle/` | x402 ($0.05 USDC) |
337
+ | Cards | `https://acp.darksol.net/api/cards/` | None |
338
+ | Facilitator | `https://facilitator.darksol.net/` | None |
339
+ | Builders | `https://builders.darksol.net/` | None |
340
+ | Casino Docs | `https://casino.darksol.net/docs` | — |
341
+ | Oracle Docs | `https://acp.darksol.net/oracle` | — |
342
+ | Facilitator Docs | `https://acp.darksol.net/facilitator` | — |
343
+
344
+ ---
164
345
 
165
- ## Agent Integration Notes
346
+ ## Security Model
166
347
 
167
- - All commands work non-interactively with flags (`-p`, `-y`, `--key`, etc.)
168
- - Set `darksol config set output json` for programmatic JSON responses
169
- - Scripts can be executed via cron: `darksol script run my-dca -p "pass" -y`
170
- - The agent signer is the recommended way to give AI agents wallet access
171
- - Helper functions available at `@darksol/terminal/src/utils/helpers.js`
348
+ - **Private keys:** AES-256-GCM + scrypt (N=2^18), never stored in plaintext
349
+ - **Agent signer:** PK-isolated HTTP proxy, bearer auth, loopback only (127.0.0.1)
350
+ - **Spending limits:** Per-tx max value + daily spend limit
351
+ - **Blocked selectors:** transferOwnership, selfdestruct, approve(max), setApprovalForAll
352
+ - **Audit log:** Every sign/send operation logged with timestamp + details
353
+ - **API key vault:** AES-256-GCM encrypted, machine-derived password
354
+ - **No PK endpoint:** Literally no code path returns the private key
172
355
 
173
- ## Security
174
- - Private keys encrypted with AES-256-GCM + scrypt KDF
175
- - Agent signer: PK never exposed, loopback-only, bearer auth, spending limits
176
- - Dangerous contract calls (transferOwnership, selfdestruct) blocked by default
177
- - Full audit logging on all signing operations
356
+ Built with teeth. 🌑
@@ -29,7 +29,7 @@ const config = new Conf({
29
29
  services: {
30
30
  type: 'object',
31
31
  default: {
32
- oracle: 'https://acp.darksol.net/oracle',
32
+ oracle: 'https://acp.darksol.net/api/oracle',
33
33
  casino: 'https://casino.darksol.net',
34
34
  cards: 'https://acp.darksol.net',
35
35
  facilitator: 'https://facilitator.darksol.net',
@@ -1,88 +1,126 @@
1
1
  import { fetchJSON } from '../utils/fetch.js';
2
- import fetch from 'node-fetch';
3
- import { getServiceURL } from '../config/store.js';
2
+ import { fetchWithX402, isSignerRunning } from '../utils/x402.js';
3
+ import { getServiceURL, getConfig } from '../config/store.js';
4
4
  import { theme } from '../ui/theme.js';
5
- import { spinner, kvDisplay, success, error } from '../ui/components.js';
5
+ import { spinner, kvDisplay, success, error, warn, info } from '../ui/components.js';
6
6
  import { showSection } from '../ui/banner.js';
7
7
 
8
- const getURL = () => getServiceURL('oracle') || 'https://acp.darksol.net/oracle';
8
+ // Oracle lives at acp.darksol.net/api/oracle/
9
+ // Health is free; game endpoints are x402-gated ($0.05 USDC on Base)
10
+ const getURL = () => getServiceURL('oracle') || 'https://acp.darksol.net/api/oracle';
9
11
 
10
- export async function oracleFlip() {
11
- const spin = spinner('Flipping coin...').start();
12
+ function getSignerToken() {
13
+ return process.env.DARKSOL_SIGNER_TOKEN || getConfig('signerToken') || null;
14
+ }
15
+
16
+ export async function oracleHealth() {
17
+ const spin = spinner('Checking oracle...').start();
12
18
  try {
13
- const data = await fetchJSON(`${getURL()}/api/coin`);
14
- spin.succeed('Coin flipped');
15
- showSection('ORACLE — COIN FLIP');
19
+ const data = await fetchJSON(`${getURL()}/health`);
20
+ spin.succeed('Oracle online');
21
+
22
+ showSection('RANDOM ORACLE 🎲');
16
23
  kvDisplay([
17
- ['Result', theme.gold.bold(data.result || data.value)],
18
- ['Proof', data.proof || data.txHash || 'N/A'],
24
+ ['Status', data.status === 'ok' ? theme.success('● Online') : theme.error('○ ' + data.status)],
25
+ ['Contract', data.contract || '-'],
26
+ ['Chain', data.chain || 'base'],
27
+ ['Block', String(data.blockNumber || '-')],
19
28
  ]);
29
+
30
+ // Check signer status
31
+ const signerUp = await isSignerRunning(getSignerToken());
32
+ console.log('');
33
+ if (signerUp) {
34
+ console.log(` ${theme.success('●')} Agent signer running — x402 auto-pay enabled`);
35
+ } else {
36
+ console.log(` ${theme.dim('○')} Agent signer not running — start for auto-pay: ${theme.gold('darksol signer start')}`);
37
+ }
38
+
39
+ console.log('');
40
+ info('Games: coin flip, dice, random number, shuffle ($0.05 USDC each)');
41
+ info('Docs: https://acp.darksol.net/oracle');
20
42
  } catch (err) {
21
- spin.fail('Oracle failed');
43
+ spin.fail('Oracle unreachable');
22
44
  error(err.message);
23
45
  }
24
46
  }
25
47
 
26
- export async function oracleDice(sides = 6) {
27
- const spin = spinner(`Rolling d${sides}...`).start();
48
+ async function oraclePlay(endpoint, label, displayFn) {
49
+ const spin = spinner(`${label}...`).start();
50
+ const token = getSignerToken();
51
+
28
52
  try {
29
- const data = await fetchJSON(`${getURL()}/api/dice?sides=${sides}`);
30
- spin.succeed('Dice rolled');
31
- showSection(`ORACLE D${sides}`);
32
- kvDisplay([
33
- ['Result', theme.gold.bold(data.result || data.value)],
34
- ['Sides', sides.toString()],
35
- ['Proof', data.proof || data.txHash || 'N/A'],
36
- ]);
53
+ const result = await fetchWithX402(`${getURL()}${endpoint}`, {}, { signerToken: token });
54
+
55
+ if (result.x402 && !result.paid) {
56
+ // Payment required but couldn't auto-pay
57
+ spin.info('x402 payment required');
58
+ const accepts = result.paymentInfo?.accepts?.[0];
59
+ if (accepts) {
60
+ warn(`Cost: $${(parseInt(accepts.amount) / 1e6).toFixed(2)} USDC on Base`);
61
+ }
62
+ if (result.error) {
63
+ info(result.error);
64
+ } else {
65
+ info('Start agent signer for auto-pay: darksol signer start');
66
+ }
67
+ return null;
68
+ }
69
+
70
+ if (result.paid) {
71
+ spin.succeed(`${label} ✓ (paid $0.05 USDC)`);
72
+ } else {
73
+ spin.succeed(label);
74
+ }
75
+
76
+ displayFn(result.data);
77
+ return result.data;
37
78
  } catch (err) {
38
- spin.fail('Oracle failed');
79
+ spin.fail(`${label} failed`);
39
80
  error(err.message);
81
+ return null;
40
82
  }
41
83
  }
42
84
 
85
+ export async function oracleFlip() {
86
+ return oraclePlay('/coin', 'Coin flip', (data) => {
87
+ showSection('ORACLE — COIN FLIP 🪙');
88
+ kvDisplay([
89
+ ['Result', theme.gold.bold(data.result || data.value || '-')],
90
+ ['Proof', data.proof || data.txHash || '-'],
91
+ ]);
92
+ });
93
+ }
94
+
95
+ export async function oracleDice(sides = 6) {
96
+ return oraclePlay(`/dice?sides=${sides}`, `Rolling d${sides}`, (data) => {
97
+ showSection(`ORACLE — D${sides} 🎲`);
98
+ kvDisplay([
99
+ ['Result', theme.gold.bold(data.result || data.value || '-')],
100
+ ['Sides', sides.toString()],
101
+ ['Proof', data.proof || data.txHash || '-'],
102
+ ]);
103
+ });
104
+ }
105
+
43
106
  export async function oracleNumber(min = 1, max = 100) {
44
- const spin = spinner(`Generating number ${min}-${max}...`).start();
45
- try {
46
- const data = await fetchJSON(`${getURL()}/api/number?min=${min}&max=${max}`);
47
- spin.succeed('Number generated');
48
- showSection('ORACLE — RANDOM NUMBER');
107
+ return oraclePlay(`/number?min=${min}&max=${max}`, `Number ${min}-${max}`, (data) => {
108
+ showSection('ORACLE — RANDOM NUMBER 🔢');
49
109
  kvDisplay([
50
- ['Result', theme.gold.bold(data.result || data.value)],
110
+ ['Result', theme.gold.bold(data.result || data.value || '-')],
51
111
  ['Range', `${min} — ${max}`],
52
- ['Proof', data.proof || data.txHash || 'N/A'],
112
+ ['Proof', data.proof || data.txHash || '-'],
53
113
  ]);
54
- } catch (err) {
55
- spin.fail('Oracle failed');
56
- error(err.message);
57
- }
114
+ });
58
115
  }
59
116
 
60
117
  export async function oracleShuffle(items) {
61
- const spin = spinner('Shuffling...').start();
62
- try {
63
- const data = await fetchJSON(`${getURL()}/api/shuffle`, {
64
- method: 'POST',
65
- headers: { 'Content-Type': 'application/json' },
66
- body: JSON.stringify({ items }),
67
- });
68
- spin.succeed('Shuffled');
69
- showSection('ORACLE — SHUFFLE');
70
- console.log(theme.gold(' Result: ') + (data.result || data.value || []).join(', '));
71
- } catch (err) {
72
- spin.fail('Oracle failed');
73
- error(err.message);
74
- }
75
- }
76
-
77
- export async function oracleHealth() {
78
- const spin = spinner('Checking oracle...').start();
79
- try {
80
- const data = await fetchJSON(`${getURL()}/api/health`);
81
- spin.succeed('Oracle online');
82
- showSection('ORACLE STATUS');
83
- kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
84
- } catch (err) {
85
- spin.fail('Oracle unreachable');
86
- error(err.message);
87
- }
118
+ return oraclePlay('/shuffle', 'Shuffling', (data) => {
119
+ showSection('ORACLE — SHUFFLE 🔀');
120
+ const result = data.result || data.value || [];
121
+ console.log(theme.gold(' Result: ') + (Array.isArray(result) ? result.join(', ') : result));
122
+ if (data.proof || data.txHash) {
123
+ console.log(theme.dim(` Proof: ${data.proof || data.txHash}`));
124
+ }
125
+ });
88
126
  }
@@ -0,0 +1,232 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * x402 Payment Flow
5
+ *
6
+ * When a server returns 402 with a `payment-required` header (base64-encoded JSON),
7
+ * this utility:
8
+ * 1. Decodes the payment requirement
9
+ * 2. Signs an EIP-3009 transferWithAuthorization via the local agent signer
10
+ * 3. Retries the original request with the signed payment in the `X-PAYMENT` header
11
+ *
12
+ * Requires: agent signer running at 127.0.0.1:18790
13
+ */
14
+
15
+ const SIGNER_URL = 'http://127.0.0.1:18790';
16
+
17
+ // USDC contract details for EIP-3009
18
+ const USDC_CONTRACTS = {
19
+ 8453: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // Base
20
+ 137: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', // Polygon
21
+ 1: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // Ethereum
22
+ 42161: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // Arbitrum
23
+ 10: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // Optimism
24
+ };
25
+
26
+ /**
27
+ * Parse the payment-required header (base64 JSON)
28
+ */
29
+ function parsePaymentRequired(header) {
30
+ try {
31
+ return JSON.parse(Buffer.from(header, 'base64').toString());
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Check if agent signer is running
39
+ */
40
+ async function isSignerRunning(token) {
41
+ try {
42
+ const headers = token ? { Authorization: `Bearer ${token}` } : {};
43
+ const resp = await fetch(`${SIGNER_URL}/health`, { headers, timeout: 2000 });
44
+ return resp.ok;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Get signer address
52
+ */
53
+ async function getSignerAddress(token) {
54
+ try {
55
+ const headers = token ? { Authorization: `Bearer ${token}` } : {};
56
+ const resp = await fetch(`${SIGNER_URL}/address`, { headers, timeout: 2000 });
57
+ if (!resp.ok) return null;
58
+ const data = await resp.json();
59
+ return data.address;
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Sign EIP-712 typed data for x402 payment (EIP-3009 transferWithAuthorization)
67
+ */
68
+ async function signX402Payment(paymentReq, signerToken) {
69
+ const accepts = paymentReq.accepts?.[0];
70
+ if (!accepts) throw new Error('No payment scheme in x402 requirement');
71
+
72
+ // Parse network — format is "eip155:<chainId>"
73
+ const chainId = parseInt(accepts.network?.split(':')[1] || '8453');
74
+ const usdcAddress = accepts.asset || USDC_CONTRACTS[chainId];
75
+ const amount = accepts.amount; // in smallest unit (e.g., 50000 = $0.05 USDC)
76
+ const payTo = accepts.payTo;
77
+ const deadline = Math.floor(Date.now() / 1000) + (accepts.maxTimeoutSeconds || 300);
78
+
79
+ // Get our address
80
+ const fromAddress = await getSignerAddress(signerToken);
81
+ if (!fromAddress) throw new Error('Cannot get signer address');
82
+
83
+ // Generate random nonce (32 bytes)
84
+ const nonce = '0x' + [...Array(32)].map(() => Math.floor(Math.random() * 256).toString(16).padStart(2, '0')).join('');
85
+
86
+ // EIP-712 domain for USDC
87
+ const domain = {
88
+ name: accepts.extra?.name || 'USD Coin',
89
+ version: accepts.extra?.version || '2',
90
+ chainId: chainId,
91
+ verifyingContract: usdcAddress,
92
+ };
93
+
94
+ // EIP-3009 TransferWithAuthorization types
95
+ const types = {
96
+ TransferWithAuthorization: [
97
+ { name: 'from', type: 'address' },
98
+ { name: 'to', type: 'address' },
99
+ { name: 'value', type: 'uint256' },
100
+ { name: 'validAfter', type: 'uint256' },
101
+ { name: 'validBefore', type: 'uint256' },
102
+ { name: 'nonce', type: 'bytes32' },
103
+ ],
104
+ };
105
+
106
+ const value = {
107
+ from: fromAddress,
108
+ to: payTo,
109
+ value: amount,
110
+ validAfter: '0',
111
+ validBefore: String(deadline),
112
+ nonce: nonce,
113
+ };
114
+
115
+ // Sign via agent signer
116
+ const headers = { 'Content-Type': 'application/json' };
117
+ if (signerToken) headers.Authorization = `Bearer ${signerToken}`;
118
+
119
+ const resp = await fetch(`${SIGNER_URL}/sign-typed-data`, {
120
+ method: 'POST',
121
+ headers,
122
+ body: JSON.stringify({ domain, types, value }),
123
+ });
124
+
125
+ if (!resp.ok) {
126
+ const err = await resp.text();
127
+ throw new Error(`Signer refused: ${err}`);
128
+ }
129
+
130
+ const result = await resp.json();
131
+
132
+ // Build the x402 payment payload
133
+ return {
134
+ x402Version: paymentReq.x402Version || 2,
135
+ scheme: 'exact',
136
+ network: accepts.network || `eip155:${chainId}`,
137
+ payload: {
138
+ signature: result.signature,
139
+ authorization: {
140
+ from: fromAddress,
141
+ to: payTo,
142
+ value: amount,
143
+ validAfter: '0',
144
+ validBefore: String(deadline),
145
+ nonce: nonce,
146
+ },
147
+ },
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Make an x402-aware fetch request.
153
+ *
154
+ * If the server returns 402, this will:
155
+ * 1. Parse the payment requirement
156
+ * 2. Sign the payment via agent signer
157
+ * 3. Retry with the X-PAYMENT header
158
+ *
159
+ * @param {string} url - Request URL
160
+ * @param {object} opts - fetch options
161
+ * @param {object} x402Opts - { signerToken, autoSign }
162
+ * @returns {object} { data, paid, paymentInfo }
163
+ */
164
+ export async function fetchWithX402(url, opts = {}, x402Opts = {}) {
165
+ const { signerToken, autoSign = true } = x402Opts;
166
+
167
+ // First attempt
168
+ const resp = await fetch(url, opts);
169
+
170
+ if (resp.status !== 402) {
171
+ // Normal response
172
+ const ct = resp.headers.get('content-type') || '';
173
+ if (!ct.includes('json')) {
174
+ const text = await resp.text();
175
+ throw new Error(`Non-JSON response (${resp.status}): ${text.substring(0, 100)}`);
176
+ }
177
+ return { data: await resp.json(), paid: false };
178
+ }
179
+
180
+ // 402 — payment required
181
+ const paymentHeader = resp.headers.get('payment-required');
182
+ if (!paymentHeader) {
183
+ throw new Error('402 but no payment-required header');
184
+ }
185
+
186
+ const paymentReq = parsePaymentRequired(paymentHeader);
187
+ if (!paymentReq) {
188
+ throw new Error('Failed to parse payment-required header');
189
+ }
190
+
191
+ if (!autoSign) {
192
+ return { data: null, paid: false, x402: true, paymentInfo: paymentReq };
193
+ }
194
+
195
+ // Check if signer is running
196
+ const signerUp = await isSignerRunning(signerToken);
197
+ if (!signerUp) {
198
+ return {
199
+ data: null,
200
+ paid: false,
201
+ x402: true,
202
+ paymentInfo: paymentReq,
203
+ error: 'Agent signer not running. Start it: darksol signer start',
204
+ };
205
+ }
206
+
207
+ // Sign the payment
208
+ const payment = await signX402Payment(paymentReq, signerToken);
209
+
210
+ // Retry with payment
211
+ const retryOpts = { ...opts };
212
+ retryOpts.headers = {
213
+ ...(opts.headers || {}),
214
+ 'X-PAYMENT': Buffer.from(JSON.stringify(payment)).toString('base64'),
215
+ };
216
+
217
+ const retryResp = await fetch(url, retryOpts);
218
+ const ct = retryResp.headers.get('content-type') || '';
219
+ if (!ct.includes('json')) {
220
+ const text = await retryResp.text();
221
+ throw new Error(`Paid but got non-JSON (${retryResp.status}): ${text.substring(0, 100)}`);
222
+ }
223
+
224
+ if (!retryResp.ok) {
225
+ const errData = await retryResp.json().catch(() => ({}));
226
+ throw new Error(`Payment sent but request failed (${retryResp.status}): ${JSON.stringify(errData)}`);
227
+ }
228
+
229
+ return { data: await retryResp.json(), paid: true, paymentInfo: paymentReq };
230
+ }
231
+
232
+ export { parsePaymentRequired, isSignerRunning, getSignerAddress, signX402Payment };
@@ -1121,17 +1121,31 @@ async function executeCardOrder(orderMeta, ws) {
1121
1121
  // ORACLE
1122
1122
  // ══════════════════════════════════════════════════
1123
1123
  async function cmdOracle(args, ws) {
1124
+ ws.sendLine(`${ANSI.gold} ◆ RANDOM ORACLE 🎲${ANSI.reset}`);
1125
+ ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
1124
1126
  try {
1125
- const resp = await fetch('https://acp.darksol.net/oracle');
1127
+ const resp = await fetch('https://acp.darksol.net/api/oracle/health');
1128
+ const ct = resp.headers.get('content-type') || '';
1129
+ if (!ct.includes('json')) throw new Error('not json');
1126
1130
  const data = await resp.json();
1127
1131
 
1128
- ws.sendLine(`${ANSI.gold} ORACLE${ANSI.reset}`);
1129
- ws.sendLine(`${ANSI.dim} ${''.repeat(50)}${ANSI.reset}`);
1130
- ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${data.status || 'unknown'}`);
1131
- ws.sendLine(` ${ANSI.darkGold}Endpoint${ANSI.reset} ${ANSI.blue}acp.darksol.net/oracle${ANSI.reset}`);
1132
+ ws.sendLine(` ${ANSI.darkGold}Status${ANSI.reset} ${data.status === 'ok' ? `${ANSI.green} Online${ANSI.reset}` : `${ANSI.red}○ ${data.status}${ANSI.reset}`}`);
1133
+ ws.sendLine(` ${ANSI.darkGold}Contract${ANSI.reset} ${ANSI.dim}${data.contract || '-'}${ANSI.reset}`);
1134
+ ws.sendLine(` ${ANSI.darkGold}Chain${ANSI.reset} ${ANSI.white}${data.chain || 'base'}${ANSI.reset}`);
1135
+ ws.sendLine(` ${ANSI.darkGold}Block${ANSI.reset} ${ANSI.white}${data.blockNumber || '-'}${ANSI.reset}`);
1136
+ ws.sendLine('');
1137
+ ws.sendLine(` ${ANSI.gold}ENDPOINTS${ANSI.reset} ${ANSI.dim}x402-gated ($0.05 USDC on Base)${ANSI.reset}`);
1138
+ ws.sendLine(` ${ANSI.white}🪙 /coin${ANSI.reset} ${ANSI.dim}Fair coin flip${ANSI.reset}`);
1139
+ ws.sendLine(` ${ANSI.white}🎲 /dice${ANSI.reset} ${ANSI.dim}Roll with N sides${ANSI.reset}`);
1140
+ ws.sendLine(` ${ANSI.white}🔢 /number${ANSI.reset} ${ANSI.dim}Random in range${ANSI.reset}`);
1141
+ ws.sendLine(` ${ANSI.white}🔀 /shuffle${ANSI.reset} ${ANSI.dim}Shuffle a list${ANSI.reset}`);
1142
+ ws.sendLine('');
1143
+ ws.sendLine(` ${ANSI.dim}CLI: darksol oracle flip / dice / number / shuffle${ANSI.reset}`);
1144
+ ws.sendLine(` ${ANSI.dim}Docs: https://acp.darksol.net/oracle${ANSI.reset}`);
1132
1145
  ws.sendLine('');
1133
1146
  } catch {
1134
- ws.sendLine(` ${ANSI.dim}Oracle unreachable${ANSI.reset}`);
1147
+ ws.sendLine(` ${ANSI.red}Oracle unreachable${ANSI.reset}`);
1148
+ ws.sendLine(` ${ANSI.dim}Check: https://acp.darksol.net/oracle${ANSI.reset}`);
1135
1149
  ws.sendLine('');
1136
1150
  }
1137
1151
  return {};