nansen-cli 1.29.0 → 1.30.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#422](https://github.com/nansen-ai/nansen-cli/pull/422) [`10da2f0`](https://github.com/nansen-ai/nansen-cli/commit/10da2f03284a501d94c433f543b9f1866005d3fc) Thanks [@gulshngill](https://github.com/gulshngill)! - Add x402 support for paying with USDT0 on X Layer alongside Base USDC and Solana SPL USDC. The CLI auto-signs the payment using whatever the API advertises in the 402 `accepts` list — no client-side allowlist, since `src/x402-evm.js` already reads `extra.name`, `extra.version`, and `asset` generically. New `NANSEN_XLAYER_RPC` env var overrides the default X Layer RPC, and `checkX402Balance()` now picks the right token + RPC based on the requirement's `network` field.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#422](https://github.com/nansen-ai/nansen-cli/pull/422) [`dc9d1c1`](https://github.com/nansen-ai/nansen-cli/commit/dc9d1c1d740128a8667e79a7a4afb3ff31ed1cc5) Thanks [@gulshngill](https://github.com/gulshngill)! - Document MPP (Tempo) as a third paid-access rail alongside API key and x402. Adds a `nansen-mpp-payment` skill, a README section explaining when to reach for the separate `tempo` CLI, and updates the no-API-key 402 error to mention tempo as a third option.
12
+
13
+ - [#422](https://github.com/nansen-ai/nansen-cli/pull/422) [`93e6a6d`](https://github.com/nansen-ai/nansen-cli/commit/93e6a6d6655380d311739e5f814dda2876b0206a) Thanks [@gulshngill](https://github.com/gulshngill)! - Fix x402 low-balance warning to use the actual stablecoin symbol (USDC or USDT0) returned by `checkX402Balance()` instead of hardcoding "USDC".
14
+
15
+ - [#422](https://github.com/nansen-ai/nansen-cli/pull/422) [`8f9397f`](https://github.com/nansen-ai/nansen-cli/commit/8f9397f2e1940a7a501cd450eae58a3b243b4782) Thanks [@gulshngill](https://github.com/gulshngill)! - Fix x402 payment header decoding and WalletConnect payment payload encoding to use UTF-8 instead of Latin-1. Previously the `Payment-Required` header was decoded with `atob()`, which corrupted multi-byte UTF-8 chars in fields like `extra.name = 'USD₮0'`. The corrupted name then signed the wrong EIP-712 domain and the server rejected with `invalid_exact_evm_signature`. X Layer USDT0 payments now sign correctly; Base USDC was unaffected because `'USD Coin'` is pure ASCII.
16
+
3
17
  ## 1.29.0
4
18
 
5
19
  ### Minor Changes
package/README.md CHANGED
@@ -14,14 +14,20 @@ npx skills add nansen-ai/nansen-cli # load agent skill files
14
14
 
15
15
  ## Auth
16
16
 
17
- ```bash
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
22
- ```
17
+ Three options — pick whichever fits your setup:
18
+
19
+ 1. **API key** (subscription):
20
+ ```bash
21
+ nansen login --api-key <key> # save key to ~/.nansen/config.json
22
+ nansen login --human # interactive prompt
23
+ export NANSEN_API_KEY=... # env var (highest priority)
24
+ nansen logout # remove saved key
25
+ ```
26
+ Get your API key at [app.nansen.ai/auth/agent-setup](https://app.nansen.ai/auth/agent-setup).
23
27
 
24
- Get your API key at [app.nansen.ai/auth/agent-setup](https://app.nansen.ai/auth/agent-setup).
28
+ 2. **x402 micropayment** (no key needed): `nansen wallet create`, fund with USDC on Base or Solana, or USDT0 on X Layer, then call any endpoint — the CLI signs `Payment-Signature` headers automatically on 402 responses. See [Wallet](#wallet).
29
+
30
+ 3. **MPP via tempo** (no key needed): install the [tempo CLI](https://docs.tempo.xyz) separately, run `tempo wallet login` to set up, then call the Nansen API through `tempo request`. The Nansen API selects the MPP rail when it sees `Authorization: Payment ...`. See [MPP / Tempo](#mpp--tempo) below.
25
31
 
26
32
  ## Commands
27
33
 
@@ -76,6 +82,40 @@ nansen wallet send --wallet <name> --to <addr> --amount <n> --chain <chain>
76
82
 
77
83
  **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).
78
84
 
85
+ ## MPP / Tempo
86
+
87
+ The Nansen API supports [MPP](https://mpp.dev/protocol) (Tempo's stablecoin payment rail) as an alternative to API keys and x402. MPP is handled by the **separate** [tempo CLI](https://docs.tempo.xyz) — `nansen-cli` itself does not sign MPP credentials. You use the two CLIs side-by-side.
88
+
89
+ **One-time setup:**
90
+
91
+ ```bash
92
+ # 1. Install the tempo CLI
93
+ curl -fsSL https://tempo.xyz/install | bash
94
+ # 2. Log in + fund the tempo wallet
95
+ tempo wallet login
96
+ tempo wallet fund # follow the on-screen instructions to deposit USDC
97
+ ```
98
+
99
+ **Calling the Nansen API via tempo:**
100
+
101
+ ```bash
102
+ tempo request POST https://api.nansen.ai/api/v1/smart-money/netflow \
103
+ --json '{"chains":["solana"],"pagination":{"page":1,"page_size":10}}'
104
+ ```
105
+
106
+ `tempo request` handles the full `Authorization: Payment` challenge/response: on a 402 with `WWW-Authenticate: Payment ...` it signs a Tempo credential, retries, and surfaces the `Payment-Receipt` header on success.
107
+
108
+ **When to use which rail:**
109
+
110
+ | Situation | Rail |
111
+ |---|---|
112
+ | You have a subscription | API key |
113
+ | You want anonymous pay-per-call with a Base/Solana wallet you already manage | x402 (`nansen wallet`) |
114
+ | You hold USDT0 on X Layer and want to pay from there | x402 (`nansen wallet`) |
115
+ | You already use tempo for other paid APIs, or want micropayments without managing your own wallet keys | MPP (`tempo request`) |
116
+
117
+ > Note: MPP is server-side opt-in (`MPP_ENABLED=true` on the API). It's available on dev today and rolling out to prod — if `tempo request` returns a non-MPP 402, fall back to x402 or an API key.
118
+
79
119
  ## Key Options
80
120
 
81
121
  | Option | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nansen-cli",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "Command-line interface for Nansen API - designed for AI agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: nansen-mpp-payment
3
+ description: Pay-per-call access to the Nansen API via MPP (Tempo). Use when a user wants anonymous Nansen access without an API key and without managing their own Base/Solana wallet — they install the tempo CLI separately and call the API through `tempo request`.
4
+ metadata:
5
+ openclaw:
6
+ requires:
7
+ bins:
8
+ - tempo
9
+ install:
10
+ - kind: external
11
+ name: tempo
12
+ docs: https://docs.tempo.xyz
13
+ allowed-tools: Bash(tempo:*), Bash(nansen:*)
14
+ ---
15
+
16
+ # MPP / Tempo
17
+
18
+ The Nansen API supports three paid-access rails: API key, x402 (handled by `nansen-cli`), and MPP via Tempo (handled by the **separate** [tempo CLI](https://docs.tempo.xyz)). This skill covers the third.
19
+
20
+ `nansen-cli` does **not** sign MPP credentials. Use this skill when the user wants to call the Nansen API through `tempo request` because they already use tempo, want micropayments without managing wallet keys themselves, or don't want to fund a Base/Solana USDC wallet.
21
+
22
+ For API-key auth, see `nansen-wallet-manager`. For x402 micropayment with a local wallet, see `nansen-trading` / `nansen-wallet-manager`.
23
+
24
+ ## When to use this skill
25
+
26
+ - User says "MPP", "tempo", "Authorization: Payment", or "Payment-Receipt".
27
+ - User has no Nansen API key and doesn't want to set up a Base/Solana wallet.
28
+ - User is already paying for other APIs through tempo and wants Nansen on the same rail.
29
+
30
+ ## One-time setup
31
+
32
+ ```bash
33
+ # 1. Install the tempo CLI
34
+ curl -fsSL https://tempo.xyz/install | bash
35
+ # 2. Log in (creates / unlocks the tempo wallet)
36
+ tempo wallet login
37
+ # 3. Fund it with USDC on the chain tempo selects for your environment
38
+ tempo wallet fund
39
+ # 4. Confirm the wallet is ready
40
+ tempo wallet whoami
41
+ ```
42
+
43
+ ## Calling the Nansen API
44
+
45
+ `tempo request` handles the full MPP challenge/response: it sends the request, signs the `Authorization: Payment` credential when the API responds 402 + `WWW-Authenticate: Payment ...`, retries, and exposes the `Payment-Receipt` header on success.
46
+
47
+ ```bash
48
+ # Smart Money netflow
49
+ tempo request POST https://api.nansen.ai/api/v1/smart-money/netflow \
50
+ --json '{"chains":["solana"],"pagination":{"page":1,"page_size":10}}'
51
+
52
+ # TGM holders
53
+ tempo request POST https://api.nansen.ai/api/v1/tgm/holders \
54
+ --json '{"token_address":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","chain":"solana"}'
55
+ ```
56
+
57
+ Endpoint paths and request shapes are the same as the rest of the Nansen API — run `nansen schema <command>` (no API key required) to look up the body shape, then call the matching `/api/v1/...` path through `tempo request`.
58
+
59
+ ## Discovering paid endpoints
60
+
61
+ ```bash
62
+ curl https://api.nansen.ai/.well-known/x402
63
+ ```
64
+
65
+ Returns `paymentProtocols: ["x402", "mpp"]` (when MPP is enabled server-side) and the list of paid resources.
66
+
67
+ ## How MPP differs from x402
68
+
69
+ | | **x402** (nansen-cli native) | **MPP via tempo** (this skill) |
70
+ |---|---|---|
71
+ | Header sent on retry | `Payment-Signature: <base64>` | `Authorization: Payment <credential>` |
72
+ | 402 challenge header | `Payment-Required: <base64>` | `WWW-Authenticate: Payment ...` |
73
+ | Success header | _(none)_ | `Payment-Receipt: <base64>` |
74
+ | Wallet | local, Privy, or WalletConnect — managed by `nansen-cli` | tempo-managed (separate CLI) |
75
+ | Chains | Base USDC, Solana SPL USDC, X Layer USDT0 | Tempo's chain (mainnet `USDC` in prod, moderato `pathUSD` in dev) |
76
+ | nansen-cli code path | `src/x402.js` auto-signs on 402 | not handled — call via `tempo request` directly |
77
+
78
+ ## Notes
79
+
80
+ - MPP is server-side opt-in. If `tempo request` returns a 402 without `WWW-Authenticate: Payment`, MPP isn't enabled for that endpoint/environment — fall back to an API key or x402.
81
+ - Don't try to add `--mpp-*` flags to `nansen-cli` — the supported integration is "use tempo separately". If the user asks for tighter integration, point them at this skill and confirm the requirement before adding code.
82
+ - Per-request price is the same as x402 (1 credit ≈ $0.001 with 10x markup, e.g. 1-credit endpoints cost $0.01).
83
+
84
+ ## Source
85
+
86
+ - npm: https://www.npmjs.com/package/nansen-cli
87
+ - GitHub: https://github.com/nansen-ai/nansen-cli
88
+ - MPP protocol: https://mpp.dev/protocol
89
+ - Tempo docs: https://docs.tempo.xyz
package/src/api.js CHANGED
@@ -498,9 +498,9 @@ export class NansenAPI {
498
498
  if (network) {
499
499
  try {
500
500
  const { checkX402Balance } = await import('./x402.js');
501
- const balance = await checkX402Balance(network);
502
- if (balance !== null && balance < 0.25) {
503
- console.error(`[x402] Warning: USDC balance low ($${balance.toFixed(2)}). Fund your wallet to avoid interruptions.`);
501
+ const result = await checkX402Balance(network);
502
+ if (result !== null && result.balance < 0.25) {
503
+ console.error(`[x402] Warning: ${result.symbol} balance low ($${result.balance.toFixed(2)}). Fund your wallet to avoid interruptions.`);
504
504
  }
505
505
  } catch { /* balance check is best-effort */ }
506
506
  }
@@ -648,7 +648,7 @@ export class NansenAPI {
648
648
  const paymentHeader = response.headers.get('payment-required');
649
649
  if (paymentHeader) {
650
650
  try {
651
- paymentRequirements = JSON.parse(atob(paymentHeader));
651
+ paymentRequirements = JSON.parse(Buffer.from(paymentHeader, 'base64').toString('utf8'));
652
652
  } catch {
653
653
  data.paymentRequiredRaw = paymentHeader;
654
654
  }
@@ -665,9 +665,10 @@ export class NansenAPI {
665
665
  if (result !== null) return result;
666
666
  } catch (x402Err) {
667
667
  if (!this.apiKey) {
668
- message = 'No API key configured. Two ways to authenticate:\n' +
668
+ message = 'No API key configured. Three ways to authenticate:\n' +
669
669
  ' 1. API key: nansen login --api-key <key> (get key at https://app.nansen.ai/auth/agent-setup)\n' +
670
- ' 2. x402 micropayment: nansen wallet create + fund with USDC (no API key needed)';
670
+ ' 2. x402 micropayment: nansen wallet create + fund with USDC on Base/Solana or USDT0 on X Layer (no API key needed)\n' +
671
+ ' 3. MPP via tempo: install tempo CLI, run `tempo wallet login`, then call the API with `tempo request` (see skills/nansen-mpp-payment)';
671
672
  } else {
672
673
  message = `x402 auto-payment failed: ${x402Err.message}`;
673
674
  }
package/src/rpc-urls.js CHANGED
@@ -8,6 +8,7 @@
8
8
  * Override env vars:
9
9
  * NANSEN_EVM_RPC Custom Ethereum RPC (also used as generic EVM fallback)
10
10
  * NANSEN_BASE_RPC Custom Base RPC
11
+ * NANSEN_XLAYER_RPC Custom X Layer RPC
11
12
  * NANSEN_SOLANA_RPC Custom Solana RPC
12
13
  *
13
14
  * Backward-compat aliases (deprecated — prefer the forms above):
@@ -19,11 +20,13 @@
19
20
 
20
21
  const DEFAULT_EVM_RPC = 'https://eth.public-rpc.com';
21
22
  const DEFAULT_BASE_RPC = 'https://mainnet.base.org';
23
+ const DEFAULT_XLAYER_RPC = 'https://rpc.xlayer.tech';
22
24
  const DEFAULT_SOLANA_RPC = 'https://api.mainnet-beta.solana.com';
23
25
 
24
26
  export const CHAIN_RPCS = {
25
27
  ethereum: process.env.NANSEN_EVM_RPC || DEFAULT_EVM_RPC,
26
28
  evm: process.env.NANSEN_EVM_RPC || DEFAULT_EVM_RPC, // generic EVM fallback
27
29
  base: process.env.NANSEN_BASE_RPC || process.env.NANSEN_RPC_BASE || DEFAULT_BASE_RPC,
30
+ xlayer: process.env.NANSEN_XLAYER_RPC || DEFAULT_XLAYER_RPC,
28
31
  solana: process.env.NANSEN_SOLANA_RPC || DEFAULT_SOLANA_RPC,
29
32
  };
@@ -113,7 +113,7 @@ export function buildPaymentSignatureHeader({ signature, authorization, resource
113
113
  authorization,
114
114
  },
115
115
  };
116
- return btoa(JSON.stringify(paymentPayload));
116
+ return Buffer.from(JSON.stringify(paymentPayload), 'utf8').toString('base64');
117
117
  }
118
118
 
119
119
  /**
package/src/x402.js CHANGED
@@ -24,7 +24,9 @@ export function parsePaymentRequirements(response) {
24
24
  if (!header) return null;
25
25
 
26
26
  try {
27
- const decoded = JSON.parse(atob(header));
27
+ // UTF-8 decode (not atob → Latin-1) — server sends UTF-8 bytes
28
+ // for fields like extra.name = 'USD₮0'.
29
+ const decoded = JSON.parse(Buffer.from(header, 'base64').toString('utf8'));
28
30
  // V2 format: { accepts: [...], ... }
29
31
  if (decoded.accepts && Array.isArray(decoded.accepts)) {
30
32
  return decoded.accepts;
@@ -155,8 +157,8 @@ export async function createPaymentSignature(response, url, options = {}) {
155
157
  }
156
158
 
157
159
  /**
158
- * Check USDC balance for x402 payment wallet.
159
- * Returns balance in USD (number) or null if check fails.
160
+ * Check stablecoin balance for x402 payment wallet on the given network.
161
+ * Returns `{ balance, symbol }` (USD amount + token symbol) or null if check fails.
160
162
  */
161
163
  export async function checkX402Balance(network) {
162
164
  try {
@@ -183,25 +185,32 @@ export async function checkX402Balance(network) {
183
185
  });
184
186
  const data = await resp.json();
185
187
  const accounts = data.result?.value || [];
186
- if (accounts.length === 0) return 0;
187
- return parseFloat(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString || '0');
188
+ const balance = accounts.length === 0
189
+ ? 0
190
+ : parseFloat(accounts[0].account.data.parsed.info.tokenAmount.uiAmountString || '0');
191
+ return { balance, symbol: 'USDC' };
188
192
  }
189
193
 
190
194
  if (network.startsWith('eip155:')) {
191
- // Base USDC balance check — RPC URL from shared registry so NANSEN_BASE_RPC override applies
192
- const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
195
+ // Per-network token + RPC. Both tokens are 6-decimals.
196
+ // Default to Base USDC if the network is unknown so existing wallets keep working.
197
+ const EVM_NETWORKS = {
198
+ 'eip155:8453': { token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', rpc: CHAIN_RPCS.base, symbol: 'USDC' }, // Base USDC
199
+ 'eip155:196': { token: '0x779Ded0c9e1022225f8E0630b35a9b54bE713736', rpc: CHAIN_RPCS.xlayer, symbol: 'USDT0' }, // X Layer USDT0
200
+ };
201
+ const { token, rpc, symbol } = EVM_NETWORKS[network] || EVM_NETWORKS['eip155:8453'];
193
202
  const addr = walletInfo.evm.replace('0x', '').toLowerCase().padStart(64, '0');
194
- const resp = await fetch(CHAIN_RPCS.base, {
203
+ const resp = await fetch(rpc, {
195
204
  method: 'POST',
196
205
  headers: { 'Content-Type': 'application/json' },
197
206
  body: JSON.stringify({
198
207
  jsonrpc: '2.0', id: 1,
199
208
  method: 'eth_call',
200
- params: [{ to: USDC_BASE, data: `0x70a08231${addr}` }, 'latest'],
209
+ params: [{ to: token, data: `0x70a08231${addr}` }, 'latest'],
201
210
  }),
202
211
  });
203
212
  const data = await resp.json();
204
- return parseInt(data.result, 16) / 1e6;
213
+ return { balance: parseInt(data.result, 16) / 1e6, symbol };
205
214
  }
206
215
 
207
216
  return null;