nansen-cli 1.11.2 → 1.12.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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/api.js +3 -1
- package/src/cli.js +58 -8
- package/src/schema.json +3 -1
- package/src/trading.js +5 -2
- package/src/transfer.js +1 -1
- package/src/wallet.js +95 -25
- package/src/x402.js +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#207](https://github.com/nansen-ai/nansen-cli/pull/207) [`73ca500`](https://github.com/nansen-ai/nansen-cli/commit/73ca5009c03ad541673165ca6b50f33ff4cc1673) Thanks [@TimNooren](https://github.com/TimNooren)! - Add --unsafe-no-password flag to wallet create for agent-friendly passwordless wallets.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#212](https://github.com/nansen-ai/nansen-cli/pull/212) [`726c29d`](https://github.com/nansen-ai/nansen-cli/commit/726c29d2676c8a37772299c6237b44890493dfa5) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Clarify empty input handling in parseAddressList with explicit early return
|
|
12
|
+
|
|
13
|
+
- [#218](https://github.com/nansen-ai/nansen-cli/pull/218) [`8c4dd71`](https://github.com/nansen-ai/nansen-cli/commit/8c4dd71ce215026e149a6b097540c46622f13d3a) Thanks [@TimNooren](https://github.com/TimNooren)! - fix: `nansen changelog --since <version>` now correctly filters changeset-format entries (## x.y.z) in addition to Keep a Changelog entries (## [x.y.z])
|
|
14
|
+
|
|
15
|
+
- [#209](https://github.com/nansen-ai/nansen-cli/pull/209) [`a6dc1ed`](https://github.com/nansen-ai/nansen-cli/commit/a6dc1ed9dc40ad3506e7debe09746d463a70c14d) Thanks [@0xlaveen](https://github.com/0xlaveen)! - fix: prevent --help from executing destructive commands (logout, schema, cache)
|
|
16
|
+
|
|
3
17
|
## 1.11.2
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -582,7 +582,9 @@ export class NansenAPI {
|
|
|
582
582
|
// No API key and no payment wallet — guide the user to login rather than
|
|
583
583
|
// showing a confusing x402 payment dump they can't act on.
|
|
584
584
|
// TODO: full fix would skip x402 entirely when no apiKey is set — see PR #<this PR number>
|
|
585
|
-
message = 'No API key configured.
|
|
585
|
+
message = 'No API key configured. Two ways to authenticate:\n' +
|
|
586
|
+
' 1. API key: nansen login --api-key <key> (get key at https://app.nansen.ai/api)\n' +
|
|
587
|
+
' 2. x402 micropayment: nansen wallet create + fund with USDC (no API key needed)';
|
|
586
588
|
} else {
|
|
587
589
|
message = `x402 auto-payment failed: ${x402Err.message}`;
|
|
588
590
|
}
|
package/src/cli.js
CHANGED
|
@@ -439,6 +439,37 @@ async function enrichTransfers(result, apiInstance, chain) {
|
|
|
439
439
|
return result;
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
// ============= Address Parsing =============
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Parse an --addresses option that may arrive as:
|
|
446
|
+
* - a pre-parsed array (arg parser split it)
|
|
447
|
+
* - a JSON array string: '["0x…","0x…"]'
|
|
448
|
+
* - a comma-separated string: "0x…,0x…"
|
|
449
|
+
* Non-array JSON values (objects, numbers, booleans) are rejected.
|
|
450
|
+
*/
|
|
451
|
+
export function parseAddressList(raw) {
|
|
452
|
+
if (Array.isArray(raw)) {
|
|
453
|
+
return raw.map(a => String(a).trim()).filter(Boolean);
|
|
454
|
+
}
|
|
455
|
+
if (!raw) return [];
|
|
456
|
+
|
|
457
|
+
const s = String(raw);
|
|
458
|
+
try {
|
|
459
|
+
const parsed = JSON.parse(s);
|
|
460
|
+
if (Array.isArray(parsed)) {
|
|
461
|
+
return parsed.map(a => String(a).trim()).filter(Boolean);
|
|
462
|
+
}
|
|
463
|
+
throw new NansenError(
|
|
464
|
+
'--addresses must be a comma-separated list or JSON array, got: ' + typeof parsed,
|
|
465
|
+
ErrorCode.INVALID_PARAMS
|
|
466
|
+
);
|
|
467
|
+
} catch (e) {
|
|
468
|
+
if (e instanceof NansenError) throw e;
|
|
469
|
+
return s.split(',').map(a => a.trim()).filter(Boolean);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
442
473
|
// ============= Composite Functions =============
|
|
443
474
|
|
|
444
475
|
export async function batchProfile(api, params = {}) {
|
|
@@ -644,7 +675,7 @@ EXAMPLES:
|
|
|
644
675
|
nansen research profiler balance --address 0x... --chain ethereum
|
|
645
676
|
nansen trade quote --chain base --from ETH --to USDC --amount 1
|
|
646
677
|
|
|
647
|
-
Research chains: ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, mantle, ronin, sei, plasma, sonic, monad, hyperevm, iotaevm
|
|
678
|
+
Research chains: ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, zksync, mantle, ronin, sei, plasma, sonic, unichain, monad, hyperevm, iotaevm
|
|
648
679
|
Trade chains: solana, base
|
|
649
680
|
Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
650
681
|
|
|
@@ -780,6 +811,10 @@ export function buildCommands(deps = {}) {
|
|
|
780
811
|
},
|
|
781
812
|
|
|
782
813
|
'changelog': async (_args, _apiInstance, _flags, _options) => {
|
|
814
|
+
if (_flags.help || _flags.h) {
|
|
815
|
+
log('changelog — Show release history\n\nUsage:\n nansen changelog [--since <version>]\n\nOptions:\n --since <version> Show only entries for versions >= this version\n\nExamples:\n nansen changelog\n nansen changelog --since 1.10.0');
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
783
818
|
const changelogPath = new URL('../CHANGELOG.md', import.meta.url).pathname;
|
|
784
819
|
let content;
|
|
785
820
|
try {
|
|
@@ -795,10 +830,10 @@ export function buildCommands(deps = {}) {
|
|
|
795
830
|
const filtered = [];
|
|
796
831
|
let include = false;
|
|
797
832
|
for (const line of lines) {
|
|
798
|
-
// Match ## [x.y.z]
|
|
799
|
-
const match = line.match(/^## \[(\d+\.\d+\.\d+)\]/);
|
|
833
|
+
// Match ## [x.y.z] (Keep a Changelog format) or ## x.y.z (changeset format)
|
|
834
|
+
const match = line.match(/^## \[(\d+\.\d+\.\d+)\]|^## (\d+\.\d+\.\d+)\b/);
|
|
800
835
|
if (match) {
|
|
801
|
-
const ver = match[1];
|
|
836
|
+
const ver = match[1] || match[2];
|
|
802
837
|
// Compare: include versions >= since, stop at versions < since
|
|
803
838
|
if (compareSemver(ver, since) >= 0) {
|
|
804
839
|
include = true;
|
|
@@ -945,7 +980,7 @@ export function buildCommands(deps = {}) {
|
|
|
945
980
|
'batch': () => {
|
|
946
981
|
let addresses = [];
|
|
947
982
|
if (options.addresses) {
|
|
948
|
-
addresses = options.addresses
|
|
983
|
+
addresses = parseAddressList(options.addresses);
|
|
949
984
|
} else if (options.file) {
|
|
950
985
|
const content = fs.readFileSync(options.file, 'utf8');
|
|
951
986
|
try {
|
|
@@ -976,13 +1011,13 @@ export function buildCommands(deps = {}) {
|
|
|
976
1011
|
return traceCounterparties(apiInstance, { address, chain, depth, width, days, delayMs });
|
|
977
1012
|
},
|
|
978
1013
|
'compare': () => {
|
|
979
|
-
const addrs = (options.addresses
|
|
1014
|
+
const addrs = parseAddressList(options.addresses);
|
|
980
1015
|
return compareWallets(apiInstance, { addresses: addrs, chain, days });
|
|
981
1016
|
},
|
|
982
1017
|
'help': () => ({
|
|
983
1018
|
commands: ['balance', 'labels', 'transactions', 'pnl', 'search', 'historical-balances', 'related-wallets', 'counterparties', 'pnl-summary', 'perp-positions', 'perp-trades', 'batch', 'trace', 'compare'],
|
|
984
1019
|
description: 'Wallet profiling endpoints',
|
|
985
|
-
example: 'nansen profiler
|
|
1020
|
+
example: 'nansen research profiler compare --addresses "0xABC...,0xDEF..." --chain ethereum'
|
|
986
1021
|
})
|
|
987
1022
|
};
|
|
988
1023
|
|
|
@@ -1294,7 +1329,8 @@ export function generateSubcommandHelp(command, subcommand) {
|
|
|
1294
1329
|
|
|
1295
1330
|
const exampleValues = { address: '0x...', token: '0x...', query: '"term"', symbol: 'BTC', date: '2024-01-01' };
|
|
1296
1331
|
const chain = subSchema.options?.chain?.default || 'solana';
|
|
1297
|
-
|
|
1332
|
+
const prefix = DEPRECATED_TO_RESEARCH.has(command) ? `research ${command}` : command;
|
|
1333
|
+
let example = `nansen ${prefix} ${subcommand}`;
|
|
1298
1334
|
if (subSchema.options) {
|
|
1299
1335
|
for (const [name, opt] of Object.entries(subSchema.options)) {
|
|
1300
1336
|
if (opt.required) example += ` --${name} ${exampleValues[name] || '<val>'}`;
|
|
@@ -1408,6 +1444,20 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1408
1444
|
return { type: 'command-help', command };
|
|
1409
1445
|
}
|
|
1410
1446
|
}
|
|
1447
|
+
// Simple commands (logout, schema, cache) — show help instead of executing
|
|
1448
|
+
// Prevents destructive commands like logout from running when user just wants help
|
|
1449
|
+
if (commands[command]) {
|
|
1450
|
+
const simpleHelp = {
|
|
1451
|
+
'logout': 'nansen logout — Remove saved API key from ~/.nansen/config.json',
|
|
1452
|
+
'schema': 'nansen schema [command] [--pretty] — Show JSON schema for all commands (or a specific command)',
|
|
1453
|
+
'cache': 'nansen cache clear — Clear the API response cache',
|
|
1454
|
+
};
|
|
1455
|
+
if (simpleHelp[command]) {
|
|
1456
|
+
output(simpleHelp[command]);
|
|
1457
|
+
notify();
|
|
1458
|
+
return { type: 'command-help', command };
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1411
1461
|
// Commands with handlers (e.g. quote, execute) show their own usage
|
|
1412
1462
|
if (command === 'help' || !commands[command]) {
|
|
1413
1463
|
output(BANNER + HELP);
|
package/src/schema.json
CHANGED
|
@@ -1622,7 +1622,7 @@
|
|
|
1622
1622
|
},
|
|
1623
1623
|
"wallet": {
|
|
1624
1624
|
"type": "string",
|
|
1625
|
-
"description": "Wallet name (or \"walletconnect\"/\"wc\" for WalletConnect, EVM only). A configured wallet is required
|
|
1625
|
+
"description": "Wallet name (or \"walletconnect\"/\"wc\" for WalletConnect, EVM only). A configured wallet is required \u2014 run `nansen wallet create` if you haven't set one up yet."
|
|
1626
1626
|
}
|
|
1627
1627
|
},
|
|
1628
1628
|
"prerequisites": [
|
|
@@ -1692,11 +1692,13 @@
|
|
|
1692
1692
|
"avalanche",
|
|
1693
1693
|
"linea",
|
|
1694
1694
|
"scroll",
|
|
1695
|
+
"zksync",
|
|
1695
1696
|
"mantle",
|
|
1696
1697
|
"ronin",
|
|
1697
1698
|
"sei",
|
|
1698
1699
|
"plasma",
|
|
1699
1700
|
"sonic",
|
|
1701
|
+
"unichain",
|
|
1700
1702
|
"monad",
|
|
1701
1703
|
"hyperevm",
|
|
1702
1704
|
"iotaevm"
|
package/src/trading.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import crypto from 'crypto';
|
|
9
9
|
import fs from 'fs';
|
|
10
10
|
import path from 'path';
|
|
11
|
-
import { exportWallet, getDefaultAddress, showWallet, listWallets } from './wallet.js';
|
|
11
|
+
import { exportWallet, getDefaultAddress, showWallet, listWallets, getWalletConfig } from './wallet.js';
|
|
12
12
|
import { base58Decode } from './transfer.js';
|
|
13
13
|
import { keccak256, signSecp256k1, rlpEncode } from './crypto.js';
|
|
14
14
|
import { getWalletConnectAddress, sendTransactionViaWalletConnect, sendApprovalViaWalletConnect } from './walletconnect-trading.js';
|
|
@@ -957,7 +957,10 @@ EXAMPLES:
|
|
|
957
957
|
let exported = null;
|
|
958
958
|
if (!isWalletConnect) {
|
|
959
959
|
// Get wallet credentials once (before the loop)
|
|
960
|
-
const
|
|
960
|
+
const walletConfig = getWalletConfig();
|
|
961
|
+
const password = walletConfig.passwordHash
|
|
962
|
+
? (process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps))
|
|
963
|
+
: null;
|
|
961
964
|
|
|
962
965
|
let effectiveWalletName = walletName;
|
|
963
966
|
if (!effectiveWalletName) {
|
package/src/transfer.js
CHANGED
|
@@ -590,7 +590,7 @@ export async function sendTokens({ to, amount, chain, token = null, wallet = nul
|
|
|
590
590
|
}
|
|
591
591
|
|
|
592
592
|
const config = getWalletConfig();
|
|
593
|
-
if (!verifyPassword(password, config)) throw new Error('Incorrect password');
|
|
593
|
+
if (config.passwordHash && !verifyPassword(password, config)) throw new Error('Incorrect password');
|
|
594
594
|
|
|
595
595
|
const walletName = wallet || config.defaultWallet;
|
|
596
596
|
if (!walletName) throw new Error('No wallet specified and no default wallet set');
|
package/src/wallet.js
CHANGED
|
@@ -58,6 +58,10 @@ function deriveKey(password, salt) {
|
|
|
58
58
|
* Returns a JSON-serializable object with all params needed for decryption.
|
|
59
59
|
*/
|
|
60
60
|
export function encryptKey(privateKeyHex, password) {
|
|
61
|
+
if (password === null) {
|
|
62
|
+
return { data: privateKeyHex, encrypted: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
const salt = crypto.randomBytes(SALT_LEN);
|
|
62
66
|
const iv = crypto.randomBytes(IV_LEN);
|
|
63
67
|
const key = deriveKey(password, salt);
|
|
@@ -79,10 +83,31 @@ export function encryptKey(privateKeyHex, password) {
|
|
|
79
83
|
|
|
80
84
|
/**
|
|
81
85
|
* Decrypt a private key with a password.
|
|
82
|
-
*
|
|
83
|
-
*
|
|
86
|
+
* For unencrypted wallets (encrypted: false), password is ignored and
|
|
87
|
+
* plaintext data is returned directly.
|
|
84
88
|
*/
|
|
85
89
|
export function decryptKey(encryptedData, password) {
|
|
90
|
+
const ENCRYPTED_FIELDS = ['salt', 'iv', 'authTag', 'ciphertext'];
|
|
91
|
+
const hasEncryptionFields = ENCRYPTED_FIELDS.some(f => f in encryptedData);
|
|
92
|
+
|
|
93
|
+
// Unencrypted blob
|
|
94
|
+
if (encryptedData.encrypted === false) {
|
|
95
|
+
if (hasEncryptionFields) {
|
|
96
|
+
throw new Error('Wallet data corrupted or tampered');
|
|
97
|
+
}
|
|
98
|
+
return encryptedData.data;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Encrypted blob with missing fields
|
|
102
|
+
if (!ENCRYPTED_FIELDS.every(f => f in encryptedData)) {
|
|
103
|
+
throw new Error('Wallet data corrupted or tampered');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Encrypted blob but no password provided
|
|
107
|
+
if (password === null || password === undefined) {
|
|
108
|
+
throw new Error('Wallet is encrypted. Set NANSEN_WALLET_PASSWORD.');
|
|
109
|
+
}
|
|
110
|
+
|
|
86
111
|
const salt = Buffer.from(encryptedData.salt, 'hex');
|
|
87
112
|
const iv = Buffer.from(encryptedData.iv, 'hex');
|
|
88
113
|
const authTag = Buffer.from(encryptedData.authTag, 'hex');
|
|
@@ -214,6 +239,7 @@ function getWalletFile(name) {
|
|
|
214
239
|
*/
|
|
215
240
|
export function verifyPassword(password, config) {
|
|
216
241
|
if (!config.passwordHash) return true; // No password set yet
|
|
242
|
+
if (password === null || password === undefined) return false;
|
|
217
243
|
const { salt, hash } = config.passwordHash;
|
|
218
244
|
const derived = crypto.scryptSync(password, Buffer.from(salt, 'hex'), 32, {
|
|
219
245
|
N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: 256 * 1024 * 1024,
|
|
@@ -308,11 +334,22 @@ export function createWallet(name, password) {
|
|
|
308
334
|
throw new Error(`Wallet "${name}" already exists`);
|
|
309
335
|
}
|
|
310
336
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
config.passwordHash
|
|
337
|
+
if (password === null) {
|
|
338
|
+
// Passwordless mode: reject if existing wallets are encrypted
|
|
339
|
+
if (config.passwordHash) {
|
|
340
|
+
throw new Error('Existing wallets are password-protected. Set NANSEN_WALLET_PASSWORD.');
|
|
341
|
+
}
|
|
314
342
|
} else {
|
|
315
|
-
|
|
343
|
+
// Encrypted mode
|
|
344
|
+
if (!config.passwordHash) {
|
|
345
|
+
// First encrypted wallet: reject if passwordless wallets exist
|
|
346
|
+
const existingWallets = fs.readdirSync(getWalletsDir())
|
|
347
|
+
.filter(f => f.endsWith('.json') && f !== 'config.json');
|
|
348
|
+
if (existingWallets.length > 0) {
|
|
349
|
+
throw new Error('Existing wallets are passwordless. Cannot mix encrypted and unencrypted wallets.');
|
|
350
|
+
}
|
|
351
|
+
config.passwordHash = hashPassword(password);
|
|
352
|
+
} else if (!verifyPassword(password, config)) {
|
|
316
353
|
throw new Error('Incorrect password');
|
|
317
354
|
}
|
|
318
355
|
}
|
|
@@ -380,7 +417,7 @@ export function exportWallet(name, password) {
|
|
|
380
417
|
}
|
|
381
418
|
|
|
382
419
|
const config = getWalletConfig();
|
|
383
|
-
if (!verifyPassword(password, config)) {
|
|
420
|
+
if (config.passwordHash && !verifyPassword(password, config)) {
|
|
384
421
|
throw new Error('Incorrect password');
|
|
385
422
|
}
|
|
386
423
|
|
|
@@ -425,7 +462,7 @@ export function deleteWallet(name, password) {
|
|
|
425
462
|
}
|
|
426
463
|
|
|
427
464
|
const config = getWalletConfig();
|
|
428
|
-
if (!verifyPassword(password, config)) {
|
|
465
|
+
if (config.passwordHash && !verifyPassword(password, config)) {
|
|
429
466
|
throw new Error('Incorrect password');
|
|
430
467
|
}
|
|
431
468
|
|
|
@@ -470,22 +507,35 @@ export function buildWalletCommands(deps = {}) {
|
|
|
470
507
|
const handlers = {
|
|
471
508
|
'create': async () => {
|
|
472
509
|
const name = options.name || args[1] || 'default';
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
510
|
+
|
|
511
|
+
let password;
|
|
512
|
+
if (flags['unsafe-no-password']) {
|
|
513
|
+
process.stderr.write('WARNING: --unsafe-no-password is set. Private keys will be stored UNENCRYPTED on disk.\nAnyone with access to this machine can steal your funds.\n');
|
|
514
|
+
password = null;
|
|
515
|
+
} else if (!process.env.NANSEN_WALLET_PASSWORD && !process.stdin.isTTY && !deps.promptFn) {
|
|
516
|
+
log('❌ No password provided. Either:');
|
|
517
|
+
log(' set NANSEN_WALLET_PASSWORD, or');
|
|
518
|
+
log(' use --unsafe-no-password (WARNING: Private keys will be stored UNENCRYPTED on disk. Anyone with access to this machine can steal your funds.)');
|
|
476
519
|
exit(1);
|
|
477
520
|
return;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (!config.passwordHash && !process.env.NANSEN_WALLET_PASSWORD) {
|
|
483
|
-
const confirm = await promptPassword('Confirm password: ', deps);
|
|
484
|
-
if (password !== confirm) {
|
|
485
|
-
log('❌ Passwords do not match');
|
|
521
|
+
} else {
|
|
522
|
+
password = process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps);
|
|
523
|
+
if (!password || password.length < 12) {
|
|
524
|
+
log('❌ Password must be at least 12 characters');
|
|
486
525
|
exit(1);
|
|
487
526
|
return;
|
|
488
527
|
}
|
|
528
|
+
|
|
529
|
+
// Confirm password for first wallet (skip if set via env var)
|
|
530
|
+
const config = getWalletConfig();
|
|
531
|
+
if (!config.passwordHash && !process.env.NANSEN_WALLET_PASSWORD) {
|
|
532
|
+
const confirm = await promptPassword('Confirm password: ', deps);
|
|
533
|
+
if (password !== confirm) {
|
|
534
|
+
log('❌ Passwords do not match');
|
|
535
|
+
exit(1);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
489
539
|
}
|
|
490
540
|
|
|
491
541
|
try {
|
|
@@ -499,8 +549,12 @@ export function buildWalletCommands(deps = {}) {
|
|
|
499
549
|
log(` Base (recommended, lower fees): send USDC to ${result.evm}`);
|
|
500
550
|
log(` Solana: send USDC to ${result.solana}`);
|
|
501
551
|
log('');
|
|
502
|
-
|
|
503
|
-
|
|
552
|
+
if (password === null) {
|
|
553
|
+
log(' ⚠️ This is an UNENCRYPTED hot wallet — private keys are stored in plaintext on disk.');
|
|
554
|
+
} else {
|
|
555
|
+
log(' ⚠️ This is a hot wallet and is fundamentally insecure — do not deposit more than you can afford to lose.');
|
|
556
|
+
log(' Store and handle your password securely, e.g. using a secrets manager or system keychain.');
|
|
557
|
+
}
|
|
504
558
|
log('');
|
|
505
559
|
return;
|
|
506
560
|
} catch (err) {
|
|
@@ -553,7 +607,10 @@ export function buildWalletCommands(deps = {}) {
|
|
|
553
607
|
exit(1);
|
|
554
608
|
return;
|
|
555
609
|
}
|
|
556
|
-
const
|
|
610
|
+
const config = getWalletConfig();
|
|
611
|
+
const password = config.passwordHash
|
|
612
|
+
? (process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps))
|
|
613
|
+
: null;
|
|
557
614
|
try {
|
|
558
615
|
const result = exportWallet(name, password);
|
|
559
616
|
log(`\n⚠️ Private keys for "${result.name}" — do not share!\n`);
|
|
@@ -595,7 +652,10 @@ export function buildWalletCommands(deps = {}) {
|
|
|
595
652
|
exit(1);
|
|
596
653
|
return;
|
|
597
654
|
}
|
|
598
|
-
const
|
|
655
|
+
const config = getWalletConfig();
|
|
656
|
+
const password = config.passwordHash
|
|
657
|
+
? (process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps))
|
|
658
|
+
: null;
|
|
599
659
|
try {
|
|
600
660
|
const result = deleteWallet(name, password);
|
|
601
661
|
log(`✓ Wallet "${result.deleted}" deleted`);
|
|
@@ -638,7 +698,15 @@ export function buildWalletCommands(deps = {}) {
|
|
|
638
698
|
}
|
|
639
699
|
|
|
640
700
|
const isWalletConnect = options.wallet === 'walletconnect' || options.wallet === 'wc';
|
|
641
|
-
|
|
701
|
+
let password;
|
|
702
|
+
if (isWalletConnect) {
|
|
703
|
+
password = null;
|
|
704
|
+
} else {
|
|
705
|
+
const sendConfig = getWalletConfig();
|
|
706
|
+
password = sendConfig.passwordHash
|
|
707
|
+
? (process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps))
|
|
708
|
+
: null;
|
|
709
|
+
}
|
|
642
710
|
const dryRun = flags['dry-run'] || flags.dryRun;
|
|
643
711
|
|
|
644
712
|
try {
|
|
@@ -695,7 +763,8 @@ USAGE:
|
|
|
695
763
|
nansen wallet <command> [options]
|
|
696
764
|
|
|
697
765
|
COMMANDS:
|
|
698
|
-
create [--name <label>]
|
|
766
|
+
create [--name <label>] [--unsafe-no-password]
|
|
767
|
+
Create a new wallet pair (EVM + Solana)
|
|
699
768
|
list List all wallets
|
|
700
769
|
show <name> Show wallet addresses
|
|
701
770
|
export <name> Export private keys (requires password)
|
|
@@ -712,6 +781,7 @@ OPTIONS:
|
|
|
712
781
|
--token <address> Token contract/mint address (optional, sends native if omitted)
|
|
713
782
|
--wallet <name> Wallet to use (optional, uses default if omitted; use "walletconnect" or "wc" for WalletConnect, EVM only)
|
|
714
783
|
--max Send entire balance (deducts gas for native transfers)
|
|
784
|
+
--unsafe-no-password Skip encryption — private keys stored UNENCRYPTED on disk (create only)
|
|
715
785
|
|
|
716
786
|
ENVIRONMENT:
|
|
717
787
|
NANSEN_WALLET_PASSWORD Password for non-interactive use (e.g. CI/scripts)
|
package/src/x402.js
CHANGED
|
@@ -96,18 +96,23 @@ export async function* createPaymentSignatures(response, url, options = {}) {
|
|
|
96
96
|
const ranked = rankRequirements(requirements);
|
|
97
97
|
if (ranked.length === 0) return;
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
if (!password) return;
|
|
101
|
-
|
|
102
|
-
let exportWallet, listWallets;
|
|
99
|
+
let exportWallet, listWallets, getWalletConfig;
|
|
103
100
|
try {
|
|
104
101
|
const walletMod = await import('./wallet.js');
|
|
105
102
|
exportWallet = walletMod.exportWallet;
|
|
106
103
|
listWallets = walletMod.listWallets;
|
|
104
|
+
getWalletConfig = walletMod.getWalletConfig;
|
|
107
105
|
} catch {
|
|
108
106
|
return;
|
|
109
107
|
}
|
|
110
108
|
|
|
109
|
+
const walletConfig = getWalletConfig();
|
|
110
|
+
const password = walletConfig.passwordHash
|
|
111
|
+
? (options.password || process.env.NANSEN_WALLET_PASSWORD || null)
|
|
112
|
+
: null;
|
|
113
|
+
// Encrypted wallets need a password -- silently skip if unavailable
|
|
114
|
+
if (walletConfig.passwordHash && password === null) return;
|
|
115
|
+
|
|
111
116
|
const wallets = listWallets();
|
|
112
117
|
if (wallets.wallets.length === 0) return;
|
|
113
118
|
|