@darksol/terminal 0.6.3 → 0.7.1
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 +24 -10
- package/package.json +1 -1
- package/skill/SKILL.md +14 -6
- package/src/cli.js +57 -11
- package/src/config/keys.js +9 -1
- package/src/llm/engine.js +13 -0
- package/src/services/skills.js +121 -15
- package/src/trading/snipe.js +23 -13
- package/src/trading/swap.js +23 -13
- package/src/wallet/manager.js +23 -13
- package/src/web/commands.js +308 -25
- package/src/web/server.js +3 -0
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ A unified CLI for market intel, trading, AI-powered analysis, on-chain oracle, c
|
|
|
15
15
|
[](https://opensource.org/licenses/MIT)
|
|
16
16
|
[](https://nodejs.org/)
|
|
17
17
|
|
|
18
|
-
- Current release: **0.
|
|
18
|
+
- Current release: **0.7.1**
|
|
19
19
|
- Changelog: `CHANGELOG.md`
|
|
20
20
|
|
|
21
21
|
## Install
|
|
@@ -67,10 +67,13 @@ darksol agent start main
|
|
|
67
67
|
|
|
68
68
|
## `darksol serve` (Web Terminal UX)
|
|
69
69
|
|
|
70
|
-
`darksol serve`
|
|
70
|
+
`darksol serve` is a full interactive web terminal with keyboard-driven menus:
|
|
71
71
|
|
|
72
|
-
- Arrow-key menus (`↑/↓` + `Enter`) for wallet/config flows
|
|
73
|
-
- Interactive
|
|
72
|
+
- Arrow-key menus (`↑/↓` + `Enter`) for wallet/config/trade flows
|
|
73
|
+
- **Interactive send** — token → recipient → amount → password → on-chain transfer
|
|
74
|
+
- **Interactive swap** — pair picker (presets + custom) → amount → password → Uniswap V3 execution
|
|
75
|
+
- **Interactive snipe** — contract input → amount → password → fast buy
|
|
76
|
+
- Wallet picker + wallet action menu (receive/send/portfolio/history/switch chain)
|
|
74
77
|
- Agent signer control center (`agent`) with guided wallet selection + start/stop/status
|
|
75
78
|
- Click-through help menu (`help`) with arrow-key command selection
|
|
76
79
|
- AI connection check at startup (shows ready/not configured)
|
|
@@ -84,8 +87,10 @@ Useful web-shell commands:
|
|
|
84
87
|
|
|
85
88
|
```bash
|
|
86
89
|
help # clickable command menu (arrow keys + Enter)
|
|
87
|
-
|
|
90
|
+
trade # interactive swap / snipe menu
|
|
91
|
+
send # interactive token transfer
|
|
88
92
|
wallet # interactive wallet picker and actions
|
|
93
|
+
keys # provider status + interactive add/update
|
|
89
94
|
agent # signer start/stop/status controls
|
|
90
95
|
config # interactive config menu
|
|
91
96
|
logs 20 # show recent AI chat log lines
|
|
@@ -201,7 +206,7 @@ darksol ai analyze AERO
|
|
|
201
206
|
darksol ai chat --provider ollama --model llama3
|
|
202
207
|
```
|
|
203
208
|
|
|
204
|
-
**Supported providers:** OpenAI, Anthropic, OpenRouter, Ollama (local = free)
|
|
209
|
+
**Supported providers:** OpenAI, Anthropic, OpenRouter, Bankr LLM Gateway, Ollama (local = free)
|
|
205
210
|
|
|
206
211
|
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.
|
|
207
212
|
|
|
@@ -227,7 +232,7 @@ darksol keys remove openai
|
|
|
227
232
|
**Supported services:**
|
|
228
233
|
| Category | Services |
|
|
229
234
|
|----------|----------|
|
|
230
|
-
| LLM | OpenAI, Anthropic, OpenRouter, Ollama |
|
|
235
|
+
| LLM | OpenAI, Anthropic, OpenRouter, Bankr LLM Gateway, Ollama |
|
|
231
236
|
| Data | CoinGecko Pro, DexScreener, DefiLlama |
|
|
232
237
|
| RPC | Alchemy, Infura, QuickNode |
|
|
233
238
|
| Trading | 1inch, ParaSwap |
|
|
@@ -239,14 +244,23 @@ Keys can also come from environment variables (e.g., `OPENAI_API_KEY`).
|
|
|
239
244
|
## 💰 Trading
|
|
240
245
|
|
|
241
246
|
```bash
|
|
242
|
-
#
|
|
247
|
+
# Interactive swap (prompts for pair + amount if flags omitted)
|
|
248
|
+
darksol trade swap
|
|
249
|
+
|
|
250
|
+
# Swap with full flags (Uniswap V3 with slippage protection)
|
|
243
251
|
darksol trade swap -i ETH -o USDC -a 0.1
|
|
244
252
|
|
|
253
|
+
# Non-interactive swap (for automation / cron)
|
|
254
|
+
darksol trade swap -i ETH -o USDC -a 0.1 -p "password" -y
|
|
255
|
+
|
|
256
|
+
# Show common pairs for current chain
|
|
257
|
+
darksol trade pairs
|
|
258
|
+
|
|
245
259
|
# Snipe a token (Uniswap V2, fast buy)
|
|
246
260
|
darksol trade snipe 0xTOKEN -a 0.05
|
|
247
261
|
|
|
248
|
-
# Snipe with gas boost
|
|
249
|
-
darksol trade snipe 0xTOKEN -a 0.05 -g 2.0
|
|
262
|
+
# Snipe with gas boost + non-interactive
|
|
263
|
+
darksol trade snipe 0xTOKEN -a 0.05 -g 2.0 -p "password" -y
|
|
250
264
|
|
|
251
265
|
# Watch for new pairs
|
|
252
266
|
darksol trade watch
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ description: "DARKSOL Terminal — unified CLI + x402 platform for trading, wall
|
|
|
7
7
|
|
|
8
8
|
**All DARKSOL services. One terminal. Zero trust required. 🌑**
|
|
9
9
|
|
|
10
|
-
`@darksol/terminal` v0.
|
|
10
|
+
`@darksol/terminal` v0.7.1 | npm: `npm install -g @darksol/terminal`
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -65,10 +65,12 @@ darksol wallet export [name] # Export (password required for PK)
|
|
|
65
65
|
|
|
66
66
|
### 📊 Trading (5 chains)
|
|
67
67
|
```bash
|
|
68
|
+
darksol trade swap # Interactive swap (prompts for pair + amount)
|
|
68
69
|
darksol trade swap -i ETH -o USDC -a 0.1 # Uniswap V3 swap with slippage protection
|
|
69
|
-
darksol trade swap -i
|
|
70
|
+
darksol trade swap -i ETH -o USDC -a 0.1 -p "pw" -y # Non-interactive (automation/cron)
|
|
71
|
+
darksol trade pairs # Show common pairs for active chain
|
|
70
72
|
darksol trade snipe <token> -a 0.05 # Fast buy with gas boost
|
|
71
|
-
darksol trade snipe <token> -a 0.05 -g 2.0
|
|
73
|
+
darksol trade snipe <token> -a 0.05 -g 2.0 -p "pw" -y # Non-interactive snipe
|
|
72
74
|
darksol trade watch # Monitor new pairs (experimental)
|
|
73
75
|
darksol send # Interactive ETH/ERC-20 transfer
|
|
74
76
|
darksol receive # Show your address for receiving
|
|
@@ -88,12 +90,16 @@ darksol dca cancel <id> # Cancel
|
|
|
88
90
|
### 🤖 AI Trading Assistant
|
|
89
91
|
```bash
|
|
90
92
|
darksol ai chat # Interactive AI chat (supports swap/send/price/casino/cards)
|
|
93
|
+
darksol ai chat --provider bankr # Use Bankr LLM Gateway (crypto credits)
|
|
94
|
+
darksol ai chat --provider bankr --model claude-opus-4.6 # Specific Bankr model
|
|
91
95
|
darksol ai ask "buy 0.5 ETH of AERO" # Parse natural language → trade intent
|
|
92
96
|
darksol ai ask "flip a coin" -x # Auto-execute if confidence ≥ 60%
|
|
93
97
|
darksol ai strategy VIRTUAL -b 500 # DCA strategy recommendation
|
|
94
98
|
darksol ai analyze AERO # Token analysis
|
|
95
99
|
```
|
|
96
100
|
|
|
101
|
+
**AI Providers:** OpenAI, Anthropic, OpenRouter, Bankr LLM Gateway (`bk_...`), Ollama (local/free)
|
|
102
|
+
**Bankr Models:** claude-opus-4.6, claude-sonnet-4.6, gemini-3-pro, gpt-5.2, kimi-k2.5, qwen3-coder
|
|
97
103
|
**AI Intent Actions:** swap, send, snipe, dca, price, balance, info, analyze, gas, cards, casino, unknown
|
|
98
104
|
|
|
99
105
|
The AI understands natural language and maps it to executable commands:
|
|
@@ -241,7 +247,7 @@ darksol config rpc base https://... # Custom RPC
|
|
|
241
247
|
```bash
|
|
242
248
|
darksol keys list # All services + status
|
|
243
249
|
darksol keys add openai # Add key (encrypted AES-256-GCM)
|
|
244
|
-
darksol keys add anthropic # Supported: openai, anthropic, openrouter, ollama,
|
|
250
|
+
darksol keys add anthropic # Supported: openai, anthropic, openrouter, bankr, ollama,
|
|
245
251
|
darksol keys add email # coingecko, dexscreener, alchemy, infura, email
|
|
246
252
|
darksol keys remove <service> # Remove key
|
|
247
253
|
```
|
|
@@ -293,8 +299,10 @@ const result = await fetchWithX402(
|
|
|
293
299
|
|
|
294
300
|
### Non-interactive mode (for cron / automation)
|
|
295
301
|
```bash
|
|
296
|
-
# All trading commands accept
|
|
297
|
-
darksol trade swap -i ETH -o USDC -a 0.1 -y
|
|
302
|
+
# All trading commands accept --password (-p) and --yes (-y) for non-interactive use
|
|
303
|
+
darksol trade swap -i ETH -o USDC -a 0.1 -p "password" -y
|
|
304
|
+
darksol trade snipe 0xTOKEN -a 0.05 -p "password" -y
|
|
305
|
+
darksol send --to 0x... --amount 0.1 --token ETH -p "password" -y
|
|
298
306
|
darksol script run my-dca -p "password" -y
|
|
299
307
|
darksol casino bet coinflip -c heads -w 0x1234...
|
|
300
308
|
|
package/src/cli.js
CHANGED
|
@@ -113,19 +113,41 @@ export function cli(argv) {
|
|
|
113
113
|
|
|
114
114
|
trade
|
|
115
115
|
.command('swap')
|
|
116
|
-
.description('Swap tokens via DEX')
|
|
117
|
-
.
|
|
118
|
-
.
|
|
119
|
-
.
|
|
116
|
+
.description('Swap tokens via DEX (interactive if flags omitted)')
|
|
117
|
+
.option('-i, --in <token>', 'Token to sell (symbol or address)')
|
|
118
|
+
.option('-o, --out <token>', 'Token to buy (symbol or address)')
|
|
119
|
+
.option('-a, --amount <amount>', 'Amount to swap')
|
|
120
120
|
.option('-s, --slippage <percent>', 'Max slippage %', '0.5')
|
|
121
121
|
.option('-w, --wallet <name>', 'Wallet to use')
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
.option('-p, --password <pw>', 'Wallet password (non-interactive)')
|
|
123
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
124
|
+
.action(async (opts) => {
|
|
125
|
+
let tokenIn = opts.in;
|
|
126
|
+
let tokenOut = opts.out;
|
|
127
|
+
let amount = opts.amount;
|
|
128
|
+
|
|
129
|
+
if (!tokenIn || !tokenOut || !amount) {
|
|
130
|
+
const inquirer = (await import('inquirer')).default;
|
|
131
|
+
const answers = await inquirer.prompt([
|
|
132
|
+
{ type: 'input', name: 'tokenIn', message: 'Token to sell (e.g. ETH):', default: tokenIn || 'ETH' },
|
|
133
|
+
{ type: 'input', name: 'tokenOut', message: 'Token to buy (e.g. USDC):', default: tokenOut || 'USDC' },
|
|
134
|
+
{ type: 'input', name: 'amount', message: 'Amount to swap:', default: amount || '0.1' },
|
|
135
|
+
]);
|
|
136
|
+
tokenIn = answers.tokenIn;
|
|
137
|
+
tokenOut = answers.tokenOut;
|
|
138
|
+
amount = answers.amount;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return executeSwap({
|
|
142
|
+
tokenIn,
|
|
143
|
+
tokenOut,
|
|
144
|
+
amount,
|
|
145
|
+
slippage: parseFloat(opts.slippage),
|
|
146
|
+
wallet: opts.wallet,
|
|
147
|
+
password: opts.password,
|
|
148
|
+
confirm: opts.yes ? true : undefined,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
129
151
|
|
|
130
152
|
trade
|
|
131
153
|
.command('snipe <token>')
|
|
@@ -134,10 +156,14 @@ export function cli(argv) {
|
|
|
134
156
|
.option('-s, --slippage <percent>', 'Max slippage %', '1')
|
|
135
157
|
.option('-g, --gas <multiplier>', 'Gas priority multiplier', '1.5')
|
|
136
158
|
.option('-w, --wallet <name>', 'Wallet to use')
|
|
159
|
+
.option('-p, --password <pw>', 'Wallet password (non-interactive)')
|
|
160
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
137
161
|
.action((token, opts) => snipeToken(token, opts.amount, {
|
|
138
162
|
slippage: parseFloat(opts.slippage),
|
|
139
163
|
gas: parseFloat(opts.gas),
|
|
140
164
|
wallet: opts.wallet,
|
|
165
|
+
password: opts.password,
|
|
166
|
+
confirm: opts.yes ? true : undefined,
|
|
141
167
|
}));
|
|
142
168
|
|
|
143
169
|
trade
|
|
@@ -147,6 +173,26 @@ export function cli(argv) {
|
|
|
147
173
|
.option('-a, --amount <eth>', 'Auto-snipe amount')
|
|
148
174
|
.action((opts) => watchSnipe(opts));
|
|
149
175
|
|
|
176
|
+
trade
|
|
177
|
+
.command('pairs')
|
|
178
|
+
.description('Show common swap pairs for current chain')
|
|
179
|
+
.action(() => {
|
|
180
|
+
const chain = getConfig('chain') || 'base';
|
|
181
|
+
const byChain = {
|
|
182
|
+
base: ['ETH/USDC', 'ETH/AERO', 'ETH/VIRTUAL', 'USDC/AERO'],
|
|
183
|
+
ethereum: ['ETH/USDC', 'ETH/USDT', 'ETH/DAI'],
|
|
184
|
+
arbitrum: ['ETH/USDC', 'ETH/USDT', 'ETH/ARB'],
|
|
185
|
+
optimism: ['ETH/USDC', 'ETH/OP'],
|
|
186
|
+
polygon: ['POL/USDC', 'POL/WETH', 'USDC/USDT'],
|
|
187
|
+
};
|
|
188
|
+
showSection(`COMMON PAIRS — ${chain.toUpperCase()}`);
|
|
189
|
+
const pairs = byChain[chain] || byChain.base;
|
|
190
|
+
pairs.forEach((p) => console.log(` ${theme.gold(p)}`));
|
|
191
|
+
console.log('');
|
|
192
|
+
info('Swap command: darksol trade swap -i <tokenIn> -o <tokenOut> -a <amount>');
|
|
193
|
+
console.log('');
|
|
194
|
+
});
|
|
195
|
+
|
|
150
196
|
// ═══════════════════════════════════════
|
|
151
197
|
// DCA COMMANDS
|
|
152
198
|
// ═══════════════════════════════════════
|
package/src/config/keys.js
CHANGED
|
@@ -92,6 +92,14 @@ export const SERVICES = {
|
|
|
92
92
|
docsUrl: 'https://ollama.ai',
|
|
93
93
|
validate: (key) => key.startsWith('http'),
|
|
94
94
|
},
|
|
95
|
+
bankr: {
|
|
96
|
+
name: 'Bankr LLM Gateway',
|
|
97
|
+
category: 'llm',
|
|
98
|
+
description: 'Multi-model gateway — Claude, Gemini, GPT via crypto credits',
|
|
99
|
+
envVar: 'BANKR_LLM_KEY',
|
|
100
|
+
docsUrl: 'https://docs.bankr.bot/llm-gateway/overview',
|
|
101
|
+
validate: (key) => key.startsWith('bk_'),
|
|
102
|
+
},
|
|
95
103
|
|
|
96
104
|
// Data Providers
|
|
97
105
|
coingecko: {
|
|
@@ -404,7 +412,7 @@ export function hasKey(service) {
|
|
|
404
412
|
*/
|
|
405
413
|
export function hasAnyLLM() {
|
|
406
414
|
// Cloud providers — need real validated API keys
|
|
407
|
-
if (['openai', 'anthropic', 'openrouter'].some(s => hasKey(s))) return true;
|
|
415
|
+
if (['openai', 'anthropic', 'openrouter', 'bankr'].some(s => hasKey(s))) return true;
|
|
408
416
|
// Ollama — check if explicitly configured via hasKey (validates URL format)
|
|
409
417
|
if (hasKey('ollama')) return true;
|
|
410
418
|
return false;
|
package/src/llm/engine.js
CHANGED
|
@@ -51,6 +51,13 @@ const PROVIDERS = {
|
|
|
51
51
|
parseResponse: (data) => data.choices?.[0]?.message?.content || data.message?.content,
|
|
52
52
|
parseUsage: () => ({ input: 0, output: 0 }),
|
|
53
53
|
},
|
|
54
|
+
bankr: {
|
|
55
|
+
url: 'https://llm.bankr.bot/v1/chat/completions',
|
|
56
|
+
defaultModel: 'claude-sonnet-4.6',
|
|
57
|
+
authHeader: (key) => ({ 'X-API-Key': key }),
|
|
58
|
+
parseResponse: (data) => data.choices?.[0]?.message?.content,
|
|
59
|
+
parseUsage: (data) => data.usage,
|
|
60
|
+
},
|
|
54
61
|
};
|
|
55
62
|
|
|
56
63
|
// ──────────────────────────────────────────────────
|
|
@@ -85,6 +92,12 @@ export class LLMEngine {
|
|
|
85
92
|
}
|
|
86
93
|
}
|
|
87
94
|
|
|
95
|
+
if (!this.apiKey && this.provider !== 'ollama') {
|
|
96
|
+
// Try auto-stored keys as last resort
|
|
97
|
+
const { getKeyAuto } = await import('../config/keys.js');
|
|
98
|
+
this.apiKey = getKeyAuto(this.provider);
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
if (!this.apiKey && this.provider !== 'ollama') {
|
|
89
102
|
throw new Error(`No API key for ${this.provider}. Run: darksol keys add ${this.provider}`);
|
|
90
103
|
}
|
package/src/services/skills.js
CHANGED
|
@@ -25,42 +25,149 @@ const SKILL_CATALOG = [
|
|
|
25
25
|
{
|
|
26
26
|
name: 'darksol-facilitator',
|
|
27
27
|
description: 'Free on-chain x402 payment facilitator — verify and settle micropayments',
|
|
28
|
-
version: '1.0.
|
|
28
|
+
version: '1.0.1',
|
|
29
29
|
source: 'url',
|
|
30
30
|
url: 'https://facilitator.darksol.net/skill/SKILL.md',
|
|
31
|
+
urlCandidates: [
|
|
32
|
+
'https://facilitator.darksol.net/skill/SKILL.md',
|
|
33
|
+
'https://facilitator.darksol.net/skill',
|
|
34
|
+
],
|
|
35
|
+
fallbackSkill: 'facilitator',
|
|
31
36
|
category: 'payments',
|
|
32
37
|
installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'darksol-facilitator', 'SKILL.md')),
|
|
33
38
|
},
|
|
34
39
|
{
|
|
35
40
|
name: 'darksol-prepaid-cards',
|
|
36
41
|
description: 'Crypto → prepaid Visa/MC cards, no KYC, agent-native REST API',
|
|
37
|
-
version: '1.0.
|
|
42
|
+
version: '1.0.1',
|
|
38
43
|
source: 'url',
|
|
39
44
|
url: 'https://acp.darksol.net/dist/darksol-prepaid-cards.skill',
|
|
40
45
|
skillMdUrl: 'https://acp.darksol.net/cards/skill/SKILL.md',
|
|
46
|
+
urlCandidates: [
|
|
47
|
+
'https://acp.darksol.net/cards/skill/SKILL.md',
|
|
48
|
+
'https://acp.darksol.net/dist/darksol-prepaid-cards.skill',
|
|
49
|
+
],
|
|
50
|
+
fallbackSkill: 'cards',
|
|
41
51
|
category: 'payments',
|
|
42
52
|
installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'darksol-prepaid-cards', 'SKILL.md')),
|
|
43
53
|
},
|
|
44
54
|
{
|
|
45
55
|
name: 'random-oracle',
|
|
46
56
|
description: 'On-chain random oracle — verifiable randomness via x402',
|
|
47
|
-
version: '1.0.
|
|
57
|
+
version: '1.0.1',
|
|
48
58
|
source: 'url',
|
|
49
59
|
url: 'https://acp.darksol.net/oracle/skill/SKILL.md',
|
|
60
|
+
urlCandidates: [
|
|
61
|
+
'https://acp.darksol.net/oracle/skill/SKILL.md',
|
|
62
|
+
'https://acp.darksol.net/oracle/skill',
|
|
63
|
+
],
|
|
64
|
+
fallbackSkill: 'oracle',
|
|
50
65
|
category: 'oracle',
|
|
51
66
|
installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'random-oracle', 'SKILL.md')),
|
|
52
67
|
},
|
|
53
68
|
{
|
|
54
69
|
name: 'the-clawsino',
|
|
55
70
|
description: 'On-chain agent casino — coin flip, dice, hi-lo, slots via x402',
|
|
56
|
-
version: '1.0.
|
|
71
|
+
version: '1.0.1',
|
|
57
72
|
source: 'url',
|
|
58
73
|
url: 'https://casino.darksol.net/skill/SKILL.md',
|
|
74
|
+
urlCandidates: [
|
|
75
|
+
'https://casino.darksol.net/skill/SKILL.md',
|
|
76
|
+
'https://casino.darksol.net/skill',
|
|
77
|
+
],
|
|
78
|
+
fallbackSkill: 'casino',
|
|
59
79
|
category: 'gaming',
|
|
60
80
|
installed: () => existsSync(join(OPENCLAW_SKILLS_DIR, 'the-clawsino', 'SKILL.md')),
|
|
61
81
|
},
|
|
62
82
|
];
|
|
63
83
|
|
|
84
|
+
const FALLBACK_SKILLS = {
|
|
85
|
+
facilitator: `---
|
|
86
|
+
name: darksol-facilitator
|
|
87
|
+
description: Free on-chain x402 payment facilitator by DARKSOL. Verifies and settles EIP-3009 micropayments on Base and Polygon.
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
# DARKSOL Facilitator
|
|
91
|
+
|
|
92
|
+
## Base URL
|
|
93
|
+
- https://facilitator.darksol.net
|
|
94
|
+
|
|
95
|
+
## Endpoints
|
|
96
|
+
- GET / (service health/info)
|
|
97
|
+
- POST /verify (verify payment payload)
|
|
98
|
+
- POST /settle (settle payment on-chain)
|
|
99
|
+
|
|
100
|
+
Use this for free x402 settlement flows.`,
|
|
101
|
+
oracle: `---
|
|
102
|
+
name: random-oracle
|
|
103
|
+
description: On-chain random oracle — verifiable randomness via x402.
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
# Random Oracle
|
|
107
|
+
|
|
108
|
+
## Base URL
|
|
109
|
+
- https://acp.darksol.net/api/oracle
|
|
110
|
+
|
|
111
|
+
## Endpoints
|
|
112
|
+
- GET /health
|
|
113
|
+
- GET /coin
|
|
114
|
+
- GET /dice?sides=6
|
|
115
|
+
- GET /number?min=1&max=100
|
|
116
|
+
- POST /shuffle
|
|
117
|
+
|
|
118
|
+
Game endpoints are x402-gated; health is public.`,
|
|
119
|
+
cards: `---
|
|
120
|
+
name: darksol-prepaid-cards
|
|
121
|
+
description: Crypto to prepaid cards via DARKSOL Cards API.
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
# DARKSOL Prepaid Cards
|
|
125
|
+
|
|
126
|
+
## Base URL
|
|
127
|
+
- https://acp.darksol.net/api/cards
|
|
128
|
+
|
|
129
|
+
## Endpoints
|
|
130
|
+
- GET /catalog
|
|
131
|
+
- POST /order
|
|
132
|
+
- GET /status?tradeId=<id>
|
|
133
|
+
|
|
134
|
+
Supports provider/amount/email + optional ticker/network route selection.`,
|
|
135
|
+
casino: `---
|
|
136
|
+
name: the-clawsino
|
|
137
|
+
description: On-chain casino endpoints for coin flip, dice, hi-lo, slots.
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
# The Clawsino
|
|
141
|
+
|
|
142
|
+
## Base URL
|
|
143
|
+
- https://casino.darksol.net
|
|
144
|
+
|
|
145
|
+
## Endpoints
|
|
146
|
+
- GET /api/stats
|
|
147
|
+
- GET /api/tables
|
|
148
|
+
- POST /api/bet
|
|
149
|
+
- GET /api/receipt/:id
|
|
150
|
+
- GET /api/verify/:id
|
|
151
|
+
|
|
152
|
+
All bets are $1 USDC with payment proof in bet request.`,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
async function fetchFirstAvailable(urls = []) {
|
|
156
|
+
for (const u of urls.filter(Boolean)) {
|
|
157
|
+
try {
|
|
158
|
+
const resp = await fetch(u);
|
|
159
|
+
if (!resp.ok) continue;
|
|
160
|
+
const content = await resp.text();
|
|
161
|
+
if (content && content.trim().length > 0) {
|
|
162
|
+
return { url: u, content };
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// try next URL
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
64
171
|
// ──────────────────────────────────────────────────
|
|
65
172
|
// LIST SKILLS
|
|
66
173
|
// ──────────────────────────────────────────────────
|
|
@@ -129,18 +236,17 @@ export async function installSkill(name) {
|
|
|
129
236
|
throw new Error('Bundled SKILL.md not found in package');
|
|
130
237
|
}
|
|
131
238
|
} else if (skill.source === 'url') {
|
|
132
|
-
// Fetch from remote
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
writeFileSync(join(targetDir, 'SKILL.md'), content);
|
|
239
|
+
// Fetch from first live endpoint, with fallback stub if remote is unavailable
|
|
240
|
+
const candidates = skill.urlCandidates || [skill.skillMdUrl, skill.url];
|
|
241
|
+
const fetched = await fetchFirstAvailable(candidates);
|
|
242
|
+
|
|
243
|
+
if (fetched?.content) {
|
|
244
|
+
writeFileSync(join(targetDir, 'SKILL.md'), fetched.content);
|
|
245
|
+
} else if (skill.fallbackSkill && FALLBACK_SKILLS[skill.fallbackSkill]) {
|
|
246
|
+
writeFileSync(join(targetDir, 'SKILL.md'), FALLBACK_SKILLS[skill.fallbackSkill]);
|
|
247
|
+
warn(`Remote skill endpoint unavailable — installed fallback spec for ${name}`);
|
|
142
248
|
} else {
|
|
143
|
-
|
|
249
|
+
throw new Error(`Failed to fetch skill from: ${candidates.filter(Boolean).join(', ')}`);
|
|
144
250
|
}
|
|
145
251
|
}
|
|
146
252
|
|
package/src/trading/snipe.js
CHANGED
|
@@ -34,6 +34,8 @@ export async function snipeToken(tokenAddress, amount, opts = {}) {
|
|
|
34
34
|
const chain = getConfig('chain') || 'base';
|
|
35
35
|
const maxSlippage = opts.slippage || getConfig('slippage') || 1.0;
|
|
36
36
|
const gasMultiplier = opts.gas || getConfig('gasMultiplier') || 1.5;
|
|
37
|
+
const providedPassword = opts.password;
|
|
38
|
+
const providedConfirm = opts.confirm;
|
|
37
39
|
|
|
38
40
|
if (!tokenAddress || !tokenAddress.startsWith('0x')) {
|
|
39
41
|
error('Provide a valid token contract address');
|
|
@@ -45,13 +47,17 @@ export async function snipeToken(tokenAddress, amount, opts = {}) {
|
|
|
45
47
|
return;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
// Get password
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
// Get password (prompt unless provided)
|
|
51
|
+
let password = providedPassword;
|
|
52
|
+
if (!password) {
|
|
53
|
+
const prompted = await inquirer.prompt([{
|
|
54
|
+
type: 'password',
|
|
55
|
+
name: 'password',
|
|
56
|
+
message: theme.gold('Wallet password:'),
|
|
57
|
+
mask: '●',
|
|
58
|
+
}]);
|
|
59
|
+
password = prompted.password;
|
|
60
|
+
}
|
|
55
61
|
|
|
56
62
|
const spin = spinner('Preparing snipe...').start();
|
|
57
63
|
|
|
@@ -107,12 +113,16 @@ export async function snipeToken(tokenAddress, amount, opts = {}) {
|
|
|
107
113
|
]);
|
|
108
114
|
console.log('');
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
let confirm = providedConfirm;
|
|
117
|
+
if (typeof confirm !== 'boolean') {
|
|
118
|
+
const prompted = await inquirer.prompt([{
|
|
119
|
+
type: 'confirm',
|
|
120
|
+
name: 'confirm',
|
|
121
|
+
message: theme.accent('Execute snipe? This is HIGH RISK.'),
|
|
122
|
+
default: false,
|
|
123
|
+
}]);
|
|
124
|
+
confirm = prompted.confirm;
|
|
125
|
+
}
|
|
116
126
|
|
|
117
127
|
if (!confirm) {
|
|
118
128
|
warn('Snipe cancelled');
|
package/src/trading/swap.js
CHANGED
|
@@ -137,6 +137,8 @@ export async function executeSwap(opts = {}) {
|
|
|
137
137
|
amount,
|
|
138
138
|
wallet: walletName,
|
|
139
139
|
slippage,
|
|
140
|
+
password: providedPassword,
|
|
141
|
+
confirm: providedConfirm,
|
|
140
142
|
} = opts;
|
|
141
143
|
|
|
142
144
|
const chain = getConfig('chain') || 'base';
|
|
@@ -155,13 +157,17 @@ export async function executeSwap(opts = {}) {
|
|
|
155
157
|
return;
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
// Get password for wallet
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
// Get password for wallet (prompt unless provided)
|
|
161
|
+
let password = providedPassword;
|
|
162
|
+
if (!password) {
|
|
163
|
+
const prompted = await inquirer.prompt([{
|
|
164
|
+
type: 'password',
|
|
165
|
+
name: 'password',
|
|
166
|
+
message: theme.gold('Wallet password:'),
|
|
167
|
+
mask: '●',
|
|
168
|
+
}]);
|
|
169
|
+
password = prompted.password;
|
|
170
|
+
}
|
|
165
171
|
|
|
166
172
|
const spin = spinner('Preparing swap...').start();
|
|
167
173
|
|
|
@@ -214,12 +220,16 @@ export async function executeSwap(opts = {}) {
|
|
|
214
220
|
]);
|
|
215
221
|
console.log('');
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
let confirm = providedConfirm;
|
|
224
|
+
if (typeof confirm !== 'boolean') {
|
|
225
|
+
const prompted = await inquirer.prompt([{
|
|
226
|
+
type: 'confirm',
|
|
227
|
+
name: 'confirm',
|
|
228
|
+
message: theme.gold('Execute swap?'),
|
|
229
|
+
default: false,
|
|
230
|
+
}]);
|
|
231
|
+
confirm = prompted.confirm;
|
|
232
|
+
}
|
|
223
233
|
|
|
224
234
|
if (!confirm) {
|
|
225
235
|
warn('Swap cancelled');
|
package/src/wallet/manager.js
CHANGED
|
@@ -269,6 +269,8 @@ const ERC20_SEND_ABI = [
|
|
|
269
269
|
|
|
270
270
|
export async function sendFunds(opts = {}) {
|
|
271
271
|
const name = opts.wallet || getConfig('activeWallet');
|
|
272
|
+
const providedPassword = opts.password;
|
|
273
|
+
const providedConfirm = opts.confirm;
|
|
272
274
|
if (!name) {
|
|
273
275
|
error('No active wallet. Set one: darksol wallet use <name>');
|
|
274
276
|
return;
|
|
@@ -331,13 +333,17 @@ export async function sendFunds(opts = {}) {
|
|
|
331
333
|
}]));
|
|
332
334
|
}
|
|
333
335
|
|
|
334
|
-
// Password
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
// Password (prompt unless provided)
|
|
337
|
+
let password = providedPassword;
|
|
338
|
+
if (!password) {
|
|
339
|
+
const prompted = await inquirer.prompt([{
|
|
340
|
+
type: 'password',
|
|
341
|
+
name: 'password',
|
|
342
|
+
message: theme.gold('Wallet password:'),
|
|
343
|
+
mask: '●',
|
|
344
|
+
}]);
|
|
345
|
+
password = prompted.password;
|
|
346
|
+
}
|
|
341
347
|
|
|
342
348
|
const spin = spinner('Preparing transaction...').start();
|
|
343
349
|
|
|
@@ -420,12 +426,16 @@ export async function sendFunds(opts = {}) {
|
|
|
420
426
|
]);
|
|
421
427
|
console.log('');
|
|
422
428
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
+
let confirm = providedConfirm;
|
|
430
|
+
if (typeof confirm !== 'boolean') {
|
|
431
|
+
const prompted = await inquirer.prompt([{
|
|
432
|
+
type: 'confirm',
|
|
433
|
+
name: 'confirm',
|
|
434
|
+
message: theme.accent('Send this transaction?'),
|
|
435
|
+
default: false,
|
|
436
|
+
}]);
|
|
437
|
+
confirm = prompted.confirm;
|
|
438
|
+
}
|
|
429
439
|
|
|
430
440
|
if (!confirm) {
|
|
431
441
|
warn('Transaction cancelled');
|
package/src/web/commands.js
CHANGED
|
@@ -115,12 +115,12 @@ export async function handleMenuSelect(id, value, item, ws) {
|
|
|
115
115
|
return {};
|
|
116
116
|
}
|
|
117
117
|
case 'send':
|
|
118
|
-
ws.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
ws.sendMenu('send_token', '◆ Send Token', [
|
|
119
|
+
{ value: 'ETH', label: 'ETH', desc: 'Native token transfer' },
|
|
120
|
+
{ value: 'USDC', label: 'USDC', desc: 'Stablecoin transfer' },
|
|
121
|
+
{ value: 'custom', label: 'Custom token (0x...)', desc: 'ERC-20 contract address' },
|
|
122
|
+
{ value: 'back', label: '← Back', desc: '' },
|
|
123
|
+
]);
|
|
124
124
|
return {};
|
|
125
125
|
case 'portfolio':
|
|
126
126
|
return await handleCommand('portfolio', ws);
|
|
@@ -221,6 +221,86 @@ export async function handleMenuSelect(id, value, item, ws) {
|
|
|
221
221
|
case 'cards_status_check':
|
|
222
222
|
return await showCardStatus(value, ws);
|
|
223
223
|
|
|
224
|
+
case 'trade_action':
|
|
225
|
+
if (value === 'swap') {
|
|
226
|
+
ws.sendMenu('trade_swap_pair', '◆ Swap Pair', [
|
|
227
|
+
{ value: 'ETH->USDC', label: 'ETH → USDC', desc: 'Most common' },
|
|
228
|
+
{ value: 'USDC->ETH', label: 'USDC → ETH', desc: 'Reverse' },
|
|
229
|
+
{ value: 'ETH->AERO', label: 'ETH → AERO', desc: 'Base ecosystem' },
|
|
230
|
+
{ value: 'ETH->VIRTUAL', label: 'ETH → VIRTUAL', desc: 'Base ecosystem' },
|
|
231
|
+
{ value: 'custom', label: 'Custom pair', desc: 'Any symbol or 0x token' },
|
|
232
|
+
{ value: 'back', label: '← Back', desc: '' },
|
|
233
|
+
]);
|
|
234
|
+
return {};
|
|
235
|
+
}
|
|
236
|
+
if (value === 'snipe') {
|
|
237
|
+
ws.sendPrompt('trade_snipe_token', 'Token contract (0x...):', {});
|
|
238
|
+
return {};
|
|
239
|
+
}
|
|
240
|
+
if (value === 'watch') {
|
|
241
|
+
return await handleCommand('trade watch', ws);
|
|
242
|
+
}
|
|
243
|
+
return {};
|
|
244
|
+
|
|
245
|
+
case 'trade_swap_pair': {
|
|
246
|
+
if (value === 'back') return {};
|
|
247
|
+
if (value === 'custom') {
|
|
248
|
+
ws.sendPrompt('trade_swap_custom_pair', 'Pair (format: TOKEN_IN TOKEN_OUT):', {});
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
const [tokenIn, tokenOut] = value.split('->');
|
|
252
|
+
ws.sendMenu('trade_swap_amount', `◆ Amount (${tokenIn} → ${tokenOut})`, [
|
|
253
|
+
{ value: '0.01', label: `0.01 ${tokenIn}`, desc: 'small' , meta: { tokenIn, tokenOut }},
|
|
254
|
+
{ value: '0.05', label: `0.05 ${tokenIn}`, desc: 'small' , meta: { tokenIn, tokenOut }},
|
|
255
|
+
{ value: '0.1', label: `0.1 ${tokenIn}`, desc: 'standard' , meta: { tokenIn, tokenOut }},
|
|
256
|
+
{ value: '0.25', label: `0.25 ${tokenIn}`, desc: 'medium' , meta: { tokenIn, tokenOut }},
|
|
257
|
+
{ value: '1', label: `1 ${tokenIn}`, desc: 'large' , meta: { tokenIn, tokenOut }},
|
|
258
|
+
{ value: 'custom', label: 'Custom amount', desc: '', meta: { tokenIn, tokenOut }},
|
|
259
|
+
]);
|
|
260
|
+
return {};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case 'trade_swap_amount':
|
|
264
|
+
if (value === 'custom') {
|
|
265
|
+
ws.sendPrompt('trade_swap_custom_amount', `Amount (${item?.meta?.tokenIn || 'token'}):`, item?.meta || {});
|
|
266
|
+
return {};
|
|
267
|
+
}
|
|
268
|
+
ws.sendPrompt('trade_swap_password', `Wallet password (${item?.meta?.tokenIn || ''} → ${item?.meta?.tokenOut || ''}, ${value}):`, {
|
|
269
|
+
...(item?.meta || {}),
|
|
270
|
+
amount: value,
|
|
271
|
+
mask: true,
|
|
272
|
+
});
|
|
273
|
+
return {};
|
|
274
|
+
|
|
275
|
+
case 'trade_snipe_amount':
|
|
276
|
+
ws.sendPrompt('trade_snipe_password', `Wallet password (snipe ${item?.meta?.token || ''} with ${value} ETH):`, {
|
|
277
|
+
...(item?.meta || {}),
|
|
278
|
+
amount: value,
|
|
279
|
+
mask: true,
|
|
280
|
+
});
|
|
281
|
+
return {};
|
|
282
|
+
|
|
283
|
+
case 'send_token':
|
|
284
|
+
if (value === 'back') return {};
|
|
285
|
+
if (value === 'custom') {
|
|
286
|
+
ws.sendPrompt('send_custom_token', 'Token contract address (0x...):', {});
|
|
287
|
+
return {};
|
|
288
|
+
}
|
|
289
|
+
ws.sendPrompt('send_to', `Recipient address (for ${value}):`, { token: value });
|
|
290
|
+
return {};
|
|
291
|
+
|
|
292
|
+
case 'send_amount':
|
|
293
|
+
if (value === 'custom') {
|
|
294
|
+
ws.sendPrompt('send_custom_amount', `Amount (${item?.meta?.token || 'token'}):`, item?.meta || {});
|
|
295
|
+
return {};
|
|
296
|
+
}
|
|
297
|
+
ws.sendPrompt('send_password', `Wallet password (send ${value} ${item?.meta?.token || 'token'}):`, {
|
|
298
|
+
...(item?.meta || {}),
|
|
299
|
+
amount: value,
|
|
300
|
+
mask: true,
|
|
301
|
+
});
|
|
302
|
+
return {};
|
|
303
|
+
|
|
224
304
|
case 'agent_action':
|
|
225
305
|
if (value === 'start') {
|
|
226
306
|
const { listWallets } = await import('../wallet/keystore.js');
|
|
@@ -348,6 +428,185 @@ export async function handlePromptResponse(id, value, meta, ws) {
|
|
|
348
428
|
return {};
|
|
349
429
|
}
|
|
350
430
|
|
|
431
|
+
if (id === 'send_custom_token') {
|
|
432
|
+
if (!value || !value.startsWith('0x') || value.length !== 42) {
|
|
433
|
+
ws.sendLine(` ${ANSI.red}✗ Invalid token address${ANSI.reset}`);
|
|
434
|
+
ws.sendLine('');
|
|
435
|
+
return {};
|
|
436
|
+
}
|
|
437
|
+
ws.sendPrompt('send_to', 'Recipient address:', { token: value.trim() });
|
|
438
|
+
return {};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (id === 'send_to') {
|
|
442
|
+
if (!value || !value.startsWith('0x') || value.length !== 42) {
|
|
443
|
+
ws.sendLine(` ${ANSI.red}✗ Invalid recipient address${ANSI.reset}`);
|
|
444
|
+
ws.sendLine('');
|
|
445
|
+
return {};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const token = meta?.token || 'ETH';
|
|
449
|
+
const defaultAmounts = token === 'ETH'
|
|
450
|
+
? ['0.005', '0.01', '0.05', '0.1']
|
|
451
|
+
: ['1', '5', '10', '25'];
|
|
452
|
+
|
|
453
|
+
ws.sendMenu('send_amount', `◆ Amount (${token})`, [
|
|
454
|
+
...defaultAmounts.map(a => ({ value: a, label: `${a} ${token === 'ETH' ? 'ETH' : ''}`.trim(), desc: 'quick', meta: { token, to: value.trim() } })),
|
|
455
|
+
{ value: 'custom', label: 'Custom amount', desc: '', meta: { token, to: value.trim() } },
|
|
456
|
+
]);
|
|
457
|
+
return {};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (id === 'send_custom_amount') {
|
|
461
|
+
const n = Number(value);
|
|
462
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
463
|
+
ws.sendLine(` ${ANSI.red}✗ Invalid amount${ANSI.reset}`);
|
|
464
|
+
ws.sendLine('');
|
|
465
|
+
return {};
|
|
466
|
+
}
|
|
467
|
+
ws.sendPrompt('send_password', `Wallet password (send ${value} ${meta?.token || 'token'}):`, {
|
|
468
|
+
...meta,
|
|
469
|
+
amount: String(value),
|
|
470
|
+
mask: true,
|
|
471
|
+
});
|
|
472
|
+
return {};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (id === 'send_password') {
|
|
476
|
+
if (!meta?.to || !meta?.token || !meta?.amount || !value) {
|
|
477
|
+
ws.sendLine(` ${ANSI.red}✗ Missing send details${ANSI.reset}`);
|
|
478
|
+
ws.sendLine('');
|
|
479
|
+
return {};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
ws.sendLine(` ${ANSI.dim}Sending ${meta.amount} ${meta.token} to ${meta.to.slice(0, 8)}...${ANSI.reset}`);
|
|
483
|
+
ws.sendLine('');
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
const { sendFunds } = await import('../wallet/manager.js');
|
|
487
|
+
await sendFunds({
|
|
488
|
+
wallet: getConfig('activeWallet'),
|
|
489
|
+
to: meta.to,
|
|
490
|
+
amount: meta.amount,
|
|
491
|
+
token: meta.token,
|
|
492
|
+
password: value,
|
|
493
|
+
confirm: true,
|
|
494
|
+
});
|
|
495
|
+
ws.sendLine(` ${ANSI.green}✓ Send flow completed (check terminal output for receipt)${ANSI.reset}`);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
ws.sendLine(` ${ANSI.red}✗ Send failed: ${err.message}${ANSI.reset}`);
|
|
498
|
+
}
|
|
499
|
+
ws.sendLine('');
|
|
500
|
+
return {};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (id === 'trade_swap_custom_pair') {
|
|
504
|
+
if (!value) { ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`); ws.sendLine(''); return {}; }
|
|
505
|
+
const parts = value.trim().split(/\s+/).filter(Boolean);
|
|
506
|
+
if (parts.length < 2) {
|
|
507
|
+
ws.sendLine(` ${ANSI.red}✗ Format: TOKEN_IN TOKEN_OUT${ANSI.reset}`);
|
|
508
|
+
ws.sendLine('');
|
|
509
|
+
return {};
|
|
510
|
+
}
|
|
511
|
+
const [tokenIn, tokenOut] = parts;
|
|
512
|
+
ws.sendMenu('trade_swap_amount', `◆ Amount (${tokenIn} → ${tokenOut})`, [
|
|
513
|
+
{ value: '0.01', label: `0.01 ${tokenIn}`, desc: 'small', meta: { tokenIn, tokenOut } },
|
|
514
|
+
{ value: '0.05', label: `0.05 ${tokenIn}`, desc: 'small', meta: { tokenIn, tokenOut } },
|
|
515
|
+
{ value: '0.1', label: `0.1 ${tokenIn}`, desc: 'standard', meta: { tokenIn, tokenOut } },
|
|
516
|
+
{ value: '0.25', label: `0.25 ${tokenIn}`, desc: 'medium', meta: { tokenIn, tokenOut } },
|
|
517
|
+
{ value: '1', label: `1 ${tokenIn}`, desc: 'large', meta: { tokenIn, tokenOut } },
|
|
518
|
+
{ value: 'custom', label: 'Custom amount', desc: '', meta: { tokenIn, tokenOut } },
|
|
519
|
+
]);
|
|
520
|
+
return {};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (id === 'trade_swap_custom_amount') {
|
|
524
|
+
if (!value) { ws.sendLine(` ${ANSI.red}✗ Cancelled${ANSI.reset}`); ws.sendLine(''); return {}; }
|
|
525
|
+
const n = Number(value);
|
|
526
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
527
|
+
ws.sendLine(` ${ANSI.red}✗ Invalid amount${ANSI.reset}`);
|
|
528
|
+
ws.sendLine('');
|
|
529
|
+
return {};
|
|
530
|
+
}
|
|
531
|
+
ws.sendPrompt('trade_swap_password', `Wallet password (${meta?.tokenIn || ''} → ${meta?.tokenOut || ''}, ${value}):`, {
|
|
532
|
+
...meta,
|
|
533
|
+
amount: String(value),
|
|
534
|
+
mask: true,
|
|
535
|
+
});
|
|
536
|
+
return {};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (id === 'trade_swap_password') {
|
|
540
|
+
if (!meta?.tokenIn || !meta?.tokenOut || !meta?.amount || !value) {
|
|
541
|
+
ws.sendLine(` ${ANSI.red}✗ Missing swap details${ANSI.reset}`);
|
|
542
|
+
ws.sendLine('');
|
|
543
|
+
return {};
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
ws.sendLine(` ${ANSI.dim}Executing swap ${meta.amount} ${meta.tokenIn} → ${meta.tokenOut}...${ANSI.reset}`);
|
|
547
|
+
ws.sendLine('');
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
const { executeSwap } = await import('../trading/swap.js');
|
|
551
|
+
await executeSwap({
|
|
552
|
+
tokenIn: meta.tokenIn,
|
|
553
|
+
tokenOut: meta.tokenOut,
|
|
554
|
+
amount: meta.amount,
|
|
555
|
+
slippage: parseFloat(getConfig('slippage') || 0.5),
|
|
556
|
+
wallet: getConfig('activeWallet'),
|
|
557
|
+
password: value,
|
|
558
|
+
confirm: true,
|
|
559
|
+
});
|
|
560
|
+
ws.sendLine(` ${ANSI.green}✓ Swap flow completed (check terminal output for receipt)${ANSI.reset}`);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
ws.sendLine(` ${ANSI.red}✗ Swap failed: ${err.message}${ANSI.reset}`);
|
|
563
|
+
}
|
|
564
|
+
ws.sendLine('');
|
|
565
|
+
return {};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (id === 'trade_snipe_token') {
|
|
569
|
+
if (!value || !value.startsWith('0x') || value.length !== 42) {
|
|
570
|
+
ws.sendLine(` ${ANSI.red}✗ Invalid token contract${ANSI.reset}`);
|
|
571
|
+
ws.sendLine('');
|
|
572
|
+
return {};
|
|
573
|
+
}
|
|
574
|
+
ws.sendMenu('trade_snipe_amount', '◆ Snipe Amount (ETH)', [
|
|
575
|
+
{ value: '0.01', label: '0.01 ETH', desc: 'small', meta: { token: value.trim() } },
|
|
576
|
+
{ value: '0.05', label: '0.05 ETH', desc: 'standard', meta: { token: value.trim() } },
|
|
577
|
+
{ value: '0.1', label: '0.1 ETH', desc: 'medium', meta: { token: value.trim() } },
|
|
578
|
+
{ value: '0.25', label: '0.25 ETH', desc: 'large', meta: { token: value.trim() } },
|
|
579
|
+
]);
|
|
580
|
+
return {};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (id === 'trade_snipe_password') {
|
|
584
|
+
if (!meta?.token || !meta?.amount || !value) {
|
|
585
|
+
ws.sendLine(` ${ANSI.red}✗ Missing snipe details${ANSI.reset}`);
|
|
586
|
+
ws.sendLine('');
|
|
587
|
+
return {};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
ws.sendLine(` ${ANSI.dim}Executing snipe ${meta.amount} ETH -> ${meta.token.slice(0, 8)}...${ANSI.reset}`);
|
|
591
|
+
ws.sendLine('');
|
|
592
|
+
|
|
593
|
+
try {
|
|
594
|
+
const { snipeToken } = await import('../trading/snipe.js');
|
|
595
|
+
await snipeToken(meta.token, meta.amount, {
|
|
596
|
+
slippage: parseFloat(getConfig('slippage') || 1),
|
|
597
|
+
gas: parseFloat(getConfig('gasMultiplier') || 1.5),
|
|
598
|
+
wallet: getConfig('activeWallet'),
|
|
599
|
+
password: value,
|
|
600
|
+
confirm: true,
|
|
601
|
+
});
|
|
602
|
+
ws.sendLine(` ${ANSI.green}✓ Snipe flow completed (check terminal output for receipt)${ANSI.reset}`);
|
|
603
|
+
} catch (err) {
|
|
604
|
+
ws.sendLine(` ${ANSI.red}✗ Snipe failed: ${err.message}${ANSI.reset}`);
|
|
605
|
+
}
|
|
606
|
+
ws.sendLine('');
|
|
607
|
+
return {};
|
|
608
|
+
}
|
|
609
|
+
|
|
351
610
|
if (id === 'agent_signer_password') {
|
|
352
611
|
const wallet = meta.wallet;
|
|
353
612
|
if (!wallet || !value) {
|
|
@@ -383,7 +642,7 @@ export function getAIStatus() {
|
|
|
383
642
|
const dim = '\x1b[38;2;102;102;102m';
|
|
384
643
|
const reset = '\x1b[0m';
|
|
385
644
|
|
|
386
|
-
const providers = ['openai', 'anthropic', 'openrouter', 'ollama'];
|
|
645
|
+
const providers = ['openai', 'anthropic', 'openrouter', 'ollama', 'bankr'];
|
|
387
646
|
const connected = providers.filter(p => hasKey(p));
|
|
388
647
|
|
|
389
648
|
if (connected.length > 0) {
|
|
@@ -398,6 +657,7 @@ export function getAIStatus() {
|
|
|
398
657
|
` ${green}keys add openai sk-...${reset} ${dim}OpenAI (GPT-4o)${reset}`,
|
|
399
658
|
` ${green}keys add anthropic sk-ant-...${reset} ${dim}Anthropic (Claude)${reset}`,
|
|
400
659
|
` ${green}keys add openrouter sk-or-...${reset} ${dim}OpenRouter (any model)${reset}`,
|
|
660
|
+
` ${green}keys add bankr bk_...${reset} ${dim}Bankr LLM Gateway (crypto credits)${reset}`,
|
|
401
661
|
` ${green}keys add ollama http://...${reset} ${dim}Ollama (free, local)${reset}`,
|
|
402
662
|
'',
|
|
403
663
|
].join('\r\n');
|
|
@@ -424,6 +684,8 @@ export async function handleCommand(cmd, ws) {
|
|
|
424
684
|
return await cmdHistory(args, ws);
|
|
425
685
|
case 'market':
|
|
426
686
|
return await cmdMarket(args, ws);
|
|
687
|
+
case 'trade':
|
|
688
|
+
return await cmdTrade(args, ws);
|
|
427
689
|
case 'wallet':
|
|
428
690
|
return await cmdWallet(args, ws);
|
|
429
691
|
case 'mail':
|
|
@@ -705,6 +967,34 @@ async function cmdMarket(args, ws) {
|
|
|
705
967
|
return {};
|
|
706
968
|
}
|
|
707
969
|
|
|
970
|
+
// ══════════════════════════════════════════════════
|
|
971
|
+
// TRADE (interactive web flow)
|
|
972
|
+
// ══════════════════════════════════════════════════
|
|
973
|
+
async function cmdTrade(args, ws) {
|
|
974
|
+
const sub = (args[0] || '').toLowerCase();
|
|
975
|
+
|
|
976
|
+
if (sub === 'watch') {
|
|
977
|
+
ws.sendLine(`${ANSI.dim}Pair watch is CLI-first right now:${ANSI.reset}`);
|
|
978
|
+
ws.sendLine(` ${ANSI.gold}darksol trade watch${ANSI.reset}`);
|
|
979
|
+
ws.sendLine('');
|
|
980
|
+
return {};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
ws.sendLine(`${ANSI.gold} ◆ TRADE${ANSI.reset}`);
|
|
984
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
985
|
+
ws.sendLine(` ${ANSI.white}Choose an execution flow:${ANSI.reset}`);
|
|
986
|
+
ws.sendLine('');
|
|
987
|
+
|
|
988
|
+
ws.sendMenu('trade_action', '◆ Trade Actions', [
|
|
989
|
+
{ value: 'swap', label: '🔄 Swap', desc: 'Interactive token swap (password prompt)' },
|
|
990
|
+
{ value: 'snipe', label: '⚡ Snipe', desc: 'Fast buy by token contract' },
|
|
991
|
+
{ value: 'watch', label: '👀 Watch Pairs', desc: 'Monitor new pairs (CLI guidance)' },
|
|
992
|
+
{ value: 'back', label: '← Back', desc: '' },
|
|
993
|
+
]);
|
|
994
|
+
|
|
995
|
+
return {};
|
|
996
|
+
}
|
|
997
|
+
|
|
708
998
|
// ══════════════════════════════════════════════════
|
|
709
999
|
// WALLET
|
|
710
1000
|
// ══════════════════════════════════════════════════
|
|
@@ -1470,7 +1760,7 @@ async function cmdKeys(args, ws) {
|
|
|
1470
1760
|
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
1471
1761
|
ws.sendLine('');
|
|
1472
1762
|
|
|
1473
|
-
const llmProviders = ['openai', 'anthropic', 'openrouter', 'ollama'];
|
|
1763
|
+
const llmProviders = ['openai', 'anthropic', 'openrouter', 'ollama', 'bankr'];
|
|
1474
1764
|
ws.sendLine(` ${ANSI.gold}LLM Providers:${ANSI.reset}`);
|
|
1475
1765
|
for (const p of llmProviders) {
|
|
1476
1766
|
const svc = SERVICES[p];
|
|
@@ -1564,24 +1854,17 @@ async function cmdSend(args, ws) {
|
|
|
1564
1854
|
return {};
|
|
1565
1855
|
}
|
|
1566
1856
|
|
|
1567
|
-
ws.sendLine(` ${ANSI.white}
|
|
1568
|
-
ws.sendLine(
|
|
1569
|
-
ws.sendLine(` ${ANSI.darkGold}Usage:${ANSI.reset}`);
|
|
1570
|
-
ws.sendLine(` ${ANSI.gold}darksol send --to 0x... --amount 0.1 --token ETH${ANSI.reset}`);
|
|
1571
|
-
ws.sendLine(` ${ANSI.gold}darksol send --to 0x... --amount 50 --token USDC${ANSI.reset}`);
|
|
1572
|
-
ws.sendLine(` ${ANSI.gold}darksol send${ANSI.reset} ${ANSI.dim}(interactive mode — prompts for everything)${ANSI.reset}`);
|
|
1573
|
-
ws.sendLine('');
|
|
1574
|
-
ws.sendLine(` ${ANSI.darkGold}Features:${ANSI.reset}`);
|
|
1575
|
-
ws.sendLine(` ${ANSI.dim}•${ANSI.reset} ETH and any ERC-20 token`);
|
|
1576
|
-
ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Balance check before sending`);
|
|
1577
|
-
ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Gas estimation in preview`);
|
|
1578
|
-
ws.sendLine(` ${ANSI.dim}•${ANSI.reset} Confirmation prompt before execution`);
|
|
1579
|
-
ws.sendLine(` ${ANSI.dim}•${ANSI.reset} On-chain receipt after confirmation`);
|
|
1580
|
-
ws.sendLine('');
|
|
1581
|
-
ws.sendLine(` ${ANSI.darkGold}Active:${ANSI.reset} ${ANSI.white}${wallet}${ANSI.reset} on ${ANSI.white}${chain}${ANSI.reset}`);
|
|
1582
|
-
ws.sendLine('');
|
|
1583
|
-
ws.sendLine(` ${ANSI.dim}⚠ Sending requires the CLI. Install: npm i -g @darksol/terminal${ANSI.reset}`);
|
|
1857
|
+
ws.sendLine(` ${ANSI.white}Wallet:${ANSI.reset} ${ANSI.gold}${wallet}${ANSI.reset} ${ANSI.dim}on ${chain}${ANSI.reset}`);
|
|
1858
|
+
ws.sendLine(` ${ANSI.dim}Interactive send flow will ask token → recipient → amount → password.${ANSI.reset}`);
|
|
1584
1859
|
ws.sendLine('');
|
|
1860
|
+
|
|
1861
|
+
ws.sendMenu('send_token', '◆ Send Token', [
|
|
1862
|
+
{ value: 'ETH', label: 'ETH', desc: 'Native token transfer' },
|
|
1863
|
+
{ value: 'USDC', label: 'USDC', desc: 'Stablecoin transfer' },
|
|
1864
|
+
{ value: 'custom', label: 'Custom token (0x...)', desc: 'ERC-20 contract address' },
|
|
1865
|
+
{ value: 'back', label: '← Back', desc: '' },
|
|
1866
|
+
]);
|
|
1867
|
+
|
|
1585
1868
|
return {};
|
|
1586
1869
|
}
|
|
1587
1870
|
|
package/src/web/server.js
CHANGED
|
@@ -163,10 +163,12 @@ export async function startWebShell(opts = {}) {
|
|
|
163
163
|
items: [
|
|
164
164
|
{ value: 'ai', label: '🧠 AI Chat', desc: 'Natural language assistant' },
|
|
165
165
|
{ value: 'wallet', label: '👛 Wallet', desc: 'Picker + balance + actions' },
|
|
166
|
+
{ value: 'send', label: '📤 Send', desc: 'Interactive transfer flow' },
|
|
166
167
|
{ value: 'agent', label: '🔐 Agent Signer', desc: 'Start/stop/status controls' },
|
|
167
168
|
{ value: 'keys', label: '🔑 Keys', desc: 'Add/update LLM providers' },
|
|
168
169
|
{ value: 'config', label: '⚙ Config', desc: 'Chain + settings' },
|
|
169
170
|
{ value: 'portfolio', label: '📊 Portfolio', desc: 'Multi-chain balances' },
|
|
171
|
+
{ value: 'trade', label: '🔄 Trade', desc: 'Swap / snipe click-through flows' },
|
|
170
172
|
{ value: 'market', label: '📈 Market', desc: 'Price + liquidity intel' },
|
|
171
173
|
{ value: 'mail', label: '📧 Mail', desc: 'AgentMail status/inbox' },
|
|
172
174
|
{ value: 'cards', label: '💳 Cards', desc: 'Order prepaid Visa/MC' },
|
|
@@ -298,6 +300,7 @@ function getHelp() {
|
|
|
298
300
|
['watch <token>', 'Live price monitor'],
|
|
299
301
|
['gas [chain]', 'Gas prices & estimates'],
|
|
300
302
|
['portfolio', 'Multi-chain balances'],
|
|
303
|
+
['trade', 'Interactive swap/snipe menu'],
|
|
301
304
|
['send', 'Send ETH or tokens'],
|
|
302
305
|
['receive', 'Show address to receive'],
|
|
303
306
|
['wallet', 'Interactive wallet menu'],
|