@darksol/terminal 0.4.2 → 0.4.4
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 +27 -2
- package/package.json +1 -1
- package/src/cli.js +42 -2
- package/src/trading/swap.js +28 -0
- package/src/ui/banner.js +9 -20
- package/src/wallet/manager.js +264 -0
package/README.md
CHANGED
|
@@ -30,15 +30,30 @@ darksol
|
|
|
30
30
|
# Create a wallet (AES-256-GCM encrypted)
|
|
31
31
|
darksol wallet create main
|
|
32
32
|
|
|
33
|
-
# Check balance
|
|
33
|
+
# Check balance + multi-chain portfolio
|
|
34
34
|
darksol wallet balance
|
|
35
|
+
darksol portfolio
|
|
35
36
|
|
|
36
|
-
#
|
|
37
|
+
# Token prices & live monitoring
|
|
38
|
+
darksol price ETH AERO VIRTUAL
|
|
39
|
+
darksol watch AERO --above 2.0
|
|
40
|
+
|
|
41
|
+
# Gas estimates
|
|
42
|
+
darksol gas base
|
|
43
|
+
|
|
44
|
+
# Swap tokens (Uniswap V3 with slippage protection)
|
|
37
45
|
darksol trade swap -i ETH -o USDC -a 0.1
|
|
38
46
|
|
|
39
47
|
# AI trading assistant
|
|
40
48
|
darksol ai chat
|
|
41
49
|
|
|
50
|
+
# Agent email
|
|
51
|
+
darksol mail setup
|
|
52
|
+
darksol mail send --to user@example.com --subject "Hello"
|
|
53
|
+
|
|
54
|
+
# Web terminal in browser
|
|
55
|
+
darksol serve
|
|
56
|
+
|
|
42
57
|
# Start agent signer for OpenClaw
|
|
43
58
|
darksol agent start main
|
|
44
59
|
```
|
|
@@ -58,6 +73,16 @@ darksol agent start main
|
|
|
58
73
|
| `market` | Market intel, top movers, token analysis | x402 micropayments |
|
|
59
74
|
| `oracle` | On-chain random number oracle | $0.05–$0.25 |
|
|
60
75
|
| `casino` | The Clawsino — on-chain betting | $1 flat bets |
|
|
76
|
+
| `portfolio` | Multi-chain balance view (5 EVM chains) | Free |
|
|
77
|
+
| `gas` | Gas prices & cost estimates | Free |
|
|
78
|
+
| `price` | Quick token price check (DexScreener) | Free |
|
|
79
|
+
| `watch` | Live price monitoring with alerts | Free |
|
|
80
|
+
| `history` | Transaction history via block explorers | Free |
|
|
81
|
+
| `mail` | AgentMail — email for AI agents | Free tier |
|
|
82
|
+
| `serve` | Web terminal in browser (xterm.js) | Free |
|
|
83
|
+
| `facilitator` | x402 payment facilitator | Free |
|
|
84
|
+
| `cards` | Prepaid Visa/MC cards | Service fees |
|
|
85
|
+
| `builders` | ERC-8021 builder code directory | Free |
|
|
61
86
|
| `cards` | Crypto → prepaid Visa/MC (no KYC) | 3% markup |
|
|
62
87
|
| `builders` | ERC-8021 builder leaderboard | Free |
|
|
63
88
|
| `facilitator` | x402 payment verification & settlement | Free |
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { showBanner, showMiniBanner, showSection } from './ui/banner.js';
|
|
|
3
3
|
import { theme } from './ui/theme.js';
|
|
4
4
|
import { kvDisplay, success, error, warn, info } from './ui/components.js';
|
|
5
5
|
import { getConfig, setConfig, getAllConfig, getRPC, setRPC, configPath } from './config/store.js';
|
|
6
|
-
import { createWallet, importWallet, showWallets, getBalance, useWallet, exportWallet } from './wallet/manager.js';
|
|
6
|
+
import { createWallet, importWallet, showWallets, getBalance, useWallet, exportWallet, sendFunds, receiveAddress } from './wallet/manager.js';
|
|
7
7
|
import { showPortfolio } from './wallet/portfolio.js';
|
|
8
8
|
import { showHistory } from './wallet/history.js';
|
|
9
9
|
import { showGas } from './services/gas.js';
|
|
@@ -26,6 +26,9 @@ import { parseIntent, startChat, adviseStrategy, analyzeToken, executeIntent } f
|
|
|
26
26
|
import { startAgentSigner, showAgentDocs } from './wallet/agent-signer.js';
|
|
27
27
|
import { listSkills, installSkill, skillInfo, uninstallSkill } from './services/skills.js';
|
|
28
28
|
import { runSetupWizard, checkFirstRun } from './setup/wizard.js';
|
|
29
|
+
import { createRequire } from 'module';
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
const { version: PKG_VERSION } = require('../package.json');
|
|
29
32
|
|
|
30
33
|
export function cli(argv) {
|
|
31
34
|
const program = new Command();
|
|
@@ -33,7 +36,7 @@ export function cli(argv) {
|
|
|
33
36
|
program
|
|
34
37
|
.name('darksol')
|
|
35
38
|
.description(theme.gold('DARKSOL Terminal') + theme.dim(' — Ghost in the machine with teeth 🌑'))
|
|
36
|
-
.version(
|
|
39
|
+
.version(PKG_VERSION)
|
|
37
40
|
;
|
|
38
41
|
|
|
39
42
|
// ═══════════════════════════════════════
|
|
@@ -70,6 +73,20 @@ export function cli(argv) {
|
|
|
70
73
|
.description('Set active wallet')
|
|
71
74
|
.action((name) => useWallet(name));
|
|
72
75
|
|
|
76
|
+
wallet
|
|
77
|
+
.command('send')
|
|
78
|
+
.description('Send ETH or tokens')
|
|
79
|
+
.option('--to <address>', 'Recipient address')
|
|
80
|
+
.option('-a, --amount <amount>', 'Amount to send')
|
|
81
|
+
.option('-t, --token <token>', 'Token (ETH, USDC, or 0x address)', 'ETH')
|
|
82
|
+
.option('-w, --wallet <name>', 'Wallet to send from')
|
|
83
|
+
.action((opts) => sendFunds(opts));
|
|
84
|
+
|
|
85
|
+
wallet
|
|
86
|
+
.command('receive [name]')
|
|
87
|
+
.description('Show your address for receiving funds')
|
|
88
|
+
.action((name) => receiveAddress(name));
|
|
89
|
+
|
|
73
90
|
wallet
|
|
74
91
|
.command('export [name]')
|
|
75
92
|
.description('Export wallet details')
|
|
@@ -417,6 +434,25 @@ export function cli(argv) {
|
|
|
417
434
|
.description('Multi-chain balance view (shortcut for: wallet portfolio)')
|
|
418
435
|
.action((name) => showPortfolio(name));
|
|
419
436
|
|
|
437
|
+
// ═══════════════════════════════════════
|
|
438
|
+
// SEND SHORTCUT
|
|
439
|
+
// ═══════════════════════════════════════
|
|
440
|
+
program
|
|
441
|
+
.command('send')
|
|
442
|
+
.description('Send ETH or tokens (shortcut for: wallet send)')
|
|
443
|
+
.option('--to <address>', 'Recipient address')
|
|
444
|
+
.option('-a, --amount <amount>', 'Amount')
|
|
445
|
+
.option('-t, --token <token>', 'Token (ETH, USDC, or 0x address)', 'ETH')
|
|
446
|
+
.action((opts) => sendFunds(opts));
|
|
447
|
+
|
|
448
|
+
// ═══════════════════════════════════════
|
|
449
|
+
// RECEIVE SHORTCUT
|
|
450
|
+
// ═══════════════════════════════════════
|
|
451
|
+
program
|
|
452
|
+
.command('receive')
|
|
453
|
+
.description('Show your address for receiving (shortcut for: wallet receive)')
|
|
454
|
+
.action(() => receiveAddress());
|
|
455
|
+
|
|
420
456
|
// ═══════════════════════════════════════
|
|
421
457
|
// GAS COMMAND
|
|
422
458
|
// ═══════════════════════════════════════
|
|
@@ -721,6 +757,8 @@ export function cli(argv) {
|
|
|
721
757
|
['Output', cfg.output],
|
|
722
758
|
['Slippage', `${cfg.slippage}%`],
|
|
723
759
|
['Gas Multiplier', `${cfg.gasMultiplier}x`],
|
|
760
|
+
['Mail', cfg.mailEmail || theme.dim('(not set)')],
|
|
761
|
+
['Version', PKG_VERSION],
|
|
724
762
|
['Config File', configPath()],
|
|
725
763
|
]);
|
|
726
764
|
console.log('');
|
|
@@ -960,6 +998,8 @@ function showCommandList() {
|
|
|
960
998
|
showSection('COMMANDS');
|
|
961
999
|
const commands = [
|
|
962
1000
|
['wallet', 'Create, import, manage wallets'],
|
|
1001
|
+
['send', 'Send ETH or tokens'],
|
|
1002
|
+
['receive', 'Show address to receive funds'],
|
|
963
1003
|
['portfolio', 'Multi-chain balance view'],
|
|
964
1004
|
['price', 'Quick token price check'],
|
|
965
1005
|
['watch', 'Live price monitoring + alerts'],
|
package/src/trading/swap.js
CHANGED
|
@@ -18,6 +18,12 @@ const ROUTERS = {
|
|
|
18
18
|
arbitrum: {
|
|
19
19
|
uniswapV3: '0xE592427A0AEce92De3Edee1F18E0157C05861564',
|
|
20
20
|
},
|
|
21
|
+
optimism: {
|
|
22
|
+
uniswapV3: '0xE592427A0AEce92De3Edee1F18E0157C05861564',
|
|
23
|
+
},
|
|
24
|
+
polygon: {
|
|
25
|
+
uniswapV3: '0xE592427A0AEce92De3Edee1F18E0157C05861564',
|
|
26
|
+
},
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
// Common token addresses per chain
|
|
@@ -38,6 +44,26 @@ const TOKENS = {
|
|
|
38
44
|
USDT: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
39
45
|
DAI: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
40
46
|
},
|
|
47
|
+
arbitrum: {
|
|
48
|
+
ETH: ethers.ZeroAddress,
|
|
49
|
+
WETH: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
|
|
50
|
+
USDC: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
|
|
51
|
+
USDT: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
|
|
52
|
+
ARB: '0x912CE59144191C1204E64559FE8253a0e49E6548',
|
|
53
|
+
},
|
|
54
|
+
optimism: {
|
|
55
|
+
ETH: ethers.ZeroAddress,
|
|
56
|
+
WETH: '0x4200000000000000000000000000000000000006',
|
|
57
|
+
USDC: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85',
|
|
58
|
+
OP: '0x4200000000000000000000000000000000000042',
|
|
59
|
+
},
|
|
60
|
+
polygon: {
|
|
61
|
+
ETH: ethers.ZeroAddress,
|
|
62
|
+
WETH: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
|
|
63
|
+
WMATIC: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
|
|
64
|
+
USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
|
|
65
|
+
USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
|
|
66
|
+
},
|
|
41
67
|
};
|
|
42
68
|
|
|
43
69
|
// ERC20 ABI for approvals and balance checks
|
|
@@ -66,6 +92,8 @@ const QUOTERS = {
|
|
|
66
92
|
base: '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a',
|
|
67
93
|
ethereum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
68
94
|
arbitrum: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
95
|
+
optimism: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
96
|
+
polygon: '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',
|
|
69
97
|
};
|
|
70
98
|
|
|
71
99
|
// Resolve token symbol to address
|
package/src/ui/banner.js
CHANGED
|
@@ -2,6 +2,9 @@ import figlet from 'figlet';
|
|
|
2
2
|
import gradient from 'gradient-string';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { theme } from './theme.js';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const { version } = require('../../package.json');
|
|
5
8
|
|
|
6
9
|
const darksol_gradient = gradient(['#B8860B', '#FFD700', '#FFF8DC', '#FFD700', '#B8860B']);
|
|
7
10
|
|
|
@@ -11,6 +14,9 @@ export function showBanner(opts = {}) {
|
|
|
11
14
|
horizontalLayout: 'fitted',
|
|
12
15
|
});
|
|
13
16
|
|
|
17
|
+
const vStr = `v${version}`;
|
|
18
|
+
const pad = ' '.repeat(Math.max(0, 48 - vStr.length));
|
|
19
|
+
|
|
14
20
|
console.log('');
|
|
15
21
|
console.log(darksol_gradient(banner));
|
|
16
22
|
console.log('');
|
|
@@ -26,8 +32,8 @@ export function showBanner(opts = {}) {
|
|
|
26
32
|
);
|
|
27
33
|
console.log(
|
|
28
34
|
theme.dim(' ║ ') +
|
|
29
|
-
theme.subtle(
|
|
30
|
-
theme.dim(
|
|
35
|
+
theme.subtle(` ${vStr}`) +
|
|
36
|
+
theme.dim(pad) +
|
|
31
37
|
theme.gold('🌑') +
|
|
32
38
|
theme.dim(' ║')
|
|
33
39
|
);
|
|
@@ -44,7 +50,7 @@ export function showBanner(opts = {}) {
|
|
|
44
50
|
|
|
45
51
|
export function showMiniBanner() {
|
|
46
52
|
console.log('');
|
|
47
|
-
console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(
|
|
53
|
+
console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(` v${version}`));
|
|
48
54
|
console.log(theme.dim(' ─────────────────────────────'));
|
|
49
55
|
console.log('');
|
|
50
56
|
}
|
|
@@ -58,20 +64,3 @@ export function showSection(title) {
|
|
|
58
64
|
export function showDivider() {
|
|
59
65
|
console.log(theme.dim(' ' + '─'.repeat(50)));
|
|
60
66
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
package/src/wallet/manager.js
CHANGED
|
@@ -248,6 +248,270 @@ export function useWallet(name) {
|
|
|
248
248
|
success(`Active wallet set to "${name}"`);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// ═══════════════════════════════════════
|
|
252
|
+
// SEND — ETH and ERC-20 transfers
|
|
253
|
+
// ═══════════════════════════════════════
|
|
254
|
+
|
|
255
|
+
const COMMON_TOKENS = {
|
|
256
|
+
base: { USDC: { addr: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6 } },
|
|
257
|
+
ethereum: { USDC: { addr: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6 }, USDT: { addr: '0xdAC17F958D2ee523a2206206994597C13D831ec7', decimals: 6 } },
|
|
258
|
+
arbitrum: { USDC: { addr: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', decimals: 6 } },
|
|
259
|
+
optimism: { USDC: { addr: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', decimals: 6 } },
|
|
260
|
+
polygon: { USDC: { addr: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', decimals: 6 } },
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const ERC20_SEND_ABI = [
|
|
264
|
+
'function transfer(address to, uint256 amount) returns (bool)',
|
|
265
|
+
'function balanceOf(address) view returns (uint256)',
|
|
266
|
+
'function decimals() view returns (uint8)',
|
|
267
|
+
'function symbol() view returns (string)',
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
export async function sendFunds(opts = {}) {
|
|
271
|
+
const name = opts.wallet || getConfig('activeWallet');
|
|
272
|
+
if (!name) {
|
|
273
|
+
error('No active wallet. Set one: darksol wallet use <name>');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const chain = getConfig('chain') || 'base';
|
|
278
|
+
const walletData = loadWallet(name);
|
|
279
|
+
|
|
280
|
+
// Interactive prompt if flags not provided
|
|
281
|
+
let to = opts.to;
|
|
282
|
+
let amount = opts.amount;
|
|
283
|
+
let token = opts.token || 'ETH';
|
|
284
|
+
|
|
285
|
+
console.log('');
|
|
286
|
+
showSection(`SEND — ${name}`);
|
|
287
|
+
console.log(theme.dim(` ${walletData.address}`));
|
|
288
|
+
console.log(theme.dim(` Chain: ${chain}`));
|
|
289
|
+
console.log('');
|
|
290
|
+
|
|
291
|
+
if (!to) {
|
|
292
|
+
({ to } = await inquirer.prompt([{
|
|
293
|
+
type: 'input',
|
|
294
|
+
name: 'to',
|
|
295
|
+
message: theme.gold('Recipient address (0x...):'),
|
|
296
|
+
validate: (v) => {
|
|
297
|
+
if (!v.startsWith('0x') || v.length !== 42) return 'Enter a valid 0x address (42 chars)';
|
|
298
|
+
return true;
|
|
299
|
+
},
|
|
300
|
+
}]));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!amount) {
|
|
304
|
+
// Show available tokens
|
|
305
|
+
const tokenChoices = ['ETH'];
|
|
306
|
+
const chainTokens = COMMON_TOKENS[chain] || {};
|
|
307
|
+
Object.keys(chainTokens).forEach(t => tokenChoices.push(t));
|
|
308
|
+
tokenChoices.push('Custom token (paste address)');
|
|
309
|
+
|
|
310
|
+
({ token } = await inquirer.prompt([{
|
|
311
|
+
type: 'list',
|
|
312
|
+
name: 'token',
|
|
313
|
+
message: theme.gold('What to send?'),
|
|
314
|
+
choices: tokenChoices,
|
|
315
|
+
}]));
|
|
316
|
+
|
|
317
|
+
if (token === 'Custom token (paste address)') {
|
|
318
|
+
({ token } = await inquirer.prompt([{
|
|
319
|
+
type: 'input',
|
|
320
|
+
name: 'token',
|
|
321
|
+
message: theme.gold('Token contract address (0x...):'),
|
|
322
|
+
validate: (v) => v.startsWith('0x') && v.length === 42 || 'Invalid address',
|
|
323
|
+
}]));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
({ amount } = await inquirer.prompt([{
|
|
327
|
+
type: 'input',
|
|
328
|
+
name: 'amount',
|
|
329
|
+
message: theme.gold(`Amount to send (${token}):`),
|
|
330
|
+
validate: (v) => parseFloat(v) > 0 || 'Enter a positive amount',
|
|
331
|
+
}]));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Password
|
|
335
|
+
const { password } = await inquirer.prompt([{
|
|
336
|
+
type: 'password',
|
|
337
|
+
name: 'password',
|
|
338
|
+
message: theme.gold('Wallet password:'),
|
|
339
|
+
mask: '●',
|
|
340
|
+
}]);
|
|
341
|
+
|
|
342
|
+
const spin = spinner('Preparing transaction...').start();
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const { signer, provider, address } = await getSigner(name, password);
|
|
346
|
+
|
|
347
|
+
const isETH = token.toUpperCase() === 'ETH';
|
|
348
|
+
const isSymbol = !token.startsWith('0x');
|
|
349
|
+
let tokenAddr = null;
|
|
350
|
+
let tokenDecimals = 18;
|
|
351
|
+
let tokenSymbol = token.toUpperCase();
|
|
352
|
+
|
|
353
|
+
if (!isETH) {
|
|
354
|
+
// Resolve token
|
|
355
|
+
const chainTokens = COMMON_TOKENS[chain] || {};
|
|
356
|
+
if (isSymbol && chainTokens[token.toUpperCase()]) {
|
|
357
|
+
const info = chainTokens[token.toUpperCase()];
|
|
358
|
+
tokenAddr = info.addr;
|
|
359
|
+
tokenDecimals = info.decimals;
|
|
360
|
+
tokenSymbol = token.toUpperCase();
|
|
361
|
+
} else if (token.startsWith('0x')) {
|
|
362
|
+
tokenAddr = token;
|
|
363
|
+
const contract = new ethers.Contract(tokenAddr, ERC20_SEND_ABI, provider);
|
|
364
|
+
tokenDecimals = Number(await contract.decimals());
|
|
365
|
+
tokenSymbol = await contract.symbol();
|
|
366
|
+
} else {
|
|
367
|
+
spin.fail('Unknown token');
|
|
368
|
+
error(`Token "${token}" not recognized. Use a symbol (USDC) or contract address.`);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check balance
|
|
374
|
+
let balanceStr;
|
|
375
|
+
if (isETH) {
|
|
376
|
+
const balance = await provider.getBalance(address);
|
|
377
|
+
const amountWei = ethers.parseEther(amount);
|
|
378
|
+
balanceStr = `${parseFloat(ethers.formatEther(balance)).toFixed(6)} ETH`;
|
|
379
|
+
if (balance < amountWei) {
|
|
380
|
+
spin.fail('Insufficient balance');
|
|
381
|
+
error(`Need ${amount} ETH, have ${balanceStr}`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
const contract = new ethers.Contract(tokenAddr, ERC20_SEND_ABI, provider);
|
|
386
|
+
const balance = await contract.balanceOf(address);
|
|
387
|
+
const amountParsed = ethers.parseUnits(amount, tokenDecimals);
|
|
388
|
+
balanceStr = `${parseFloat(ethers.formatUnits(balance, tokenDecimals)).toFixed(tokenDecimals > 6 ? 6 : 2)} ${tokenSymbol}`;
|
|
389
|
+
if (balance < amountParsed) {
|
|
390
|
+
spin.fail('Insufficient balance');
|
|
391
|
+
error(`Need ${amount} ${tokenSymbol}, have ${balanceStr}`);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Estimate gas
|
|
397
|
+
let gasEstimate;
|
|
398
|
+
const feeData = await provider.getFeeData();
|
|
399
|
+
if (isETH) {
|
|
400
|
+
gasEstimate = 21000n;
|
|
401
|
+
} else {
|
|
402
|
+
const contract = new ethers.Contract(tokenAddr, ERC20_SEND_ABI, signer);
|
|
403
|
+
gasEstimate = await contract.transfer.estimateGas(to, ethers.parseUnits(amount, tokenDecimals));
|
|
404
|
+
}
|
|
405
|
+
const gasCostWei = gasEstimate * (feeData.gasPrice || 0n);
|
|
406
|
+
const gasCostEth = parseFloat(ethers.formatEther(gasCostWei));
|
|
407
|
+
|
|
408
|
+
spin.succeed('Transaction ready');
|
|
409
|
+
|
|
410
|
+
// Confirmation
|
|
411
|
+
console.log('');
|
|
412
|
+
showSection('SEND PREVIEW');
|
|
413
|
+
kvDisplay([
|
|
414
|
+
['From', `${name} (${address.slice(0, 6)}...${address.slice(-4)})`],
|
|
415
|
+
['To', to],
|
|
416
|
+
['Amount', `${amount} ${tokenSymbol}`],
|
|
417
|
+
['Balance', balanceStr],
|
|
418
|
+
['Est. Gas', `${gasCostEth.toFixed(6)} ETH`],
|
|
419
|
+
['Chain', chain],
|
|
420
|
+
]);
|
|
421
|
+
console.log('');
|
|
422
|
+
|
|
423
|
+
const { confirm } = await inquirer.prompt([{
|
|
424
|
+
type: 'confirm',
|
|
425
|
+
name: 'confirm',
|
|
426
|
+
message: theme.accent('Send this transaction?'),
|
|
427
|
+
default: false,
|
|
428
|
+
}]);
|
|
429
|
+
|
|
430
|
+
if (!confirm) {
|
|
431
|
+
warn('Transaction cancelled');
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const txSpin = spinner('Sending...').start();
|
|
436
|
+
|
|
437
|
+
let tx;
|
|
438
|
+
if (isETH) {
|
|
439
|
+
tx = await signer.sendTransaction({
|
|
440
|
+
to,
|
|
441
|
+
value: ethers.parseEther(amount),
|
|
442
|
+
});
|
|
443
|
+
} else {
|
|
444
|
+
const contract = new ethers.Contract(tokenAddr, ERC20_SEND_ABI, signer);
|
|
445
|
+
tx = await contract.transfer(to, ethers.parseUnits(amount, tokenDecimals));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
txSpin.text = 'Waiting for confirmation...';
|
|
449
|
+
const receipt = await tx.wait();
|
|
450
|
+
|
|
451
|
+
txSpin.succeed(theme.success('Transaction confirmed!'));
|
|
452
|
+
|
|
453
|
+
console.log('');
|
|
454
|
+
showSection('TRANSACTION RECEIPT');
|
|
455
|
+
kvDisplay([
|
|
456
|
+
['TX Hash', receipt.hash],
|
|
457
|
+
['Block', receipt.blockNumber.toString()],
|
|
458
|
+
['Gas Used', receipt.gasUsed.toString()],
|
|
459
|
+
['Status', receipt.status === 1 ? theme.success('✓ Success') : theme.error('✗ Failed')],
|
|
460
|
+
]);
|
|
461
|
+
console.log('');
|
|
462
|
+
|
|
463
|
+
} catch (err) {
|
|
464
|
+
spin.fail('Send failed');
|
|
465
|
+
if (err.message.includes('incorrect password') || err.message.includes('bad decrypt')) {
|
|
466
|
+
error('Wrong password');
|
|
467
|
+
} else {
|
|
468
|
+
error(err.message);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// ═══════════════════════════════════════
|
|
474
|
+
// RECEIVE — Show address + QR-friendly display
|
|
475
|
+
// ═══════════════════════════════════════
|
|
476
|
+
|
|
477
|
+
export async function receiveAddress(walletName) {
|
|
478
|
+
const name = walletName || getConfig('activeWallet');
|
|
479
|
+
if (!name) {
|
|
480
|
+
error('No active wallet. Set one: darksol wallet use <name>');
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const walletData = loadWallet(name);
|
|
485
|
+
const chain = getConfig('chain') || 'base';
|
|
486
|
+
|
|
487
|
+
console.log('');
|
|
488
|
+
showSection(`RECEIVE — ${name}`);
|
|
489
|
+
console.log('');
|
|
490
|
+
console.log(theme.gold(' Your address:'));
|
|
491
|
+
console.log('');
|
|
492
|
+
console.log(theme.gold.bold(` ${walletData.address}`));
|
|
493
|
+
console.log('');
|
|
494
|
+
|
|
495
|
+
// Visual box around address for easy copy
|
|
496
|
+
const addr = walletData.address;
|
|
497
|
+
const boxWidth = addr.length + 4;
|
|
498
|
+
console.log(theme.dim(` ┌${'─'.repeat(boxWidth)}┐`));
|
|
499
|
+
console.log(theme.dim(` │ `) + theme.gold(addr) + theme.dim(` │`));
|
|
500
|
+
console.log(theme.dim(` └${'─'.repeat(boxWidth)}┘`));
|
|
501
|
+
console.log('');
|
|
502
|
+
|
|
503
|
+
console.log(theme.dim(' This address works on ALL EVM chains:'));
|
|
504
|
+
console.log(theme.dim(' Base • Ethereum • Arbitrum • Optimism • Polygon'));
|
|
505
|
+
console.log('');
|
|
506
|
+
console.log(theme.dim(` Active chain: ${theme.gold(chain)}`));
|
|
507
|
+
console.log(theme.dim(' Make sure the sender is on the same chain!'));
|
|
508
|
+
console.log('');
|
|
509
|
+
|
|
510
|
+
warn('Double-check the address before sharing.');
|
|
511
|
+
warn('Only send EVM-compatible tokens to this address.');
|
|
512
|
+
console.log('');
|
|
513
|
+
}
|
|
514
|
+
|
|
251
515
|
// Export wallet (show address only, never PK without password)
|
|
252
516
|
export async function exportWallet(name) {
|
|
253
517
|
if (!name) {
|