nansen-cli 1.5.1 → 1.7.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/AGENTS.md +222 -0
- package/README.md +36 -2
- package/SKILL.md +25 -21
- package/TODO.md +17 -0
- package/package.json +1 -1
- package/src/api.js +101 -29
- package/src/cli.js +65 -18
- package/src/crypto.js +215 -0
- package/src/trading.js +1081 -0
- package/src/transfer.js +723 -0
- package/src/wallet.js +764 -0
- package/src/x402-evm.js +207 -0
- package/src/x402-svm.js +474 -0
- package/src/x402.js +205 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# AGENTS.md — Agent Quick Start
|
|
2
|
+
|
|
3
|
+
> **This file is for AI agents (OpenClaw, Claude Code, Cursor, etc.) that need to query Nansen data on behalf of their human.** If you're a human, see [README.md](README.md).
|
|
4
|
+
|
|
5
|
+
## CLI vs REST API
|
|
6
|
+
|
|
7
|
+
**Prefer the [REST API](https://docs.nansen.ai) for most agent use cases** — no install step, no dependency issues.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Direct API call — no CLI needed
|
|
11
|
+
curl -s -X POST https://api.nansen.ai/api/v1/token-screener \
|
|
12
|
+
-H "apiKey: YOUR_KEY" \
|
|
13
|
+
-H "Content-Type: application/json" \
|
|
14
|
+
-d '{"chain":"solana","pagination":{"page":1,"per_page":5}}' | jq .
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use this CLI guide if the CLI is already installed or your human specifically wants CLI usage. The CLI adds `--pretty`, `--table`, `--fields`, `--stream`, built-in retries, and schema introspection.
|
|
18
|
+
|
|
19
|
+
## Install & Auth in One Shot
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Install
|
|
23
|
+
npm install -g nansen-cli
|
|
24
|
+
|
|
25
|
+
# Auth — pick ONE method:
|
|
26
|
+
# Method 1: Interactive (human needs to paste key)
|
|
27
|
+
nansen login
|
|
28
|
+
|
|
29
|
+
# Method 2: Environment variable (no interaction needed)
|
|
30
|
+
export NANSEN_API_KEY=<key>
|
|
31
|
+
|
|
32
|
+
# Method 3: Config file (write directly — no validation call burned)
|
|
33
|
+
mkdir -p ~/.nansen && echo '{"apiKey":"<key>","baseUrl":"https://api.nansen.ai"}' > ~/.nansen/config.json && chmod 600 ~/.nansen/config.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Get an API key:** [app.nansen.ai/api](https://app.nansen.ai/api)
|
|
37
|
+
|
|
38
|
+
### Auth Priority
|
|
39
|
+
|
|
40
|
+
1. `NANSEN_API_KEY` env var (highest)
|
|
41
|
+
2. `~/.nansen/config.json` file
|
|
42
|
+
3. Prompt (interactive only)
|
|
43
|
+
|
|
44
|
+
### Common Auth Pitfall
|
|
45
|
+
|
|
46
|
+
`nansen login` validates your key by making a real API call (burns 1 credit). If you already know the key is valid, writing `~/.nansen/config.json` directly is cheaper.
|
|
47
|
+
|
|
48
|
+
## Verify It Works
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# This is cheap and fast:
|
|
52
|
+
nansen schema | head -1
|
|
53
|
+
# Expected: {"version":"1.x.x","commands":{...}}
|
|
54
|
+
|
|
55
|
+
# This burns a credit but proves API access:
|
|
56
|
+
nansen token screener --chain solana --limit 1
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Agent-Optimized Patterns
|
|
60
|
+
|
|
61
|
+
### 1. Always Use `--fields` to Reduce Token Burn
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# ❌ Returns everything (huge JSON, wastes agent context)
|
|
65
|
+
nansen smart-money netflow --chain solana
|
|
66
|
+
|
|
67
|
+
# ✅ Only what you need
|
|
68
|
+
nansen smart-money netflow --chain solana --fields token_symbol,net_flow_usd,chain --limit 10
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 2. Use Schema for Self-Discovery
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Don't guess commands — introspect:
|
|
75
|
+
nansen schema --pretty # All commands
|
|
76
|
+
nansen schema smart-money --pretty # One command's options & return fields
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3. Parse Errors Programmatically
|
|
80
|
+
|
|
81
|
+
Every response has `success: true/false`. Errors include `code` for routing:
|
|
82
|
+
|
|
83
|
+
| Code | What To Do |
|
|
84
|
+
|------|------------|
|
|
85
|
+
| `CREDITS_EXHAUSTED` | **Stop all calls.** Tell the human. |
|
|
86
|
+
| `RATE_LIMITED` | Wait. Auto-retry handles this. |
|
|
87
|
+
| `UNSUPPORTED_FILTER` | Remove the filter, retry without it. |
|
|
88
|
+
| `UNAUTHORIZED` | Key is wrong. Re-auth. |
|
|
89
|
+
| `INVALID_ADDRESS` | Check address format for the chain. |
|
|
90
|
+
|
|
91
|
+
### 4. Budget Credits
|
|
92
|
+
|
|
93
|
+
- Most calls cost 1 credit
|
|
94
|
+
- `profiler labels` + `profiler balance` are expensive — batch 3-4 at a time
|
|
95
|
+
- `schema`, `help`, `cache` are free (no API key needed)
|
|
96
|
+
- If you get `CREDITS_EXHAUSTED`, **stop immediately** — don't retry
|
|
97
|
+
|
|
98
|
+
### 5. Use `--stream` for Large Results
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# NDJSON mode — process line by line, don't buffer giant arrays
|
|
102
|
+
nansen token dex-trades --chain solana --limit 100 --stream
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Pagination
|
|
106
|
+
|
|
107
|
+
The CLI exposes `--limit N` which maps to `{page: 1, per_page: N}` in the API request. **There is no `--page` flag** — the CLI always fetches page 1. To access later pages, use the REST API directly.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# CLI: page 1 only, up to N results
|
|
111
|
+
nansen smart-money netflow --chain solana --limit 50
|
|
112
|
+
|
|
113
|
+
# REST API: full pagination control
|
|
114
|
+
curl -s -X POST https://api.nansen.ai/api/v1/smart-money/netflow \
|
|
115
|
+
-H "apiKey: $NANSEN_API_KEY" -H "Content-Type: application/json" \
|
|
116
|
+
-d '{"chains":["solana"],"pagination":{"page":2,"per_page":50}}'
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Pagination key inconsistency:** Profiler endpoints (which use the beta API) take `recordsPerPage` instead of `per_page`. The CLI sends the right key automatically based on the command, but if you're calling the REST API directly, check which key each endpoint expects.
|
|
120
|
+
|
|
121
|
+
**Detecting the last page:** The raw API response does not include a `total_pages` or `has_more` field in the CLI-visible output. Reliable heuristic: if the number of results returned is less than your `--limit`, you're on the last page.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Request 50, get 23 back → last page
|
|
125
|
+
nansen token holders --token <addr> --chain solana --limit 50
|
|
126
|
+
# Check: .data.data.length (or .data.results.length) < 50 → done
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**`profiler perp-positions` has no pagination** — the API ignores the pagination parameter for this endpoint.
|
|
130
|
+
|
|
131
|
+
## Output Parsing Gotchas
|
|
132
|
+
|
|
133
|
+
### Response envelope
|
|
134
|
+
|
|
135
|
+
Every CLI response is wrapped in a standard envelope:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{ "success": true, "data": <raw_api_response> }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Errors follow a different shape:
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{ "success": false, "error": "message", "code": "ERROR_CODE", "status": 401, "details": {...} }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Raw API response shapes vary by endpoint
|
|
148
|
+
|
|
149
|
+
The `data` field inside the envelope is the raw JSON from the Nansen API. Its internal structure differs across endpoints — there is no single canonical key for the results array:
|
|
150
|
+
|
|
151
|
+
| Shape | Example endpoints |
|
|
152
|
+
|-------|------------------|
|
|
153
|
+
| `data.data` (array) | token screener |
|
|
154
|
+
| `data.results` (array) | entity search |
|
|
155
|
+
| `data.data.results` (array) | most profiler endpoints |
|
|
156
|
+
| `data.netflows` | smart-money netflow |
|
|
157
|
+
| `data.trades` | smart-money dex-trades |
|
|
158
|
+
| `data.holdings` | smart-money holdings |
|
|
159
|
+
| `data.holders` | token holders |
|
|
160
|
+
|
|
161
|
+
When parsing, check for the array at each level. `--table` and `--stream` handle this automatically, but if you're parsing raw JSON with `jq`, probe the shape first:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
nansen smart-money netflow --chain solana | jq 'keys, .data | keys'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `--fields` applies to the entire response tree
|
|
168
|
+
|
|
169
|
+
`--fields token_symbol,net_flow_usd` strips everything except those keys from the **entire** response, including the `success` and `data` wrapper fields. The result will be a bare object containing only the matched keys found anywhere in the tree.
|
|
170
|
+
|
|
171
|
+
### Client-side vs server-side filtering
|
|
172
|
+
|
|
173
|
+
`token screener --search <term>` is **client-side**: the CLI fetches up to 500 results from the server, then filters locally. Set `--limit` higher than your expected result count when using `--search`.
|
|
174
|
+
|
|
175
|
+
### Fields absent for some chain/token combinations
|
|
176
|
+
|
|
177
|
+
Some fields are only populated for specific chains or tokens:
|
|
178
|
+
- `smart_money_holders` — absent for tokens without smart money tracking
|
|
179
|
+
- Flow intelligence fields — may all be `0` for illiquid tokens (not an error)
|
|
180
|
+
- Perp endpoints (`--symbol`) only work for Hyperliquid; `--token` is for on-chain tokens
|
|
181
|
+
|
|
182
|
+
## Chains Quick Reference
|
|
183
|
+
|
|
184
|
+
`ethereum` `solana` `base` `bnb` `arbitrum` `polygon` `optimism` `avalanche` `linea` `scroll` `mantle` `ronin` `sei` `plasma` `sonic` `monad` `hyperevm` `iotaevm`
|
|
185
|
+
|
|
186
|
+
> Run `nansen schema` to get the current chain list (source of truth).
|
|
187
|
+
|
|
188
|
+
## Troubleshooting
|
|
189
|
+
|
|
190
|
+
### Quick fixes
|
|
191
|
+
|
|
192
|
+
| Symptom | Fix |
|
|
193
|
+
|---------|-----|
|
|
194
|
+
| `command not found: nansen` | `npm install -g nansen-cli` or `npx nansen-cli` |
|
|
195
|
+
| `UNAUTHORIZED` after login | Check `cat ~/.nansen/config.json` — key may not have saved. Write it directly. |
|
|
196
|
+
| Login hangs or fails | Skip `nansen login`, write config directly (see Install & Auth above) |
|
|
197
|
+
| Huge JSON response | Use `--fields` to select only needed columns |
|
|
198
|
+
| Perp endpoints empty or erroring | Use `--symbol BTC` not `--token`. Perp endpoints are Hyperliquid-only. |
|
|
199
|
+
| JUP DCA returns error | Solana-only endpoint |
|
|
200
|
+
| `UNSUPPORTED_FILTER` on token holders | Not all tokens have smart money data. Remove `--smart-money` and retry. |
|
|
201
|
+
| `CREDITS_EXHAUSTED` | Stop all calls. Check [app.nansen.ai](https://app.nansen.ai). No retry will help. |
|
|
202
|
+
|
|
203
|
+
### Error codes
|
|
204
|
+
|
|
205
|
+
| Code | What to do |
|
|
206
|
+
|------|------------|
|
|
207
|
+
| `CREDITS_EXHAUSTED` | **Stop all calls.** Tell the human. Check dashboard. |
|
|
208
|
+
| `RATE_LIMITED` | Wait — auto-retry handles this by default. |
|
|
209
|
+
| `UNSUPPORTED_FILTER` | Remove the filter, retry without it. |
|
|
210
|
+
| `UNAUTHORIZED` | Key is wrong or missing. Re-auth. |
|
|
211
|
+
| `INVALID_ADDRESS` | Check address format matches the chain (EVM: `0x...`, Solana: Base58). |
|
|
212
|
+
|
|
213
|
+
### Known endpoint quirks
|
|
214
|
+
|
|
215
|
+
- **`token holders --smart-money`** — Fails with `UNSUPPORTED_FILTER` for tokens without smart money tracking (e.g., WCT on Optimism). Not all tokens have this data. Do not retry.
|
|
216
|
+
- **`token flow-intelligence`** — May return all-zero flows for tokens without significant smart money activity. Normal, not an error.
|
|
217
|
+
- **`profiler labels` and `profiler balance`** consume credits. Budget ~20 calls per session. Batch calls in groups of 3–4.
|
|
218
|
+
- **`profiler perp-positions`** — No pagination support; the API ignores the pagination parameter.
|
|
219
|
+
|
|
220
|
+
## For OpenClaw / Skill Users
|
|
221
|
+
|
|
222
|
+
If you installed this as an OpenClaw skill, the `SKILL.md` file has the skill interface. This `AGENTS.md` covers the CLI directly. Both work.
|
package/README.md
CHANGED
|
@@ -27,7 +27,20 @@ npm link
|
|
|
27
27
|
|
|
28
28
|
## Configuration
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
### For AI Agents (Recommended)
|
|
31
|
+
|
|
32
|
+
Use the [AI Agent Setup](https://app.nansen.ai/auth/agent-setup) flow:
|
|
33
|
+
|
|
34
|
+
1. Your agent will ask you to visit: **[app.nansen.ai/auth/agent-setup](https://app.nansen.ai/auth/agent-setup)**
|
|
35
|
+
2. Sign in with your Nansen account
|
|
36
|
+
3. Copy the message shown
|
|
37
|
+
4. Paste it back to your agent
|
|
38
|
+
|
|
39
|
+
Your agent saves the key and handles everything else automatically.
|
|
40
|
+
|
|
41
|
+
### Manual Setup
|
|
42
|
+
|
|
43
|
+
**Option 1: Interactive login**
|
|
31
44
|
```bash
|
|
32
45
|
nansen login
|
|
33
46
|
# Enter your API key when prompted
|
|
@@ -123,6 +136,21 @@ Deep analytics for any token.
|
|
|
123
136
|
|------------|-------------|
|
|
124
137
|
| `defi` | DeFi holdings across protocols |
|
|
125
138
|
|
|
139
|
+
### `search` - Search
|
|
140
|
+
|
|
141
|
+
Search for tokens and entities across Nansen.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
nansen search "uniswap" --pretty
|
|
145
|
+
nansen search "uniswap" --type token --chain ethereum
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Option | Description |
|
|
149
|
+
|--------|-------------|
|
|
150
|
+
| `--type` | Filter by result type: `token`, `entity`, or `any` (default) |
|
|
151
|
+
| `--chain` | Filter by chain |
|
|
152
|
+
| `--limit` | Max results, 1-50 (default: 25) |
|
|
153
|
+
|
|
126
154
|
### `schema` - Schema Discovery
|
|
127
155
|
|
|
128
156
|
Output JSON schema for agent introspection. No API key required.
|
|
@@ -177,6 +205,9 @@ nansen cache clear
|
|
|
177
205
|
|
|
178
206
|
This CLI is built specifically for AI agents. Every design decision prioritizes agent usability.
|
|
179
207
|
|
|
208
|
+
**Getting Started:**
|
|
209
|
+
Direct your users to [app.nansen.ai/auth/agent-setup](https://app.nansen.ai/auth/agent-setup) for seamless authentication. See [AI Agent Access](https://docs.nansen.ai/reference/ai-agent-access) for full documentation.
|
|
210
|
+
|
|
180
211
|
**Why agents love it:**
|
|
181
212
|
- **Structured Output**: All responses are JSON with consistent schema — no parsing HTML or unstructured text
|
|
182
213
|
- **Predictable Errors**: Errors include status codes and actionable details agents can handle programmatically
|
|
@@ -248,6 +279,8 @@ NANSEN_API_KEY=your-key npm run test:live
|
|
|
248
279
|
|
|
249
280
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines.
|
|
250
281
|
|
|
282
|
+
**AI agents:** See [AGENTS.md](AGENTS.md) for the agent quick-start (install, auth, patterns, troubleshooting).
|
|
283
|
+
|
|
251
284
|
**AI contributors:** See [CLAUDE.md](CLAUDE.md) for agent-specific guidance on contributing to this repo.
|
|
252
285
|
|
|
253
286
|
## API Coverage
|
|
@@ -258,7 +291,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines.
|
|
|
258
291
|
| Profiler | 11 | 100% |
|
|
259
292
|
| Token God Mode | 12 | 100% |
|
|
260
293
|
| Portfolio | 1 | 100% |
|
|
261
|
-
|
|
|
294
|
+
| Search | 1 | 100% |
|
|
295
|
+
| **Total** | **31** | **100%** |
|
|
262
296
|
|
|
263
297
|
## License
|
|
264
298
|
|
package/SKILL.md
CHANGED
|
@@ -12,21 +12,40 @@ compatibility: Requires Node.js 18+. Needs NANSEN_API_KEY environment variable o
|
|
|
12
12
|
|
|
13
13
|
Command-line interface for the [Nansen API](https://docs.nansen.ai) - onchain analytics for crypto investors and AI agents.
|
|
14
14
|
|
|
15
|
+
## CLI vs Direct API
|
|
16
|
+
|
|
17
|
+
If your agent has HTTP access (curl, fetch), you can skip the CLI and hit the [Nansen REST API](https://docs.nansen.ai) directly with `apiKey` header auth. The CLI is most useful for terminal-native agents (Claude Code, Codex, Cursor) that benefit from `--pretty`, `--table`, `--fields`, built-in retries, and schema introspection.
|
|
18
|
+
|
|
15
19
|
## Setup
|
|
16
20
|
|
|
17
21
|
```bash
|
|
18
22
|
# Install globally
|
|
19
23
|
npm install -g nansen-cli
|
|
20
24
|
|
|
21
|
-
# Authenticate
|
|
22
|
-
|
|
25
|
+
# Authenticate — pick the method that works for your context:
|
|
26
|
+
|
|
27
|
+
# Option A: Non-interactive (best for agents — no prompts, no wasted credits)
|
|
28
|
+
mkdir -p ~/.nansen && echo '{"apiKey":"YOUR_KEY","baseUrl":"https://api.nansen.ai"}' > ~/.nansen/config.json && chmod 600 ~/.nansen/config.json
|
|
23
29
|
|
|
24
|
-
#
|
|
30
|
+
# Option B: Environment variable (good for CI/scripts)
|
|
25
31
|
export NANSEN_API_KEY=your-api-key
|
|
32
|
+
|
|
33
|
+
# Option C: Interactive login (burns 1 credit to validate)
|
|
34
|
+
nansen login
|
|
26
35
|
```
|
|
27
36
|
|
|
28
37
|
Get your API key at [app.nansen.ai/api](https://app.nansen.ai/api).
|
|
29
38
|
|
|
39
|
+
### Verify Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Free check (no API key needed):
|
|
43
|
+
nansen schema | head -1
|
|
44
|
+
|
|
45
|
+
# Full check (uses 1 credit):
|
|
46
|
+
nansen token screener --chain solana --limit 1
|
|
47
|
+
```
|
|
48
|
+
|
|
30
49
|
## Commands
|
|
31
50
|
|
|
32
51
|
### Smart Money
|
|
@@ -82,7 +101,7 @@ nansen portfolio defi --wallet 0x123...
|
|
|
82
101
|
|
|
83
102
|
## Supported Chains
|
|
84
103
|
|
|
85
|
-
ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll,
|
|
104
|
+
ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, mantle, ronin, sei, plasma, sonic, monad, hyperevm, iotaevm
|
|
86
105
|
|
|
87
106
|
## Smart Money Labels
|
|
88
107
|
|
|
@@ -96,24 +115,9 @@ nansen schema --pretty
|
|
|
96
115
|
nansen schema smart-money --pretty
|
|
97
116
|
```
|
|
98
117
|
|
|
99
|
-
##
|
|
100
|
-
|
|
101
|
-
### Chain/Token-Specific Limitations
|
|
102
|
-
- `token holders --smart-money` — Fails with `UNSUPPORTED_FILTER` for tokens without smart money tracking (e.g., WCT on Optimism). Not all tokens have smart money data. Do not retry.
|
|
103
|
-
- `token flow-intelligence` — May return all-zero flows for tokens without significant smart money activity. This is normal, not an error.
|
|
104
|
-
|
|
105
|
-
### Credit Management
|
|
106
|
-
- `profiler labels` and `profiler balance` consume credits. Budget ~20 calls per session.
|
|
107
|
-
- `Insufficient credits` (403, code `CREDITS_EXHAUSTED`) is a hard stop — no retry will help.
|
|
108
|
-
- Check your Nansen dashboard for credit balance: [app.nansen.ai](https://app.nansen.ai).
|
|
109
|
-
- Run balance checks in batches of 3-4 to avoid burning credits on rate-limit retries.
|
|
118
|
+
## Troubleshooting
|
|
110
119
|
|
|
111
|
-
|
|
112
|
-
| Code | Meaning | Action |
|
|
113
|
-
|------|---------|--------|
|
|
114
|
-
| `UNSUPPORTED_FILTER` | Filter not available for this token/chain | Remove the filter and retry, or skip this token |
|
|
115
|
-
| `CREDITS_EXHAUSTED` | API credits depleted | Stop all API calls. Check dashboard. |
|
|
116
|
-
| `RATE_LIMITED` | Too many requests (429) | Wait and retry (automatic with default retry) |
|
|
120
|
+
See [AGENTS.md — Troubleshooting](AGENTS.md#troubleshooting) for the full troubleshooting guide, including error codes, known endpoint quirks, and pagination gotchas.
|
|
117
121
|
|
|
118
122
|
## Examples
|
|
119
123
|
|
package/TODO.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
> **Built by agents, for agents.** We prioritize improvements that create the best possible AI agent experience.
|
|
4
4
|
|
|
5
|
+
## P0 - Trading Agent Support
|
|
6
|
+
|
|
7
|
+
### Wallet Management
|
|
8
|
+
- [ ] `nansen wallet create` — generate a local wallet (per chain), store key securely on disk
|
|
9
|
+
- [ ] `nansen wallet address` — print the wallet address for funding
|
|
10
|
+
- [ ] `nansen wallet balance` — check wallet balance
|
|
11
|
+
|
|
12
|
+
### Trading Execution
|
|
13
|
+
- [x] `nansen quote` — get a quote for a DEX swap (chain, tokens, amount)
|
|
14
|
+
- [x] `nansen execute` — sign and submit a trade via Nansen API (takes quote-id)
|
|
15
|
+
- [ ] ⚠️ EVM transaction signing — requires thorough security review before production use
|
|
16
|
+
|
|
17
|
+
> These commands are required for `nansen-trading-agent` — the autonomous trading sub-agent.
|
|
18
|
+
> Wallet commands should keep the agent self-contained with zero external dependencies.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
5
22
|
## P2 - Nice to Have
|
|
6
23
|
|
|
7
24
|
### Test Coverage Gaps
|
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,40 @@ 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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
322
|
+
|
|
323
|
+
// Ensure baseUrl default (config file from older versions may omit it)
|
|
324
|
+
if (!config.baseUrl) {
|
|
325
|
+
config.baseUrl = 'https://api.nansen.ai';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Env vars override individual fields
|
|
329
|
+
if (process.env.NANSEN_API_KEY) {
|
|
330
|
+
config.apiKey = process.env.NANSEN_API_KEY;
|
|
331
|
+
}
|
|
332
|
+
if (process.env.NANSEN_BASE_URL) {
|
|
333
|
+
config.baseUrl = process.env.NANSEN_BASE_URL;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return config;
|
|
329
337
|
}
|
|
330
338
|
|
|
331
339
|
const config = loadConfig();
|
|
@@ -388,10 +396,11 @@ export class NansenAPI {
|
|
|
388
396
|
this.apiKey = apiKey || null;
|
|
389
397
|
this.baseUrl = baseUrl;
|
|
390
398
|
this.retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...options.retry };
|
|
391
|
-
this.cacheOptions = {
|
|
399
|
+
this.cacheOptions = {
|
|
392
400
|
enabled: options.cache?.enabled ?? false,
|
|
393
401
|
ttl: options.cache?.ttl ?? DEFAULT_CACHE_TTL
|
|
394
402
|
};
|
|
403
|
+
this.defaultHeaders = options.defaultHeaders || {};
|
|
395
404
|
}
|
|
396
405
|
|
|
397
406
|
static cleanBody(body) {
|
|
@@ -428,7 +437,10 @@ export class NansenAPI {
|
|
|
428
437
|
method: 'POST',
|
|
429
438
|
headers: {
|
|
430
439
|
'Content-Type': 'application/json',
|
|
440
|
+
'X-Client-Type': 'nansen-cli',
|
|
441
|
+
'X-Client-Version': packageVersion,
|
|
431
442
|
...(this.apiKey ? { 'apikey': this.apiKey } : {}),
|
|
443
|
+
...this.defaultHeaders,
|
|
432
444
|
...options.headers
|
|
433
445
|
},
|
|
434
446
|
body: JSON.stringify(NansenAPI.cleanBody(body))
|
|
@@ -482,7 +494,41 @@ export class NansenAPI {
|
|
|
482
494
|
} else if (code === ErrorCode.CREDITS_EXHAUSTED) {
|
|
483
495
|
message = message.replace(/\.+$/, '') + '. No retry will help. Check your Nansen dashboard for credit balance.';
|
|
484
496
|
} else if (code === ErrorCode.PAYMENT_REQUIRED) {
|
|
485
|
-
|
|
497
|
+
// Try x402 auto-payment with fallback across payment networks
|
|
498
|
+
if (!this.defaultHeaders['Payment-Signature']) {
|
|
499
|
+
try {
|
|
500
|
+
const { createPaymentSignatures } = await import('./x402.js');
|
|
501
|
+
for await (const { signature, network } of createPaymentSignatures(response, url)) {
|
|
502
|
+
const paidResponse = await fetch(url, {
|
|
503
|
+
method: 'POST',
|
|
504
|
+
headers: {
|
|
505
|
+
'Content-Type': 'application/json',
|
|
506
|
+
'X-Client-Type': 'nansen-cli',
|
|
507
|
+
'X-Client-Version': packageVersion,
|
|
508
|
+
'Payment-Signature': signature,
|
|
509
|
+
...this.defaultHeaders,
|
|
510
|
+
...options.headers,
|
|
511
|
+
},
|
|
512
|
+
body: JSON.stringify(NansenAPI.cleanBody(body)),
|
|
513
|
+
});
|
|
514
|
+
if (paidResponse.ok) {
|
|
515
|
+
const chain = network.startsWith('solana:') ? 'Solana' : 'Base';
|
|
516
|
+
console.error(`[x402] Paid via ${chain} USDC`);
|
|
517
|
+
// Check remaining balance and warn if low
|
|
518
|
+
try {
|
|
519
|
+
const { checkX402Balance } = await import('./x402.js');
|
|
520
|
+
const balance = await checkX402Balance(network);
|
|
521
|
+
if (balance !== null && balance < 0.25) {
|
|
522
|
+
console.error(`[x402] Warning: USDC balance low ($${balance.toFixed(2)}). Fund your wallet to avoid interruptions.`);
|
|
523
|
+
}
|
|
524
|
+
} catch { /* balance check is best-effort */ }
|
|
525
|
+
return await paidResponse.json();
|
|
526
|
+
}
|
|
527
|
+
// This payment option was rejected, try next
|
|
528
|
+
}
|
|
529
|
+
} catch { /* x402 auto-pay unavailable, fall through */ }
|
|
530
|
+
}
|
|
531
|
+
message = 'Payment required. To access this endpoint:\n • Set an API key: nansen login --api-key <key> (get one at https://app.nansen.ai/api)\n • Or pay per call: nansen wallet create, fund with USDC on Base or Solana (from $0.01/call, min $0.05 balance)\n • Docs: https://docs.x402.org';
|
|
486
532
|
const paymentHeader = response.headers.get('payment-required');
|
|
487
533
|
if (paymentHeader) {
|
|
488
534
|
try {
|
|
@@ -668,6 +714,20 @@ export class NansenAPI {
|
|
|
668
714
|
});
|
|
669
715
|
}
|
|
670
716
|
|
|
717
|
+
async generalSearch(params = {}) {
|
|
718
|
+
const { query, resultType = 'any', chain, limit = 25 } = params;
|
|
719
|
+
if (!query) {
|
|
720
|
+
throw new NansenError('Search query is required', ErrorCode.MISSING_PARAM);
|
|
721
|
+
}
|
|
722
|
+
const body = {
|
|
723
|
+
search_query: query,
|
|
724
|
+
result_type: resultType,
|
|
725
|
+
limit
|
|
726
|
+
};
|
|
727
|
+
if (chain) body.chain = chain;
|
|
728
|
+
return this.request('/api/v1/search/general', body);
|
|
729
|
+
}
|
|
730
|
+
|
|
671
731
|
async addressHistoricalBalances(params = {}) {
|
|
672
732
|
const { address, chain = 'ethereum', filters = {}, orderBy, pagination, days = 30 } = params;
|
|
673
733
|
if (address) {
|
|
@@ -954,8 +1014,20 @@ export class NansenAPI {
|
|
|
954
1014
|
});
|
|
955
1015
|
}
|
|
956
1016
|
|
|
1017
|
+
async tokenIndicators(params = {}) {
|
|
1018
|
+
const { tokenAddress, chain = 'ethereum' } = params;
|
|
1019
|
+
if (tokenAddress) {
|
|
1020
|
+
const validation = validateTokenAddress(tokenAddress, chain);
|
|
1021
|
+
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|
|
1022
|
+
}
|
|
1023
|
+
return this.request('/api/v1/tgm/indicators', {
|
|
1024
|
+
token_address: tokenAddress,
|
|
1025
|
+
chain
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
|
|
957
1029
|
async tokenInformation(params = {}) {
|
|
958
|
-
const { tokenAddress, chain = 'solana', timeframe = '
|
|
1030
|
+
const { tokenAddress, chain = 'solana', timeframe = '1d' } = params;
|
|
959
1031
|
if (tokenAddress) {
|
|
960
1032
|
const validation = validateTokenAddress(tokenAddress, chain);
|
|
961
1033
|
if (!validation.valid) throw new NansenError(validation.error, validation.code);
|