@darksol/terminal 0.6.3 → 0.7.0
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 +22 -8
- package/package.json +1 -1
- package/skill/SKILL.md +9 -5
- package/src/cli.js +57 -11
- 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 +305 -23
- 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.0**
|
|
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
|
|
@@ -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.x | 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
|
|
@@ -293,8 +295,10 @@ const result = await fetchWithX402(
|
|
|
293
295
|
|
|
294
296
|
### Non-interactive mode (for cron / automation)
|
|
295
297
|
```bash
|
|
296
|
-
# All trading commands accept
|
|
297
|
-
darksol trade swap -i ETH -o USDC -a 0.1 -y
|
|
298
|
+
# All trading commands accept --password (-p) and --yes (-y) for non-interactive use
|
|
299
|
+
darksol trade swap -i ETH -o USDC -a 0.1 -p "password" -y
|
|
300
|
+
darksol trade snipe 0xTOKEN -a 0.05 -p "password" -y
|
|
301
|
+
darksol send --to 0x... --amount 0.1 --token ETH -p "password" -y
|
|
298
302
|
darksol script run my-dca -p "password" -y
|
|
299
303
|
darksol casino bet coinflip -c heads -w 0x1234...
|
|
300
304
|
|
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/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) {
|
|
@@ -424,6 +683,8 @@ export async function handleCommand(cmd, ws) {
|
|
|
424
683
|
return await cmdHistory(args, ws);
|
|
425
684
|
case 'market':
|
|
426
685
|
return await cmdMarket(args, ws);
|
|
686
|
+
case 'trade':
|
|
687
|
+
return await cmdTrade(args, ws);
|
|
427
688
|
case 'wallet':
|
|
428
689
|
return await cmdWallet(args, ws);
|
|
429
690
|
case 'mail':
|
|
@@ -705,6 +966,34 @@ async function cmdMarket(args, ws) {
|
|
|
705
966
|
return {};
|
|
706
967
|
}
|
|
707
968
|
|
|
969
|
+
// ══════════════════════════════════════════════════
|
|
970
|
+
// TRADE (interactive web flow)
|
|
971
|
+
// ══════════════════════════════════════════════════
|
|
972
|
+
async function cmdTrade(args, ws) {
|
|
973
|
+
const sub = (args[0] || '').toLowerCase();
|
|
974
|
+
|
|
975
|
+
if (sub === 'watch') {
|
|
976
|
+
ws.sendLine(`${ANSI.dim}Pair watch is CLI-first right now:${ANSI.reset}`);
|
|
977
|
+
ws.sendLine(` ${ANSI.gold}darksol trade watch${ANSI.reset}`);
|
|
978
|
+
ws.sendLine('');
|
|
979
|
+
return {};
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
ws.sendLine(`${ANSI.gold} ◆ TRADE${ANSI.reset}`);
|
|
983
|
+
ws.sendLine(`${ANSI.dim} ${'─'.repeat(50)}${ANSI.reset}`);
|
|
984
|
+
ws.sendLine(` ${ANSI.white}Choose an execution flow:${ANSI.reset}`);
|
|
985
|
+
ws.sendLine('');
|
|
986
|
+
|
|
987
|
+
ws.sendMenu('trade_action', '◆ Trade Actions', [
|
|
988
|
+
{ value: 'swap', label: '🔄 Swap', desc: 'Interactive token swap (password prompt)' },
|
|
989
|
+
{ value: 'snipe', label: '⚡ Snipe', desc: 'Fast buy by token contract' },
|
|
990
|
+
{ value: 'watch', label: '👀 Watch Pairs', desc: 'Monitor new pairs (CLI guidance)' },
|
|
991
|
+
{ value: 'back', label: '← Back', desc: '' },
|
|
992
|
+
]);
|
|
993
|
+
|
|
994
|
+
return {};
|
|
995
|
+
}
|
|
996
|
+
|
|
708
997
|
// ══════════════════════════════════════════════════
|
|
709
998
|
// WALLET
|
|
710
999
|
// ══════════════════════════════════════════════════
|
|
@@ -1564,24 +1853,17 @@ async function cmdSend(args, ws) {
|
|
|
1564
1853
|
return {};
|
|
1565
1854
|
}
|
|
1566
1855
|
|
|
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}`);
|
|
1856
|
+
ws.sendLine(` ${ANSI.white}Wallet:${ANSI.reset} ${ANSI.gold}${wallet}${ANSI.reset} ${ANSI.dim}on ${chain}${ANSI.reset}`);
|
|
1857
|
+
ws.sendLine(` ${ANSI.dim}Interactive send flow will ask token → recipient → amount → password.${ANSI.reset}`);
|
|
1584
1858
|
ws.sendLine('');
|
|
1859
|
+
|
|
1860
|
+
ws.sendMenu('send_token', '◆ Send Token', [
|
|
1861
|
+
{ value: 'ETH', label: 'ETH', desc: 'Native token transfer' },
|
|
1862
|
+
{ value: 'USDC', label: 'USDC', desc: 'Stablecoin transfer' },
|
|
1863
|
+
{ value: 'custom', label: 'Custom token (0x...)', desc: 'ERC-20 contract address' },
|
|
1864
|
+
{ value: 'back', label: '← Back', desc: '' },
|
|
1865
|
+
]);
|
|
1866
|
+
|
|
1585
1867
|
return {};
|
|
1586
1868
|
}
|
|
1587
1869
|
|
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'],
|