nansen-cli 1.10.0 → 1.11.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,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#186](https://github.com/nansen-ai/nansen-cli/pull/186) [`feecc50`](https://github.com/nansen-ai/nansen-cli/commit/feecc5080254b55aaef0addb646279d52a468063) Thanks [@TimNooren](https://github.com/TimNooren)! - Trade commands output to stdout instead of stderr; wallet send prints human-readable text instead of JSON
8
+
9
+ ### Patch Changes
10
+
11
+ - [#166](https://github.com/nansen-ai/nansen-cli/pull/166) [`c1034db`](https://github.com/nansen-ai/nansen-cli/commit/c1034dbb4bf2fc173f377cbc0adbbbe3e67873aa) Thanks [@0xlaveen](https://github.com/0xlaveen)! - fix: pass --page parameter correctly in smart-money, profiler, token, perp, and points commands
12
+
13
+ - [#137](https://github.com/nansen-ai/nansen-cli/pull/137) [`1214767`](https://github.com/nansen-ai/nansen-cli/commit/12147675aadfd0bd97627cb2f41f1dcc5205b0d7) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Add missing sort/filters options to profiler schema and fix pnl sort/filters forwarding
14
+
15
+ ## 1.10.1
16
+
17
+ ### Patch Changes
18
+
19
+ - [#133](https://github.com/nansen-ai/nansen-cli/pull/133) [`4cbeb65`](https://github.com/nansen-ai/nansen-cli/commit/4cbeb6510c286660f611117d2d8b0508f2340e31) Thanks [@0xlaveen](https://github.com/0xlaveen)! - fix: correct profiler pagination parameter from `recordsPerPage` to `per_page`; remove unsupported pagination from pnl-summary; add --limit to labels, historical-balances, counterparties schema
20
+
21
+ - [#164](https://github.com/nansen-ai/nansen-cli/pull/164) [`ec6ab78`](https://github.com/nansen-ai/nansen-cli/commit/ec6ab78d604a177c3459833091531de3fc07add1) Thanks [@DMagowan](https://github.com/DMagowan)! - fix: correct `--date` option marked as `required: true` when it is optional
22
+
23
+ The schema incorrectly marked `--date` as `required: true` for three commands:
24
+
25
+ - `research token flows`
26
+ - `research token who-bought-sold`
27
+ - `research profiler transactions`
28
+
29
+ All three use `parseDateOption` with a `days` fallback, so `--date` is optional — omitting it defaults to a rolling window based on `--days`. An agent following the schema strictly would unnecessarily refuse to run these commands without a date.
30
+
31
+ - [#162](https://github.com/nansen-ai/nansen-cli/pull/162) [`4dbe181`](https://github.com/nansen-ai/nansen-cli/commit/4dbe181d3b4973881bcb7fb445cf6559819006b6) Thanks [@DMagowan](https://github.com/DMagowan)! - fix: surface wallet prerequisite in `trade quote` help text and schema
32
+
33
+ `nansen trade quote` requires a configured wallet (the trading API builds a transaction specific to the sender address), but this was not communicated until the command failed. Adds a PREREQUISITE section to the usage text and a `prerequisites` field to the schema so agents can discover this requirement before running the command.
34
+
35
+ - [#165](https://github.com/nansen-ai/nansen-cli/pull/165) [`92f37ea`](https://github.com/nansen-ai/nansen-cli/commit/92f37eaa8655ae1a39b9200aafaf4771a0859229) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Fix trading docs and config to reflect actual supported chains (Base and Solana only)
36
+
3
37
  ## 1.10.0
4
38
 
5
39
  ### Minor Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/nansen-cli.svg)](https://www.npmjs.com/package/nansen-cli)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- > **Built by agents, for agents.** Command-line interface for the [Nansen API](https://docs.nansen.ai) with structured JSON output.
6
+ > **Built by agents, for agents.** Command-line interface for the [Nansen API](https://docs.nansen.ai), designed for AI agents.
7
7
 
8
8
  ## Installation
9
9
 
@@ -32,7 +32,7 @@ nansen schema [command] [--pretty] # full command reference (no API key neede
32
32
 
33
33
  **Research categories:** `smart-money` (`sm`), `token` (`tgm`), `profiler` (`prof`), `portfolio` (`port`), `search`, `perp`, `points`
34
34
 
35
- **Trade:** `quote`, `execute` — DEX swaps via LiFi/Jupiter.
35
+ **Trade:** `quote`, `execute` — DEX swaps on Solana and Base.
36
36
 
37
37
  **Wallet:** `create`, `list`, `show`, `export`, `default`, `delete`, `send` — local keypairs (EVM + Solana).
38
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "src/*.js",
12
+ "src/*.json",
12
13
  "CHANGELOG.md"
13
14
  ],
14
15
  "scripts": {
@@ -60,4 +61,4 @@
60
61
  "globals": "^17.4.0",
61
62
  "vitest": "^4.0.18"
62
63
  }
63
- }
64
+ }
package/src/api.js CHANGED
@@ -62,7 +62,7 @@ export class NansenError extends Error {
62
62
  this.name = 'NansenError';
63
63
  this.code = code;
64
64
  this.status = status;
65
- this.data = data;
65
+ this.details = data;
66
66
  }
67
67
 
68
68
  toJSON() {
@@ -70,7 +70,7 @@ export class NansenError extends Error {
70
70
  error: this.message,
71
71
  code: this.code,
72
72
  status: this.status,
73
- details: this.data,
73
+ details: this.details,
74
74
  };
75
75
  }
76
76
  }
@@ -646,11 +646,12 @@ export class NansenAPI {
646
646
  }
647
647
 
648
648
  async smartMoneyPerpTrades(params = {}) {
649
- const { filters = {}, orderBy, pagination } = params;
649
+ const { filters = {}, orderBy, pagination, onlyNewPositions } = params;
650
650
  return this.request('/api/v1/smart-money/perp-trades', {
651
651
  filters,
652
652
  order_by: orderBy,
653
- pagination
653
+ pagination,
654
+ only_new_positions: onlyNewPositions
654
655
  });
655
656
  }
656
657
 
@@ -703,7 +704,7 @@ export class NansenAPI {
703
704
  }
704
705
 
705
706
  async addressLabels(params = {}) {
706
- const { address, chain = 'ethereum', pagination = { page: 1, recordsPerPage: 100 } } = params;
707
+ const { address, chain = 'ethereum', pagination = { page: 1, per_page: 100 } } = params;
707
708
  if (address) {
708
709
  const validation = validateAddress(address, chain);
709
710
  if (!validation.valid) throw new NansenError(validation.error, validation.code);
@@ -732,7 +733,7 @@ export class NansenAPI {
732
733
  }
733
734
 
734
735
  async addressPnl(params = {}) {
735
- const { address, chain = 'ethereum', date, days = 30, pagination } = params;
736
+ const { address, chain = 'ethereum', date, days = 30, filters = {}, orderBy, pagination } = params;
736
737
  if (address) {
737
738
  const validation = validateAddress(address, chain);
738
739
  if (!validation.valid) throw new NansenError(validation.error, validation.code);
@@ -742,6 +743,8 @@ export class NansenAPI {
742
743
  address,
743
744
  chain,
744
745
  date: dateRange,
746
+ filters,
747
+ order_by: orderBy,
745
748
  pagination
746
749
  });
747
750
  }
@@ -814,7 +817,9 @@ export class NansenAPI {
814
817
  }
815
818
 
816
819
  async addressPnlSummary(params = {}) {
817
- const { address, chain = 'ethereum', orderBy, pagination, days = 30 } = params;
820
+ // Note: pnl-summary endpoint is non-paginated (returns aggregate stats, not a list).
821
+ // Pagination param intentionally omitted from this request.
822
+ const { address, chain = 'ethereum', orderBy, days = 30 } = params;
818
823
  if (address) {
819
824
  const validation = validateAddress(address, chain);
820
825
  if (!validation.valid) throw new NansenError(validation.error, validation.code);
@@ -823,8 +828,7 @@ export class NansenAPI {
823
828
  address,
824
829
  chain,
825
830
  date: buildDateRange(days),
826
- order_by: orderBy,
827
- pagination
831
+ order_by: orderBy
828
832
  });
829
833
  }
830
834
 
package/src/cli.js CHANGED
@@ -17,159 +17,22 @@ const { version: VERSION } = require('../package.json');
17
17
 
18
18
  // ============= Schema Definition =============
19
19
 
20
- export const SCHEMA = {
21
- version: VERSION,
22
- commands: {
23
- 'research': {
24
- description: 'Research and analytics commands',
25
- subcommands: {
26
- 'smart-money': {
27
- description: 'Smart Money analytics - track sophisticated market participants',
28
- subcommands: {
29
- 'netflow': {
30
- description: 'Net capital flows (inflows vs outflows)',
31
- options: {
32
- chain: { type: 'string', default: 'solana', description: 'Blockchain to query' },
33
- chains: { type: 'array', description: 'Multiple chains as JSON array' },
34
- limit: { type: 'number', description: 'Number of results' },
35
- labels: { type: 'string|array', description: 'Smart Money label filter' },
36
- sort: { type: 'string', description: 'Sort field:direction (e.g., value_usd:desc)' },
37
- filters: { type: 'object', description: 'Additional filters as JSON' }
38
- },
39
- returns: ['token_address', 'token_symbol', 'token_name', 'chain', 'inflow_usd', 'outflow_usd', 'net_flow_usd']
40
- },
41
- 'dex-trades': {
42
- description: 'Real-time DEX trading activity',
43
- options: { chain: { type: 'string', default: 'solana' }, chains: { type: 'array' }, limit: { type: 'number' }, labels: { type: 'string|array' }, sort: { type: 'string' }, filters: { type: 'object' } },
44
- returns: ['chain', 'block_timestamp', 'transaction_hash', 'trader_address', 'trader_address_label', 'token_bought_address', 'token_sold_address', 'token_bought_amount', 'token_sold_amount', 'token_bought_symbol', 'token_sold_symbol', 'trade_value_usd']
45
- },
46
- 'perp-trades': {
47
- description: 'Perpetual trading on Hyperliquid',
48
- options: { limit: { type: 'number' }, sort: { type: 'string' }, filters: { type: 'object' } },
49
- returns: ['trader_address', 'trader_address_label', 'token_symbol', 'side', 'action', 'token_amount', 'price_usd', 'value_usd', 'type', 'block_timestamp', 'transaction_hash']
50
- },
51
- 'holdings': {
52
- description: 'Aggregated token balances',
53
- options: { chain: { type: 'string', default: 'solana' }, chains: { type: 'array' }, limit: { type: 'number' }, labels: { type: 'string|array' } },
54
- returns: ['chain', 'token_address', 'token_symbol', 'token_sectors', 'value_usd', 'balance_24h_percent_change', 'holders_count', 'share_of_holdings_percent', 'token_age_days', 'market_cap_usd']
55
- },
56
- 'dcas': {
57
- description: 'DCA strategies on Jupiter',
58
- options: { limit: { type: 'number' }, filters: { type: 'object' } },
59
- returns: ['dca_created_at', 'dca_updated_at', 'trader_address', 'trader_address_label', 'dca_vault_address', 'input_token_address', 'output_token_address', 'deposit_token_amount', 'token_spent_amount', 'output_token_redeemed_amount', 'dca_status', 'input_token_symbol', 'output_token_symbol', 'deposit_value_usd']
60
- },
61
- 'historical-holdings': {
62
- description: 'Historical holdings over time',
63
- options: { chain: { type: 'string', default: 'solana' }, chains: { type: 'array' }, days: { type: 'number', default: 30 }, limit: { type: 'number' } },
64
- returns: ['date', 'token_address', 'token_symbol', 'balance', 'balance_usd']
65
- }
66
- }
67
- },
68
- 'profiler': {
69
- description: 'Wallet profiling - detailed information about any blockchain address',
70
- subcommands: {
71
- 'balance': { description: 'Current token holdings', options: { address: { type: 'string', required: true, description: 'Wallet address to query' }, chain: { type: 'string', default: 'ethereum' }, entity: { type: 'string', description: 'Entity name instead of address' } }, returns: ['chain', 'address', 'token_address', 'token_symbol', 'token_name', 'token_amount', 'price_usd', 'value_usd'] },
72
- 'labels': { description: 'Behavioral and entity labels', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' } }, returns: ['label', 'label_type', 'label_subtype'] },
73
- 'transactions': { description: 'Transaction history', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, date: { type: 'string', required: true, description: 'Date or date range' }, limit: { type: 'number' }, days: { type: 'number', default: 30 } }, returns: ['chain', 'method', 'tokens_sent', 'tokens_received', 'volume_usd', 'block_timestamp', 'transaction_hash'] },
74
- 'pnl': { description: 'PnL and trade performance', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, date: { type: 'string', description: 'Date or date range' }, days: { type: 'number', default: 30 }, limit: { type: 'number' } }, returns: ['token_address', 'token_symbol', 'realized_pnl_usd', 'unrealized_pnl_usd', 'total_pnl_usd'] },
75
- 'search': { description: 'Search for entities by name', options: { query: { type: 'string', required: true, description: 'Search query' }, limit: { type: 'number' } }, returns: ['entity_name'] },
76
- 'historical-balances': { description: 'Historical balances over time', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, days: { type: 'number', default: 30 } }, returns: ['date', 'token_address', 'token_symbol', 'balance', 'balance_usd'] },
77
- 'related-wallets': { description: 'Find wallets related to an address', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, limit: { type: 'number' } }, returns: ['address', 'address_label', 'relation', 'transaction_hash', 'block_timestamp', 'order', 'chain'] },
78
- 'counterparties': { description: 'Top counterparties by volume', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, days: { type: 'number', default: 30 } }, returns: ['counterparty_address', 'counterparty_address_label', 'interaction_count', 'total_volume_usd', 'volume_in_usd', 'volume_out_usd', 'tokens_info'] },
79
- 'pnl-summary': { description: 'Summarized PnL metrics', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, days: { type: 'number', default: 30 } }, returns: ['top5_tokens', 'traded_token_count', 'traded_times', 'realized_pnl_usd', 'realized_pnl_percent', 'win_rate'] },
80
- 'perp-positions': { description: 'Current perpetual positions', options: { address: { type: 'string', required: true }, limit: { type: 'number' } }, returns: ['symbol', 'side', 'size', 'entry_price', 'mark_price', 'unrealized_pnl', 'leverage'] },
81
- 'perp-trades': { description: 'Perpetual trading history', options: { address: { type: 'string', required: true }, days: { type: 'number', default: 30 }, limit: { type: 'number' } }, returns: ['symbol', 'side', 'size', 'price', 'value_usd', 'pnl_usd', 'timestamp'] },
82
- 'batch': { description: 'Batch profile multiple addresses', options: { addresses: { type: 'string', description: 'Comma-separated addresses' }, file: { type: 'string', description: 'File with one address per line' }, chain: { type: 'string', default: 'ethereum' }, include: { type: 'string', default: 'labels,balance', description: 'Comma-separated: labels,balance,pnl' }, delay: { type: 'number', default: 1000, description: 'Delay between requests in ms' } }, returns: ['address', 'chain', 'labels', 'balance', 'pnl', 'error'] },
83
- 'trace': { description: 'Multi-hop counterparty trace (BFS)', options: { address: { type: 'string', required: true }, chain: { type: 'string', default: 'ethereum' }, depth: { type: 'number', default: 2, description: 'Max hops (1-5)' }, width: { type: 'number', default: 10, description: 'Top N counterparties per hop' }, days: { type: 'number', default: 30 }, delay: { type: 'number', default: 1000, description: 'Delay between requests in ms' } }, returns: ['root', 'chain', 'depth', 'nodes', 'edges', 'stats'] },
84
- 'compare': { description: 'Compare two wallets (shared counterparties, tokens)', options: { addresses: { type: 'string', required: true, description: 'Two comma-separated addresses' }, chain: { type: 'string', default: 'ethereum' }, days: { type: 'number', default: 30 } }, returns: ['addresses', 'chain', 'shared_counterparties', 'shared_tokens', 'balances'] }
85
- }
86
- },
87
- 'token': {
88
- description: 'Token God Mode - deep analytics for any token',
89
- subcommands: {
90
- 'indicators': { description: 'Risk and reward indicators for a token (Nansen Score)', options: { token: { type: 'string', required: true, description: 'Token address' }, chain: { type: 'string', default: 'ethereum' } }, returns: ['token_info[market_cap_usd, market_cap_group, is_stablecoin]', 'risk_indicators[indicator_type, score, signal, signal_percentile, last_trigger_on]', 'reward_indicators[indicator_type, score, signal, signal_percentile, last_trigger_on]'] },
91
- 'ohlcv': { description: 'OHLCV candle data for a token', options: { token: { type: 'string', required: true, description: 'Token address' }, chain: { type: 'string', default: 'solana' }, timeframe: { type: 'string', default: '1h', enum: ['1m', '5m', '15m', '30m', '1h', '2h', '4h', '1d', '1w', '1M'], description: 'Candle timeframe (e.g., 1h, 4h, 1d)' } }, returns: ['timestamp', 'open', 'high', 'low', 'close', 'volume'] },
92
- 'info': { description: 'Get detailed information for a specific token', options: { token: { type: 'string', required: true, description: 'Token address' }, chain: { type: 'string', default: 'solana' }, timeframe: { type: 'string', default: '1d', enum: ['5m', '1h', '6h', '12h', '1d', '7d'] } }, returns: ['token_address', 'token_symbol', 'token_name', 'chain', 'price_usd', 'volume_usd', 'market_cap', 'holder_count', 'liquidity_usd'] },
93
- 'screener': { description: 'Discover and filter tokens', options: { chain: { type: 'string', default: 'solana' }, chains: { type: 'array' }, timeframe: { type: 'string', default: '24h', enum: ['5m', '10m', '1h', '6h', '24h', '7d', '30d'] }, 'smart-money': { type: 'boolean', description: 'Filter for Smart Money only' }, search: { type: 'string', description: 'Filter results by token symbol or name (client-side)' }, limit: { type: 'number' }, sort: { type: 'string' } }, returns: ['token_address', 'token_symbol', 'token_name', 'chain', 'price_usd', 'volume_usd', 'market_cap', 'holder_count', 'smart_money_holders'] },
94
- 'holders': { description: 'Token holder analysis', options: { token: { type: 'string', required: true }, chain: { type: 'string', default: 'solana' }, 'smart-money': { type: 'boolean' }, limit: { type: 'number' } }, returns: ['address', 'address_label', 'token_amount', 'total_outflow', 'total_inflow', 'balance_change_24h', 'balance_change_7d', 'balance_change_30d', 'ownership_percentage', 'value_usd'] },
95
- 'flows': { description: 'Token flow metrics', options: { token: { type: 'string', required: true }, chain: { type: 'string', default: 'solana' }, date: { type: 'string', required: true, description: 'Date or date range' }, days: { type: 'number', default: 30 }, limit: { type: 'number' } }, returns: ['date', 'price_usd', 'token_amount', 'value_usd', 'holders_count', 'total_inflows_count', 'total_outflows_count'] },
96
- 'dex-trades': { description: 'DEX trading activity', options: { token: { type: 'string', required: true }, chain: { type: 'string', default: 'solana' }, 'smart-money': { type: 'boolean' }, days: { type: 'number', default: 30 }, limit: { type: 'number' } }, returns: ['tx_hash', 'wallet_address', 'side', 'amount', 'price_usd', 'value_usd', 'timestamp'] },
97
- 'pnl': { description: 'PnL leaderboard', options: { token: { type: 'string', required: true }, chain: { type: 'string', default: 'solana' }, days: { type: 'number', default: 30 }, limit: { type: 'number' }, sort: { type: 'string' } }, returns: ['wallet_address', 'realized_pnl_usd', 'unrealized_pnl_usd', 'total_pnl_usd', 'labels'] },
98
- 'who-bought-sold': { description: 'Recent buyers and sellers', options: { token: { type: 'string', required: true }, chain: { type: 'string', default: 'solana' }, date: { type: 'string', required: true, description: 'Date or date range' }, days: { type: 'number', default: 30 }, limit: { type: 'number' } }, returns: ['address', 'address_label', 'bought_token_volume', 'sold_token_volume', 'token_trade_volume', 'bought_volume_usd', 'sold_volume_usd', 'trade_volume_usd'] },
99
- 'flow-intelligence': { description: 'Detailed flow intelligence by label', options: { token: { type: 'string', required: true }, chain: { type: 'string', default: 'solana' }, days: { type: 'number', default: 30 } }, returns: ['public_figure_net_flow_usd', 'public_figure_wallet_count', 'top_pnl_net_flow_usd', 'top_pnl_wallet_count', 'whale_net_flow_usd', 'whale_wallet_count', 'smart_trader_net_flow_usd', 'smart_trader_wallet_count', 'exchange_net_flow_usd', 'exchange_wallet_count', 'fresh_wallets_net_flow_usd', 'fresh_wallets_wallet_count'] },
100
- 'transfers': { description: 'Token transfer history', options: { token: { type: 'string', required: true }, chain: { type: 'string', default: 'solana' }, days: { type: 'number', default: 30 }, limit: { type: 'number' }, from: { type: 'string', description: 'Filter by sender address' }, to: { type: 'string', description: 'Filter by recipient address' }, enrich: { type: 'boolean', description: 'Enrich addresses with Nansen labels' } }, returns: ['tx_hash', 'from', 'to', 'amount', 'value_usd', 'timestamp'] },
101
- 'jup-dca': { description: 'Jupiter DCA orders for token', options: { token: { type: 'string', required: true }, limit: { type: 'number' } }, returns: ['wallet_address', 'input_token', 'output_token', 'total_input', 'executed', 'remaining'] },
102
- 'perp-trades': { description: 'Perp trades by token symbol', options: { symbol: { type: 'string', required: true, description: 'Token symbol (e.g., BTC, ETH)' }, days: { type: 'number', default: 30 }, limit: { type: 'number' } }, returns: ['wallet_address', 'side', 'size', 'price', 'value_usd', 'pnl_usd', 'timestamp'] },
103
- 'perp-positions': { description: 'Open perp positions by token symbol', options: { symbol: { type: 'string', required: true }, limit: { type: 'number' } }, returns: ['wallet_address', 'side', 'size', 'entry_price', 'mark_price', 'unrealized_pnl', 'leverage'] },
104
- 'perp-pnl-leaderboard': { description: 'Perp PnL leaderboard by token', options: { symbol: { type: 'string', required: true }, days: { type: 'number', default: 30 }, limit: { type: 'number' } }, returns: ['wallet_address', 'realized_pnl', 'unrealized_pnl', 'total_pnl', 'trade_count'] }
105
- }
106
- },
107
- 'search': {
108
- description: 'Search for tokens and entities across Nansen',
109
- options: {
110
- query: { type: 'string', required: true, description: 'Search query (token name, symbol, address, or entity)' },
111
- type: { type: 'string', default: 'any', enum: ['token', 'entity', 'any'], description: 'Result type filter' },
112
- chain: { type: 'string', description: 'Filter by chain (e.g., ethereum, solana)' },
113
- limit: { type: 'number', default: 25, description: 'Max results (1-50)' }
114
- },
115
- returns: ['tokens[name, symbol, chain, address, price, volume_24h, market_cap, rank]', 'entities[name, tags, rank]', 'total_results']
116
- },
117
- 'perp': {
118
- description: 'Perpetual futures analytics',
119
- subcommands: {
120
- 'screener': { description: 'Screen perpetual futures contracts', options: { days: { type: 'number', default: 30 }, limit: { type: 'number' }, sort: { type: 'string' }, filters: { type: 'object' } }, returns: ['token_symbol', 'volume_usd', 'open_interest', 'funding_rate', 'price_change_24h'] },
121
- 'leaderboard': { description: 'Perpetual futures PnL leaderboard', options: { days: { type: 'number', default: 30 }, limit: { type: 'number' }, sort: { type: 'string' }, filters: { type: 'object' } }, returns: ['address', 'address_label', 'realized_pnl', 'unrealized_pnl', 'total_pnl', 'trade_count', 'win_rate'] }
122
- }
123
- },
124
- 'portfolio': {
125
- description: 'Portfolio analytics',
126
- subcommands: {
127
- 'defi': { description: 'DeFi holdings across protocols', options: { wallet: { type: 'string', required: true, description: 'Wallet address' } }, returns: ['protocol', 'chain', 'position_type', 'token_symbol', 'balance', 'balance_usd'] }
128
- }
129
- },
130
- 'points': {
131
- description: 'Nansen Points analytics',
132
- subcommands: {
133
- 'leaderboard': { description: 'Points leaderboard', options: { tier: { type: 'string', description: 'Filter by tier' }, limit: { type: 'number' } }, returns: ['rank', 'address', 'address_label', 'points', 'tier'] }
134
- }
135
- }
136
- }
137
- },
138
- 'trade': {
139
- description: 'DEX trading commands',
140
- subcommands: {
141
- 'quote': {
142
- description: 'Get a DEX swap quote (chain, tokens, amount)',
143
- options: {
144
- chain: { type: 'string', default: 'ethereum', description: 'Blockchain' },
145
- from: { type: 'string', required: true, description: 'Token to sell (address or symbol)' },
146
- to: { type: 'string', required: true, description: 'Token to buy (address or symbol)' },
147
- amount: { type: 'string', required: true, description: 'Amount to swap' },
148
- wallet: { type: 'string', description: 'Wallet name, or "walletconnect"/"wc" for WalletConnect (EVM only)' }
149
- }
150
- },
151
- 'execute': {
152
- description: 'Sign and broadcast a quoted trade',
153
- options: {
154
- chain: { type: 'string', default: 'ethereum', description: 'Blockchain' },
155
- wallet: { type: 'string', description: 'Wallet name, or "walletconnect"/"wc" for WalletConnect (EVM only)' }
156
- }
157
- }
158
- }
159
- }
160
- },
161
- globalOptions: {
162
- pretty: { type: 'boolean', description: 'Format JSON output for readability' },
163
- table: { type: 'boolean', description: 'Format output as human-readable table' },
164
- fields: { type: 'string', description: 'Comma-separated list of fields to include in output' },
165
- 'no-retry': { type: 'boolean', description: 'Disable automatic retry on rate limits/errors' },
166
- retries: { type: 'number', default: 3, description: 'Max retry attempts' },
167
- format: { type: 'string', enum: ['json', 'csv'], description: 'Output format (default: json)' },
168
- 'x402-payment-signature': { type: 'string', description: 'Pre-signed x402 payment signature header' }
169
- },
170
- chains: ['ethereum', 'solana', 'base', 'bnb', 'arbitrum', 'polygon', 'optimism', 'avalanche', 'linea', 'scroll', 'mantle', 'ronin', 'sei', 'plasma', 'sonic', 'monad', 'hyperevm', 'iotaevm'],
171
- smartMoneyLabels: ['Fund', 'Smart Trader', '30D Smart Trader', '90D Smart Trader', '180D Smart Trader', 'Smart HL Perps Trader']
172
- };
20
+ const schemaDefinition = require('./schema.json');
21
+
22
+ // SCHEMA is the static definition with version injected at runtime.
23
+ // The schema.json file is the source of truth for command metadata (returns, options, etc.)
24
+ // and should be updated whenever the API changes — do not edit returns arrays here.
25
+ export const SCHEMA = { version: VERSION, ...schemaDefinition };
26
+
27
+ // ============= Pagination =============
28
+
29
+ export function buildPagination(options) {
30
+ if (!options.limit && !options.page) return undefined;
31
+ return {
32
+ page: Math.max(1, parseInt(options.page, 10) || 1),
33
+ per_page: options.limit,
34
+ };
35
+ }
173
36
 
174
37
  // ============= Field Filtering =============
175
38
 
@@ -460,13 +323,17 @@ export function formatOutput(data, { pretty = false, table = false, csv = false
460
323
 
461
324
  // Format error data (returns object, does not exit)
462
325
  export function formatError(error) {
463
- return {
326
+ const details = error.details ?? error.data ?? null;
327
+ const result = {
464
328
  success: false,
465
329
  error: error.message,
466
330
  code: error.code || 'UNKNOWN',
467
331
  status: error.status || null,
468
- details: error.data || null
469
332
  };
333
+ if (details != null && !(typeof details === 'object' && !Array.isArray(details) && Object.keys(details).length === 0)) {
334
+ result.details = details;
335
+ }
336
+ return result;
470
337
  }
471
338
 
472
339
  /**
@@ -753,7 +620,7 @@ export async function compareWallets(api, params = {}) {
753
620
 
754
621
  export const BANNER = '';
755
622
 
756
- export const HELP = `Nansen CLI v${VERSION} — structured JSON output for AI agents.
623
+ export const HELP = `Nansen CLI v${VERSION} — designed for AI agents.
757
624
 
758
625
  USAGE: nansen <command> [subcommand] [options]
759
626
 
@@ -1002,7 +869,7 @@ export function buildCommands(deps = {}) {
1002
869
  const chains = options.chains || [chain];
1003
870
  const filters = options.filters || {};
1004
871
  const orderBy = parseSort(options.sort, options['order-by']);
1005
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
872
+ const pagination = buildPagination(options);
1006
873
 
1007
874
  // Add smart money label filter if specified
1008
875
  if (options.labels) {
@@ -1016,7 +883,7 @@ export function buildCommands(deps = {}) {
1016
883
  const handlers = {
1017
884
  'netflow': () => apiInstance.smartMoneyNetflow({ chains, filters, orderBy, pagination }),
1018
885
  'dex-trades': () => apiInstance.smartMoneyDexTrades({ chains, filters, orderBy, pagination }),
1019
- 'perp-trades': () => apiInstance.smartMoneyPerpTrades({ filters, orderBy, pagination }),
886
+ 'perp-trades': () => apiInstance.smartMoneyPerpTrades({ filters, orderBy, pagination, onlyNewPositions: options['only-new-positions'] ?? flags['only-new-positions'] }),
1020
887
  'holdings': () => apiInstance.smartMoneyHoldings({ chains, filters, orderBy, pagination }),
1021
888
  'dcas': () => apiInstance.smartMoneyDcas({ filters, orderBy, pagination }),
1022
889
  'historical-holdings': () => apiInstance.smartMoneyHistoricalHoldings({ chains, filters, orderBy, pagination, days }),
@@ -1053,7 +920,7 @@ export function buildCommands(deps = {}) {
1053
920
  }
1054
921
  const filters = options.filters || {};
1055
922
  const orderBy = parseSort(options.sort, options['order-by']);
1056
- const pagination = options.limit ? { page: 1, recordsPerPage: options.limit } : undefined;
923
+ const pagination = buildPagination(options);
1057
924
  const days = options.days ? parseInt(options.days) : 30;
1058
925
 
1059
926
  const handlers = {
@@ -1065,7 +932,7 @@ export function buildCommands(deps = {}) {
1065
932
  },
1066
933
  'pnl': () => {
1067
934
  const date = parseDateOption(options.date, days);
1068
- return apiInstance.addressPnl({ address, chain, date, days, pagination });
935
+ return apiInstance.addressPnl({ address, chain, date, days, filters, orderBy, pagination });
1069
936
  },
1070
937
  'search': () => apiInstance.entitySearch({ query: options.query }),
1071
938
  'historical-balances': () => apiInstance.addressHistoricalBalances({ address, chain, filters, orderBy, pagination, days }),
@@ -1139,7 +1006,7 @@ export function buildCommands(deps = {}) {
1139
1006
  const timeframe = options.timeframe || '24h';
1140
1007
  const filters = options.filters || {};
1141
1008
  const orderBy = parseSort(options.sort, options['order-by']);
1142
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
1009
+ const pagination = buildPagination(options);
1143
1010
  const days = options.days ? parseInt(options.days) : 30;
1144
1011
 
1145
1012
  // Convenience filter for smart money only
@@ -1245,7 +1112,7 @@ export function buildCommands(deps = {}) {
1245
1112
  const subcommand = args[0] || 'help';
1246
1113
  const filters = options.filters || {};
1247
1114
  const orderBy = parseSort(options.sort, options['order-by']);
1248
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
1115
+ const pagination = buildPagination(options);
1249
1116
  const days = options.days ? parseInt(options.days) : 30;
1250
1117
 
1251
1118
  const handlers = {
@@ -1277,7 +1144,7 @@ export function buildCommands(deps = {}) {
1277
1144
  'points': async (args, apiInstance, flags, options) => {
1278
1145
  const subcommand = args[0] || 'help';
1279
1146
  const tier = options.tier;
1280
- const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
1147
+ const pagination = buildPagination(options);
1281
1148
 
1282
1149
  const handlers = {
1283
1150
  'leaderboard': () => apiInstance.pointsLeaderboard({ tier, pagination }),
@@ -1342,7 +1209,7 @@ WALLET:
1342
1209
  Defaults to the default local wallet if omitted.
1343
1210
 
1344
1211
  SYMBOLS:
1345
- Common tokens resolve automatically: SOL, ETH, BNB, USDC, USDT, WETH, WBNB
1212
+ Common tokens resolve automatically: SOL, ETH, USDC, USDT, WETH
1346
1213
  Raw addresses are also accepted.`);
1347
1214
  return;
1348
1215
  }
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Nansen CLI - Command-line interface for Nansen API
4
- * Designed for AI agents with structured JSON output
5
- *
4
+ * Designed for AI agents.
5
+ *
6
6
  * Usage: nansen <command> [options]
7
- *
8
- * All output is JSON for easy parsing by AI agents.
7
+ *
8
+ * Research commands return JSON; operational commands print human-readable text.
9
9
  * Use --pretty for human-readable formatting.
10
10
  *
11
11
  * Core logic lives in cli.js for testability.