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.
- package/package.json +4 -4
- package/src/api.js +13 -0
- package/src/cli.js +17 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nansen-cli",
|
|
3
|
-
"version": "1.
|
|
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/
|
|
37
|
+
"url": "git+https://github.com/nansen-ai/nansen-cli.git"
|
|
38
38
|
},
|
|
39
39
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/
|
|
40
|
+
"url": "https://github.com/nansen-ai/nansen-cli/issues"
|
|
41
41
|
},
|
|
42
|
-
"homepage": "https://github.com/
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
},
|