nansen-cli 1.9.3 → 1.10.1
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 +42 -0
- package/README.md +41 -281
- package/package.json +8 -2
- package/src/api.js +13 -12
- package/src/cli.js +161 -375
- package/src/schema.json +1516 -0
- package/src/trading.js +16 -25
- package/src/transfer.js +10 -14
- package/src/wallet.js +2 -2
- package/src/walletconnect-exec.js +1 -1
- package/src/x402-svm.js +3 -3
- package/src/x402.js +1 -1
package/src/cli.js
CHANGED
|
@@ -17,159 +17,12 @@ const { version: VERSION } = require('../package.json');
|
|
|
17
17
|
|
|
18
18
|
// ============= Schema Definition =============
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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)' }, limit: { type: 'number' } }, 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 };
|
|
173
26
|
|
|
174
27
|
// ============= Field Filtering =============
|
|
175
28
|
|
|
@@ -220,7 +73,52 @@ export function parseFields(fieldsOption) {
|
|
|
220
73
|
return fieldsOption.split(',').map(f => f.trim()).filter(f => f.length > 0);
|
|
221
74
|
}
|
|
222
75
|
|
|
223
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Produce a compact schema listing commands with params* notation.
|
|
78
|
+
* Use `nansen schema --full` for the verbose version.
|
|
79
|
+
*/
|
|
80
|
+
export function compactSchema(schema) {
|
|
81
|
+
function compactOptions(opts) {
|
|
82
|
+
if (!opts) return '';
|
|
83
|
+
return Object.entries(opts)
|
|
84
|
+
.map(([name, o]) => `${name}${o.required ? '*' : ''}`)
|
|
85
|
+
.join(', ');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function compactCmd(prefix, cmd) {
|
|
89
|
+
const entries = [];
|
|
90
|
+
if (cmd.subcommands) {
|
|
91
|
+
for (const [name, sub] of Object.entries(cmd.subcommands)) {
|
|
92
|
+
const path = prefix ? `${prefix} ${name}` : name;
|
|
93
|
+
if (sub.subcommands) {
|
|
94
|
+
entries.push(...compactCmd(path, sub));
|
|
95
|
+
} else {
|
|
96
|
+
const params = compactOptions(sub.options);
|
|
97
|
+
entries.push({ command: path, description: sub.description, params, returns: sub.returns });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
const params = compactOptions(cmd.options);
|
|
102
|
+
entries.push({ command: prefix, description: cmd.description, params, returns: cmd.returns });
|
|
103
|
+
}
|
|
104
|
+
return entries;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const commands = [];
|
|
108
|
+
for (const [name, cmd] of Object.entries(schema.commands)) {
|
|
109
|
+
commands.push(...compactCmd(name, cmd));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
version: schema.version,
|
|
114
|
+
params_legend: '* = required',
|
|
115
|
+
commands,
|
|
116
|
+
globalOptions: Object.keys(schema.globalOptions).join(', '),
|
|
117
|
+
chains: schema.chains,
|
|
118
|
+
smartMoneyLabels: schema.smartMoneyLabels
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
224
122
|
/**
|
|
225
123
|
* Compare two semver strings. Returns 1 if a > b, -1 if a < b, 0 if equal.
|
|
226
124
|
*/
|
|
@@ -244,7 +142,7 @@ export function parseArgs(args) {
|
|
|
244
142
|
const key = arg.slice(2);
|
|
245
143
|
const next = args[i + 1];
|
|
246
144
|
|
|
247
|
-
if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich') {
|
|
145
|
+
if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich' || key === 'full') {
|
|
248
146
|
result.flags[key] = true;
|
|
249
147
|
} else if (next && !next.startsWith('-')) {
|
|
250
148
|
// Try to parse as JSON first (for objects/arrays/booleans),
|
|
@@ -635,7 +533,7 @@ export async function traceCounterparties(api, params = {}) {
|
|
|
635
533
|
queue.push({ addr: cpAddr, hop: hop + 1 });
|
|
636
534
|
}
|
|
637
535
|
}
|
|
638
|
-
} catch
|
|
536
|
+
} catch {
|
|
639
537
|
// Skip addresses that fail (404, etc) but continue the traversal
|
|
640
538
|
}
|
|
641
539
|
|
|
@@ -706,91 +604,41 @@ export async function compareWallets(api, params = {}) {
|
|
|
706
604
|
};
|
|
707
605
|
}
|
|
708
606
|
|
|
709
|
-
|
|
710
|
-
export const BANNER = `
|
|
711
|
-
███╗ ██╗ █████╗ ███╗ ██╗███████╗███████╗███╗ ██╗
|
|
712
|
-
████╗ ██║██╔══██╗████╗ ██║██╔════╝██╔════╝████╗ ██║
|
|
713
|
-
██╔██╗ ██║███████║██╔██╗ ██║███████╗█████╗ ██╔██╗ ██║
|
|
714
|
-
██║╚██╗██║██╔══██║██║╚██╗██║╚════██║██╔══╝ ██║╚██╗██║
|
|
715
|
-
██║ ╚████║██║ ██║██║ ╚████║███████║███████╗██║ ╚████║
|
|
716
|
-
╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝╚═╝ ╚═══╝
|
|
717
|
-
Surface The Signal
|
|
718
|
-
`;
|
|
607
|
+
export const BANNER = '';
|
|
719
608
|
|
|
720
|
-
|
|
721
|
-
export const HELP = `
|
|
722
|
-
Nansen CLI - Command-line interface for Nansen API
|
|
723
|
-
Designed for AI agents with structured JSON output.
|
|
609
|
+
export const HELP = `Nansen CLI v${VERSION} — structured JSON output for AI agents.
|
|
724
610
|
|
|
725
|
-
USAGE:
|
|
726
|
-
nansen <command> [subcommand] [options]
|
|
611
|
+
USAGE: nansen <command> [subcommand] [options]
|
|
727
612
|
|
|
728
613
|
COMMANDS:
|
|
729
|
-
research
|
|
730
|
-
trade
|
|
731
|
-
wallet
|
|
732
|
-
login
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
--pretty
|
|
740
|
-
|
|
741
|
-
--fields Comma-separated list of fields to include (e.g., --fields address,value_usd)
|
|
742
|
-
--chain Blockchain to query (ethereum, solana, base, etc.)
|
|
743
|
-
--chains Multiple chains as JSON array
|
|
744
|
-
--limit Number of results (shorthand for pagination)
|
|
745
|
-
--filters JSON object with filters
|
|
746
|
-
--sort Sort by field (e.g., --sort value_usd:desc)
|
|
747
|
-
--order-by JSON array with sort order (advanced)
|
|
748
|
-
--days Date range in days (default: 30 for most endpoints)
|
|
749
|
-
--symbol Token symbol (for perp endpoints)
|
|
750
|
-
--no-retry Disable automatic retry on rate limits/errors
|
|
751
|
-
--retries <n> Max retry attempts (default: 3)
|
|
752
|
-
--x402-payment-signature <sig> Pre-signed x402 payment signature header
|
|
753
|
-
--cache Enable response caching (default: off)
|
|
754
|
-
--no-cache Disable cache for this request
|
|
755
|
-
--cache-ttl <s> Cache TTL in seconds (default: 300)
|
|
756
|
-
--stream Output as JSON lines (NDJSON) for incremental processing
|
|
757
|
-
--format csv Output as CSV with header row
|
|
614
|
+
research smart-money, profiler, token, search, perp, portfolio, points
|
|
615
|
+
trade quote, execute
|
|
616
|
+
wallet create, list, show, export, default, delete
|
|
617
|
+
login Save API key (--api-key <key> or interactive)
|
|
618
|
+
logout Remove saved API key
|
|
619
|
+
schema JSON schema for all commands (use "nansen schema <cmd>" for one)
|
|
620
|
+
cache clear
|
|
621
|
+
changelog --since <version> to filter
|
|
622
|
+
|
|
623
|
+
OPTIONS: --chain --limit --sort field:dir --fields a,b --days N --filters '{}'
|
|
624
|
+
FORMAT: --pretty --table --format csv --stream (NDJSON)
|
|
625
|
+
RETRY: --no-retry --retries N --cache --cache-ttl N
|
|
758
626
|
|
|
759
627
|
EXAMPLES:
|
|
760
|
-
# Get Smart Money netflow on Solana
|
|
761
628
|
nansen research smart-money netflow --chain solana
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
nansen research token screener --chain solana --timeframe 24h --pretty
|
|
765
|
-
|
|
766
|
-
# Get wallet balance
|
|
767
|
-
nansen research profiler balance --address 0x123... --chain ethereum
|
|
768
|
-
|
|
769
|
-
# Search for tokens/entities
|
|
770
|
-
nansen research search "Vitalik"
|
|
771
|
-
|
|
772
|
-
# Get a DEX swap quote
|
|
629
|
+
nansen research token screener --chain solana --timeframe 24h
|
|
630
|
+
nansen research profiler balance --address 0x... --chain ethereum
|
|
773
631
|
nansen trade quote --chain ethereum --from ETH --to USDC --amount 1
|
|
774
632
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
180D Smart Trader, Smart HL Perps Trader
|
|
778
|
-
|
|
779
|
-
SUPPORTED CHAINS:
|
|
780
|
-
ethereum, solana, base, bnb, arbitrum, polygon, optimism,
|
|
781
|
-
avalanche, linea, scroll, mantle, ronin, sei,
|
|
782
|
-
plasma, sonic, monad, hyperevm, iotaevm
|
|
633
|
+
Chains: ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, mantle, ronin, sei, plasma, sonic, monad, hyperevm, iotaevm
|
|
634
|
+
Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
783
635
|
|
|
784
|
-
|
|
636
|
+
Docs: https://docs.nansen.ai
|
|
637
|
+
Skills: npx skills add nansen-ai/nansen-cli (agent-optimised docs per command group)
|
|
785
638
|
`;
|
|
786
639
|
|
|
787
640
|
// Helper to prompt for input (exported for mocking)
|
|
788
641
|
export async function prompt(question, hidden = false) {
|
|
789
|
-
const rl = readline.createInterface({
|
|
790
|
-
input: process.stdin,
|
|
791
|
-
output: process.stdout
|
|
792
|
-
});
|
|
793
|
-
|
|
794
642
|
return new Promise((resolve) => {
|
|
795
643
|
if (hidden && process.stdout.isTTY) {
|
|
796
644
|
process.stdout.write(question);
|
|
@@ -805,7 +653,6 @@ export async function prompt(question, hidden = false) {
|
|
|
805
653
|
process.stdin.pause();
|
|
806
654
|
process.stdin.removeListener('data', onData);
|
|
807
655
|
process.stdout.write('\n');
|
|
808
|
-
rl.close();
|
|
809
656
|
resolve(input);
|
|
810
657
|
} else if (char === '\u0003') {
|
|
811
658
|
// Ctrl+C
|
|
@@ -824,6 +671,10 @@ export async function prompt(question, hidden = false) {
|
|
|
824
671
|
|
|
825
672
|
process.stdin.on('data', onData);
|
|
826
673
|
} else {
|
|
674
|
+
const rl = readline.createInterface({
|
|
675
|
+
input: process.stdin,
|
|
676
|
+
output: process.stdout
|
|
677
|
+
});
|
|
827
678
|
rl.question(question, (answer) => {
|
|
828
679
|
rl.close();
|
|
829
680
|
resolve(answer);
|
|
@@ -836,11 +687,11 @@ export async function prompt(question, hidden = false) {
|
|
|
836
687
|
export function buildCommands(deps = {}) {
|
|
837
688
|
// Allow dependency injection for testing
|
|
838
689
|
const {
|
|
839
|
-
api = null,
|
|
690
|
+
api: _api = null,
|
|
840
691
|
promptFn = prompt,
|
|
841
692
|
log = console.log,
|
|
842
|
-
errorOutput = console.error,
|
|
843
|
-
NansenAPIClass = NansenAPI,
|
|
693
|
+
errorOutput: _errorOutput = console.error,
|
|
694
|
+
NansenAPIClass: _NansenAPIClass = NansenAPI,
|
|
844
695
|
saveConfigFn = saveConfig,
|
|
845
696
|
deleteConfigFn = deleteConfig,
|
|
846
697
|
getConfigFileFn = getConfigFile,
|
|
@@ -850,6 +701,18 @@ export function buildCommands(deps = {}) {
|
|
|
850
701
|
|
|
851
702
|
const cmds = {
|
|
852
703
|
'login': async (args, apiInstance, flags, options) => {
|
|
704
|
+
if (flags.help || flags.h) {
|
|
705
|
+
log('nansen login - Save your Nansen API key\n');
|
|
706
|
+
log('USAGE:');
|
|
707
|
+
log(' nansen login (interactive)');
|
|
708
|
+
log(' nansen login --api-key <key> (non-interactive)\n');
|
|
709
|
+
log('OPTIONS:');
|
|
710
|
+
log(' --api-key <key> Your Nansen API key');
|
|
711
|
+
log(' --help Show this help\n');
|
|
712
|
+
log('Get your API key at: https://app.nansen.ai/api');
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
853
716
|
// Support non-interactive: nansen login --api-key <key>
|
|
854
717
|
let apiKey = options['api-key'] || options.apiKey;
|
|
855
718
|
|
|
@@ -885,10 +748,10 @@ export function buildCommands(deps = {}) {
|
|
|
885
748
|
|
|
886
749
|
log(`✓ Saved to ${getConfigFileFn()}\n`);
|
|
887
750
|
log('You can now use the Nansen CLI. Try:');
|
|
888
|
-
log(' nansen token screener --chain solana --pretty');
|
|
751
|
+
log(' nansen research token screener --chain solana --pretty');
|
|
889
752
|
},
|
|
890
753
|
|
|
891
|
-
'logout': async (
|
|
754
|
+
'logout': async (_args, _apiInstance, _flags, _options) => {
|
|
892
755
|
const deleted = deleteConfigFn();
|
|
893
756
|
if (deleted) {
|
|
894
757
|
log(`✓ Removed ${getConfigFileFn()}`);
|
|
@@ -897,11 +760,11 @@ export function buildCommands(deps = {}) {
|
|
|
897
760
|
}
|
|
898
761
|
},
|
|
899
762
|
|
|
900
|
-
'help': async (
|
|
763
|
+
'help': async (_args, _apiInstance, _flags, _options) => {
|
|
901
764
|
log(HELP);
|
|
902
765
|
},
|
|
903
766
|
|
|
904
|
-
'changelog': async (
|
|
767
|
+
'changelog': async (_args, _apiInstance, _flags, _options) => {
|
|
905
768
|
const changelogPath = new URL('../CHANGELOG.md', import.meta.url).pathname;
|
|
906
769
|
let content;
|
|
907
770
|
try {
|
|
@@ -910,7 +773,7 @@ export function buildCommands(deps = {}) {
|
|
|
910
773
|
log('CHANGELOG.md not found. Visit https://github.com/nansen-ai/nansen-cli/blob/main/CHANGELOG.md');
|
|
911
774
|
return;
|
|
912
775
|
}
|
|
913
|
-
const since =
|
|
776
|
+
const since = _options.since;
|
|
914
777
|
if (since) {
|
|
915
778
|
// Show only entries from the given version onwards
|
|
916
779
|
const lines = content.split('\n');
|
|
@@ -936,8 +799,7 @@ export function buildCommands(deps = {}) {
|
|
|
936
799
|
}
|
|
937
800
|
},
|
|
938
801
|
|
|
939
|
-
'schema': async (args,
|
|
940
|
-
// Return schema for agent introspection
|
|
802
|
+
'schema': async (args, _apiInstance, flags, _options) => {
|
|
941
803
|
const subcommand = args[0];
|
|
942
804
|
const schemaEntry = subcommand && (SCHEMA.commands[subcommand] || SCHEMA.commands.research.subcommands[subcommand]);
|
|
943
805
|
|
|
@@ -951,11 +813,14 @@ export function buildCommands(deps = {}) {
|
|
|
951
813
|
};
|
|
952
814
|
}
|
|
953
815
|
|
|
954
|
-
|
|
955
|
-
|
|
816
|
+
if (flags.full) {
|
|
817
|
+
return SCHEMA;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
return compactSchema(SCHEMA);
|
|
956
821
|
},
|
|
957
822
|
|
|
958
|
-
'cache': async (args,
|
|
823
|
+
'cache': async (args, _apiInstance, _flags, _options) => {
|
|
959
824
|
const subcommand = args[0] || 'help';
|
|
960
825
|
|
|
961
826
|
const handlers = {
|
|
@@ -1004,7 +869,7 @@ export function buildCommands(deps = {}) {
|
|
|
1004
869
|
const handlers = {
|
|
1005
870
|
'netflow': () => apiInstance.smartMoneyNetflow({ chains, filters, orderBy, pagination }),
|
|
1006
871
|
'dex-trades': () => apiInstance.smartMoneyDexTrades({ chains, filters, orderBy, pagination }),
|
|
1007
|
-
'perp-trades': () => apiInstance.smartMoneyPerpTrades({ filters, orderBy, pagination }),
|
|
872
|
+
'perp-trades': () => apiInstance.smartMoneyPerpTrades({ filters, orderBy, pagination, onlyNewPositions: options['only-new-positions'] ?? flags['only-new-positions'] }),
|
|
1008
873
|
'holdings': () => apiInstance.smartMoneyHoldings({ chains, filters, orderBy, pagination }),
|
|
1009
874
|
'dcas': () => apiInstance.smartMoneyDcas({ filters, orderBy, pagination }),
|
|
1010
875
|
'historical-holdings': () => apiInstance.smartMoneyHistoricalHoldings({ chains, filters, orderBy, pagination, days }),
|
|
@@ -1041,7 +906,7 @@ export function buildCommands(deps = {}) {
|
|
|
1041
906
|
}
|
|
1042
907
|
const filters = options.filters || {};
|
|
1043
908
|
const orderBy = parseSort(options.sort, options['order-by']);
|
|
1044
|
-
const pagination = options.limit ? { page: 1,
|
|
909
|
+
const pagination = options.limit ? { page: 1, per_page: options.limit } : undefined;
|
|
1045
910
|
const days = options.days ? parseInt(options.days) : 30;
|
|
1046
911
|
|
|
1047
912
|
const handlers = {
|
|
@@ -1139,7 +1004,7 @@ export function buildCommands(deps = {}) {
|
|
|
1139
1004
|
|
|
1140
1005
|
const handlers = {
|
|
1141
1006
|
'indicators': () => apiInstance.tokenIndicators({ tokenAddress, chain }),
|
|
1142
|
-
'ohlcv': () => apiInstance.tokenOhlcv({ tokenAddress, chain, timeframe: options.timeframe || '1d'
|
|
1007
|
+
'ohlcv': () => apiInstance.tokenOhlcv({ tokenAddress, chain, timeframe: options.timeframe || '1d' }),
|
|
1143
1008
|
'info': () => apiInstance.tokenInformation({ tokenAddress, chain, timeframe: options.timeframe }),
|
|
1144
1009
|
'screener': async () => {
|
|
1145
1010
|
const search = options.search;
|
|
@@ -1299,7 +1164,7 @@ export function buildCommands(deps = {}) {
|
|
|
1299
1164
|
}
|
|
1300
1165
|
const category = RESEARCH_CATEGORY_ALIASES[rawCategory] || rawCategory;
|
|
1301
1166
|
if (!RESEARCH_CATEGORIES.has(category)) {
|
|
1302
|
-
|
|
1167
|
+
throw new NansenError(`Unknown research category: ${rawCategory}. Available: ${[...RESEARCH_CATEGORIES].join(', ')}`, ErrorCode.UNKNOWN);
|
|
1303
1168
|
}
|
|
1304
1169
|
return cmds[category](args.slice(1), apiInstance, flags, options);
|
|
1305
1170
|
};
|
|
@@ -1330,15 +1195,12 @@ WALLET:
|
|
|
1330
1195
|
Defaults to the default local wallet if omitted.
|
|
1331
1196
|
|
|
1332
1197
|
SYMBOLS:
|
|
1333
|
-
Common tokens resolve automatically: SOL, ETH,
|
|
1198
|
+
Common tokens resolve automatically: SOL, ETH, USDC, USDT, WETH
|
|
1334
1199
|
Raw addresses are also accepted.`);
|
|
1335
1200
|
return;
|
|
1336
1201
|
}
|
|
1337
1202
|
if (!tradingCmds[sub]) {
|
|
1338
|
-
|
|
1339
|
-
log(`Available: quote, execute`);
|
|
1340
|
-
log(`Run 'nansen trade help' for usage.`);
|
|
1341
|
-
return;
|
|
1203
|
+
throw new NansenError(`Unknown trade subcommand: ${sub}. Available: quote, execute`, ErrorCode.UNKNOWN);
|
|
1342
1204
|
}
|
|
1343
1205
|
return tradingCmds[sub](args.slice(1), apiInstance, flags, options);
|
|
1344
1206
|
};
|
|
@@ -1351,9 +1213,6 @@ export const DEPRECATED_TO_RESEARCH = new Set(['smart-money', 'profiler', 'token
|
|
|
1351
1213
|
// Subcommands that moved under 'trade'
|
|
1352
1214
|
export const DEPRECATED_TO_TRADE = new Set(['quote', 'execute']);
|
|
1353
1215
|
|
|
1354
|
-
// Commands that don't require API authentication
|
|
1355
|
-
export const NO_AUTH_COMMANDS = ['login', 'logout', 'help', 'schema', 'cache', 'wallet', 'trade', 'quote', 'execute', 'changelog'];
|
|
1356
|
-
|
|
1357
1216
|
// Command aliases: top-level shortcuts that resolve before routing
|
|
1358
1217
|
export const COMMAND_ALIASES = {
|
|
1359
1218
|
'tgm': 'token', // Token God Mode
|
|
@@ -1372,7 +1231,6 @@ export const RESEARCH_CATEGORY_ALIASES = {
|
|
|
1372
1231
|
|
|
1373
1232
|
// Generate help text for a specific subcommand using SCHEMA
|
|
1374
1233
|
export function generateSubcommandHelp(command, subcommand) {
|
|
1375
|
-
// Look up in top-level commands, then fall back to research subcommands
|
|
1376
1234
|
const cmdSchema = SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command];
|
|
1377
1235
|
if (!cmdSchema) return null;
|
|
1378
1236
|
|
|
@@ -1380,100 +1238,36 @@ export function generateSubcommandHelp(command, subcommand) {
|
|
|
1380
1238
|
if (!subSchema) return null;
|
|
1381
1239
|
|
|
1382
1240
|
const lines = [];
|
|
1383
|
-
lines.push(
|
|
1384
|
-
|
|
1385
|
-
// Usage
|
|
1386
|
-
const requiredOpts = [];
|
|
1387
|
-
const optionalOpts = [];
|
|
1388
|
-
|
|
1241
|
+
lines.push(`${command} ${subcommand} — ${subSchema.description || 'No description'}`);
|
|
1242
|
+
|
|
1389
1243
|
if (subSchema.options) {
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
let usage = `USAGE:\n nansen ${command} ${subcommand}`;
|
|
1400
|
-
if (requiredOpts.length) {
|
|
1401
|
-
usage += ' ' + requiredOpts.map(o => `--${o} <value>`).join(' ');
|
|
1402
|
-
}
|
|
1403
|
-
if (optionalOpts.length) {
|
|
1404
|
-
usage += ' [options]';
|
|
1405
|
-
}
|
|
1406
|
-
lines.push(usage);
|
|
1407
|
-
|
|
1408
|
-
// Required options
|
|
1409
|
-
if (requiredOpts.length) {
|
|
1410
|
-
lines.push('\nREQUIRED:');
|
|
1411
|
-
for (const name of requiredOpts) {
|
|
1412
|
-
const opt = subSchema.options[name];
|
|
1413
|
-
const desc = opt.description || `${opt.type}`;
|
|
1414
|
-
lines.push(` --${name.padEnd(16)} ${desc}`);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
// Optional options
|
|
1419
|
-
if (optionalOpts.length) {
|
|
1420
|
-
lines.push('\nOPTIONS:');
|
|
1421
|
-
for (const name of optionalOpts) {
|
|
1422
|
-
const opt = subSchema.options[name];
|
|
1423
|
-
const defaultStr = opt.default !== undefined ? ` (default: ${opt.default})` : '';
|
|
1424
|
-
const desc = (opt.description || opt.type) + defaultStr;
|
|
1425
|
-
lines.push(` --${name.padEnd(16)} ${desc}`);
|
|
1426
|
-
}
|
|
1244
|
+
const params = Object.entries(subSchema.options).map(([name, opt]) => {
|
|
1245
|
+
const parts = [`--${name}`];
|
|
1246
|
+
if (opt.required) parts[0] += '*';
|
|
1247
|
+
if (opt.default !== undefined) parts.push(`(${opt.default})`);
|
|
1248
|
+
if (opt.enum) parts.push(`[${opt.enum.join('|')}]`);
|
|
1249
|
+
return parts.join(' ');
|
|
1250
|
+
});
|
|
1251
|
+
lines.push(`Params (* required): ${params.join(', ')}`);
|
|
1427
1252
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
lines.push('\nRETURNS:');
|
|
1432
|
-
lines.push(` ${subSchema.returns.join(', ')}`);
|
|
1253
|
+
|
|
1254
|
+
if (subSchema.returns?.length) {
|
|
1255
|
+
lines.push(`Returns: ${subSchema.returns.join(', ')}`);
|
|
1433
1256
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
lines.push('\nEXAMPLES:');
|
|
1257
|
+
|
|
1258
|
+
const exampleValues = { address: '0x...', token: '0x...', query: '"term"', symbol: 'BTC', date: '2024-01-01' };
|
|
1437
1259
|
const chain = subSchema.options?.chain?.default || 'solana';
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
query: '"search term"',
|
|
1444
|
-
symbol: 'BTC',
|
|
1445
|
-
date: '2024-01-01'
|
|
1446
|
-
};
|
|
1447
|
-
|
|
1448
|
-
// Build example based on required options
|
|
1449
|
-
let example = ` nansen ${command} ${subcommand}`;
|
|
1450
|
-
for (const name of requiredOpts) {
|
|
1451
|
-
const value = exampleValues[name] || '<value>';
|
|
1452
|
-
example += ` --${name} ${value}`;
|
|
1260
|
+
let example = `nansen ${command} ${subcommand}`;
|
|
1261
|
+
if (subSchema.options) {
|
|
1262
|
+
for (const [name, opt] of Object.entries(subSchema.options)) {
|
|
1263
|
+
if (opt.required) example += ` --${name} ${exampleValues[name] || '<val>'}`;
|
|
1264
|
+
}
|
|
1453
1265
|
}
|
|
1454
|
-
if (subSchema.options?.chain && !
|
|
1266
|
+
if (subSchema.options?.chain && !subSchema.options.chain.required) {
|
|
1455
1267
|
example += ` --chain ${chain}`;
|
|
1456
1268
|
}
|
|
1457
|
-
example
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
// Add a filtered example if filters are supported
|
|
1461
|
-
if (subSchema.options?.filters || subSchema.options?.labels) {
|
|
1462
|
-
let filterExample = ` nansen ${command} ${subcommand}`;
|
|
1463
|
-
for (const name of requiredOpts) {
|
|
1464
|
-
const value = exampleValues[name] || '<value>';
|
|
1465
|
-
filterExample += ` --${name} ${value}`;
|
|
1466
|
-
}
|
|
1467
|
-
if (subSchema.options?.chain && !requiredOpts.includes('chain')) {
|
|
1468
|
-
filterExample += ` --chain ${chain}`;
|
|
1469
|
-
}
|
|
1470
|
-
if (subSchema.options?.labels) {
|
|
1471
|
-
filterExample += ' --labels "Smart Trader"';
|
|
1472
|
-
}
|
|
1473
|
-
filterExample += ' --limit 10 --table';
|
|
1474
|
-
lines.push(filterExample);
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1269
|
+
lines.push(`Example: ${example}`);
|
|
1270
|
+
|
|
1477
1271
|
return lines.join('\n');
|
|
1478
1272
|
}
|
|
1479
1273
|
|
|
@@ -1542,13 +1336,10 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1542
1336
|
const researchCat = SCHEMA.commands.research.subcommands[category];
|
|
1543
1337
|
if (researchCat) {
|
|
1544
1338
|
const catSchema = researchCat;
|
|
1545
|
-
const lines = [
|
|
1339
|
+
const lines = [`research ${category} — ${catSchema.description}`];
|
|
1546
1340
|
if (catSchema.subcommands) {
|
|
1547
|
-
lines.push('
|
|
1548
|
-
|
|
1549
|
-
lines.push(` ${sub.padEnd(20)} ${subSchema.description || ''}`);
|
|
1550
|
-
}
|
|
1551
|
-
lines.push(`\nFor detailed help: nansen research ${category} <subcommand> --help`);
|
|
1341
|
+
lines.push('Subcommands: ' + Object.keys(catSchema.subcommands).join(', '));
|
|
1342
|
+
lines.push(`Use: nansen research ${category} <subcommand> --help`);
|
|
1552
1343
|
}
|
|
1553
1344
|
output(lines.join('\n'));
|
|
1554
1345
|
notify();
|
|
@@ -1577,13 +1368,10 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1577
1368
|
const cmdSchemaLookup = SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command];
|
|
1578
1369
|
if (command && cmdSchemaLookup) {
|
|
1579
1370
|
const cmdSchema = cmdSchemaLookup;
|
|
1580
|
-
const lines = [
|
|
1371
|
+
const lines = [`${command} — ${cmdSchema.description}`];
|
|
1581
1372
|
if (cmdSchema.subcommands) {
|
|
1582
|
-
lines.push('
|
|
1583
|
-
|
|
1584
|
-
lines.push(` ${sub.padEnd(20)} ${subSchema.description || ''}`);
|
|
1585
|
-
}
|
|
1586
|
-
lines.push(`\nFor detailed help: nansen ${command} <subcommand> --help`);
|
|
1373
|
+
lines.push('Subcommands: ' + Object.keys(cmdSchema.subcommands).join(', '));
|
|
1374
|
+
lines.push(`Use: nansen ${command} <subcommand> --help`);
|
|
1587
1375
|
}
|
|
1588
1376
|
output(lines.join('\n'));
|
|
1589
1377
|
notify();
|
|
@@ -1610,48 +1398,46 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1610
1398
|
return { type: 'error', data: errorData };
|
|
1611
1399
|
}
|
|
1612
1400
|
|
|
1613
|
-
// Commands that don't require API authentication
|
|
1614
|
-
if (NO_AUTH_COMMANDS.includes(command)) {
|
|
1615
|
-
const result = await commands[command](subArgs, null, flags, options);
|
|
1616
|
-
|
|
1617
|
-
// Schema command returns data that should be output
|
|
1618
|
-
if (command === 'schema' && result) {
|
|
1619
|
-
const formatted = formatOutput(result, { pretty, table: false });
|
|
1620
|
-
output(formatted.text);
|
|
1621
|
-
notify();
|
|
1622
|
-
return { type: 'schema', data: result };
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
notify();
|
|
1626
|
-
return { type: 'no-auth', command };
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
1401
|
try {
|
|
1630
1402
|
// Configure retry options
|
|
1631
|
-
const retryOptions = flags['no-retry']
|
|
1632
|
-
? { maxRetries: 0 }
|
|
1403
|
+
const retryOptions = flags['no-retry']
|
|
1404
|
+
? { maxRetries: 0 }
|
|
1633
1405
|
: { maxRetries: options.retries !== undefined ? (Number.isNaN(parseInt(options.retries, 10)) ? 3 : parseInt(options.retries, 10)) : 3 };
|
|
1634
|
-
|
|
1406
|
+
|
|
1635
1407
|
// Configure cache options
|
|
1636
1408
|
const cacheTtl = options['cache-ttl'] !== undefined ? parseInt(options['cache-ttl'], 10) : 300;
|
|
1637
1409
|
const cacheOptions = {
|
|
1638
1410
|
enabled: flags['cache'] && !flags['no-cache'],
|
|
1639
1411
|
ttl: Number.isNaN(cacheTtl) ? 300 : cacheTtl
|
|
1640
1412
|
};
|
|
1641
|
-
|
|
1413
|
+
|
|
1642
1414
|
const defaultHeaders = {};
|
|
1643
1415
|
if (options['x402-payment-signature']) {
|
|
1644
1416
|
defaultHeaders['Payment-Signature'] = options['x402-payment-signature'];
|
|
1645
1417
|
}
|
|
1646
1418
|
const api = new NansenAPIClass(undefined, undefined, { retry: retryOptions, cache: cacheOptions, defaultHeaders });
|
|
1647
1419
|
let result = await commands[command](subArgs, api, flags, options);
|
|
1648
|
-
|
|
1420
|
+
|
|
1421
|
+
// Commands that handle their own output return undefined
|
|
1422
|
+
if (result === undefined) {
|
|
1423
|
+
notify();
|
|
1424
|
+
return { type: 'no-output', command };
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Schema returns data directly (not wrapped in { success, data })
|
|
1428
|
+
if (command === 'schema') {
|
|
1429
|
+
const formatted = formatOutput(result, { pretty, table: false });
|
|
1430
|
+
output(formatted.text);
|
|
1431
|
+
notify();
|
|
1432
|
+
return { type: 'schema', data: result };
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1649
1435
|
// Apply field filtering if --fields is specified
|
|
1650
1436
|
const fields = parseFields(options.fields);
|
|
1651
1437
|
if (fields) {
|
|
1652
1438
|
result = filterFields(result, fields);
|
|
1653
1439
|
}
|
|
1654
|
-
|
|
1440
|
+
|
|
1655
1441
|
// Output in requested format
|
|
1656
1442
|
if (stream) {
|
|
1657
1443
|
// Stream mode: output each record as a JSON line (NDJSON)
|