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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
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. Run: nansen login --api-key <key>. Get your key at https://app.nansen.ai/api';
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] headers
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.split(',').map(a => a.trim()).filter(Boolean);
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 || '').split(',').map(a => a.trim()).filter(Boolean);
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 balance --address 0x123... --chain ethereum'
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
- let example = `nansen ${command} ${subcommand}`;
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 run `nansen wallet create` if you haven't set one up yet."
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 password = process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps);
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
- * @returns {string} Private key hex string
83
- * @throws {Error} If password is wrong
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
- // If this is the first wallet, set the password hash
312
- if (!config.passwordHash) {
313
- config.passwordHash = hashPassword(password);
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
- if (!verifyPassword(password, config)) {
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
- const password = process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps);
474
- if (!password || password.length < 12) {
475
- log('❌ Password must be at least 12 characters');
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
- // Confirm password for first wallet (skip if set via env var)
481
- const config = getWalletConfig();
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
- log(' ⚠️ This is a hot wallet and is fundamentally insecure — do not deposit more than you can afford to lose.');
503
- log(' Store and handle your password securely, e.g. using a secrets manager or system keychain.');
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 password = process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps);
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 password = process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps);
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
- const password = isWalletConnect ? null : (process.env.NANSEN_WALLET_PASSWORD || await promptPassword('Enter wallet password: ', deps));
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>] Create a new wallet pair (EVM + Solana)
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
- const password = options.password || process.env.NANSEN_WALLET_PASSWORD;
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