nansen-cli 1.5.1 → 1.6.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/README.md +17 -1
- package/package.json +1 -1
- package/src/api.js +62 -29
- package/src/cli.js +42 -8
package/README.md
CHANGED
|
@@ -123,6 +123,21 @@ Deep analytics for any token.
|
|
|
123
123
|
|------------|-------------|
|
|
124
124
|
| `defi` | DeFi holdings across protocols |
|
|
125
125
|
|
|
126
|
+
### `search` - Search
|
|
127
|
+
|
|
128
|
+
Search for tokens and entities across Nansen.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
nansen search "uniswap" --pretty
|
|
132
|
+
nansen search "uniswap" --type token --chain ethereum
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Option | Description |
|
|
136
|
+
|--------|-------------|
|
|
137
|
+
| `--type` | Filter by result type: `token`, `entity`, or `any` (default) |
|
|
138
|
+
| `--chain` | Filter by chain |
|
|
139
|
+
| `--limit` | Max results, 1-50 (default: 25) |
|
|
140
|
+
|
|
126
141
|
### `schema` - Schema Discovery
|
|
127
142
|
|
|
128
143
|
Output JSON schema for agent introspection. No API key required.
|
|
@@ -258,7 +273,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines.
|
|
|
258
273
|
| Profiler | 11 | 100% |
|
|
259
274
|
| Token God Mode | 12 | 100% |
|
|
260
275
|
| Portfolio | 1 | 100% |
|
|
261
|
-
|
|
|
276
|
+
| Search | 1 | 100% |
|
|
277
|
+
| **Total** | **31** | **100%** |
|
|
262
278
|
|
|
263
279
|
## License
|
|
264
280
|
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -9,6 +9,10 @@ import { fileURLToPath } from 'url';
|
|
|
9
9
|
|
|
10
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
|
|
12
|
+
const { version: packageVersion } = JSON.parse(
|
|
13
|
+
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
|
|
14
|
+
);
|
|
15
|
+
|
|
12
16
|
// ============= Error Codes =============
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -296,36 +300,35 @@ export function validateTokenAddress(tokenAddress, chain = 'solana') {
|
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
function loadConfig() {
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
apiKey: process.env.NANSEN_API_KEY,
|
|
305
|
-
baseUrl: process.env.NANSEN_BASE_URL || 'https://api.nansen.ai'
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Check ~/.nansen/config.json (from `nansen login`)
|
|
303
|
+
// Base config from files, then env vars override individual fields
|
|
304
|
+
let config = null;
|
|
305
|
+
|
|
306
|
+
// ~/.nansen/config.json (from `nansen login`)
|
|
310
307
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
311
|
-
try {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
308
|
+
try { config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch (e) {}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Local config.json (for development)
|
|
312
|
+
if (!config) {
|
|
313
|
+
const localConfig = path.join(__dirname, '..', 'config.json');
|
|
314
|
+
if (fs.existsSync(localConfig)) {
|
|
315
|
+
config = JSON.parse(fs.readFileSync(localConfig, 'utf8'));
|
|
315
316
|
}
|
|
316
317
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (fs.existsSync(localConfig)) {
|
|
321
|
-
return JSON.parse(fs.readFileSync(localConfig, 'utf8'));
|
|
318
|
+
|
|
319
|
+
if (!config) {
|
|
320
|
+
config = { apiKey: null, baseUrl: 'https://api.nansen.ai' };
|
|
322
321
|
}
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
apiKey
|
|
327
|
-
|
|
328
|
-
|
|
322
|
+
|
|
323
|
+
// Env vars override individual fields
|
|
324
|
+
if (process.env.NANSEN_API_KEY) {
|
|
325
|
+
config.apiKey = process.env.NANSEN_API_KEY;
|
|
326
|
+
}
|
|
327
|
+
if (process.env.NANSEN_BASE_URL) {
|
|
328
|
+
config.baseUrl = process.env.NANSEN_BASE_URL;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return config;
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
const config = loadConfig();
|
|
@@ -388,10 +391,11 @@ export class NansenAPI {
|
|
|
388
391
|
this.apiKey = apiKey || null;
|
|
389
392
|
this.baseUrl = baseUrl;
|
|
390
393
|
this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...options.retry };
|
|
391
|
-
this.cacheOptions = {
|
|
394
|
+
this.cacheOptions = {
|
|
392
395
|
enabled: options.cache?.enabled ?? false,
|
|
393
396
|
ttl: options.cache?.ttl ?? DEFAULT_CACHE_TTL
|
|
394
397
|
};
|
|
398
|
+
this.defaultHeaders = options.defaultHeaders || {};
|
|
395
399
|
}
|
|
396
400
|
|
|
397
401
|
static cleanBody(body) {
|
|
@@ -428,7 +432,10 @@ export class NansenAPI {
|
|
|
428
432
|
method: 'POST',
|
|
429
433
|
headers: {
|
|
430
434
|
'Content-Type': 'application/json',
|
|
435
|
+
'X-Client-Type': 'nansen-cli',
|
|
436
|
+
'X-Client-Version': packageVersion,
|
|
431
437
|
...(this.apiKey ? { 'apikey': this.apiKey } : {}),
|
|
438
|
+
...this.defaultHeaders,
|
|
432
439
|
...options.headers
|
|
433
440
|
},
|
|
434
441
|
body: JSON.stringify(NansenAPI.cleanBody(body))
|
|
@@ -482,7 +489,7 @@ export class NansenAPI {
|
|
|
482
489
|
} else if (code === ErrorCode.CREDITS_EXHAUSTED) {
|
|
483
490
|
message = message.replace(/\.+$/, '') + '. No retry will help. Check your Nansen dashboard for credit balance.';
|
|
484
491
|
} else if (code === ErrorCode.PAYMENT_REQUIRED) {
|
|
485
|
-
message = 'Payment required (x402).
|
|
492
|
+
message = 'Payment required (x402). Sign the paymentRequirements below per https://docs.x402.org and pass the result with --x402-payment-signature <value>.';
|
|
486
493
|
const paymentHeader = response.headers.get('payment-required');
|
|
487
494
|
if (paymentHeader) {
|
|
488
495
|
try {
|
|
@@ -668,6 +675,20 @@ export class NansenAPI {
|
|
|
668
675
|
});
|
|
669
676
|
}
|
|
670
677
|
|
|
678
|
+
async generalSearch(params = {}) {
|
|
679
|
+
const { query, resultType = 'any', chain, limit = 25 } = params;
|
|
680
|
+
if (!query) {
|
|
681
|
+
throw new NansenError('Search query is required', ErrorCode.MISSING_PARAM);
|
|
682
|
+
}
|
|
683
|
+
const body = {
|
|
684
|
+
search_query: query,
|
|
685
|
+
result_type: resultType,
|
|
686
|
+
limit
|
|
687
|
+
};
|
|
688
|
+
if (chain) body.chain = chain;
|
|
689
|
+
return this.request('/api/v1/search/general', body);
|
|
690
|
+
}
|
|
691
|
+
|
|
671
692
|
async addressHistoricalBalances(params = {}) {
|
|
672
693
|
const { address, chain = 'ethereum', filters = {}, orderBy, pagination, days = 30 } = params;
|
|
673
694
|
if (address) {
|
|
@@ -954,8 +975,20 @@ export class NansenAPI {
|
|
|
954
975
|
});
|
|
955
976
|
}
|
|
956
977
|
|
|
978
|
+
async tokenIndicators(params = {}) {
|
|
979
|
+
const { tokenAddress, chain = 'ethereum' } = params;
|
|
980
|
+
if (tokenAddress) {
|
|
981
|
+
const validation = validateTokenAddress(tokenAddress, chain);
|
|
982
|
+
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
983
|
+
}
|
|
984
|
+
return this.request('/api/v1/tgm/indicators', {
|
|
985
|
+
token_address: tokenAddress,
|
|
986
|
+
chain
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
|
|
957
990
|
async tokenInformation(params = {}) {
|
|
958
|
-
const { tokenAddress, chain = 'solana', timeframe = '
|
|
991
|
+
const { tokenAddress, chain = 'solana', timeframe = '1d' } = params;
|
|
959
992
|
if (tokenAddress) {
|
|
960
993
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
961
994
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
package/src/cli.js
CHANGED
|
@@ -165,12 +165,20 @@ export const SCHEMA = {
|
|
|
165
165
|
'token': {
|
|
166
166
|
description: 'Token God Mode - deep analytics for any token',
|
|
167
167
|
subcommands: {
|
|
168
|
+
'indicators': {
|
|
169
|
+
description: 'Risk and reward indicators for a token (Nansen Score)',
|
|
170
|
+
options: {
|
|
171
|
+
token: { type: 'string', required: true, description: 'Token address' },
|
|
172
|
+
chain: { type: 'string', default: 'ethereum' }
|
|
173
|
+
},
|
|
174
|
+
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]']
|
|
175
|
+
},
|
|
168
176
|
'info': {
|
|
169
177
|
description: 'Get detailed information for a specific token',
|
|
170
178
|
options: {
|
|
171
179
|
token: { type: 'string', required: true, description: 'Token address' },
|
|
172
180
|
chain: { type: 'string', default: 'solana' },
|
|
173
|
-
timeframe: { type: 'string', default: '
|
|
181
|
+
timeframe: { type: 'string', default: '1d', enum: ['5m', '1h', '6h', '12h', '1d', '7d'] }
|
|
174
182
|
},
|
|
175
183
|
returns: ['token_address', 'token_symbol', 'token_name', 'chain', 'price_usd', 'volume_usd', 'market_cap', 'holder_count', 'liquidity_usd']
|
|
176
184
|
},
|
|
@@ -279,6 +287,16 @@ export const SCHEMA = {
|
|
|
279
287
|
}
|
|
280
288
|
}
|
|
281
289
|
},
|
|
290
|
+
'search': {
|
|
291
|
+
description: 'Search for tokens and entities across Nansen',
|
|
292
|
+
options: {
|
|
293
|
+
query: { type: 'string', required: true, description: 'Search query (token name, symbol, address, or entity)' },
|
|
294
|
+
type: { type: 'string', default: 'any', enum: ['token', 'entity', 'any'], description: 'Result type filter' },
|
|
295
|
+
chain: { type: 'string', description: 'Filter by chain (e.g., ethereum, solana)' },
|
|
296
|
+
limit: { type: 'number', default: 25, description: 'Max results (1-50)' }
|
|
297
|
+
},
|
|
298
|
+
returns: ['tokens[name, symbol, chain, address, price, volume_24h, market_cap, rank]', 'entities[name, tags, rank]', 'total_results']
|
|
299
|
+
},
|
|
282
300
|
'points': {
|
|
283
301
|
description: 'Nansen Points analytics',
|
|
284
302
|
subcommands: {
|
|
@@ -299,7 +317,8 @@ export const SCHEMA = {
|
|
|
299
317
|
fields: { type: 'string', description: 'Comma-separated list of fields to include in output' },
|
|
300
318
|
'no-retry': { type: 'boolean', description: 'Disable automatic retry on rate limits/errors' },
|
|
301
319
|
retries: { type: 'number', default: 3, description: 'Max retry attempts' },
|
|
302
|
-
format: { type: 'string', enum: ['json', 'csv'], description: 'Output format (default: json)' }
|
|
320
|
+
format: { type: 'string', enum: ['json', 'csv'], description: 'Output format (default: json)' },
|
|
321
|
+
'x402-payment-signature': { type: 'string', description: 'Pre-signed x402 payment signature header' }
|
|
303
322
|
},
|
|
304
323
|
chains: ['ethereum', 'solana', 'base', 'bnb', 'arbitrum', 'polygon', 'optimism', 'avalanche', 'linea', 'scroll', 'mantle', 'ronin', 'sei', 'plasma', 'sonic', 'monad', 'hyperevm', 'iotaevm'],
|
|
305
324
|
smartMoneyLabels: ['Fund', 'Smart Trader', '30D Smart Trader', '90D Smart Trader', '180D Smart Trader', 'Smart HL Perps Trader']
|
|
@@ -825,9 +844,9 @@ COMMANDS:
|
|
|
825
844
|
profiler Wallet profiling (balance, labels, transactions, pnl, pnl-summary, search,
|
|
826
845
|
historical-balances, related-wallets, counterparties, perp-positions, perp-trades,
|
|
827
846
|
batch, trace, compare)
|
|
828
|
-
token Token God Mode (info, screener, holders, flows, dex-trades, pnl,
|
|
829
|
-
flow-intelligence, transfers, jup-dca, perp-trades,
|
|
830
|
-
perp-pnl-leaderboard)
|
|
847
|
+
token Token God Mode (info, indicators, screener, holders, flows, dex-trades, pnl,
|
|
848
|
+
who-bought-sold, flow-intelligence, transfers, jup-dca, perp-trades,
|
|
849
|
+
perp-positions, perp-pnl-leaderboard)
|
|
831
850
|
portfolio Portfolio analytics (defi)
|
|
832
851
|
perp Perpetual futures analytics (screener, leaderboard)
|
|
833
852
|
points Nansen Points analytics (leaderboard)
|
|
@@ -847,6 +866,7 @@ GLOBAL OPTIONS:
|
|
|
847
866
|
--symbol Token symbol (for perp endpoints)
|
|
848
867
|
--no-retry Disable automatic retry on rate limits/errors
|
|
849
868
|
--retries <n> Max retry attempts (default: 3)
|
|
869
|
+
--x402-payment-signature <sig> Pre-signed x402 payment signature header
|
|
850
870
|
--cache Enable response caching (default: off)
|
|
851
871
|
--no-cache Disable cache for this request
|
|
852
872
|
--cache-ttl <s> Cache TTL in seconds (default: 300)
|
|
@@ -1179,7 +1199,8 @@ export function buildCommands(deps = {}) {
|
|
|
1179
1199
|
}
|
|
1180
1200
|
|
|
1181
1201
|
const handlers = {
|
|
1182
|
-
'
|
|
1202
|
+
'indicators': () => apiInstance.tokenIndicators({ tokenAddress, chain }),
|
|
1203
|
+
'info': () => apiInstance.tokenInformation({ tokenAddress, chain, timeframe: options.timeframe }),
|
|
1183
1204
|
'screener': async () => {
|
|
1184
1205
|
const search = options.search;
|
|
1185
1206
|
// When searching, fetch more results to filter from (API has no server-side search)
|
|
@@ -1292,6 +1313,15 @@ export function buildCommands(deps = {}) {
|
|
|
1292
1313
|
return handlers[subcommand]();
|
|
1293
1314
|
},
|
|
1294
1315
|
|
|
1316
|
+
'search': async (args, apiInstance, flags, options) => {
|
|
1317
|
+
return apiInstance.generalSearch({
|
|
1318
|
+
query: args[0] || options.query,
|
|
1319
|
+
resultType: options.type,
|
|
1320
|
+
chain: options.chain,
|
|
1321
|
+
limit: options.limit
|
|
1322
|
+
});
|
|
1323
|
+
},
|
|
1324
|
+
|
|
1295
1325
|
'points': async (args, apiInstance, flags, options) => {
|
|
1296
1326
|
const subcommand = args[0] || 'help';
|
|
1297
1327
|
const tier = options.tier;
|
|
@@ -1538,7 +1568,11 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1538
1568
|
ttl: options['cache-ttl'] !== undefined ? options['cache-ttl'] : 300
|
|
1539
1569
|
};
|
|
1540
1570
|
|
|
1541
|
-
const
|
|
1571
|
+
const defaultHeaders = {};
|
|
1572
|
+
if (options['x402-payment-signature']) {
|
|
1573
|
+
defaultHeaders['Payment-Signature'] = options['x402-payment-signature'];
|
|
1574
|
+
}
|
|
1575
|
+
const api = new NansenAPIClass(undefined, undefined, { retry: retryOptions, cache: cacheOptions, defaultHeaders });
|
|
1542
1576
|
let result = await commands[command](subArgs, api, flags, options);
|
|
1543
1577
|
|
|
1544
1578
|
// Apply field filtering if --fields is specified
|
|
@@ -1566,7 +1600,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1566
1600
|
} catch (error) {
|
|
1567
1601
|
const errorData = formatError(error);
|
|
1568
1602
|
const formatted = formatOutput(errorData, { pretty, table, csv });
|
|
1569
|
-
|
|
1603
|
+
output(formatted.text);
|
|
1570
1604
|
notify();
|
|
1571
1605
|
exit(1);
|
|
1572
1606
|
return { type: 'error', data: errorData };
|