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/CHANGELOG.md +80 -0
- package/README.md +34 -1
- package/package.json +1 -1
- package/skills/nansen-trading/SKILL.md +49 -1
- package/src/api.js +3 -1
- package/src/bridge.js +1102 -0
- package/src/cli.js +130 -7
- package/src/hl-action.js +528 -0
- package/src/hl-client.js +168 -0
- package/src/hl-env.js +37 -0
- package/src/keychain.js +6 -2
- package/src/limit-order.js +18 -4
- package/src/perp.js +835 -0
- package/src/rpc-urls.js +18 -11
- package/src/schema.json +415 -6
- package/src/trading.js +162 -17
- package/src/wallet-signing.js +87 -0
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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/limit-order.js
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
436
|
-
|
|
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+$/, '');
|