@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 +1 -1
- package/skill/SKILL.md +273 -94
- package/src/config/store.js +1 -1
- package/src/services/oracle.js +99 -61
- package/src/utils/x402.js +232 -0
- package/src/web/commands.js +20 -6
package/package.json
CHANGED
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] #
|
|
61
|
+
darksol wallet balance [name] # ETH + USDC balance
|
|
24
62
|
darksol wallet use <name> # Set active wallet
|
|
25
|
-
darksol wallet export [name] # Export
|
|
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 #
|
|
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
|
-
|
|
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
|
|
39
|
-
darksol dca list #
|
|
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
|
|
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
|
|
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 #
|
|
94
|
+
darksol ai analyze AERO # Token analysis
|
|
50
95
|
```
|
|
51
96
|
|
|
52
|
-
|
|
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 #
|
|
55
|
-
darksol script create #
|
|
56
|
-
darksol script list #
|
|
57
|
-
darksol script run <name> # Execute (
|
|
58
|
-
darksol script run <name> -p "pw" -y # Non-interactive (for automation
|
|
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
|
|
61
|
-
darksol script clone <name> <new> # Clone
|
|
62
|
-
darksol script delete <name> # Delete
|
|
113
|
+
darksol script edit <name> # Edit
|
|
114
|
+
darksol script clone <name> <new> # Clone
|
|
115
|
+
darksol script delete <name> # Delete
|
|
63
116
|
```
|
|
64
117
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
### Market Intel
|
|
118
|
+
### 📈 Market Intel
|
|
68
119
|
```bash
|
|
69
|
-
darksol market top # Top movers on
|
|
70
|
-
darksol market top -c ethereum # Top movers on
|
|
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
|
-
###
|
|
128
|
+
### 🎰 Casino (The Clawsino)
|
|
129
|
+
All bets are $1 USDC. House edge: 5%. Results verified on-chain.
|
|
130
|
+
|
|
76
131
|
```bash
|
|
77
|
-
darksol
|
|
78
|
-
darksol
|
|
79
|
-
darksol
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
-
|
|
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
|
-
|
|
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
|
|
104
|
-
darksol
|
|
105
|
-
darksol
|
|
106
|
-
darksol
|
|
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
|
-
|
|
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
|
|
112
|
-
darksol
|
|
113
|
-
darksol
|
|
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
|
-
|
|
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
|
-
###
|
|
216
|
+
### 📧 AgentMail
|
|
124
217
|
```bash
|
|
125
|
-
darksol
|
|
126
|
-
darksol
|
|
127
|
-
darksol
|
|
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
|
-
###
|
|
225
|
+
### ⛽ Gas & Network
|
|
131
226
|
```bash
|
|
132
|
-
darksol
|
|
133
|
-
darksol
|
|
134
|
-
darksol
|
|
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
|
-
|
|
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 #
|
|
146
|
-
darksol config rpc base https://... # Custom RPC
|
|
236
|
+
darksol config set slippage 1.0 # Slippage %
|
|
237
|
+
darksol config rpc base https://... # Custom RPC
|
|
147
238
|
```
|
|
148
239
|
|
|
149
|
-
###
|
|
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... #
|
|
261
|
+
darksol lookup 0x... # On-chain address lookup
|
|
262
|
+
darksol setup # Re-run setup wizard
|
|
156
263
|
```
|
|
157
264
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
-
|
|
163
|
-
|
|
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
|
-
##
|
|
346
|
+
## Security Model
|
|
166
347
|
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
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
|
-
|
|
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. 🌑
|
package/src/config/store.js
CHANGED
|
@@ -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',
|
package/src/services/oracle.js
CHANGED
|
@@ -1,88 +1,126 @@
|
|
|
1
1
|
import { fetchJSON } from '../utils/fetch.js';
|
|
2
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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()}/
|
|
14
|
-
spin.succeed('
|
|
15
|
-
|
|
19
|
+
const data = await fetchJSON(`${getURL()}/health`);
|
|
20
|
+
spin.succeed('Oracle online');
|
|
21
|
+
|
|
22
|
+
showSection('RANDOM ORACLE 🎲');
|
|
16
23
|
kvDisplay([
|
|
17
|
-
['
|
|
18
|
-
['
|
|
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
|
|
43
|
+
spin.fail('Oracle unreachable');
|
|
22
44
|
error(err.message);
|
|
23
45
|
}
|
|
24
46
|
}
|
|
25
47
|
|
|
26
|
-
|
|
27
|
-
const spin = spinner(
|
|
48
|
+
async function oraclePlay(endpoint, label, displayFn) {
|
|
49
|
+
const spin = spinner(`${label}...`).start();
|
|
50
|
+
const token = getSignerToken();
|
|
51
|
+
|
|
28
52
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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(
|
|
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
|
-
|
|
45
|
-
|
|
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 || '
|
|
112
|
+
['Proof', data.proof || data.txHash || '-'],
|
|
53
113
|
]);
|
|
54
|
-
}
|
|
55
|
-
spin.fail('Oracle failed');
|
|
56
|
-
error(err.message);
|
|
57
|
-
}
|
|
114
|
+
});
|
|
58
115
|
}
|
|
59
116
|
|
|
60
117
|
export async function oracleShuffle(items) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
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 };
|
package/src/web/commands.js
CHANGED
|
@@ -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.
|
|
1129
|
-
ws.sendLine(
|
|
1130
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
1131
|
-
ws.sendLine(` ${ANSI.darkGold}
|
|
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.
|
|
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 {};
|