nansen-cli 1.34.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 +98 -0
- package/README.md +48 -4
- package/package.json +1 -1
- package/scripts/postinstall.js +13 -3
- package/skills/nansen-trading/SKILL.md +49 -1
- package/src/api.js +59 -6
- package/src/bridge.js +1102 -0
- package/src/cli.js +139 -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/response-meta.js +123 -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/cli.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { NansenAPI, NansenError, CommandError, ErrorCode, saveConfig, deleteConfig, getConfigFile, clearCache, getCacheDir, validateAddress, normalizeAddress, sleep } from './api.js';
|
|
7
7
|
import { buildWalletCommands } from './wallet.js';
|
|
8
|
+
import { buildBridgeCommands, formatBridgeRoutes } from './bridge.js';
|
|
9
|
+
import { buildPerpCommands } from './perp.js';
|
|
8
10
|
import { buildTradingCommands } from './trading.js';
|
|
9
11
|
import { buildLimitOrderCommands } from './limit-order.js';
|
|
10
12
|
import { formatAlertsTable, buildAlertsCommands } from './commands/alerts.js';
|
|
@@ -14,6 +16,7 @@ import { resolveAddress, isEnsName } from './ens.js';
|
|
|
14
16
|
import fs from 'fs';
|
|
15
17
|
import { getUpdateNotification, getUpgradeNotice, scheduleUpdateCheck } from './update-check.js';
|
|
16
18
|
import { refreshCostMapIfStale, getCostForEndpoint } from './cost-cache.js';
|
|
19
|
+
import { creditWarning, noticeWarnings } from './response-meta.js';
|
|
17
20
|
import { trackCommandSucceeded, trackCommandFailed } from './telemetry.js';
|
|
18
21
|
import { createRequire } from 'module';
|
|
19
22
|
import * as readline from 'readline';
|
|
@@ -365,6 +368,19 @@ export function formatOutput(data, { pretty = false, table = false, csv = false
|
|
|
365
368
|
}
|
|
366
369
|
}
|
|
367
370
|
|
|
371
|
+
// Codes whose message is a usage banner written for a human to read: multi-line,
|
|
372
|
+
// indented, with a blank line between sections. Serialising one into the error
|
|
373
|
+
// envelope turns every newline into a literal \n and makes it unreadable, so an
|
|
374
|
+
// interactive terminal gets the message as written instead. Piped or explicitly
|
|
375
|
+
// formatted output still gets the envelope, so agents keep one shape to branch on.
|
|
376
|
+
export const USAGE_ERROR_CODES = new Set(['MISSING_PARAM', 'MISSING_ARGS']);
|
|
377
|
+
|
|
378
|
+
export function isUsageError(errorData, { pretty, table, csv, stream, isTTY }) {
|
|
379
|
+
if (!USAGE_ERROR_CODES.has(errorData.code)) return false;
|
|
380
|
+
if (pretty || table || csv || stream) return false;
|
|
381
|
+
return !!isTTY;
|
|
382
|
+
}
|
|
383
|
+
|
|
368
384
|
// Format error data (returns object, does not exit)
|
|
369
385
|
export function formatError(error) {
|
|
370
386
|
const details = error.details ?? error.data ?? null;
|
|
@@ -701,6 +717,8 @@ USAGE: nansen <command> [subcommand] [options]
|
|
|
701
717
|
|
|
702
718
|
COMMANDS:
|
|
703
719
|
trade DEX swaps/bridges: quote, execute, bridge-status, limit-order
|
|
720
|
+
bridge Hyperliquid bridge: quote, execute, status (EVM <-> HL)
|
|
721
|
+
perp Hyperliquid perps: order, cancel, close, leverage, positions
|
|
704
722
|
research analytics: smart-money, profiler, token, search, perp, portfolio, points
|
|
705
723
|
wallet create, list, show, export, default, delete, forget-password
|
|
706
724
|
agent Ask the Nansen AI research agent (fast/expert modes)
|
|
@@ -724,6 +742,12 @@ TRADING:
|
|
|
724
742
|
nansen trade limit-order create --from SOL --to USDC --amount 1.5 --trigger-mint SOL --trigger-condition below --trigger-price 80
|
|
725
743
|
Supports Solana/Base DEX swaps, cross-chain bridges, and Solana limit orders.
|
|
726
744
|
|
|
745
|
+
BRIDGE (Hyperliquid):
|
|
746
|
+
nansen bridge quote --from-chain base --to-chain hyperliquid --from-token USDC --amount 1000000
|
|
747
|
+
nansen bridge execute --quote <quoteId>
|
|
748
|
+
nansen bridge status --request-id <id>
|
|
749
|
+
Supports EVM chains (ethereum, base, arbitrum, polygon, bnb) <-> Hyperliquid.
|
|
750
|
+
|
|
727
751
|
EXAMPLES:
|
|
728
752
|
nansen trade quote --chain base --from ETH --to USDC --amount 1000000000000000000
|
|
729
753
|
nansen trade quote --chain base --to-chain solana --from USDC --to USDC --amount 1000000
|
|
@@ -737,6 +761,7 @@ DEPRECATED ALIASES (still work, will be removed in a future version):
|
|
|
737
761
|
|
|
738
762
|
Research chains: ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, mantle, ronin, sei, plasma, sonic, monad, hyperevm, iotaevm
|
|
739
763
|
Trade chains: solana, base
|
|
764
|
+
Bridge chains: ethereum, base, arbitrum, polygon, bnb, hyperliquid
|
|
740
765
|
Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
741
766
|
|
|
742
767
|
Docs: https://docs.nansen.ai
|
|
@@ -1488,6 +1513,11 @@ export function buildCommands(deps = {}) {
|
|
|
1488
1513
|
// 'research' delegates to the category handlers defined above
|
|
1489
1514
|
const RESEARCH_CATEGORIES = new Set(['smart-money', 'profiler', 'token', 'search', 'perp', 'portfolio', 'points', 'prediction-market']);
|
|
1490
1515
|
|
|
1516
|
+
// The analytics-only perp handler, captured before the trading wrapper below
|
|
1517
|
+
// replaces cmds['perp']. Both the wrapper and the research dispatch route to
|
|
1518
|
+
// it, so it has to be taken exactly once, here.
|
|
1519
|
+
const perpAnalytics = cmds['perp'];
|
|
1520
|
+
|
|
1491
1521
|
const researchHistorical = buildResearchCommands(deps).research;
|
|
1492
1522
|
|
|
1493
1523
|
cmds['research'] = async (args, apiInstance, flags, options) => {
|
|
@@ -1508,6 +1538,13 @@ export function buildCommands(deps = {}) {
|
|
|
1508
1538
|
if (!RESEARCH_CATEGORIES.has(category)) {
|
|
1509
1539
|
throw new NansenError(`Unknown research category: ${rawCategory}. Available: ${[...RESEARCH_CATEGORIES, ...RESEARCH_HISTORICAL_SUBCOMMANDS].join(', ')}`, ErrorCode.UNKNOWN);
|
|
1510
1540
|
}
|
|
1541
|
+
// `research perp` reaches only the analytics half (screener/leaderboard) —
|
|
1542
|
+
// the trading subcommands live at the top level. Routing its help through
|
|
1543
|
+
// cmds['perp'] printed the trading help, advertising order/close/leverage
|
|
1544
|
+
// from a command that can't run them.
|
|
1545
|
+
if (category === 'perp' && (!args[1] || args[1] === 'help')) {
|
|
1546
|
+
return perpAnalytics(['help'], apiInstance, flags, options);
|
|
1547
|
+
}
|
|
1511
1548
|
return cmds[category](args.slice(1), apiInstance, flags, options);
|
|
1512
1549
|
};
|
|
1513
1550
|
|
|
@@ -1589,11 +1626,83 @@ USAGE:
|
|
|
1589
1626
|
return tradingCmds[sub](args.slice(1), apiInstance, flags, options);
|
|
1590
1627
|
};
|
|
1591
1628
|
|
|
1629
|
+
// 'bridge' delegates to quote/execute/status from buildBridgeCommands
|
|
1630
|
+
const bridgeCmds = buildBridgeCommands(deps);
|
|
1631
|
+
cmds['bridge'] = async (args, apiInstance, flags, options) => {
|
|
1632
|
+
const sub = args[0];
|
|
1633
|
+
if (!sub || sub === 'help') {
|
|
1634
|
+
log(`nansen bridge — Hyperliquid bridge commands (EVM <-> Hyperliquid via Relay)
|
|
1635
|
+
|
|
1636
|
+
SUBCOMMANDS:
|
|
1637
|
+
quote Get a bridge quote
|
|
1638
|
+
execute Execute a bridge quote (sign + broadcast)
|
|
1639
|
+
status Check bridge transaction status
|
|
1640
|
+
|
|
1641
|
+
USAGE:
|
|
1642
|
+
nansen bridge quote --from-chain base --to-chain hyperliquid --from-token USDC --amount 1000000
|
|
1643
|
+
nansen bridge execute --quote <quoteId>
|
|
1644
|
+
nansen bridge status --request-id <id>
|
|
1645
|
+
|
|
1646
|
+
SUPPORTED ROUTES:
|
|
1647
|
+
${formatBridgeRoutes()}`);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (!bridgeCmds[sub]) {
|
|
1651
|
+
throw new NansenError(`Unknown bridge subcommand: ${sub}. Available: quote, execute, status`, ErrorCode.UNKNOWN);
|
|
1652
|
+
}
|
|
1653
|
+
return bridgeCmds[sub](args.slice(1), apiInstance, flags, options);
|
|
1654
|
+
};
|
|
1655
|
+
|
|
1656
|
+
// 'perp' delegates to buildPerpCommands. The trading subcommands are added on
|
|
1657
|
+
// top of the pre-existing perp analytics command, so capture that handler and
|
|
1658
|
+
// keep screener/leaderboard reachable instead of shadowing them — both
|
|
1659
|
+
// `nansen perp screener` and `nansen research perp screener` route through here.
|
|
1660
|
+
const perpCmds = buildPerpCommands(deps);
|
|
1661
|
+
const PERP_ANALYTICS_SUBCOMMANDS = new Set(['screener', 'leaderboard']);
|
|
1662
|
+
cmds['perp'] = async (args, apiInstance, flags, options) => {
|
|
1663
|
+
const sub = args[0];
|
|
1664
|
+
if (!sub || sub === 'help') {
|
|
1665
|
+
log(`nansen perp — Hyperliquid perpetual trading
|
|
1666
|
+
|
|
1667
|
+
SUBCOMMANDS:
|
|
1668
|
+
order Place a perp order (market/limit with optional TP/SL)
|
|
1669
|
+
cancel Cancel an open order
|
|
1670
|
+
close Close a position (reduce-only market order)
|
|
1671
|
+
leverage Set leverage and margin mode
|
|
1672
|
+
transfer Move USDC between Spot and Perps balances
|
|
1673
|
+
approve-builder-fee Authorize the Nansen builder fee (one-time; auto-fired on first trade)
|
|
1674
|
+
positions View open positions
|
|
1675
|
+
orders View open orders
|
|
1676
|
+
account View account state (balance, equity, margin, spot)
|
|
1677
|
+
meta View available assets
|
|
1678
|
+
screener Perp market screener (analytics)
|
|
1679
|
+
leaderboard Perp trader leaderboard (analytics)
|
|
1680
|
+
|
|
1681
|
+
USAGE:
|
|
1682
|
+
nansen perp order --coin BTC --side buy --size 0.001 --price 50000 --type limit
|
|
1683
|
+
nansen perp cancel --coin BTC --oid 12345
|
|
1684
|
+
nansen perp close --coin BTC --size 0.001 --price 100000 --side sell
|
|
1685
|
+
nansen perp leverage --coin BTC --leverage 10 --margin-type cross
|
|
1686
|
+
nansen perp transfer --direction spot-to-perp --amount 25
|
|
1687
|
+
nansen perp approve-builder-fee
|
|
1688
|
+
nansen perp positions
|
|
1689
|
+
nansen perp account`);
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (!perpCmds[sub]) {
|
|
1693
|
+
if (PERP_ANALYTICS_SUBCOMMANDS.has(sub)) {
|
|
1694
|
+
return perpAnalytics(args, apiInstance, flags, options);
|
|
1695
|
+
}
|
|
1696
|
+
throw new NansenError(`Unknown perp subcommand: ${sub}. Available: order, cancel, close, leverage, transfer, approve-builder-fee, positions, orders, account, meta, screener, leaderboard`, ErrorCode.UNKNOWN);
|
|
1697
|
+
}
|
|
1698
|
+
return perpCmds[sub](args.slice(1), apiInstance, flags, options);
|
|
1699
|
+
};
|
|
1700
|
+
|
|
1592
1701
|
return cmds;
|
|
1593
1702
|
}
|
|
1594
1703
|
|
|
1595
1704
|
// Categories that moved under 'research'
|
|
1596
|
-
export const DEPRECATED_TO_RESEARCH = new Set(['smart-money', 'profiler', 'token', 'search', '
|
|
1705
|
+
export const DEPRECATED_TO_RESEARCH = new Set(['smart-money', 'profiler', 'token', 'search', 'portfolio', 'points']);
|
|
1597
1706
|
// Subcommands that moved under 'trade'
|
|
1598
1707
|
export const DEPRECATED_TO_TRADE = new Set(['quote', 'execute']);
|
|
1599
1708
|
|
|
@@ -1670,7 +1779,10 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1670
1779
|
errorOutput = console.error,
|
|
1671
1780
|
exit = process.exit,
|
|
1672
1781
|
NansenAPIClass = NansenAPI,
|
|
1673
|
-
commandOverrides = {}
|
|
1782
|
+
commandOverrides = {},
|
|
1783
|
+
// Injectable so tests can exercise both renderings; defaults to the real
|
|
1784
|
+
// terminal, which is false under a pipe or in CI.
|
|
1785
|
+
isTTY = process.stdout.isTTY,
|
|
1674
1786
|
} = deps;
|
|
1675
1787
|
|
|
1676
1788
|
const { _: positional, flags, options } = parseArgs(rawArgs);
|
|
@@ -1852,8 +1964,25 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1852
1964
|
defaultHeaders['Payment-Signature'] = options['x402-payment-signature'];
|
|
1853
1965
|
}
|
|
1854
1966
|
const api = new NansenAPIClass(undefined, undefined, { retry: retryOptions, cache: cacheOptions, defaultHeaders });
|
|
1967
|
+
|
|
1968
|
+
// Deprecated top-level aliases otherwise run silently (the notice was only
|
|
1969
|
+
// shown in --help). Warn on stderr so it doesn't pollute parsed stdout.
|
|
1970
|
+
if (DEPRECATED_TO_TRADE.has(command)) {
|
|
1971
|
+
process.stderr.write(`Note: "nansen ${command}" is deprecated. Use "nansen trade ${command}" instead.\n`);
|
|
1972
|
+
} else if (DEPRECATED_TO_RESEARCH.has(command)) {
|
|
1973
|
+
process.stderr.write(`Note: "nansen ${command}" is deprecated. Use "nansen research ${command}" instead.\n`);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1855
1976
|
let result = await commands[command](subArgs, api, flags, options);
|
|
1856
1977
|
|
|
1978
|
+
// Credit balance warning, from the headers on the call just made. Goes to
|
|
1979
|
+
// stderr so it never contaminates the JSON on stdout that agents parse.
|
|
1980
|
+
// Placed before every return path below so it fires for operational
|
|
1981
|
+
// commands too, which print their own output and return undefined.
|
|
1982
|
+
const lowCredits = creditWarning(api.lastResponseMeta);
|
|
1983
|
+
if (lowCredits) errorOutput(lowCredits);
|
|
1984
|
+
for (const notice of noticeWarnings(api.lastResponseMeta)) errorOutput(notice);
|
|
1985
|
+
|
|
1857
1986
|
// Commands that handle their own output return undefined
|
|
1858
1987
|
if (result === undefined) {
|
|
1859
1988
|
await trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
|
|
@@ -1898,12 +2027,15 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1898
2027
|
await trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
|
|
1899
2028
|
return { type: csv ? 'csv' : 'success', data: result };
|
|
1900
2029
|
} catch (error) {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
2030
|
+
// Unified error envelope across all command families (perp/bridge/trade):
|
|
2031
|
+
// every failure serializes through formatError as
|
|
2032
|
+
// {success:false, error, code, status, details}. A CommandError's structured
|
|
2033
|
+
// data (e.g. PASSWORD_REQUIRED resolution steps) is preserved under `details`,
|
|
2034
|
+
// so agents get one consistent shape to branch on regardless of command.
|
|
2035
|
+
const errorData = formatError(error);
|
|
2036
|
+
if (isUsageError(errorData, { pretty, table, csv, stream, isTTY })) {
|
|
2037
|
+
output(errorData.error);
|
|
1905
2038
|
} else {
|
|
1906
|
-
errorData = formatError(error);
|
|
1907
2039
|
const formatted = formatOutput(errorData, { pretty, table, csv });
|
|
1908
2040
|
output(formatted.text);
|
|
1909
2041
|
}
|