nansen-cli 1.33.0 → 1.35.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 +33 -0
- package/README.md +14 -3
- package/package.json +2 -2
- package/scripts/postinstall.js +13 -3
- package/skills/nansen-perp-screener/SKILL.md +59 -0
- package/src/api.js +64 -8
- package/src/cli.js +22 -1
- package/src/response-meta.js +123 -0
- package/src/schema.json +202 -49
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.35.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#469](https://github.com/nansen-ai/nansen-cli/pull/469) [`85b1934`](https://github.com/nansen-ai/nansen-cli/commit/85b1934ae25ed02d1726de8ebe92ea41a98f4454) Thanks [@gulshngill](https://github.com/gulshngill)! - Surface the API's credit and rate-limit response headers.
|
|
8
|
+
|
|
9
|
+
Failed calls now report quota state in their error details: an out-of-credits error carries your actual remaining balance, and a rate-limited error carries the limit, what is left, and how long the window needs to drain. Previously the only credit figure the CLI could show was the static per-endpoint estimate published in the API reference — a quote, not what you were charged.
|
|
10
|
+
|
|
11
|
+
A warning goes to stderr when your balance will not cover another call of the size just made, so it never interferes with the JSON on stdout.
|
|
12
|
+
|
|
13
|
+
Successful responses carry the same numbers under an exported `RESPONSE_META` symbol, and the client exposes `lastResponseMeta`. Both are additive: the JSON each command prints is unchanged.
|
|
14
|
+
|
|
15
|
+
- [#470](https://github.com/nansen-ai/nansen-cli/pull/470) [`8159300`](https://github.com/nansen-ai/nansen-cli/commit/81593008f829cc83f1d4a6ee9e5c1a10237a9553) Thanks [@gulshngill](https://github.com/gulshngill)! - Surface the API's request id.
|
|
16
|
+
|
|
17
|
+
Failed calls now carry `details.requestId` — the value that identifies the call end to end. Quote it when reporting a problem; previously nothing identifying a failed request ever reached the user, which made server errors effectively unreportable. Successful responses expose it alongside the credit and rate-limit figures under the `RESPONSE_META` symbol.
|
|
18
|
+
|
|
19
|
+
Absent on deployments that do not send the header yet, in which case the field is simply omitted.
|
|
20
|
+
|
|
21
|
+
## 1.34.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- [#459](https://github.com/nansen-ai/nansen-cli/pull/459) [`37e6725`](https://github.com/nansen-ai/nansen-cli/commit/37e6725aeeb3b83eb29c4650908b8dbb522ed316) Thanks [@dependabot](https://github.com/apps/dependabot)! - Drop support for Node.js 18 (EOL since April 2025). The minimum supported version is now Node.js 20, matching our test toolchain (vitest 4.x requires Node 20+).
|
|
26
|
+
|
|
27
|
+
- [#460](https://github.com/nansen-ai/nansen-cli/pull/460) [`aac4bbe`](https://github.com/nansen-ai/nansen-cli/commit/aac4bbe18312edb48c91df60ab555f9c1d8334ce) Thanks [@gulshngill](https://github.com/gulshngill)! - Add trader_type, sectors_filter, sm_label_filter, and trader_label_filter filters to `nansen research perp screener` (ECINT-6680).
|
|
28
|
+
|
|
29
|
+
New CLI options:
|
|
30
|
+
|
|
31
|
+
- `--trader-type <type>` — filter by trader type: all, sm, whale, public_figure, high_winrate_hl_perps_trader
|
|
32
|
+
- `--sectors-filter <sectors>` — comma-separated sector:subcategory pairs, e.g. "Crypto:AI,TradFi:Stocks"
|
|
33
|
+
- `--sm-label-filter <labels>` — comma-separated Nansen SM labels (applies when trader-type is all or sm)
|
|
34
|
+
- `--trader-label-filter <labels>` — comma-separated HL perps trader labels (applies when trader-type is all or sm)
|
|
35
|
+
|
|
3
36
|
## 1.33.0
|
|
4
37
|
|
|
5
38
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -177,17 +177,28 @@ nansen research smart-money netflow --chain solana --fields token_symbol,net_flo
|
|
|
177
177
|
|
|
178
178
|
```json
|
|
179
179
|
{ "success": true, "data": <api_response> }
|
|
180
|
-
{ "success": false, "error": "message", "code": "ERROR_CODE", "status": 401 }
|
|
180
|
+
{ "success": false, "error": "message", "code": "ERROR_CODE", "status": 401, "details": { ... } }
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
**Critical error codes:**
|
|
184
184
|
|
|
185
185
|
| Code | Action |
|
|
186
186
|
|------|--------|
|
|
187
|
-
| `CREDITS_EXHAUSTED` | Stop all API calls immediately.
|
|
187
|
+
| `CREDITS_EXHAUSTED` | Stop all API calls immediately. `details.credits.remaining` is your actual balance. Top up at [app.nansen.ai/api](https://app.nansen.ai/api). |
|
|
188
188
|
| `UNAUTHORIZED` | Wrong or missing key. Re-auth. |
|
|
189
|
-
| `RATE_LIMITED` | Auto-retried by CLI. |
|
|
189
|
+
| `RATE_LIMITED` | Auto-retried by CLI. `details.rateLimit.resetSeconds` is how long the window needs to drain. |
|
|
190
190
|
| `UNSUPPORTED_FILTER` | Remove the filter and retry. |
|
|
191
|
+
| `SERVER_ERROR` | Not your fault. Quote `details.requestId` when reporting it. |
|
|
192
|
+
|
|
193
|
+
**Error metadata.** When the API reports them, `details` carries:
|
|
194
|
+
|
|
195
|
+
| Field | Meaning |
|
|
196
|
+
|-------|---------|
|
|
197
|
+
| `requestId` | Identifies this call end to end. Quote it in any support report. Opaque — do not parse it. |
|
|
198
|
+
| `credits` | `used`, `remaining` |
|
|
199
|
+
| `rateLimit` | `limit`, `remaining`, `resetSeconds` |
|
|
200
|
+
|
|
201
|
+
Any field may be absent or `null`, meaning unknown — never assume zero. A low-balance warning goes to **stderr**, so stdout stays pure JSON.
|
|
191
202
|
|
|
192
203
|
## Troubleshooting
|
|
193
204
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nansen-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "AI-agent CLI for Nansen API analytics, DEX swaps, and cross-chain trading",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"homepage": "https://github.com/nansen-ai/nansen-cli#readme",
|
|
61
61
|
"engines": {
|
|
62
|
-
"node": ">=
|
|
62
|
+
"node": ">=20.0.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@changesets/changelog-github": "^0.5.1",
|
package/scripts/postinstall.js
CHANGED
|
@@ -41,9 +41,19 @@ function hasTTY() {
|
|
|
41
41
|
return process.stdin.isTTY && process.stderr.isTTY;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// npx is a .cmd shim on Windows, and Node refuses to spawn .cmd/.bat without a
|
|
45
|
+
// shell (CVE-2024-27980). Go through cmd.exe explicitly rather than enabling
|
|
46
|
+
// `shell: true`, which would hand the whole command line to the shell parser.
|
|
47
|
+
const IS_WIN = process.platform === "win32";
|
|
48
|
+
|
|
49
|
+
function npxInvocation(args) {
|
|
50
|
+
return IS_WIN ? ["cmd.exe", ["/c", "npx", ...args]] : ["npx", args];
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
function hasNpx() {
|
|
45
54
|
try {
|
|
46
|
-
|
|
55
|
+
const [cmd, cmdArgs] = npxInvocation(["--version"]);
|
|
56
|
+
execFileSync(cmd, cmdArgs, { stdio: "ignore", shell: false });
|
|
47
57
|
return true;
|
|
48
58
|
} catch {
|
|
49
59
|
return false;
|
|
@@ -86,7 +96,7 @@ function prompt(question) {
|
|
|
86
96
|
|
|
87
97
|
function runCommand(cmd, args) {
|
|
88
98
|
return new Promise((resolve) => {
|
|
89
|
-
const child = spawn(cmd, args, { stdio: "inherit", shell:
|
|
99
|
+
const child = spawn(cmd, args, { stdio: "inherit", shell: false });
|
|
90
100
|
child.on("close", (code) => resolve(code === 0));
|
|
91
101
|
child.on("error", () => resolve(false));
|
|
92
102
|
});
|
|
@@ -113,7 +123,7 @@ async function installSkill() {
|
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
log(`Installing Nansen skill...`);
|
|
116
|
-
const ok = await runCommand(
|
|
126
|
+
const ok = await runCommand(...npxInvocation(["-y", "skills", "add", SKILL_REPO]));
|
|
117
127
|
if (!ok) {
|
|
118
128
|
log(`${YELLOW}Skill installation failed. You can retry with: npx skills add ${SKILL_REPO}${RESET}`);
|
|
119
129
|
}
|
|
@@ -30,3 +30,62 @@ nansen research perp leaderboard --days 7 --limit 20
|
|
|
30
30
|
nansen research smart-money perp-trades --limit 20
|
|
31
31
|
# → token_symbol, side, action (Open/Close), value_usd, price_usd, trader_address_label
|
|
32
32
|
```
|
|
33
|
+
|
|
34
|
+
## New Filters (ECINT-6680)
|
|
35
|
+
|
|
36
|
+
### `--trader-type`
|
|
37
|
+
Filter by trader type. Accepted values: `all` (default), `sm`, `whale`, `public_figure`, `high_winrate_hl_perps_trader`.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Show only whale traders
|
|
41
|
+
nansen research perp screener --trader-type whale --limit 10
|
|
42
|
+
|
|
43
|
+
# Show only smart money traders
|
|
44
|
+
nansen research perp screener --trader-type sm --limit 20
|
|
45
|
+
|
|
46
|
+
# Show high win-rate HL perps traders
|
|
47
|
+
nansen research perp screener --trader-type high_winrate_hl_perps_trader --limit 20
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `--sm-label-filter`
|
|
51
|
+
Comma-separated Nansen SM labels to filter by. Only applies when `--trader-type` is `all` or `sm`.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Filter to a specific SM label
|
|
55
|
+
nansen research perp screener --trader-type sm --sm-label-filter "30D Smart Trader"
|
|
56
|
+
|
|
57
|
+
# Multiple labels
|
|
58
|
+
nansen research perp screener --sm-label-filter "30D Smart Trader,Smart LP"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `--trader-label-filter`
|
|
62
|
+
Comma-separated HL perps trader labels to filter by. Only applies when `--trader-type` is `all` or `sm`.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Filter to HL Perps Whale label
|
|
66
|
+
nansen research perp screener --trader-label-filter "HL Perps Whale"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `--sectors-filter`
|
|
70
|
+
Comma-separated `category:subcategory` pairs to filter coins by sector.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Filter to AI and DeFi crypto sectors
|
|
74
|
+
nansen research perp screener --sectors-filter "Crypto:AI,Crypto:DeFi" --trader-type whale
|
|
75
|
+
|
|
76
|
+
# Combine with trader type and limit
|
|
77
|
+
nansen research perp screener --sectors-filter "Crypto:AI,Crypto:DeFi" --trader-type whale --limit 10 --sort volume:desc
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Combined Examples
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Whale traders in AI crypto, sorted by volume
|
|
84
|
+
nansen research perp screener --trader-type whale --sectors-filter "Crypto:AI" --sort volume:desc --limit 10
|
|
85
|
+
|
|
86
|
+
# Smart money with specific label, last 7 days
|
|
87
|
+
nansen research perp screener --trader-type sm --sm-label-filter "30D Smart Trader" --days 7 --limit 20
|
|
88
|
+
|
|
89
|
+
# All traders in TradFi stocks sector
|
|
90
|
+
nansen research perp screener --sectors-filter "TradFi:Stocks" --sort open_interest:desc --limit 20
|
|
91
|
+
```
|
package/src/api.js
CHANGED
|
@@ -8,6 +8,16 @@ import path from 'path';
|
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { EVM_CHAINS } from './chain-ids.js';
|
|
10
10
|
import { getAnonymousId, TELEMETRY_DISABLED } from './telemetry.js';
|
|
11
|
+
import { readResponseMeta } from './response-meta.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Key for the credit/rate-limit metadata attached to a successful response.
|
|
15
|
+
*
|
|
16
|
+
* A symbol on purpose: JSON.stringify and Object.keys both skip it, so the JSON
|
|
17
|
+
* every command prints is byte-for-byte unchanged while callers that want the
|
|
18
|
+
* numbers can still read them off the returned object.
|
|
19
|
+
*/
|
|
20
|
+
export const RESPONSE_META = Symbol('nansenResponseMeta');
|
|
11
21
|
|
|
12
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
23
|
|
|
@@ -465,6 +475,15 @@ export class NansenAPI {
|
|
|
465
475
|
ttl: options.cache?.ttl ?? DEFAULT_CACHE_TTL
|
|
466
476
|
};
|
|
467
477
|
this.defaultHeaders = options.defaultHeaders || {};
|
|
478
|
+
/**
|
|
479
|
+
* Credit/rate-limit metadata from the most recent response, or null.
|
|
480
|
+
*
|
|
481
|
+
* Survives any reshaping a command handler does to the response body, which
|
|
482
|
+
* the RESPONSE_META symbol on the returned object does not. Last write wins
|
|
483
|
+
* when a handler makes several calls — the freshest balance, which is what a
|
|
484
|
+
* low-credit warning wants.
|
|
485
|
+
*/
|
|
486
|
+
this.lastResponseMeta = null;
|
|
468
487
|
}
|
|
469
488
|
|
|
470
489
|
static cleanBody(body) {
|
|
@@ -520,7 +539,11 @@ export class NansenAPI {
|
|
|
520
539
|
}
|
|
521
540
|
} catch { /* balance check is best-effort */ }
|
|
522
541
|
}
|
|
523
|
-
|
|
542
|
+
const data = await paidResponse.json();
|
|
543
|
+
const meta = readResponseMeta(paidResponse);
|
|
544
|
+
this.lastResponseMeta = meta;
|
|
545
|
+
if (meta && data !== null && typeof data === 'object') data[RESPONSE_META] = meta;
|
|
546
|
+
return data;
|
|
524
547
|
}
|
|
525
548
|
|
|
526
549
|
async request(endpoint, body = {}, options = {}) {
|
|
@@ -581,11 +604,17 @@ export class NansenAPI {
|
|
|
581
604
|
data = await response.json();
|
|
582
605
|
} catch (_err) {
|
|
583
606
|
// Non-JSON response (rare, usually server errors)
|
|
607
|
+
const meta = readResponseMeta(response);
|
|
608
|
+
this.lastResponseMeta = meta;
|
|
584
609
|
const error = new NansenError(
|
|
585
610
|
`Invalid response from API (status ${response.status})`,
|
|
586
611
|
response.status >= 500 ? ErrorCode.SERVER_ERROR : ErrorCode.UNKNOWN,
|
|
587
612
|
response.status,
|
|
588
|
-
{
|
|
613
|
+
{
|
|
614
|
+
body: await response.text().catch(() => null),
|
|
615
|
+
attempt: attempt + 1,
|
|
616
|
+
...(meta?.requestId && { requestId: meta.requestId })
|
|
617
|
+
}
|
|
589
618
|
);
|
|
590
619
|
|
|
591
620
|
if (shouldRetry && attempt < maxRetries && response.status >= 500) {
|
|
@@ -702,10 +731,23 @@ export class NansenAPI {
|
|
|
702
731
|
}
|
|
703
732
|
}
|
|
704
733
|
|
|
734
|
+
// Quota state and the request id belong on the error above all: an
|
|
735
|
+
// out-of-credits or rate-limited failure is exactly when the caller
|
|
736
|
+
// needs the numbers, and a 5xx is worthless to support without the id.
|
|
737
|
+
// formatError() surfaces details, so this needs no plumbing.
|
|
738
|
+
//
|
|
739
|
+
// On a retried call this is the LAST attempt's id — each attempt gets
|
|
740
|
+
// its own server-side id, and the last one is the failure worth
|
|
741
|
+
// reporting.
|
|
742
|
+
const meta = readResponseMeta(response);
|
|
743
|
+
this.lastResponseMeta = meta;
|
|
705
744
|
lastError = new NansenError(message, code, response.status, {
|
|
706
745
|
...data,
|
|
707
746
|
attempt: attempt + 1,
|
|
708
|
-
retryAfterMs
|
|
747
|
+
retryAfterMs,
|
|
748
|
+
...(meta?.requestId && { requestId: meta.requestId }),
|
|
749
|
+
...(meta?.credits && { credits: meta.credits }),
|
|
750
|
+
...(meta?.rateLimit && { rateLimit: meta.rateLimit })
|
|
709
751
|
});
|
|
710
752
|
|
|
711
753
|
// Retry on specific status codes
|
|
@@ -722,12 +764,21 @@ export class NansenAPI {
|
|
|
722
764
|
if (attempt > 0) {
|
|
723
765
|
data._meta = { ...(data._meta || {}), retriedAttempts: attempt };
|
|
724
766
|
}
|
|
725
|
-
|
|
767
|
+
|
|
726
768
|
// Cache successful response
|
|
727
769
|
if (useCache) {
|
|
728
770
|
setCachedResponse(endpoint, body, data);
|
|
729
771
|
}
|
|
730
|
-
|
|
772
|
+
|
|
773
|
+
// Attach after caching so the cache stores the payload alone — quota
|
|
774
|
+
// numbers are per-response and would be stale on a cache hit.
|
|
775
|
+
// Guarded: a response body can be a primitive, which cannot take a property.
|
|
776
|
+
const meta = readResponseMeta(response);
|
|
777
|
+
this.lastResponseMeta = meta;
|
|
778
|
+
if (meta) {
|
|
779
|
+
if (data !== null && typeof data === 'object') data[RESPONSE_META] = meta;
|
|
780
|
+
}
|
|
781
|
+
|
|
731
782
|
return data;
|
|
732
783
|
}
|
|
733
784
|
|
|
@@ -1187,13 +1238,18 @@ export class NansenAPI {
|
|
|
1187
1238
|
// ============= Perp Endpoints =============
|
|
1188
1239
|
|
|
1189
1240
|
async perpScreener(params = {}) {
|
|
1190
|
-
const { filters = {}, orderBy, pagination, days = 30 } = params;
|
|
1191
|
-
|
|
1241
|
+
const { filters = {}, orderBy, pagination, days = 30, traderType, sectorsFilter, smLabelFilter, traderLabelFilter } = params;
|
|
1242
|
+
const body = {
|
|
1192
1243
|
date: buildDateRange(days),
|
|
1193
1244
|
filters,
|
|
1194
1245
|
order_by: orderBy,
|
|
1195
1246
|
pagination
|
|
1196
|
-
}
|
|
1247
|
+
};
|
|
1248
|
+
if (traderType !== undefined) body.filters.trader_type = traderType;
|
|
1249
|
+
if (sectorsFilter !== undefined) body.filters.sectors_filter = sectorsFilter;
|
|
1250
|
+
if (smLabelFilter !== undefined) body.filters.sm_label_filter = smLabelFilter;
|
|
1251
|
+
if (traderLabelFilter !== undefined) body.filters.trader_label_filter = traderLabelFilter;
|
|
1252
|
+
return this.request('/api/v1/perp-screener', body);
|
|
1197
1253
|
}
|
|
1198
1254
|
|
|
1199
1255
|
async perpLeaderboard(params = {}) {
|
package/src/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { resolveAddress, isEnsName } from './ens.js';
|
|
|
14
14
|
import fs from 'fs';
|
|
15
15
|
import { getUpdateNotification, getUpgradeNotice, scheduleUpdateCheck } from './update-check.js';
|
|
16
16
|
import { refreshCostMapIfStale, getCostForEndpoint } from './cost-cache.js';
|
|
17
|
+
import { creditWarning, noticeWarnings } from './response-meta.js';
|
|
17
18
|
import { trackCommandSucceeded, trackCommandFailed } from './telemetry.js';
|
|
18
19
|
import { createRequire } from 'module';
|
|
19
20
|
import * as readline from 'readline';
|
|
@@ -1367,7 +1368,19 @@ export function buildCommands(deps = {}) {
|
|
|
1367
1368
|
const days = options.days ? parseInt(options.days) : 30;
|
|
1368
1369
|
|
|
1369
1370
|
const handlers = {
|
|
1370
|
-
'screener': () =>
|
|
1371
|
+
'screener': () => {
|
|
1372
|
+
const traderType = options['trader-type'];
|
|
1373
|
+
const sectorsFilter = options['sectors-filter']
|
|
1374
|
+
? options['sectors-filter'].split(',').map(s => s.trim()).filter(Boolean)
|
|
1375
|
+
: undefined;
|
|
1376
|
+
const smLabelFilter = options['sm-label-filter']
|
|
1377
|
+
? options['sm-label-filter'].split(',').map(s => s.trim()).filter(Boolean)
|
|
1378
|
+
: undefined;
|
|
1379
|
+
const traderLabelFilter = options['trader-label-filter']
|
|
1380
|
+
? options['trader-label-filter'].split(',').map(s => s.trim()).filter(Boolean)
|
|
1381
|
+
: undefined;
|
|
1382
|
+
return apiInstance.perpScreener({ filters, orderBy, pagination, days, traderType, sectorsFilter, smLabelFilter, traderLabelFilter });
|
|
1383
|
+
},
|
|
1371
1384
|
'leaderboard': () => {
|
|
1372
1385
|
const withLabels = resolveBooleanOption(options, flags, 'premium-labels');
|
|
1373
1386
|
return apiInstance.perpLeaderboard({ filters, orderBy, pagination, days, withLabels });
|
|
@@ -1842,6 +1855,14 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1842
1855
|
const api = new NansenAPIClass(undefined, undefined, { retry: retryOptions, cache: cacheOptions, defaultHeaders });
|
|
1843
1856
|
let result = await commands[command](subArgs, api, flags, options);
|
|
1844
1857
|
|
|
1858
|
+
// Credit balance warning, from the headers on the call just made. Goes to
|
|
1859
|
+
// stderr so it never contaminates the JSON on stdout that agents parse.
|
|
1860
|
+
// Placed before every return path below so it fires for operational
|
|
1861
|
+
// commands too, which print their own output and return undefined.
|
|
1862
|
+
const lowCredits = creditWarning(api.lastResponseMeta);
|
|
1863
|
+
if (lowCredits) errorOutput(lowCredits);
|
|
1864
|
+
for (const notice of noticeWarnings(api.lastResponseMeta)) errorOutput(notice);
|
|
1865
|
+
|
|
1845
1866
|
// Commands that handle their own output return undefined
|
|
1846
1867
|
if (result === undefined) {
|
|
1847
1868
|
await trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-id, credit, rate-limit, and notice metadata, read from Nansen API
|
|
3
|
+
* response headers.
|
|
4
|
+
*
|
|
5
|
+
* The API reports what a call actually cost, what quota is left, and an id that
|
|
6
|
+
* identifies the call end to end. Until now the CLI dropped those headers on the
|
|
7
|
+
* floor and showed only the static per-endpoint estimate published in the
|
|
8
|
+
* OpenAPI spec (see cost-cache.js), which is a quote rather than a charge.
|
|
9
|
+
*
|
|
10
|
+
* readResponseMeta(response) — parse the headers, or null if none are present
|
|
11
|
+
* creditWarning(meta) — stderr warning string when the balance is short, else null
|
|
12
|
+
*
|
|
13
|
+
* Every header is optional. Some auth rails charge no credits, some responses
|
|
14
|
+
* are served before quota is resolved, and older deployments may send neither
|
|
15
|
+
* the rate-limit triplet nor the request id — so a missing header means
|
|
16
|
+
* "unknown", never zero.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Header names, as documented in the API reference. */
|
|
20
|
+
const CREDITS_USED = 'x-nansen-credits-used';
|
|
21
|
+
const CREDITS_REMAINING = 'x-nansen-credits-remaining';
|
|
22
|
+
const RATE_LIMIT = 'x-ratelimit-limit';
|
|
23
|
+
const RATE_REMAINING = 'x-ratelimit-remaining';
|
|
24
|
+
const RATE_RESET = 'x-ratelimit-reset';
|
|
25
|
+
const UPGRADE_HINT = 'x-nansen-upgrade-hint';
|
|
26
|
+
const PLAN_NOTICE = 'x-nansen-plan-notice';
|
|
27
|
+
const API_KEY_NOTICE = 'x-nansen-api-key-notice';
|
|
28
|
+
const REQUEST_ID = 'x-request-id';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read a header as a non-negative integer, or null when absent/unparseable.
|
|
32
|
+
* Tolerates any header bag with a .get() — a real Headers, or a Map in tests.
|
|
33
|
+
*/
|
|
34
|
+
function intHeader(response, name) {
|
|
35
|
+
const raw = stringHeader(response, name);
|
|
36
|
+
if (raw === null) return null;
|
|
37
|
+
const value = Number.parseInt(raw, 10);
|
|
38
|
+
return Number.isInteger(value) && value >= 0 ? value : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Read a header as a trimmed non-empty string, or null when absent.
|
|
43
|
+
* Tolerates any header bag with a .get() — a real Headers, or a Map in tests.
|
|
44
|
+
*/
|
|
45
|
+
function stringHeader(response, name) {
|
|
46
|
+
const raw = response?.headers?.get?.(name);
|
|
47
|
+
if (raw == null) return null;
|
|
48
|
+
const value = String(raw).trim();
|
|
49
|
+
return value === '' ? null : value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract request-id, credit, rate-limit, and notice metadata from a fetch Response.
|
|
54
|
+
* Returns null when the response carries none of it, so callers can skip
|
|
55
|
+
* attaching an object full of nulls.
|
|
56
|
+
*/
|
|
57
|
+
export function readResponseMeta(response) {
|
|
58
|
+
const used = intHeader(response, CREDITS_USED);
|
|
59
|
+
const remaining = intHeader(response, CREDITS_REMAINING);
|
|
60
|
+
const limit = intHeader(response, RATE_LIMIT);
|
|
61
|
+
const rateRemaining = intHeader(response, RATE_REMAINING);
|
|
62
|
+
const resetSeconds = intHeader(response, RATE_RESET);
|
|
63
|
+
const upgradeHint = stringHeader(response, UPGRADE_HINT);
|
|
64
|
+
const planNotice = stringHeader(response, PLAN_NOTICE);
|
|
65
|
+
const apiKeyNotice = stringHeader(response, API_KEY_NOTICE);
|
|
66
|
+
const requestId = stringHeader(response, REQUEST_ID);
|
|
67
|
+
|
|
68
|
+
const meta = {};
|
|
69
|
+
if (requestId !== null) {
|
|
70
|
+
// The single value that identifies this call to Nansen support. Opaque —
|
|
71
|
+
// never parse it or assume a format.
|
|
72
|
+
meta.requestId = requestId;
|
|
73
|
+
}
|
|
74
|
+
if (used !== null || remaining !== null) {
|
|
75
|
+
meta.credits = { used, remaining };
|
|
76
|
+
}
|
|
77
|
+
if (limit !== null || rateRemaining !== null || resetSeconds !== null) {
|
|
78
|
+
// resetSeconds is a delta in seconds — how long the tripped window needs to
|
|
79
|
+
// drain — not a wall-clock timestamp.
|
|
80
|
+
meta.rateLimit = { limit, remaining: rateRemaining, resetSeconds };
|
|
81
|
+
}
|
|
82
|
+
if (upgradeHint !== null || planNotice !== null || apiKeyNotice !== null) {
|
|
83
|
+
meta.notices = {
|
|
84
|
+
...(upgradeHint !== null && { upgradeHint }),
|
|
85
|
+
...(planNotice !== null && { planNotice }),
|
|
86
|
+
...(apiKeyNotice !== null && { apiKeyNotice }),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return Object.keys(meta).length > 0 ? meta : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Yield notice strings for any server-set advisory headers.
|
|
94
|
+
* Each yields a `⚠️ <message>` line for stderr.
|
|
95
|
+
* Order: apiKeyNotice (most urgent) → upgradeHint → planNotice.
|
|
96
|
+
*/
|
|
97
|
+
export function noticeWarnings(meta) {
|
|
98
|
+
const notices = meta?.notices;
|
|
99
|
+
if (!notices) return [];
|
|
100
|
+
const out = [];
|
|
101
|
+
if (notices.apiKeyNotice) out.push(`⚠️ ${notices.apiKeyNotice}`);
|
|
102
|
+
if (notices.upgradeHint) out.push(`ℹ️ ${notices.upgradeHint}`);
|
|
103
|
+
if (notices.planNotice) out.push(`ℹ️ ${notices.planNotice}`);
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Warn only when the remaining balance will not cover another call of the size
|
|
109
|
+
* just made.
|
|
110
|
+
*/
|
|
111
|
+
export function creditWarning(meta) {
|
|
112
|
+
const credits = meta?.credits;
|
|
113
|
+
if (!credits) return null;
|
|
114
|
+
const { used, remaining } = credits;
|
|
115
|
+
if (remaining === null) return null;
|
|
116
|
+
if (remaining === 0) {
|
|
117
|
+
return '⚠️ Out of API credits. Top up at https://app.nansen.ai/api';
|
|
118
|
+
}
|
|
119
|
+
if (used !== null && used > 0 && remaining < used) {
|
|
120
|
+
return `⚠️ ${remaining} API credit${remaining === 1 ? '' : 's'} left — less than this call cost (${used}). Top up at https://app.nansen.ai/api`;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
package/src/schema.json
CHANGED
|
@@ -522,7 +522,11 @@
|
|
|
522
522
|
"options": {
|
|
523
523
|
"market-cap": {
|
|
524
524
|
"description": "Filter by market cap group",
|
|
525
|
-
"enum": [
|
|
525
|
+
"enum": [
|
|
526
|
+
"lowcap",
|
|
527
|
+
"midcap",
|
|
528
|
+
"largecap"
|
|
529
|
+
]
|
|
526
530
|
},
|
|
527
531
|
"limit": {
|
|
528
532
|
"default": 25
|
|
@@ -555,6 +559,25 @@
|
|
|
555
559
|
"options": {
|
|
556
560
|
"days": {
|
|
557
561
|
"default": 30
|
|
562
|
+
},
|
|
563
|
+
"trader-type": {
|
|
564
|
+
"description": "Filter by trader type. One of: all, sm, whale, public_figure, high_winrate_hl_perps_trader. Defaults to all.",
|
|
565
|
+
"enum": [
|
|
566
|
+
"all",
|
|
567
|
+
"sm",
|
|
568
|
+
"whale",
|
|
569
|
+
"public_figure",
|
|
570
|
+
"high_winrate_hl_perps_trader"
|
|
571
|
+
]
|
|
572
|
+
},
|
|
573
|
+
"sectors-filter": {
|
|
574
|
+
"description": "Comma-separated sector:subcategory pairs to filter by, e.g. \"Crypto:AI,TradFi:Stocks\"."
|
|
575
|
+
},
|
|
576
|
+
"sm-label-filter": {
|
|
577
|
+
"description": "Comma-separated Nansen SM labels to filter by, e.g. \"30D Smart Trader\". Only applies when trader-type is all or sm."
|
|
578
|
+
},
|
|
579
|
+
"trader-label-filter": {
|
|
580
|
+
"description": "Comma-separated HL perps trader labels to filter by, e.g. \"HL Perps Whale\". Only applies when trader-type is all or sm."
|
|
558
581
|
}
|
|
559
582
|
}
|
|
560
583
|
},
|
|
@@ -616,8 +639,12 @@
|
|
|
616
639
|
"endpoint": "/api/v1/prediction-market/market-screener",
|
|
617
640
|
"description": "Get Prediction Market Screener",
|
|
618
641
|
"options": {
|
|
619
|
-
"query": {
|
|
620
|
-
|
|
642
|
+
"query": {
|
|
643
|
+
"default": ""
|
|
644
|
+
},
|
|
645
|
+
"sort-by": {
|
|
646
|
+
"description": "Deprecated: use --sort field:dir instead"
|
|
647
|
+
},
|
|
621
648
|
"tags": {},
|
|
622
649
|
"min-liquidity": {},
|
|
623
650
|
"max-liquidity": {},
|
|
@@ -638,8 +665,12 @@
|
|
|
638
665
|
"endpoint": "/api/v1/prediction-market/event-screener",
|
|
639
666
|
"description": "Get Prediction Market Event Screener",
|
|
640
667
|
"options": {
|
|
641
|
-
"query": {
|
|
642
|
-
|
|
668
|
+
"query": {
|
|
669
|
+
"default": ""
|
|
670
|
+
},
|
|
671
|
+
"sort-by": {
|
|
672
|
+
"description": "Deprecated: use --sort field:dir instead"
|
|
673
|
+
},
|
|
643
674
|
"tags": {},
|
|
644
675
|
"min-liquidity": {},
|
|
645
676
|
"max-liquidity": {},
|
|
@@ -717,104 +748,218 @@
|
|
|
717
748
|
"endpoint": "/api/v1beta1/tgm/historical-dex-trades",
|
|
718
749
|
"description": "Historical DEX trades for a token at a point in time",
|
|
719
750
|
"options": {
|
|
720
|
-
"token-address": {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
751
|
+
"token-address": {
|
|
752
|
+
"required": true,
|
|
753
|
+
"description": "Token address"
|
|
754
|
+
},
|
|
755
|
+
"from-date": {
|
|
756
|
+
"required": true,
|
|
757
|
+
"description": "Start of date range (YYYY-MM-DD)"
|
|
758
|
+
},
|
|
759
|
+
"to-date": {
|
|
760
|
+
"required": true,
|
|
761
|
+
"description": "End of date range (YYYY-MM-DD)"
|
|
762
|
+
},
|
|
763
|
+
"chain": {
|
|
764
|
+
"default": "solana",
|
|
765
|
+
"description": "Chain"
|
|
766
|
+
}
|
|
724
767
|
}
|
|
725
768
|
},
|
|
726
769
|
"historical-pnl-leaderboard": {
|
|
727
770
|
"endpoint": "/api/v1beta1/tgm/historical-pnl-leaderboard",
|
|
728
771
|
"description": "Historical PnL leaderboard for a token",
|
|
729
772
|
"options": {
|
|
730
|
-
"token-address": {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
773
|
+
"token-address": {
|
|
774
|
+
"required": true,
|
|
775
|
+
"description": "Token address"
|
|
776
|
+
},
|
|
777
|
+
"from-date": {
|
|
778
|
+
"required": true,
|
|
779
|
+
"description": "Start of date range (YYYY-MM-DD)"
|
|
780
|
+
},
|
|
781
|
+
"to-date": {
|
|
782
|
+
"required": true,
|
|
783
|
+
"description": "End of date range (YYYY-MM-DD)"
|
|
784
|
+
},
|
|
785
|
+
"chain": {
|
|
786
|
+
"default": "solana",
|
|
787
|
+
"description": "Chain"
|
|
788
|
+
}
|
|
734
789
|
}
|
|
735
790
|
},
|
|
736
791
|
"historical-token-flow-summary": {
|
|
737
792
|
"endpoint": "/api/v1beta1/tgm/historical-token-flow-summary",
|
|
738
793
|
"description": "Historical token flow summary (no pagination)",
|
|
739
794
|
"options": {
|
|
740
|
-
"token-address": {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
795
|
+
"token-address": {
|
|
796
|
+
"required": true,
|
|
797
|
+
"description": "Token address"
|
|
798
|
+
},
|
|
799
|
+
"from-date": {
|
|
800
|
+
"required": true,
|
|
801
|
+
"description": "Start of date range (YYYY-MM-DD)"
|
|
802
|
+
},
|
|
803
|
+
"to-date": {
|
|
804
|
+
"required": true,
|
|
805
|
+
"description": "End of date range (YYYY-MM-DD)"
|
|
806
|
+
},
|
|
807
|
+
"chain": {
|
|
808
|
+
"default": "solana",
|
|
809
|
+
"description": "Chain"
|
|
810
|
+
}
|
|
744
811
|
}
|
|
745
812
|
},
|
|
746
813
|
"historical-token-quant-scores": {
|
|
747
814
|
"endpoint": "/api/v1beta1/tgm/historical-token-quant-scores",
|
|
748
815
|
"description": "Historical token quantitative scores at a snapshot date",
|
|
749
816
|
"options": {
|
|
750
|
-
"token-address": {
|
|
751
|
-
|
|
752
|
-
|
|
817
|
+
"token-address": {
|
|
818
|
+
"required": true,
|
|
819
|
+
"description": "Token address"
|
|
820
|
+
},
|
|
821
|
+
"as-of-date": {
|
|
822
|
+
"required": true,
|
|
823
|
+
"description": "Snapshot date (YYYY-MM-DD)"
|
|
824
|
+
},
|
|
825
|
+
"chain": {
|
|
826
|
+
"default": "solana",
|
|
827
|
+
"description": "Chain"
|
|
828
|
+
}
|
|
753
829
|
}
|
|
754
830
|
},
|
|
755
831
|
"historical-top-holders": {
|
|
756
832
|
"endpoint": "/api/v1beta1/tgm/historical-top-holders",
|
|
757
833
|
"description": "Historical top holders of a token at a snapshot date",
|
|
758
834
|
"options": {
|
|
759
|
-
"token-address": {
|
|
760
|
-
|
|
761
|
-
|
|
835
|
+
"token-address": {
|
|
836
|
+
"required": true,
|
|
837
|
+
"description": "Token address"
|
|
838
|
+
},
|
|
839
|
+
"as-of-date": {
|
|
840
|
+
"required": true,
|
|
841
|
+
"description": "Snapshot date (YYYY-MM-DD)"
|
|
842
|
+
},
|
|
843
|
+
"chain": {
|
|
844
|
+
"default": "solana",
|
|
845
|
+
"description": "Chain"
|
|
846
|
+
}
|
|
762
847
|
}
|
|
763
848
|
},
|
|
764
849
|
"historical-who-bought-sold": {
|
|
765
850
|
"endpoint": "/api/v1beta1/tgm/historical-who-bought-sold",
|
|
766
851
|
"description": "Historical buyers/sellers of a token",
|
|
767
852
|
"options": {
|
|
768
|
-
"token-address": {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
"
|
|
853
|
+
"token-address": {
|
|
854
|
+
"required": true,
|
|
855
|
+
"description": "Token address"
|
|
856
|
+
},
|
|
857
|
+
"from-date": {
|
|
858
|
+
"required": true,
|
|
859
|
+
"description": "Start of date range (YYYY-MM-DD)"
|
|
860
|
+
},
|
|
861
|
+
"to-date": {
|
|
862
|
+
"required": true,
|
|
863
|
+
"description": "End of date range (YYYY-MM-DD)"
|
|
864
|
+
},
|
|
865
|
+
"buy-or-sell": {
|
|
866
|
+
"default": "BUY",
|
|
867
|
+
"description": "BUY or SELL"
|
|
868
|
+
},
|
|
869
|
+
"chain": {
|
|
870
|
+
"default": "solana",
|
|
871
|
+
"description": "Chain"
|
|
872
|
+
}
|
|
773
873
|
}
|
|
774
874
|
},
|
|
775
875
|
"historical-smart-money-balances": {
|
|
776
876
|
"endpoint": "/api/v1beta1/smart-money/historical-token-balances",
|
|
777
877
|
"description": "Historical smart money token balances at a snapshot date (no order_by)",
|
|
778
878
|
"options": {
|
|
779
|
-
"as-of-date": {
|
|
780
|
-
|
|
879
|
+
"as-of-date": {
|
|
880
|
+
"required": true,
|
|
881
|
+
"description": "Snapshot date (YYYY-MM-DD)"
|
|
882
|
+
},
|
|
883
|
+
"chains": {
|
|
884
|
+
"default": "solana",
|
|
885
|
+
"description": "Comma-separated chains"
|
|
886
|
+
}
|
|
781
887
|
}
|
|
782
888
|
},
|
|
783
889
|
"historical-token-screener": {
|
|
784
890
|
"endpoint": "/api/v1beta1/token-screener/historical",
|
|
785
891
|
"description": "Historical token screener over a trailing window",
|
|
786
892
|
"options": {
|
|
787
|
-
"timeframe-days": {
|
|
788
|
-
|
|
789
|
-
|
|
893
|
+
"timeframe-days": {
|
|
894
|
+
"required": true,
|
|
895
|
+
"type": "number",
|
|
896
|
+
"description": "Trailing window size in days"
|
|
897
|
+
},
|
|
898
|
+
"to-date": {
|
|
899
|
+
"required": true,
|
|
900
|
+
"description": "End date for the window (YYYY-MM-DD)"
|
|
901
|
+
},
|
|
902
|
+
"chains": {
|
|
903
|
+
"default": "solana",
|
|
904
|
+
"description": "Comma-separated chains"
|
|
905
|
+
}
|
|
790
906
|
}
|
|
791
907
|
},
|
|
792
908
|
"historical-wallet-balances": {
|
|
793
909
|
"endpoint": "/api/v1beta1/profiler/address/historical-token-balances",
|
|
794
910
|
"description": "Historical token balances for a wallet at a snapshot date",
|
|
795
911
|
"options": {
|
|
796
|
-
"address": {
|
|
797
|
-
|
|
798
|
-
|
|
912
|
+
"address": {
|
|
913
|
+
"required": true,
|
|
914
|
+
"description": "Wallet address"
|
|
915
|
+
},
|
|
916
|
+
"as-of-date": {
|
|
917
|
+
"required": true,
|
|
918
|
+
"description": "Snapshot date (YYYY-MM-DD)"
|
|
919
|
+
},
|
|
920
|
+
"chain": {
|
|
921
|
+
"default": "ethereum",
|
|
922
|
+
"description": "Chain"
|
|
923
|
+
}
|
|
799
924
|
}
|
|
800
925
|
},
|
|
801
926
|
"historical-tx-lookup": {
|
|
802
927
|
"endpoint": "/api/v1beta1/profiler/historical-transaction-lookup",
|
|
803
928
|
"description": "Lookup a historical transaction by hash",
|
|
804
929
|
"options": {
|
|
805
|
-
"transaction-hash": {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
930
|
+
"transaction-hash": {
|
|
931
|
+
"required": true,
|
|
932
|
+
"description": "Transaction hash (0x-prefixed, 66 chars)"
|
|
933
|
+
},
|
|
934
|
+
"as-of-date": {
|
|
935
|
+
"required": true,
|
|
936
|
+
"description": "Reference date for label and pricing resolution (YYYY-MM-DD)"
|
|
937
|
+
},
|
|
938
|
+
"block-timestamp": {
|
|
939
|
+
"description": "Block timestamp (YYYY-MM-DD HH:MM:SS) \u2014 skips slow hash-resolution step if provided"
|
|
940
|
+
},
|
|
941
|
+
"chain": {
|
|
942
|
+
"default": "ethereum",
|
|
943
|
+
"description": "Chain (ethereum, bnb, base)"
|
|
944
|
+
}
|
|
809
945
|
}
|
|
810
946
|
},
|
|
811
947
|
"historical-wallet-transactions": {
|
|
812
948
|
"endpoint": "/api/v1beta1/profiler/address/historical-transactions",
|
|
813
949
|
"description": "Historical transactions for a wallet at a snapshot date",
|
|
814
950
|
"options": {
|
|
815
|
-
"address": {
|
|
816
|
-
|
|
817
|
-
|
|
951
|
+
"address": {
|
|
952
|
+
"required": true,
|
|
953
|
+
"description": "Wallet address"
|
|
954
|
+
},
|
|
955
|
+
"as-of-date": {
|
|
956
|
+
"required": true,
|
|
957
|
+
"description": "Snapshot date (YYYY-MM-DD)"
|
|
958
|
+
},
|
|
959
|
+
"chain": {
|
|
960
|
+
"default": "ethereum",
|
|
961
|
+
"description": "Chain"
|
|
962
|
+
}
|
|
818
963
|
}
|
|
819
964
|
}
|
|
820
965
|
}
|
|
@@ -971,7 +1116,7 @@
|
|
|
971
1116
|
},
|
|
972
1117
|
"to-chain": {
|
|
973
1118
|
"type": "string",
|
|
974
|
-
"description": "Destination blockchain for cross-chain swap (solana or base). Omit for same-chain. At least one side must be USDC or a native token (ETH, SOL). Non-native to non-native is not supported
|
|
1119
|
+
"description": "Destination blockchain for cross-chain swap (solana or base). Omit for same-chain. At least one side must be USDC or a native token (ETH, SOL). Non-native to non-native is not supported \u2014 swap to USDC first, then bridge. Bridge providers (Li.Fi or Relay) are selected automatically based on best price. Sub-dollar swaps are supported via Relay."
|
|
975
1120
|
},
|
|
976
1121
|
"from": {
|
|
977
1122
|
"type": "string",
|
|
@@ -1047,7 +1192,7 @@
|
|
|
1047
1192
|
},
|
|
1048
1193
|
"aggregator": {
|
|
1049
1194
|
"type": "string",
|
|
1050
|
-
"description": "lifi or relay. Overrides auto-detection
|
|
1195
|
+
"description": "lifi or relay. Overrides auto-detection \u2014 use when polling from a different machine or after the 30-day local record TTL has expired."
|
|
1051
1196
|
}
|
|
1052
1197
|
}
|
|
1053
1198
|
},
|
|
@@ -1101,7 +1246,9 @@
|
|
|
1101
1246
|
"description": "Wallet name (or \"walletconnect\"/\"wc\")"
|
|
1102
1247
|
}
|
|
1103
1248
|
},
|
|
1104
|
-
"chains": [
|
|
1249
|
+
"chains": [
|
|
1250
|
+
"solana"
|
|
1251
|
+
],
|
|
1105
1252
|
"prerequisites": [
|
|
1106
1253
|
"A Solana wallet must be configured. Run: nansen wallet create"
|
|
1107
1254
|
]
|
|
@@ -1141,7 +1288,9 @@
|
|
|
1141
1288
|
"description": "Wallet name (or \"walletconnect\"/\"wc\")"
|
|
1142
1289
|
}
|
|
1143
1290
|
},
|
|
1144
|
-
"chains": [
|
|
1291
|
+
"chains": [
|
|
1292
|
+
"solana"
|
|
1293
|
+
]
|
|
1145
1294
|
},
|
|
1146
1295
|
"cancel": {
|
|
1147
1296
|
"description": "Cancel an open limit order",
|
|
@@ -1156,7 +1305,9 @@
|
|
|
1156
1305
|
"description": "Wallet name (or \"walletconnect\"/\"wc\")"
|
|
1157
1306
|
}
|
|
1158
1307
|
},
|
|
1159
|
-
"chains": [
|
|
1308
|
+
"chains": [
|
|
1309
|
+
"solana"
|
|
1310
|
+
]
|
|
1160
1311
|
},
|
|
1161
1312
|
"update": {
|
|
1162
1313
|
"description": "Update trigger price or slippage on an existing order",
|
|
@@ -1179,7 +1330,9 @@
|
|
|
1179
1330
|
"description": "Wallet name (or \"walletconnect\"/\"wc\")"
|
|
1180
1331
|
}
|
|
1181
1332
|
},
|
|
1182
|
-
"chains": [
|
|
1333
|
+
"chains": [
|
|
1334
|
+
"solana"
|
|
1335
|
+
]
|
|
1183
1336
|
}
|
|
1184
1337
|
}
|
|
1185
1338
|
}
|