nansen-cli 1.19.0 → 1.21.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 +26 -0
- package/README.md +31 -4
- package/package.json +3 -1
- package/scripts/postinstall.js +179 -0
- package/skills/nansen-trading/SKILL.md +5 -1
- package/skills/nansen-wallet-manager/SKILL.md +60 -2
- package/src/api.js +5 -5
- package/src/cli.js +32 -24
- package/src/commands/agent.js +344 -0
- package/src/cost-cache.js +67 -0
- package/src/schema.json +76 -0
- package/src/wallet.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#315](https://github.com/nansen-ai/nansen-cli/pull/315) [`908fa0c`](https://github.com/nansen-ai/nansen-cli/commit/908fa0ccdf18cb79b7efb58b9b31f66e0afedff6) Thanks [@TimNooren](https://github.com/TimNooren)! - Add `nansen agent` command for the Nansen AI research agent with fast/expert modes, SSE streaming, conversation continuation, and JSON output.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#326](https://github.com/nansen-ai/nansen-cli/pull/326) [`1532ba4`](https://github.com/nansen-ai/nansen-cli/commit/1532ba420174ed7635a42641f0c1a2802077fdc0) Thanks [@TimNooren](https://github.com/TimNooren)! - Show API credit cost in research subcommand help text (fetched from OpenAPI spec, cached 24h).
|
|
12
|
+
|
|
13
|
+
- [#330](https://github.com/nansen-ai/nansen-cli/pull/330) [`a6b9b8f`](https://github.com/nansen-ai/nansen-cli/commit/a6b9b8fc7d291ee7375941bb275c224939338161) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Suppress misleading PASSWORD_REQUIRED error when `--provider privy` is specified. Privy wallets don't need a password — only the Privy-specific credentials error is now shown when PRIVY_APP_ID/PRIVY_APP_SECRET are missing.
|
|
14
|
+
|
|
15
|
+
- [#329](https://github.com/nansen-ai/nansen-cli/pull/329) [`f047833`](https://github.com/nansen-ai/nansen-cli/commit/f047833c7e6fb55cb713e0b69f82282fe87d4566) Thanks [@TimNooren](https://github.com/TimNooren)! - Limit deprecation warnings and update notices to help output only, keeping stdout/stderr clean for programmatic usage.
|
|
16
|
+
|
|
17
|
+
- [#332](https://github.com/nansen-ai/nansen-cli/pull/332) [`e9b6de1`](https://github.com/nansen-ai/nansen-cli/commit/e9b6de17fdba3f46733dff026c2495c095bfbf35) Thanks [@0xlaveen](https://github.com/0xlaveen)! - docs: add trading examples and Privy wallet setup to README
|
|
18
|
+
|
|
19
|
+
## 1.20.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- [#302](https://github.com/nansen-ai/nansen-cli/pull/302) [`3f0a5ab`](https://github.com/nansen-ai/nansen-cli/commit/3f0a5abad463c0386122efbe746809913aa823ba) Thanks [@arein](https://github.com/arein)! - Add post-install onboarding that interactively offers to install the Nansen AI coding skill and run a test query after `npm install -g nansen-cli`. Non-interactive environments (CI, piped stdin) receive a one-liner tip and are never blocked.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- [#313](https://github.com/nansen-ai/nansen-cli/pull/313) [`bb4d9e4`](https://github.com/nansen-ai/nansen-cli/commit/bb4d9e475158147645cae9b8bdd2555568a1e515) Thanks [@0xlaveen](https://github.com/0xlaveen)! - Update API key setup URL from app.nansen.ai/api to app.nansen.ai/auth/agent-setup across CLI help text, error messages, README, and postinstall script.
|
|
28
|
+
|
|
3
29
|
## 1.19.0
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -15,11 +15,13 @@ npx skills add nansen-ai/nansen-cli # load agent skill files
|
|
|
15
15
|
## Auth
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
nansen login
|
|
19
|
-
|
|
18
|
+
nansen login --api-key <key> # save key to ~/.nansen/config.json
|
|
19
|
+
nansen login --human # interactive prompt
|
|
20
|
+
export NANSEN_API_KEY=... # env var (highest priority)
|
|
21
|
+
nansen logout # remove saved key
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
Get your API key at [app.nansen.ai/
|
|
24
|
+
Get your API key at [app.nansen.ai/auth/agent-setup](https://app.nansen.ai/auth/agent-setup).
|
|
23
25
|
|
|
24
26
|
## Commands
|
|
25
27
|
|
|
@@ -34,10 +36,35 @@ nansen schema [command] [--pretty] # full command reference (no API key neede
|
|
|
34
36
|
|
|
35
37
|
**Trade:** `quote`, `execute` — DEX swaps on Solana and Base.
|
|
36
38
|
|
|
37
|
-
**Wallet:** `create`, `list`, `show`, `export`, `default`, `delete`, `send` — local
|
|
39
|
+
**Wallet:** `create`, `list`, `show`, `export`, `default`, `delete`, `send` — local or Privy server-side wallets (EVM + Solana).
|
|
38
40
|
|
|
39
41
|
Run `nansen schema --pretty` for the full subcommand and field reference.
|
|
40
42
|
|
|
43
|
+
## Trading
|
|
44
|
+
|
|
45
|
+
DEX swaps on `solana` and `base`. Two-step: quote then execute.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
nansen trade quote --chain solana --from SOL --to USDC --amount 1000000000
|
|
49
|
+
nansen trade execute --quote <quoteId>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Amounts are in base units (lamports, wei). Common symbols (`SOL`, `ETH`, `USDC`, `USDT`) resolve automatically. A wallet is required — set one with `nansen wallet default <name>`.
|
|
53
|
+
|
|
54
|
+
## Wallet
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
nansen wallet create --name my-wallet # local keypair (EVM + Solana)
|
|
58
|
+
nansen wallet create --name my-wallet --provider privy # server-side via Privy
|
|
59
|
+
nansen wallet list
|
|
60
|
+
nansen wallet default <name>
|
|
61
|
+
nansen wallet send --wallet <name> --to <addr> --amount <n> --chain <chain>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Local wallets** are password-encrypted. Set `NANSEN_WALLET_PASSWORD` to skip the prompt.
|
|
65
|
+
|
|
66
|
+
**Privy wallets** are server-side — no password, no local key storage. Requires `PRIVY_APP_ID` and `PRIVY_APP_SECRET` env vars. Get credentials at [dashboard.privy.io](https://dashboard.privy.io).
|
|
67
|
+
|
|
41
68
|
## Key Options
|
|
42
69
|
|
|
43
70
|
| Option | Description |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nansen-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "Command-line interface for Nansen API - designed for AI agents",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
"src/**/*.json",
|
|
13
13
|
"!src/__tests__/**",
|
|
14
14
|
"skills/**",
|
|
15
|
+
"scripts/postinstall.js",
|
|
15
16
|
"CHANGELOG.md"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
19
|
+
"postinstall": "node scripts/postinstall.js",
|
|
18
20
|
"start": "node src/index.js",
|
|
19
21
|
"pretest": "node scripts/check-changeset.js",
|
|
20
22
|
"test": "vitest run",
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install onboarding for nansen-cli.
|
|
5
|
+
*
|
|
6
|
+
* Runs after `npm install -g nansen-cli` and offers two optional steps:
|
|
7
|
+
* 1. Install the Nansen AI coding skill (`npx skills add nansen-ai/nansen-cli`)
|
|
8
|
+
* 2. Check account status to verify the API key works (0 credits)
|
|
9
|
+
*
|
|
10
|
+
* Non-interactive environments (CI, piped stdin) get a one-liner tip instead.
|
|
11
|
+
* Always exits 0 — onboarding failures must never break installation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createInterface } from "readline";
|
|
15
|
+
import { execFileSync, spawn } from "child_process";
|
|
16
|
+
import { existsSync, readFileSync } from "fs";
|
|
17
|
+
import { join, dirname } from "path";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
19
|
+
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
|
|
22
|
+
const BOLD = "\x1b[1m";
|
|
23
|
+
const DIM = "\x1b[2m";
|
|
24
|
+
const GREEN = "\x1b[32m";
|
|
25
|
+
const YELLOW = "\x1b[33m";
|
|
26
|
+
const CYAN = "\x1b[36m";
|
|
27
|
+
const RESET = "\x1b[0m";
|
|
28
|
+
|
|
29
|
+
const SKILL_REPO = "nansen-ai/nansen-cli";
|
|
30
|
+
const TEST_QUERY = ["account"];
|
|
31
|
+
const TEST_QUERY_DISPLAY = "nansen account";
|
|
32
|
+
|
|
33
|
+
// Path to the CLI entry point (works even if `nansen` bin isn't linked yet)
|
|
34
|
+
const CLI_ENTRY = join(__dirname, "..", "src", "index.js");
|
|
35
|
+
|
|
36
|
+
function log(msg = "") {
|
|
37
|
+
process.stderr.write(` ${msg}\n`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasTTY() {
|
|
41
|
+
return process.stdin.isTTY && process.stderr.isTTY;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function hasNpx() {
|
|
45
|
+
try {
|
|
46
|
+
execFileSync("npx", ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isLoggedIn() {
|
|
54
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
55
|
+
const configFile = join(home, ".nansen", "config.json");
|
|
56
|
+
if (!existsSync(configFile)) return false;
|
|
57
|
+
try {
|
|
58
|
+
const config = JSON.parse(readFileSync(configFile, "utf8"));
|
|
59
|
+
return !!(config.apiKey || config.api_key);
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isSkillInstalled() {
|
|
66
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
67
|
+
const locations = [
|
|
68
|
+
join(home, ".claude", "skills", "nansen-cli"),
|
|
69
|
+
join(home, ".claude", "skills", "nansen-ai--nansen-cli"),
|
|
70
|
+
];
|
|
71
|
+
return locations.some((loc) => existsSync(loc));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function prompt(question) {
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
77
|
+
let answered = false;
|
|
78
|
+
rl.on("close", () => { if (!answered) resolve(""); });
|
|
79
|
+
rl.question(question, (answer) => {
|
|
80
|
+
answered = true;
|
|
81
|
+
rl.close();
|
|
82
|
+
resolve(answer.trim());
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function runCommand(cmd, args) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
const child = spawn(cmd, args, { stdio: "inherit", shell: process.platform === "win32" });
|
|
90
|
+
child.on("close", (code) => resolve(code === 0));
|
|
91
|
+
child.on("error", () => resolve(false));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function installSkill() {
|
|
96
|
+
if (isSkillInstalled()) {
|
|
97
|
+
log(`${GREEN}✓${RESET} Nansen skill already installed.`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!hasNpx()) {
|
|
102
|
+
log(`${DIM}Tip: Run 'npx skills add ${SKILL_REPO}' to install the Nansen AI coding skill.${RESET}`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
log(`The Nansen skill lets AI coding agents (Cursor, Claude Code, etc.) query`);
|
|
107
|
+
log(`on-chain data, track smart money, and analyze tokens on your behalf.`);
|
|
108
|
+
const answer = await prompt(` Install Nansen skill for your AI coding agent? [Y/n] `);
|
|
109
|
+
|
|
110
|
+
if (/^n/i.test(answer)) {
|
|
111
|
+
log(`Skipped. You can install it later with: ${CYAN}npx skills add ${SKILL_REPO}${RESET}`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
log(`Installing Nansen skill...`);
|
|
116
|
+
const ok = await runCommand("npx", ["-y", "skills", "add", SKILL_REPO]);
|
|
117
|
+
if (!ok) {
|
|
118
|
+
log(`${YELLOW}Skill installation failed. You can retry with: npx skills add ${SKILL_REPO}${RESET}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function testQuery() {
|
|
123
|
+
if (!isLoggedIn()) {
|
|
124
|
+
log();
|
|
125
|
+
log(`Not logged in yet. Run ${CYAN}nansen login --api-key <key>${RESET} to authenticate.`);
|
|
126
|
+
log(`Get your API key at: ${CYAN}https://app.nansen.ai/auth/agent-setup${RESET}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
log();
|
|
131
|
+
log(`Your API key is configured. Let's verify it works.`);
|
|
132
|
+
const answer = await prompt(` Check account status? (${DIM}${TEST_QUERY_DISPLAY}${RESET}) [Y/n] `);
|
|
133
|
+
|
|
134
|
+
if (/^n/i.test(answer)) {
|
|
135
|
+
log(`Skipped. You're all set! Try: ${CYAN}nansen research smart-money netflow --chain solana${RESET}`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
log(`Running: ${DIM}${TEST_QUERY_DISPLAY}${RESET}`);
|
|
140
|
+
log();
|
|
141
|
+
// Use process.execPath + CLI_ENTRY so it works even if `nansen` bin isn't linked yet
|
|
142
|
+
const ok = await runCommand(process.execPath, [CLI_ENTRY, ...TEST_QUERY, "--pretty"]);
|
|
143
|
+
if (ok) {
|
|
144
|
+
log();
|
|
145
|
+
log(`${GREEN}✓${RESET} All set! Run ${CYAN}nansen help${RESET} to see all available commands.`);
|
|
146
|
+
} else {
|
|
147
|
+
log();
|
|
148
|
+
log(`${YELLOW}Query failed. Check your API key with: nansen login --api-key <key>${RESET}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function main() {
|
|
153
|
+
// Only run for global installs; skip local npm install / npm ci
|
|
154
|
+
if (process.env.npm_lifecycle_event === "postinstall" && process.env.npm_config_global !== "true") {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
log();
|
|
159
|
+
|
|
160
|
+
if (!hasTTY()) {
|
|
161
|
+
log(`${BOLD}Nansen CLI installed!${RESET}`);
|
|
162
|
+
log();
|
|
163
|
+
log(`Tip: Run '${CYAN}npx skills add ${SKILL_REPO}${RESET}' to install the Nansen AI coding skill.`);
|
|
164
|
+
log(`Tip: Run '${CYAN}nansen login --api-key <key>${RESET}' to authenticate.`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
log(`${BOLD}Nansen CLI installed!${RESET}`);
|
|
169
|
+
log();
|
|
170
|
+
|
|
171
|
+
await installSkill();
|
|
172
|
+
await testQuery();
|
|
173
|
+
|
|
174
|
+
log();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
main().catch(() => {
|
|
178
|
+
// Never fail installation due to onboarding errors
|
|
179
|
+
});
|
|
@@ -58,7 +58,9 @@ nansen trade execute --quote "$quote_id"
|
|
|
58
58
|
| ETH | Base | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` |
|
|
59
59
|
| USDC | Base | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
|
|
60
60
|
|
|
61
|
-
## Amounts are in base units
|
|
61
|
+
## Amounts are in base units — NEVER USD
|
|
62
|
+
|
|
63
|
+
`--amount` accepts **integer base units only** (lamports, wei, etc). It is never a USD value.
|
|
62
64
|
|
|
63
65
|
| Token | Decimals | 1 token = |
|
|
64
66
|
|-------|----------|-----------|
|
|
@@ -66,6 +68,8 @@ nansen trade execute --quote "$quote_id"
|
|
|
66
68
|
| ETH | 18 | `1000000000000000000` |
|
|
67
69
|
| USDC | 6 | `1000000` |
|
|
68
70
|
|
|
71
|
+
If the user says "$20 worth of X", you must convert USD → token amount → base units. For example, to buy $20 of SOL at $150/SOL: $20 ÷ $150 = 0.1333 SOL = 133,300,000 lamports → `--amount 133300000`. Use a price lookup (e.g. `nansen research token info`) to get the current price first.
|
|
72
|
+
|
|
69
73
|
## Flags
|
|
70
74
|
|
|
71
75
|
| Flag | Purpose |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nansen-wallet-manager
|
|
3
|
-
description: Wallet management — create, list, show, export, send, delete. Use when creating wallets, checking balances, or sending tokens.
|
|
3
|
+
description: Wallet management — create (local or Privy server-side), list, show, export, send, delete. Use when creating wallets, checking balances, or sending tokens.
|
|
4
4
|
metadata:
|
|
5
5
|
openclaw:
|
|
6
6
|
requires:
|
|
@@ -30,7 +30,49 @@ NANSEN_API_KEY=<key> nansen login
|
|
|
30
30
|
nansen research profiler labels --address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 --chain ethereum
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
## Wallet
|
|
33
|
+
## Wallet Providers
|
|
34
|
+
|
|
35
|
+
The CLI supports two wallet providers:
|
|
36
|
+
|
|
37
|
+
| | **Local** (default) | **Privy** (server-side) |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Key storage | Encrypted on disk | Server-side via Privy API |
|
|
40
|
+
| Password required | Yes (min 12 chars) | No |
|
|
41
|
+
| Export private keys | Yes (`wallet export`) | No — keys are managed by Privy |
|
|
42
|
+
| Best for | Human users, manual trading | Agents, automated workflows |
|
|
43
|
+
| Flag | `--provider local` (default) | `--provider privy` |
|
|
44
|
+
| Required env vars | `NANSEN_WALLET_PASSWORD` | `PRIVY_APP_ID` + `PRIVY_APP_SECRET` |
|
|
45
|
+
|
|
46
|
+
## Privy Wallet Creation
|
|
47
|
+
|
|
48
|
+
Privy wallets are server-side wallets managed by the Privy API. No password is needed — keys never touch the local machine.
|
|
49
|
+
|
|
50
|
+
### Prerequisites
|
|
51
|
+
|
|
52
|
+
The following environment variables must be set:
|
|
53
|
+
|
|
54
|
+
| Var | Purpose |
|
|
55
|
+
|-----|---------|
|
|
56
|
+
| `PRIVY_APP_ID` | Privy application ID |
|
|
57
|
+
| `PRIVY_APP_SECRET` | Privy application secret |
|
|
58
|
+
|
|
59
|
+
### Create a Privy wallet
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
nansen wallet create --provider privy
|
|
63
|
+
# Or with a custom name:
|
|
64
|
+
nansen wallet create --name agent-wallet --provider privy
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Critical rules for agents (Privy)
|
|
68
|
+
|
|
69
|
+
- **No password needed** — Privy manages keys server-side
|
|
70
|
+
- **Cannot export keys** — `wallet export` only works for local wallets
|
|
71
|
+
- All other operations (`list`, `show`, `send`, `delete`, `default`) work identically for both providers
|
|
72
|
+
|
|
73
|
+
## Local Wallet Creation (Two-Step Agent Flow)
|
|
74
|
+
|
|
75
|
+
> This section covers **local** wallet creation. For Privy server-side wallets, see the [Privy Wallet Creation](#privy-wallet-creation) section above — no password is needed.
|
|
34
76
|
|
|
35
77
|
Wallet creation requires a password from the **human user**. The agent must NOT generate or store the password itself.
|
|
36
78
|
|
|
@@ -67,6 +109,18 @@ If the `.credentials` file fallback is used, the CLI prints a warning on every o
|
|
|
67
109
|
|
|
68
110
|
## Create
|
|
69
111
|
|
|
112
|
+
### Privy (server-side, no password)
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
nansen wallet create --provider privy
|
|
116
|
+
# Or with a custom name:
|
|
117
|
+
nansen wallet create --name trading --provider privy
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Requires `PRIVY_APP_ID` + `PRIVY_APP_SECRET` env vars. No password needed.
|
|
121
|
+
|
|
122
|
+
### Local (encrypted on disk, password required)
|
|
123
|
+
|
|
70
124
|
```bash
|
|
71
125
|
# Ask the user for a password first, then:
|
|
72
126
|
NANSEN_WALLET_PASSWORD="<password_from_user>" nansen wallet create
|
|
@@ -127,6 +181,7 @@ For detailed migration steps (from `~/.nansen/.env`, `.credentials`, or env-var-
|
|
|
127
181
|
| `--chain` | `evm` or `solana` |
|
|
128
182
|
| `--max` | Send entire balance |
|
|
129
183
|
| `--dry-run` | Preview without broadcasting |
|
|
184
|
+
| `--provider` | Wallet provider: `local` (default, encrypted on disk) or `privy` (server-side via Privy API) |
|
|
130
185
|
| `--human` | Enable interactive prompts (human terminal use only — agents must NOT use this) |
|
|
131
186
|
| `--unsafe-no-password` | Skip encryption (keys stored in plaintext — NOT recommended) |
|
|
132
187
|
|
|
@@ -136,5 +191,8 @@ For detailed migration steps (from `~/.nansen/.env`, `.credentials`, or env-var-
|
|
|
136
191
|
|-----|---------|
|
|
137
192
|
| `NANSEN_WALLET_PASSWORD` | Wallet encryption password — only needed for initial `wallet create`. After that, the OS keychain handles it. |
|
|
138
193
|
| `NANSEN_API_KEY` | API key (also set via `nansen login --api-key <key>`) |
|
|
194
|
+
| `PRIVY_APP_ID` | Privy application ID (required for `--provider privy`) |
|
|
195
|
+
| `PRIVY_APP_SECRET` | Privy application secret (required for `--provider privy`) |
|
|
196
|
+
| `NANSEN_WALLET_PROVIDER` | Default provider for wallet create — `local` or `privy` |
|
|
139
197
|
| `NANSEN_EVM_RPC` | Custom EVM RPC endpoint |
|
|
140
198
|
| `NANSEN_SOLANA_RPC` | Custom Solana RPC endpoint |
|
package/src/api.js
CHANGED
|
@@ -11,12 +11,12 @@ import { getAnonymousId, TELEMETRY_DISABLED } from './telemetry.js';
|
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
|
|
14
|
-
function telemetryHeaders() {
|
|
14
|
+
export function telemetryHeaders() {
|
|
15
15
|
if (TELEMETRY_DISABLED) return {};
|
|
16
16
|
return { 'X-Anonymous-Id': getAnonymousId() };
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { version: packageVersion } = JSON.parse(
|
|
19
|
+
export const { version: packageVersion } = JSON.parse(
|
|
20
20
|
fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
|
|
21
21
|
);
|
|
22
22
|
|
|
@@ -85,7 +85,7 @@ export class NansenError extends Error {
|
|
|
85
85
|
/**
|
|
86
86
|
* Map HTTP status codes to error codes
|
|
87
87
|
*/
|
|
88
|
-
function statusToErrorCode(status, data = {}) {
|
|
88
|
+
export function statusToErrorCode(status, data = {}) {
|
|
89
89
|
const message = data?.message || data?.error || '';
|
|
90
90
|
const messageLower = message.toLowerCase();
|
|
91
91
|
|
|
@@ -486,7 +486,7 @@ export class NansenAPI {
|
|
|
486
486
|
return cached;
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
|
-
|
|
489
|
+
|
|
490
490
|
let lastError;
|
|
491
491
|
|
|
492
492
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
@@ -630,7 +630,7 @@ export class NansenAPI {
|
|
|
630
630
|
} catch (x402Err) {
|
|
631
631
|
if (!this.apiKey) {
|
|
632
632
|
message = 'No API key configured. Two ways to authenticate:\n' +
|
|
633
|
-
' 1. API key: nansen login --api-key <key> (get key at https://app.nansen.ai/
|
|
633
|
+
' 1. API key: nansen login --api-key <key> (get key at https://app.nansen.ai/auth/agent-setup)\n' +
|
|
634
634
|
' 2. x402 micropayment: nansen wallet create + fund with USDC (no API key needed)';
|
|
635
635
|
} else {
|
|
636
636
|
message = `x402 auto-payment failed: ${x402Err.message}`;
|
package/src/cli.js
CHANGED
|
@@ -7,9 +7,11 @@ import { NansenAPI, NansenError, ErrorCode, saveConfig, deleteConfig, getConfigF
|
|
|
7
7
|
import { buildWalletCommands } from './wallet.js';
|
|
8
8
|
import { buildTradingCommands } from './trading.js';
|
|
9
9
|
import { formatAlertsTable, buildAlertsCommands } from './commands/alerts.js';
|
|
10
|
+
import { buildAgentCommands } from './commands/agent.js';
|
|
10
11
|
import { resolveAddress, isEnsName } from './ens.js';
|
|
11
12
|
import fs from 'fs';
|
|
12
13
|
import { getUpdateNotification, getUpgradeNotice, scheduleUpdateCheck } from './update-check.js';
|
|
14
|
+
import { refreshCostMapIfStale, getCostForEndpoint } from './cost-cache.js';
|
|
13
15
|
import { trackCommandSucceeded, trackCommandFailed } from './telemetry.js';
|
|
14
16
|
import { createRequire } from 'module';
|
|
15
17
|
import * as readline from 'readline';
|
|
@@ -165,7 +167,7 @@ export function parseArgs(args) {
|
|
|
165
167
|
const key = arg.slice(2);
|
|
166
168
|
const next = args[i + 1];
|
|
167
169
|
|
|
168
|
-
if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich' || key === 'full' || key === 'human' || key === 'enabled' || key === 'disabled') {
|
|
170
|
+
if (key === 'pretty' || key === 'help' || key === 'version' || key === 'table' || key === 'no-retry' || key === 'cache' || key === 'no-cache' || key === 'stream' || key === 'enrich' || key === 'full' || key === 'human' || key === 'enabled' || key === 'disabled' || key === 'expert' || key === 'json') {
|
|
169
171
|
result.flags[key] = true;
|
|
170
172
|
} else if (next && (!next.startsWith('-') || /^-\d/.test(next))) {
|
|
171
173
|
// Try to parse as JSON first (for objects/arrays/booleans),
|
|
@@ -682,10 +684,11 @@ COMMANDS:
|
|
|
682
684
|
research smart-money, profiler, token, search, perp, portfolio, points
|
|
683
685
|
trade quote, execute
|
|
684
686
|
wallet create, list, show, export, default, delete, forget-password
|
|
687
|
+
agent Ask the Nansen AI research agent (fast/expert modes)
|
|
685
688
|
alerts list, create, update, toggle, delete
|
|
686
689
|
web search, fetch
|
|
687
690
|
account Show API key status, plan, and remaining credits
|
|
688
|
-
login Save API key (--api-key <key
|
|
691
|
+
login Save API key (--api-key <key>, --human, or NANSEN_API_KEY env var)
|
|
689
692
|
logout Remove saved API key
|
|
690
693
|
schema JSON schema for all commands (use "nansen schema <cmd>" for one)
|
|
691
694
|
cache clear
|
|
@@ -701,6 +704,10 @@ EXAMPLES:
|
|
|
701
704
|
nansen research profiler balance --address 0x... --chain ethereum
|
|
702
705
|
nansen trade quote --chain base --from ETH --to USDC --amount 1000000000000000000
|
|
703
706
|
|
|
707
|
+
DEPRECATED ALIASES (still work, will be removed in a future version):
|
|
708
|
+
smart-money, profiler, token, search, perp, portfolio, points → use "nansen research <command>"
|
|
709
|
+
quote, execute → use "nansen trade <command>"
|
|
710
|
+
|
|
704
711
|
Research chains: ethereum, solana, base, bnb, arbitrum, polygon, optimism, avalanche, linea, scroll, mantle, ronin, sei, plasma, sonic, monad, hyperevm, iotaevm
|
|
705
712
|
Trade chains: solana, base
|
|
706
713
|
Labels: Fund, Smart Trader, 30D/90D/180D Smart Trader, Smart HL Perps Trader
|
|
@@ -859,7 +866,7 @@ export function buildCommands(deps = {}) {
|
|
|
859
866
|
log(' --api-key <key> Your Nansen API key');
|
|
860
867
|
log(' --human Enable interactive prompt');
|
|
861
868
|
log(' --help Show this help\n');
|
|
862
|
-
log('Get your API key at: https://app.nansen.ai/
|
|
869
|
+
log('Get your API key at: https://app.nansen.ai/auth/agent-setup');
|
|
863
870
|
return;
|
|
864
871
|
}
|
|
865
872
|
|
|
@@ -879,7 +886,7 @@ export function buildCommands(deps = {}) {
|
|
|
879
886
|
return;
|
|
880
887
|
}
|
|
881
888
|
log('Nansen CLI Login\n');
|
|
882
|
-
log('Get your API key at: https://app.nansen.ai/
|
|
889
|
+
log('Get your API key at: https://app.nansen.ai/auth/agent-setup\n');
|
|
883
890
|
apiKey = await promptFn('Enter your API key: ', true);
|
|
884
891
|
}
|
|
885
892
|
|
|
@@ -890,7 +897,7 @@ export function buildCommands(deps = {}) {
|
|
|
890
897
|
resolution: [
|
|
891
898
|
'Run: nansen login --api-key <key>',
|
|
892
899
|
'Or set NANSEN_API_KEY environment variable',
|
|
893
|
-
'Get your API key at: https://app.nansen.ai/
|
|
900
|
+
'Get your API key at: https://app.nansen.ai/auth/agent-setup',
|
|
894
901
|
],
|
|
895
902
|
}));
|
|
896
903
|
exit(1);
|
|
@@ -912,7 +919,7 @@ export function buildCommands(deps = {}) {
|
|
|
912
919
|
log(JSON.stringify({
|
|
913
920
|
error: 'INVALID_API_KEY',
|
|
914
921
|
message: 'The API key is not valid.',
|
|
915
|
-
resolution: ['Check your key at https://app.nansen.ai/
|
|
922
|
+
resolution: ['Check your key at https://app.nansen.ai/auth/agent-setup']
|
|
916
923
|
}));
|
|
917
924
|
} else {
|
|
918
925
|
log(JSON.stringify({
|
|
@@ -1509,6 +1516,11 @@ export function generateSubcommandHelp(command, subcommand, prefix = null) {
|
|
|
1509
1516
|
lines.push(`Params (* required): ${params.join(', ')}`);
|
|
1510
1517
|
}
|
|
1511
1518
|
|
|
1519
|
+
if (subSchema.endpoint) {
|
|
1520
|
+
const cost = getCostForEndpoint(subSchema.endpoint);
|
|
1521
|
+
if (cost) lines.push(`Cost: ${cost.free} credit${cost.free === 1 ? '' : 's'} (Free tier) / ${cost.pro} credit${cost.pro === 1 ? '' : 's'} (Pro tier)`);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1512
1524
|
if (subSchema.returns?.length) {
|
|
1513
1525
|
lines.push(`Returns: ${subSchema.returns.join(', ')}`);
|
|
1514
1526
|
}
|
|
@@ -1548,12 +1560,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1548
1560
|
const subArgs = positional.slice(1);
|
|
1549
1561
|
const subcommand = subArgs[0];
|
|
1550
1562
|
|
|
1551
|
-
|
|
1552
|
-
if (DEPRECATED_TO_RESEARCH.has(command)) {
|
|
1553
|
-
errorOutput(`Warning: "nansen ${command}" is deprecated. Use "nansen research ${command}" instead.`);
|
|
1554
|
-
} else if (DEPRECATED_TO_TRADE.has(command)) {
|
|
1555
|
-
errorOutput(`Warning: "nansen ${command}" is deprecated. Use "nansen trade ${command}" instead.`);
|
|
1556
|
-
}
|
|
1563
|
+
|
|
1557
1564
|
|
|
1558
1565
|
const pretty = flags.pretty || flags.p;
|
|
1559
1566
|
const table = flags.table || flags.t;
|
|
@@ -1569,7 +1576,14 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1569
1576
|
if (updateNotification) errorOutput(updateNotification);
|
|
1570
1577
|
};
|
|
1571
1578
|
|
|
1572
|
-
|
|
1579
|
+
// Deprecation note for help output
|
|
1580
|
+
const deprecationNote = (cmd) => {
|
|
1581
|
+
if (DEPRECATED_TO_RESEARCH.has(cmd)) return `Note: "nansen ${cmd}" is deprecated. Use "nansen research ${cmd}" instead.\n\n`;
|
|
1582
|
+
if (DEPRECATED_TO_TRADE.has(cmd)) return `Note: "nansen ${cmd}" is deprecated. Use "nansen trade ${cmd}" instead.\n\n`;
|
|
1583
|
+
return '';
|
|
1584
|
+
};
|
|
1585
|
+
|
|
1586
|
+
const commands = { ...buildCommands(deps), ...buildWalletCommands(deps), ...buildTradingCommands(deps), ...buildAlertsCommands(deps), ...buildAgentCommands(deps), ...commandOverrides };
|
|
1573
1587
|
|
|
1574
1588
|
if (flags.version || flags.v) {
|
|
1575
1589
|
output(VERSION);
|
|
@@ -1577,6 +1591,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1577
1591
|
}
|
|
1578
1592
|
|
|
1579
1593
|
if (command === 'help' || flags.help || flags.h) {
|
|
1594
|
+
await refreshCostMapIfStale();
|
|
1580
1595
|
// Check for subcommand-specific help: nansen <command> <subcommand> --help
|
|
1581
1596
|
if (flags.help || flags.h) {
|
|
1582
1597
|
// Handle 'research <category> <sub> --help' (3-level)
|
|
@@ -1607,17 +1622,17 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1607
1622
|
}
|
|
1608
1623
|
// First try subcommand help
|
|
1609
1624
|
// Skip for 'trade'/'alerts' — their handlers show their own rich usage
|
|
1610
|
-
if (command && subcommand && command !== 'trade' && command !== 'alerts') {
|
|
1625
|
+
if (command && subcommand && command !== 'trade' && command !== 'alerts' && command !== 'agent') {
|
|
1611
1626
|
const subHelp = generateSubcommandHelp(command, subcommand);
|
|
1612
1627
|
if (subHelp) {
|
|
1613
|
-
output(subHelp);
|
|
1628
|
+
output(deprecationNote(command) + subHelp);
|
|
1614
1629
|
notify();
|
|
1615
1630
|
return { type: 'subcommand-help', command, subcommand };
|
|
1616
1631
|
}
|
|
1617
1632
|
}
|
|
1618
1633
|
// Then try command-level help (list subcommands)
|
|
1619
1634
|
// Skip for 'trade'/'alerts' — let the handler show its own usage
|
|
1620
|
-
const cmdSchemaLookup = command !== 'trade' && command !== 'alerts' && (SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command]);
|
|
1635
|
+
const cmdSchemaLookup = command !== 'trade' && command !== 'alerts' && command !== 'agent' && (SCHEMA.commands[command] || SCHEMA.commands.research.subcommands[command]);
|
|
1621
1636
|
if (command && cmdSchemaLookup) {
|
|
1622
1637
|
const cmdSchema = cmdSchemaLookup;
|
|
1623
1638
|
const lines = [`${command} — ${cmdSchema.description}`];
|
|
@@ -1638,7 +1653,7 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1638
1653
|
if (cmdSchema.examples?.length) {
|
|
1639
1654
|
lines.push(`\nExamples:\n ${cmdSchema.examples.join('\n ')}`);
|
|
1640
1655
|
}
|
|
1641
|
-
output(lines.join('\n'));
|
|
1656
|
+
output(deprecationNote(command) + lines.join('\n'));
|
|
1642
1657
|
notify();
|
|
1643
1658
|
return { type: 'command-help', command };
|
|
1644
1659
|
}
|
|
@@ -1681,7 +1696,6 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1681
1696
|
const formatted = formatOutput(errorData, { pretty, table });
|
|
1682
1697
|
output(formatted.text);
|
|
1683
1698
|
trackCommandFailed({ command: fullCommand, duration_ms: Date.now() - startTime, error_code: 'UNKNOWN_COMMAND', flags: usedFlags, chain });
|
|
1684
|
-
notify();
|
|
1685
1699
|
exit(1);
|
|
1686
1700
|
return { type: 'error', data: errorData };
|
|
1687
1701
|
}
|
|
@@ -1709,7 +1723,6 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1709
1723
|
// Commands that handle their own output return undefined
|
|
1710
1724
|
if (result === undefined) {
|
|
1711
1725
|
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
|
|
1712
|
-
notify();
|
|
1713
1726
|
return { type: 'no-output', command };
|
|
1714
1727
|
}
|
|
1715
1728
|
|
|
@@ -1718,7 +1731,6 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1718
1731
|
const formatted = formatOutput(result, { pretty, table: false });
|
|
1719
1732
|
output(formatted.text);
|
|
1720
1733
|
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, flags: usedFlags, chain });
|
|
1721
|
-
notify();
|
|
1722
1734
|
return { type: 'schema', data: result };
|
|
1723
1735
|
}
|
|
1724
1736
|
|
|
@@ -1731,7 +1743,6 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1731
1743
|
// Alerts list with --table uses custom table format
|
|
1732
1744
|
if (command === 'alerts' && subcommand === 'list' && table) {
|
|
1733
1745
|
output(formatAlertsTable(result));
|
|
1734
|
-
notify();
|
|
1735
1746
|
return { type: 'success', data: result };
|
|
1736
1747
|
}
|
|
1737
1748
|
|
|
@@ -1743,7 +1754,6 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1743
1754
|
output(streamOutput);
|
|
1744
1755
|
}
|
|
1745
1756
|
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
|
|
1746
|
-
notify();
|
|
1747
1757
|
return { type: 'stream', data: result };
|
|
1748
1758
|
}
|
|
1749
1759
|
|
|
@@ -1751,7 +1761,6 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1751
1761
|
const formatted = formatOutput(successData, { pretty, table, csv });
|
|
1752
1762
|
output(formatted.text);
|
|
1753
1763
|
trackCommandSucceeded({ command: fullCommand, duration_ms: Date.now() - startTime, from_cache: !!result?.fromCache, flags: usedFlags, chain });
|
|
1754
|
-
notify();
|
|
1755
1764
|
return { type: csv ? 'csv' : 'success', data: result };
|
|
1756
1765
|
} catch (error) {
|
|
1757
1766
|
const errorData = formatError(error);
|
|
@@ -1765,7 +1774,6 @@ export async function runCLI(rawArgs, deps = {}) {
|
|
|
1765
1774
|
flags: usedFlags,
|
|
1766
1775
|
chain,
|
|
1767
1776
|
});
|
|
1768
|
-
notify();
|
|
1769
1777
|
exit(1);
|
|
1770
1778
|
return { type: 'error', data: errorData };
|
|
1771
1779
|
}
|