@darksol/terminal 0.5.4 → 0.5.6
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/src/llm/intent.js +163 -24
- package/src/services/cards.js +90 -29
- package/src/web/commands.js +5 -2
package/package.json
CHANGED
package/src/llm/intent.js
CHANGED
|
@@ -9,17 +9,36 @@ import { showSection } from '../ui/banner.js';
|
|
|
9
9
|
// INTENT SYSTEM PROMPT
|
|
10
10
|
// ──────────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
-
const INTENT_SYSTEM_PROMPT = `You are DARKSOL Terminal's trading AI assistant. You help users execute trades, send/receive tokens, analyze markets, manage DCA strategies, and navigate the DARKSOL ecosystem.
|
|
12
|
+
const INTENT_SYSTEM_PROMPT = `You are DARKSOL Terminal's trading AI assistant. You help users execute trades, send/receive tokens, analyze markets, manage DCA strategies, order prepaid cards, and navigate the DARKSOL ecosystem.
|
|
13
|
+
|
|
14
|
+
You are embedded in a CLI/web terminal. Your responses become actions — when you output structured JSON, the terminal executes real on-chain transactions, card orders, and wallet operations. Be precise. Be careful. Real money is at stake.
|
|
13
15
|
|
|
14
16
|
CAPABILITIES:
|
|
15
|
-
- Parse natural language into structured trade/transfer commands
|
|
17
|
+
- Parse natural language into structured trade/transfer/card-order commands
|
|
18
|
+
- Execute swaps via Uniswap V3 (Base SwapRouter02, V1 on ETH/Arb/OP/Polygon)
|
|
19
|
+
- Send ETH and ERC-20 tokens to any address
|
|
20
|
+
- Snipe tokens via Uniswap V2 (Base, Ethereum)
|
|
21
|
+
- Order prepaid Visa/Mastercard cards with crypto (via Trocador)
|
|
16
22
|
- Analyze token prices, liquidity, and market conditions
|
|
17
23
|
- Suggest DCA strategies based on user goals
|
|
18
24
|
- Explain transaction results and gas costs
|
|
19
25
|
- Warn about risks (low liquidity, high slippage, unverified contracts)
|
|
20
26
|
|
|
21
27
|
SUPPORTED CHAINS: Base (default), Ethereum, Polygon, Arbitrum, Optimism
|
|
22
|
-
KNOWN TOKENS
|
|
28
|
+
KNOWN TOKENS PER CHAIN:
|
|
29
|
+
- Base: ETH, WETH, USDC, USDbC, DAI, AERO, VIRTUAL
|
|
30
|
+
- Ethereum: ETH, WETH, USDC, USDT, DAI
|
|
31
|
+
- Arbitrum: ETH, WETH, USDC, USDT, ARB
|
|
32
|
+
- Optimism: ETH, WETH, USDC, OP
|
|
33
|
+
- Polygon: MATIC/POL (native), WETH, WMATIC, USDC, USDT
|
|
34
|
+
|
|
35
|
+
WEB3 KNOWLEDGE:
|
|
36
|
+
- Uniswap V3 uses fee tiers: 500 (0.05%), 3000 (0.3%), 10000 (1%). Default: 3000.
|
|
37
|
+
- Slippage protection: Quoter V2 gets expected output, then applies tolerance (default 0.5%).
|
|
38
|
+
- Token approvals: ERC-20 tokens need approval before swapping (approve → swap is 2 TXs).
|
|
39
|
+
- Gas: Base is very cheap (~$0.01-0.05/TX), Ethereum is expensive ($2-20+/TX).
|
|
40
|
+
- Never send ETH/tokens to a contract address unless you know what you're doing.
|
|
41
|
+
- Always verify contract addresses — don't guess or hallucinate them.
|
|
23
42
|
|
|
24
43
|
USER CONTEXT:
|
|
25
44
|
- Active chain: {{chain}}
|
|
@@ -45,25 +64,80 @@ ACTIONS (use the most specific one):
|
|
|
45
64
|
- "info" — general question about a token or protocol
|
|
46
65
|
- "analyze" — deep analysis of a token
|
|
47
66
|
- "gas" — gas price check
|
|
67
|
+
- "cards" — order a prepaid Visa/Mastercard with crypto (e.g. "order a $50 card", "get me a prepaid card")
|
|
48
68
|
- "unknown" — can't determine what the user wants
|
|
49
69
|
|
|
70
|
+
CARDS ORDERING:
|
|
71
|
+
When the user wants to order a prepaid card, you MUST collect:
|
|
72
|
+
1. amount — ONLY these denominations: $10, $25, $50, $100, $250, $500, $1000
|
|
73
|
+
2. email — delivery address for the card activation link
|
|
74
|
+
3. provider — default "swype" (Global Mastercard). Also: "mpc" (US Mastercard), "reward" (US Visa)
|
|
75
|
+
4. ticker — payment crypto, default "usdc"
|
|
76
|
+
|
|
77
|
+
VERIFIED PAYMENT METHODS (ONLY these work — reject anything else):
|
|
78
|
+
- usdc on base (DEFAULT — cheapest, fastest)
|
|
79
|
+
- usdc on ERC20 (Ethereum — higher gas)
|
|
80
|
+
- usdt on trc20 (Tron — cheap)
|
|
81
|
+
- btc on Mainnet (Bitcoin)
|
|
82
|
+
- eth on ERC20 (Ethereum)
|
|
83
|
+
- sol on Mainnet (Solana)
|
|
84
|
+
- xmr on Mainnet (Monero)
|
|
85
|
+
|
|
86
|
+
⚠ DO NOT accept: eth/base, sol/sol, usdc/polygon, usdt/eth, or any combo not listed above.
|
|
87
|
+
If the user asks for an unsupported combo (like "pay with ETH on Base"), tell them it's not available and suggest alternatives.
|
|
88
|
+
|
|
89
|
+
If the user says "order me a $50 card" but doesn't provide an email, set "needsInfo": ["email"] and ask: "What email should I send the card activation link to?"
|
|
90
|
+
If they mention AgentMail or "my email", suggest using their configured agent email.
|
|
91
|
+
|
|
50
92
|
When parsing, respond with ONLY valid JSON:
|
|
51
93
|
{
|
|
52
|
-
"action": "swap|send|snipe|dca|price|balance|info|analyze|gas|unknown",
|
|
94
|
+
"action": "swap|send|snipe|dca|price|balance|info|analyze|gas|cards|unknown",
|
|
53
95
|
"tokenIn": "symbol or address (for swaps)",
|
|
54
96
|
"tokenOut": "symbol or address (for swaps)",
|
|
55
97
|
"token": "symbol (for send/price/analyze)",
|
|
56
98
|
"amount": "number as string",
|
|
57
99
|
"to": "recipient address (for send)",
|
|
100
|
+
"email": "delivery email (for cards)",
|
|
101
|
+
"provider": "card provider (for cards, default: swype)",
|
|
102
|
+
"ticker": "payment crypto (for cards, default: usdc)",
|
|
58
103
|
"chain": "chain name if specified, null if not",
|
|
59
104
|
"interval": "for DCA: 1h, 4h, 1d, etc.",
|
|
60
105
|
"orders": "for DCA: number of orders",
|
|
61
106
|
"confidence": 0.0-1.0,
|
|
62
107
|
"reasoning": "brief explanation of interpretation",
|
|
63
108
|
"warnings": ["array of risk warnings"],
|
|
109
|
+
"needsInfo": ["array of missing fields the AI should ask about"],
|
|
110
|
+
"followUp": "natural language question to ask the user if info is missing",
|
|
64
111
|
"command": "the exact darksol CLI command to run"
|
|
65
112
|
}
|
|
66
113
|
|
|
114
|
+
CONVERSATIONAL RULES:
|
|
115
|
+
- If the user's request is missing required info (email for cards, address for send, amount for swap), DON'T set action to "unknown". Set the correct action, list what's missing in "needsInfo", and write a natural "followUp" question.
|
|
116
|
+
- Be conversational — "What email should I send the card to?" not "Error: email required"
|
|
117
|
+
- If they mention AgentMail or "my email", suggest using their configured agent email
|
|
118
|
+
- For cards without a specified provider, default to "swype" (global Mastercard)
|
|
119
|
+
- For swaps without a specified chain, use the user's active chain ({{chain}})
|
|
120
|
+
- Never hallucinate contract addresses — if you don't know it, say so
|
|
121
|
+
- When a user asks "how do I..." give them the exact darksol CLI command
|
|
122
|
+
|
|
123
|
+
AGENT/TOOL-USE BEHAVIOR:
|
|
124
|
+
When an AI agent (like OpenClaw) is using this terminal programmatically:
|
|
125
|
+
- Always return structured JSON for actionable intents
|
|
126
|
+
- Include the exact "command" field so the agent can run it
|
|
127
|
+
- Include "warnings" for anything risky (high value, unverified token, etc.)
|
|
128
|
+
- If confidence < 0.6, ask for clarification rather than guessing
|
|
129
|
+
- For card orders: validate amount is in [10,25,50,100,250,500,1000] and ticker is verified
|
|
130
|
+
- For swaps: validate both tokens are known symbols or valid 0x addresses
|
|
131
|
+
- For sends: validate "to" looks like a valid address (0x + 40 hex chars)
|
|
132
|
+
|
|
133
|
+
ERROR GUIDANCE:
|
|
134
|
+
When something fails, help the user fix it:
|
|
135
|
+
- "CALL_EXCEPTION" → likely an RPC issue, suggest switching RPCs
|
|
136
|
+
- "insufficient funds" → tell them their balance, suggest a lower amount
|
|
137
|
+
- "coin not found" → the crypto/network combo isn't supported, list what works
|
|
138
|
+
- "nonce" → pending transaction, wait and retry
|
|
139
|
+
- Don't just say "error" — explain what went wrong and what to do next
|
|
140
|
+
|
|
67
141
|
COMMAND MAPPING:
|
|
68
142
|
- swap → darksol trade swap -i <tokenIn> -o <tokenOut> -a <amount>
|
|
69
143
|
- send → darksol send --to <address> --amount <amount> --token <token>
|
|
@@ -72,6 +146,7 @@ COMMAND MAPPING:
|
|
|
72
146
|
- price → darksol price <token>
|
|
73
147
|
- balance → darksol wallet balance
|
|
74
148
|
- gas → darksol gas <chain>
|
|
149
|
+
- cards → darksol cards order -p <provider> -a <amount> -e <email> --ticker <crypto>
|
|
75
150
|
- analyze → darksol ai analyze <token>`;
|
|
76
151
|
|
|
77
152
|
// ──────────────────────────────────────────────────
|
|
@@ -217,7 +292,7 @@ export async function startChat(opts = {}) {
|
|
|
217
292
|
}
|
|
218
293
|
|
|
219
294
|
// Try to detect actionable intent
|
|
220
|
-
const actionKeywords = /\b(swap|send|transfer|buy|sell|snipe|dca|price|balance|gas)\b/i;
|
|
295
|
+
const actionKeywords = /\b(swap|send|transfer|buy|sell|snipe|dca|price|balance|gas|card|cards|order|prepaid|visa|mastercard)\b/i;
|
|
221
296
|
const isActionable = actionKeywords.test(input);
|
|
222
297
|
|
|
223
298
|
let result;
|
|
@@ -456,8 +531,27 @@ export async function executeIntent(intent, opts = {}) {
|
|
|
456
531
|
}
|
|
457
532
|
|
|
458
533
|
try {
|
|
534
|
+
// Check if the AI needs more info before executing
|
|
535
|
+
if (intent.needsInfo?.length > 0) {
|
|
536
|
+
if (intent.followUp) {
|
|
537
|
+
console.log('');
|
|
538
|
+
console.log(theme.gold(' DARKSOL AI:'));
|
|
539
|
+
console.log(theme.dim(' ') + intent.followUp);
|
|
540
|
+
console.log('');
|
|
541
|
+
}
|
|
542
|
+
return { success: false, reason: 'needs_info', needsInfo: intent.needsInfo, followUp: intent.followUp };
|
|
543
|
+
}
|
|
544
|
+
|
|
459
545
|
switch (intent.action) {
|
|
460
546
|
case 'swap': {
|
|
547
|
+
if (!intent.tokenIn || !intent.tokenOut) {
|
|
548
|
+
info('I need to know what tokens to swap. Example: "swap 0.1 ETH to USDC"');
|
|
549
|
+
return { success: false, reason: 'Missing token pair — tell me what to swap from and to.' };
|
|
550
|
+
}
|
|
551
|
+
if (!intent.amount) {
|
|
552
|
+
info('How much do you want to swap? Example: "swap 0.1 ETH to USDC"');
|
|
553
|
+
return { success: false, reason: 'Missing amount — how much do you want to swap?' };
|
|
554
|
+
}
|
|
461
555
|
const { executeSwap } = await import('../trading/swap.js');
|
|
462
556
|
return await executeSwap({
|
|
463
557
|
tokenIn: intent.tokenIn,
|
|
@@ -469,29 +563,40 @@ export async function executeIntent(intent, opts = {}) {
|
|
|
469
563
|
}
|
|
470
564
|
|
|
471
565
|
case 'snipe': {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
566
|
+
const snipeToken = intent.tokenOut || intent.tokenIn;
|
|
567
|
+
if (!snipeToken || !snipeToken.startsWith('0x')) {
|
|
568
|
+
info('Snipe needs a contract address. Example: "snipe 0x1234... with 0.1 ETH"');
|
|
569
|
+
return { success: false, reason: 'I need a token contract address to snipe.' };
|
|
570
|
+
}
|
|
571
|
+
const { snipeToken: doSnipe } = await import('../trading/snipe.js');
|
|
572
|
+
return await doSnipe(snipeToken, intent.amount || '0.01', {
|
|
476
573
|
chain: intent.chain,
|
|
477
|
-
|
|
478
|
-
|
|
574
|
+
slippage: intent.slippage,
|
|
575
|
+
gas: intent.gasMultiplier,
|
|
479
576
|
});
|
|
480
577
|
}
|
|
481
578
|
|
|
482
579
|
case 'dca': {
|
|
483
|
-
const {
|
|
484
|
-
return await
|
|
485
|
-
|
|
580
|
+
const { createDCA } = await import('../trading/dca.js');
|
|
581
|
+
return await createDCA({
|
|
582
|
+
tokenOut: intent.tokenOut || intent.tokenIn,
|
|
486
583
|
amount: intent.amount,
|
|
487
584
|
interval: intent.interval || '1h',
|
|
488
|
-
|
|
585
|
+
totalOrders: intent.orders || 10,
|
|
489
586
|
chain: intent.chain,
|
|
490
587
|
});
|
|
491
588
|
}
|
|
492
589
|
|
|
493
590
|
case 'send':
|
|
494
591
|
case 'transfer': {
|
|
592
|
+
if (!intent.to) {
|
|
593
|
+
info('Where should I send it? Give me a wallet address (0x...)');
|
|
594
|
+
return { success: false, reason: 'Missing recipient address — who are you sending to?' };
|
|
595
|
+
}
|
|
596
|
+
if (!intent.amount) {
|
|
597
|
+
info('How much? Example: "send 10 USDC to 0x..."');
|
|
598
|
+
return { success: false, reason: 'Missing amount — how much do you want to send?' };
|
|
599
|
+
}
|
|
495
600
|
const { sendFunds } = await import('../wallet/manager.js');
|
|
496
601
|
return await sendFunds({
|
|
497
602
|
to: intent.to,
|
|
@@ -507,8 +612,8 @@ export async function executeIntent(intent, opts = {}) {
|
|
|
507
612
|
await checkPrices([token]);
|
|
508
613
|
return { success: true, action: 'price' };
|
|
509
614
|
}
|
|
510
|
-
info('
|
|
511
|
-
return { success: false, reason: '
|
|
615
|
+
info('Which token? Example: "price ETH" or "how much is AERO"');
|
|
616
|
+
return { success: false, reason: 'Which token do you want the price for?' };
|
|
512
617
|
}
|
|
513
618
|
|
|
514
619
|
case 'balance': {
|
|
@@ -523,6 +628,23 @@ export async function executeIntent(intent, opts = {}) {
|
|
|
523
628
|
return { success: true, action: 'gas' };
|
|
524
629
|
}
|
|
525
630
|
|
|
631
|
+
case 'cards': {
|
|
632
|
+
if (!intent.amount) {
|
|
633
|
+
info('What denomination? We have $10, $25, $50, $100, $250, $500, $1000');
|
|
634
|
+
return { success: false, reason: 'What card amount do you want?' };
|
|
635
|
+
}
|
|
636
|
+
if (!intent.email) {
|
|
637
|
+
info('I need an email to deliver the card activation link to.');
|
|
638
|
+
return { success: false, reason: 'What email should I send the card to?' };
|
|
639
|
+
}
|
|
640
|
+
const { cardsOrder } = await import('../services/cards.js');
|
|
641
|
+
return await cardsOrder(intent.provider || 'swype', intent.amount, {
|
|
642
|
+
email: intent.email,
|
|
643
|
+
ticker: intent.ticker || 'usdc',
|
|
644
|
+
network: intent.network,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
|
|
526
648
|
case 'info':
|
|
527
649
|
case 'analyze': {
|
|
528
650
|
const token = intent.tokenOut || intent.tokenIn || intent.token;
|
|
@@ -530,20 +652,37 @@ export async function executeIntent(intent, opts = {}) {
|
|
|
530
652
|
await analyzeToken(token, opts);
|
|
531
653
|
return { success: true, action: 'analyze' };
|
|
532
654
|
}
|
|
533
|
-
info('
|
|
534
|
-
return { success: false, reason: '
|
|
655
|
+
info('Which token do you want me to analyze?');
|
|
656
|
+
return { success: false, reason: 'Tell me which token to look at.' };
|
|
535
657
|
}
|
|
536
658
|
|
|
537
659
|
default:
|
|
538
|
-
warn(`
|
|
660
|
+
warn(`I don't know how to do "${intent.action}" yet.`);
|
|
539
661
|
if (intent.command) {
|
|
540
|
-
info(`
|
|
662
|
+
info(`Try running: ${theme.gold(intent.command)}`);
|
|
541
663
|
}
|
|
542
|
-
return { success: false, reason: `
|
|
664
|
+
return { success: false, reason: `Action "${intent.action}" isn't wired up yet.` };
|
|
543
665
|
}
|
|
544
666
|
} catch (err) {
|
|
545
|
-
error
|
|
546
|
-
|
|
667
|
+
// Human-readable error messages
|
|
668
|
+
const msg = err.message || String(err);
|
|
669
|
+
if (msg.includes('CALL_EXCEPTION')) {
|
|
670
|
+
error('The on-chain call failed. This usually means the RPC is having issues or the contract call reverted.');
|
|
671
|
+
info('Try again in a moment, or switch to a different RPC with: darksol config set rpcs.base <url>');
|
|
672
|
+
} else if (msg.includes('insufficient funds') || msg.includes('Insufficient')) {
|
|
673
|
+
error('Not enough funds in your wallet for this transaction (including gas fees).');
|
|
674
|
+
} else if (msg.includes('nonce')) {
|
|
675
|
+
error('Transaction nonce conflict. You may have a pending transaction — wait for it to confirm or try again.');
|
|
676
|
+
} else if (msg.includes('timeout') || msg.includes('ETIMEDOUT')) {
|
|
677
|
+
error('Network timeout — the RPC server didn\'t respond in time. Try again or switch RPCs.');
|
|
678
|
+
} else if (msg.includes('could not detect network')) {
|
|
679
|
+
error('Can\'t connect to the blockchain. Check your internet connection and RPC settings.');
|
|
680
|
+
} else if (msg.includes('password') || msg.includes('decrypt')) {
|
|
681
|
+
error('Wrong wallet password. The private key couldn\'t be decrypted.');
|
|
682
|
+
} else {
|
|
683
|
+
error(`Failed: ${msg}`);
|
|
684
|
+
}
|
|
685
|
+
return { success: false, error: msg };
|
|
547
686
|
}
|
|
548
687
|
}
|
|
549
688
|
|
package/src/services/cards.js
CHANGED
|
@@ -6,40 +6,80 @@ import { showSection } from '../ui/banner.js';
|
|
|
6
6
|
|
|
7
7
|
const BASE = () => getServiceURL('cards') || 'https://acp.darksol.net';
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'
|
|
9
|
+
// ══════════════════════════════════════════════════
|
|
10
|
+
// VERIFIED CRYPTO COMBOS (tested against Trocador API 2026-03-09)
|
|
11
|
+
// Only these combos are allowed — everything else is rejected before hitting the API
|
|
12
|
+
// ══════════════════════════════════════════════════
|
|
13
|
+
const VERIFIED_COMBOS = [
|
|
14
|
+
{ ticker: 'usdc', network: 'base', display: 'USDC on Base', default: true },
|
|
15
|
+
{ ticker: 'usdc', network: 'ERC20', display: 'USDC on Ethereum' },
|
|
16
|
+
{ ticker: 'usdt', network: 'trc20', display: 'USDT on Tron (TRC-20)' },
|
|
17
|
+
{ ticker: 'btc', network: 'Mainnet', display: 'Bitcoin (BTC)' },
|
|
18
|
+
{ ticker: 'eth', network: 'ERC20', display: 'Ethereum (ETH)' },
|
|
19
|
+
{ ticker: 'sol', network: 'Mainnet', display: 'Solana (SOL)' },
|
|
20
|
+
{ ticker: 'xmr', network: 'Mainnet', display: 'Monero (XMR)' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Aliases: what users might type → what Trocador expects
|
|
24
|
+
const TICKER_ALIASES = {
|
|
25
|
+
'ethereum': 'eth', 'ether': 'eth',
|
|
26
|
+
'bitcoin': 'btc',
|
|
27
|
+
'solana': 'sol',
|
|
28
|
+
'monero': 'xmr',
|
|
29
|
+
'tether': 'usdt',
|
|
30
|
+
'usd coin': 'usdc', 'usd-c': 'usdc',
|
|
20
31
|
};
|
|
21
32
|
|
|
22
|
-
|
|
23
|
-
const NETWORK_MAP = {
|
|
33
|
+
const NETWORK_ALIASES = {
|
|
24
34
|
'base': 'base',
|
|
25
|
-
'ethereum': 'ERC20', 'eth': 'ERC20', 'erc20': 'ERC20',
|
|
26
|
-
'tron': 'trc20', 'trc20': 'trc20',
|
|
27
|
-
'mainnet': 'Mainnet', '
|
|
35
|
+
'ethereum': 'ERC20', 'eth': 'ERC20', 'erc20': 'ERC20', 'erc-20': 'ERC20',
|
|
36
|
+
'tron': 'trc20', 'trc20': 'trc20', 'trc-20': 'trc20',
|
|
37
|
+
'mainnet': 'Mainnet', 'main': 'Mainnet',
|
|
28
38
|
};
|
|
29
39
|
|
|
40
|
+
const VALID_AMOUNTS = [10, 25, 50, 100, 250, 500, 1000];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve user input to a verified ticker/network combo.
|
|
44
|
+
* Returns { ticker, network, display } or null if invalid.
|
|
45
|
+
*/
|
|
30
46
|
function resolveTickerNetwork(ticker, network) {
|
|
31
|
-
const t = (ticker || '').toLowerCase();
|
|
32
|
-
const n = (network || '').toLowerCase();
|
|
47
|
+
const t = TICKER_ALIASES[(ticker || '').toLowerCase()] || (ticker || '').toLowerCase();
|
|
33
48
|
|
|
34
|
-
// If
|
|
35
|
-
if (
|
|
36
|
-
const
|
|
37
|
-
|
|
49
|
+
// If network specified, try to match exactly
|
|
50
|
+
if (network) {
|
|
51
|
+
const n = NETWORK_ALIASES[(network || '').toLowerCase()] || network;
|
|
52
|
+
const match = VERIFIED_COMBOS.find(c => c.ticker === t && c.network === n);
|
|
53
|
+
if (match) return match;
|
|
54
|
+
// Try just the ticker with its default network
|
|
38
55
|
}
|
|
39
56
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
57
|
+
// Just ticker — find the verified default for it
|
|
58
|
+
const match = VERIFIED_COMBOS.find(c => c.ticker === t);
|
|
59
|
+
if (match) return match;
|
|
60
|
+
|
|
61
|
+
return null; // Not a verified combo
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get all verified combos (for menus / agent listings)
|
|
66
|
+
*/
|
|
67
|
+
export function getVerifiedCombos() {
|
|
68
|
+
return VERIFIED_COMBOS;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the default combo
|
|
73
|
+
*/
|
|
74
|
+
export function getDefaultCombo() {
|
|
75
|
+
return VERIFIED_COMBOS.find(c => c.default) || VERIFIED_COMBOS[0];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get valid amounts
|
|
80
|
+
*/
|
|
81
|
+
export function getValidAmounts() {
|
|
82
|
+
return VALID_AMOUNTS;
|
|
43
83
|
}
|
|
44
84
|
|
|
45
85
|
export async function cardsCatalog() {
|
|
@@ -87,19 +127,40 @@ export async function cardsOrder(provider, amount, opts = {}) {
|
|
|
87
127
|
return;
|
|
88
128
|
}
|
|
89
129
|
|
|
130
|
+
// Validate amount
|
|
131
|
+
const numAmount = Number(amount);
|
|
132
|
+
if (!VALID_AMOUNTS.includes(numAmount)) {
|
|
133
|
+
error(`Invalid card amount: $${amount}. Valid amounts: ${VALID_AMOUNTS.map(a => '$' + a).join(', ')}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Validate provider
|
|
138
|
+
const validProviders = ['swype', 'mpc', 'reward'];
|
|
139
|
+
if (!validProviders.includes((provider || '').toLowerCase())) {
|
|
140
|
+
error(`Invalid provider: ${provider}. Options: swype (Global MC), mpc (US MC), reward (US Visa)`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
90
144
|
const spin = spinner('Processing card order...').start();
|
|
91
145
|
try {
|
|
92
146
|
const body = {
|
|
93
|
-
provider,
|
|
94
|
-
amount:
|
|
147
|
+
provider: provider.toLowerCase(),
|
|
148
|
+
amount: numAmount,
|
|
95
149
|
email: opts.email,
|
|
96
150
|
};
|
|
97
|
-
|
|
151
|
+
|
|
152
|
+
// Resolve and validate crypto
|
|
98
153
|
if (opts.ticker) {
|
|
99
154
|
const resolved = resolveTickerNetwork(opts.ticker, opts.network);
|
|
155
|
+
if (!resolved) {
|
|
156
|
+
spin.fail('Invalid payment method');
|
|
157
|
+
error(`"${opts.ticker}${opts.network ? '/' + opts.network : ''}" is not a supported payment option.`);
|
|
158
|
+
info('Supported: ' + VERIFIED_COMBOS.map(c => c.display).join(', '));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
100
161
|
body.tickerFrom = resolved.ticker;
|
|
101
162
|
body.networkFrom = resolved.network;
|
|
102
|
-
info(`Payment: ${resolved.
|
|
163
|
+
info(`Payment: ${resolved.display}`);
|
|
103
164
|
}
|
|
104
165
|
|
|
105
166
|
const data = await fetchJSON(`${BASE()}/api/cards/order`, {
|
package/src/web/commands.js
CHANGED
|
@@ -215,9 +215,12 @@ export async function handleMenuSelect(id, value, item, ws) {
|
|
|
215
215
|
|
|
216
216
|
case 'cards_crypto':
|
|
217
217
|
if (value === 'back') return {};
|
|
218
|
-
// Execute the order
|
|
218
|
+
// Execute the order with verified combo
|
|
219
219
|
return await executeCardOrder(item?.meta || {}, ws);
|
|
220
220
|
|
|
221
|
+
case 'cards_status_check':
|
|
222
|
+
return await showCardStatus(value, ws);
|
|
223
|
+
|
|
221
224
|
case 'agent_action':
|
|
222
225
|
if (value === 'start') {
|
|
223
226
|
const { listWallets } = await import('../wallet/keystore.js');
|
|
@@ -454,7 +457,7 @@ export async function handleCommand(cmd, ws) {
|
|
|
454
457
|
return await cmdChatLogs(args, ws);
|
|
455
458
|
default: {
|
|
456
459
|
// Fuzzy: if it looks like natural language, route to AI
|
|
457
|
-
const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca)\b/i;
|
|
460
|
+
const nlKeywords = /\b(swap|buy|sell|send|transfer|price|what|how|should|analyze|check|balance|gas|dca|order|card|prepaid|visa|mastercard)\b/i;
|
|
458
461
|
if (nlKeywords.test(cmd)) {
|
|
459
462
|
return await cmdAI(cmd.split(/\s+/), ws);
|
|
460
463
|
}
|