nansen-cli 1.4.0 → 1.5.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.
Files changed (3) hide show
  1. package/package.json +4 -4
  2. package/src/api.js +13 -0
  3. package/src/cli.js +17 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -34,12 +34,12 @@
34
34
  "license": "MIT",
35
35
  "repository": {
36
36
  "type": "git",
37
- "url": "git+https://github.com/askeluv/nansen-cli.git"
37
+ "url": "git+https://github.com/nansen-ai/nansen-cli.git"
38
38
  },
39
39
  "bugs": {
40
- "url": "https://github.com/askeluv/nansen-cli/issues"
40
+ "url": "https://github.com/nansen-ai/nansen-cli/issues"
41
41
  },
42
- "homepage": "https://github.com/askeluv/nansen-cli#readme",
42
+ "homepage": "https://github.com/nansen-ai/nansen-cli#readme",
43
43
  "engines": {
44
44
  "node": ">=18.0.0"
45
45
  },
package/src/api.js CHANGED
@@ -19,6 +19,7 @@ export const ErrorCode = {
19
19
  UNAUTHORIZED: 'UNAUTHORIZED', // 401 - Invalid or missing API key
20
20
  FORBIDDEN: 'FORBIDDEN', // 403 - Valid key but insufficient permissions
21
21
  CREDITS_EXHAUSTED: 'CREDITS_EXHAUSTED', // 403 - Insufficient API credits
22
+ PAYMENT_REQUIRED: 'PAYMENT_REQUIRED', // 402 - x402 payment required
22
23
 
23
24
  // Rate Limiting
24
25
  RATE_LIMITED: 'RATE_LIMITED', // 429 - Too many requests
@@ -87,6 +88,8 @@ function statusToErrorCode(status, data = {}) {
87
88
  return ErrorCode.INVALID_PARAMS;
88
89
  case 401:
89
90
  return ErrorCode.UNAUTHORIZED;
91
+ case 402:
92
+ return ErrorCode.PAYMENT_REQUIRED;
90
93
  case 403:
91
94
  if (messageLower.includes('credit') || messageLower.includes('insufficient')) return ErrorCode.CREDITS_EXHAUSTED;
92
95
  return ErrorCode.FORBIDDEN;
@@ -484,6 +487,16 @@ export class NansenAPI {
484
487
  message = message.replace(/\.+$/, '') + '. This filter is not supported for this token/chain combination. Do not retry.';
485
488
  } else if (code === ErrorCode.CREDITS_EXHAUSTED) {
486
489
  message = message.replace(/\.+$/, '') + '. No retry will help. Check your Nansen dashboard for credit balance.';
490
+ } else if (code === ErrorCode.PAYMENT_REQUIRED) {
491
+ message = 'Payment required (x402). This endpoint requires on-chain payment.';
492
+ const paymentHeader = response.headers.get('payment-required');
493
+ if (paymentHeader) {
494
+ try {
495
+ data.paymentRequirements = JSON.parse(atob(paymentHeader));
496
+ } catch {
497
+ data.paymentRequiredRaw = paymentHeader;
498
+ }
499
+ }
487
500
  }
488
501
 
489
502
  lastError = new NansenError(message, code, response.status, {
package/src/cli.js CHANGED
@@ -1181,16 +1181,26 @@ export function buildCommands(deps = {}) {
1181
1181
  const handlers = {
1182
1182
  'info': () => apiInstance.tokenInformation({ tokenAddress, chain, timeframe }),
1183
1183
  'screener': async () => {
1184
- const result = await apiInstance.tokenScreener({ chains, timeframe, filters, orderBy, pagination });
1185
- // Client-side search filter (API doesn't support server-side search)
1186
1184
  const search = options.search;
1187
- if (search && result?.data) {
1185
+ // When searching, fetch more results to filter from (API has no server-side search)
1186
+ const searchPagination = search
1187
+ ? { page: 1, per_page: Math.max(500, pagination?.per_page || 0) }
1188
+ : pagination;
1189
+ const result = await apiInstance.tokenScreener({ chains, timeframe, filters, orderBy, pagination: searchPagination });
1190
+ if (search) {
1188
1191
  const q = search.toLowerCase();
1189
- const filtered = result.data.filter(t =>
1192
+ const requestedLimit = pagination?.per_page || 100;
1193
+ const filterArr = (arr) => arr.filter(t =>
1190
1194
  (t.token_symbol && t.token_symbol.toLowerCase().includes(q)) ||
1191
- (t.token_name && t.token_name.toLowerCase().includes(q))
1192
- );
1193
- return { ...result, data: filtered };
1195
+ (t.token_name && t.token_name.toLowerCase().includes(q)) ||
1196
+ (t.token_address && t.token_address.toLowerCase() === q)
1197
+ ).slice(0, requestedLimit);
1198
+ // Handle nested response shapes: {data: [...]} or {data: {data: [...]}}
1199
+ if (Array.isArray(result?.data)) {
1200
+ return { ...result, data: filterArr(result.data) };
1201
+ } else if (result?.data?.data && Array.isArray(result.data.data)) {
1202
+ return { ...result, data: { ...result.data, data: filterArr(result.data.data) } };
1203
+ }
1194
1204
  }
1195
1205
  return result;
1196
1206
  },