nansen-cli 1.35.0 → 1.36.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/src/hl-env.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Nansen CLI — which Hyperliquid network we are talking to.
3
+ *
4
+ * Its own module because both halves of the HL path need it and neither should
5
+ * have to import the other: hl-action.js builds actions (pure, golden-vector
6
+ * pinned) and hl-client.js submits them. Deriving the network from one place
7
+ * keeps a signed action in step with the URL it is submitted to.
8
+ */
9
+
10
+ export const HL_MAINNET_API_URL = 'https://api.hyperliquid.xyz';
11
+ export const HL_TESTNET_API_URL = 'https://api.hyperliquid-testnet.xyz';
12
+
13
+ // Resolve the HL API base. NANSEN_HL_API_URL overrides it (tests, or pointing at
14
+ // the testnet); defaults to mainnet.
15
+ export function hlApiUrl() {
16
+ return process.env.NANSEN_HL_API_URL || HL_MAINNET_API_URL;
17
+ }
18
+
19
+ // Which HL network the resolved base URL points at.
20
+ //
21
+ // Actions are network-specific in two places: the L1 phantom agent's `source`
22
+ // ("a" mainnet, "b" testnet) and the `hyperliquidChain` field of a user-signed
23
+ // action ("Mainnet"/"Testnet"). Both used to be hardcoded to mainnet, so
24
+ // pointing NANSEN_HL_API_URL at the testnet signed mainnet-shaped actions that
25
+ // the testnet rejects.
26
+ //
27
+ // Anything not recognisably the testnet host is treated as mainnet, which keeps
28
+ // a local mock (tests) on the mainnet vectors.
29
+ export function hlNetwork() {
30
+ let host;
31
+ try {
32
+ host = new URL(hlApiUrl()).hostname.toLowerCase();
33
+ } catch {
34
+ return 'Mainnet';
35
+ }
36
+ return host.includes('hyperliquid-testnet') ? 'Testnet' : 'Mainnet';
37
+ }
package/src/keychain.js CHANGED
@@ -73,7 +73,9 @@ function keychainRetrieve() {
73
73
  '-a', ACCOUNT,
74
74
  '-w',
75
75
  ], { timeout: TIMEOUT_MS, stdio: ['pipe', 'pipe', 'pipe'] });
76
- const pw = result.toString().trim();
76
+ // Strip only the trailing newline the OS tool appends — trimming all
77
+ // whitespace would corrupt a password with leading/trailing spaces.
78
+ const pw = result.toString().replace(/\r?\n$/, '');
77
79
  return pw || null;
78
80
  }
79
81
 
@@ -83,7 +85,9 @@ function keychainRetrieve() {
83
85
  'service', SERVICE,
84
86
  'account', ACCOUNT,
85
87
  ], { timeout: TIMEOUT_MS, stdio: ['pipe', 'pipe', 'pipe'] });
86
- const pw = result.toString().trim();
88
+ // Strip only the trailing newline the OS tool appends — trimming all
89
+ // whitespace would corrupt a password with leading/trailing spaces.
90
+ const pw = result.toString().replace(/\r?\n$/, '');
87
91
  return pw || null;
88
92
  }
89
93
 
@@ -381,14 +381,20 @@ export function parseExpiry(expiryStr) {
381
381
  const match = expiryStr.match(/^(\d+)(h|d)$/i);
382
382
  if (match) {
383
383
  const value = parseInt(match[1], 10);
384
+ if (value <= 0) {
385
+ throw new Error(`Invalid expiry "${expiryStr}". Duration must be greater than 0.`);
386
+ }
384
387
  const unit = match[2].toLowerCase();
385
388
  const ms = unit === 'h' ? value * 3600 * 1000 : value * 24 * 3600 * 1000;
386
389
  return Date.now() + ms;
387
390
  }
388
391
 
389
- // Try as raw epoch ms
392
+ // Try as raw epoch ms — must be in the future, or the order expires on arrival.
390
393
  const num = Number(expiryStr);
391
- if (!isNaN(num) && num > Date.now() - 86400000) {
394
+ if (!isNaN(num)) {
395
+ if (num <= Date.now()) {
396
+ throw new Error(`Expiry "${expiryStr}" is in the past. Provide a future time (e.g. "24h", "7d", or a future epoch in ms).`);
397
+ }
392
398
  return num;
393
399
  }
394
400
 
@@ -432,8 +438,16 @@ function formatAmount(amount, mintAddress) {
432
438
  if (!amount) return '?';
433
439
  const info = KNOWN_SOLANA_TOKENS[mintAddress];
434
440
  if (!info) return `${amount} ${mintAddress || '?'}`;
435
- const raw = BigInt(amount);
436
- const divisor = BigInt(10 ** info.decimals);
441
+ // amount is backend-controlled; a float or scientific-notation string makes
442
+ // BigInt() throw. Fall back to the raw amount rather than aborting the whole
443
+ // list render.
444
+ let raw;
445
+ try {
446
+ raw = BigInt(amount);
447
+ } catch {
448
+ return `${amount} ${info.symbol}`;
449
+ }
450
+ const divisor = 10n ** BigInt(info.decimals);
437
451
  const whole = raw / divisor;
438
452
  const frac = raw % divisor;
439
453
  const fracStr = frac.toString().padStart(info.decimals, '0').replace(/0+$/, '');