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/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';
|
|
@@ -366,6 +368,19 @@ export function formatOutput(data, { pretty = false, table = false, csv = false
|
|
|
366
368
|
}
|
|
367
369
|
}
|
|
368
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
|
+
|
|
369
384
|
// Format error data (returns object, does not exit)
|
|
370
385
|
export function formatError(error) {
|
|
371
386
|
const details = error.details ?? error.data ?? null;
|
|
@@ -702,6 +717,8 @@ USAGE: nansen <command> [subcommand] [options]
|
|
|
702
717
|
|
|
703
718
|
COMMANDS:
|
|
704
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
|
|
705
722
|
research analytics: smart-money, profiler, token, search, perp, portfolio, points
|
|
706
723
|
wallet create, list, show, export, default, delete, forget-password
|
|
707
724
|
agent Ask the Nansen AI research agent (fast/expert modes)
|
|
@@ -725,6 +742,12 @@ TRADING:
|
|
|
725
742
|
nansen trade limit-order create --from SOL --to USDC --amount 1.5 --trigger-mint SOL --trigger-condition below --trigger-price 80
|
|
726
743
|
Supports Solana/Base DEX swaps, cross-chain bridges, and Solana limit orders.
|
|
727
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
|
+
|
|
728
751
|
EXAMPLES:
|
|
729
752
|
nansen trade quote --chain base --from ETH --to USDC --amount 1000000000000000000
|
|
730
753
|
nansen trade quote --chain base --to-chain solana --from USDC --to USDC --amount 1000000
|
|
@@ -738,6 +761,7 @@ DEPRECATED ALIASES (still work, will be removed in a future version):
|
|
|
738
761
|
|
|
739
762
|
Research chains: ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, mantle, ronin, sei, plasma, sonic, monad, hyperevm, iotaevm
|
|
740
763
|
Trade chains: solana, base
|
|
764
|
+
Bridge chains: ethereum, base, arbitrum, polygon, bnb, hyperliquid
|
|
741
765
|
Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
742
766
|
|
|
743
767
|
Docs: https://docs.nansen.ai
|
|
@@ -1489,6 +1513,11 @@ export function buildCommands(deps = {}) {
|
|
|
1489
1513
|
// 'research' delegates to the category handlers defined above
|
|
1490
1514
|
const RESEARCH_CATEGORIES = new Set(['smart-money', 'profiler', 'token', 'search', 'perp', 'portfolio', 'points', 'prediction-market']);
|
|
1491
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
|
+
|
|
1492
1521
|
const researchHistorical = buildResearchCommands(deps).research;
|
|
1493
1522
|
|
|
1494
1523
|
cmds['research'] = async (args, apiInstance, flags, options) => {
|
|
@@ -1509,6 +1538,13 @@ export function buildCommands(deps = {}) {
|
|
|
1509
1538
|
if (!RESEARCH_CATEGORIES.has(category)) {
|
|
1510
1539
|
throw new NansenError(`Unknown research category: ${rawCategory}. Available: ${[...RESEARCH_CATEGORIES, ...RESEARCH_HISTORICAL_SUBCOMMANDS].join(', ')}`, ErrorCode.UNKNOWN);
|
|
1511
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
|
+
}
|
|
1512
1548
|
return cmds[category](args.slice(1), apiInstance, flags, options);
|
|
1513
1549
|
};
|
|
1514
1550
|
|
|
@@ -1590,11 +1626,83 @@ USAGE:
|
|
|
1590
1626
|
return tradingCmds[sub](args.slice(1), apiInstance, flags, options);
|
|
1591
1627
|
};
|
|
1592
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
|
+
|
|
1593
1701
|
return cmds;
|
|
1594
1702
|
}
|
|
1595
1703
|
|
|
1596
1704
|
// Categories that moved under 'research'
|
|
1597
|
-
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']);
|
|
1598
1706
|
// Subcommands that moved under 'trade'
|
|
1599
1707
|
export const DEPRECATED_TO_TRADE = new Set(['quote', 'execute']);
|
|
1600
1708
|
|
|
@@ -1671,7 +1779,10 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1671
1779
|
errorOutput = console.error,
|
|
1672
1780
|
exit = process.exit,
|
|
1673
1781
|
NansenAPIClass = NansenAPI,
|
|
1674
|
-
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,
|
|
1675
1786
|
} = deps;
|
|
1676
1787
|
|
|
1677
1788
|
const { _: positional, flags, options } = parseArgs(rawArgs);
|
|
@@ -1853,6 +1964,15 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1853
1964
|
defaultHeaders['Payment-Signature'] = options['x402-payment-signature'];
|
|
1854
1965
|
}
|
|
1855
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
|
+
|
|
1856
1976
|
let result = await commands[command](subArgs, api, flags, options);
|
|
1857
1977
|
|
|
1858
1978
|
// Credit balance warning, from the headers on the call just made. Goes to
|
|
@@ -1907,12 +2027,15 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1907
2027
|
await trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
|
|
1908
2028
|
return { type: csv ? 'csv' : 'success', data: result };
|
|
1909
2029
|
} catch (error) {
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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);
|
|
1914
2038
|
} else {
|
|
1915
|
-
errorData = formatError(error);
|
|
1916
2039
|
const formatted = formatOutput(errorData, { pretty, table, csv });
|
|
1917
2040
|
output(formatted.text);
|
|
1918
2041
|
}
|