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 +14 -0
- package/README.md +47 -7
- package/package.json +1 -1
- package/skills/nansen-mpp-payment/SKILL.md +89 -0
- package/src/api.js +7 -6
- package/src/rpc-urls.js +3 -0
- package/src/walletconnect-x402.js +1 -1
- package/src/x402.js +19 -10
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
nansen
|
|
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
|
-
|
|
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
|
@@ -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
|
|
502
|
-
if (
|
|
503
|
-
console.error(`[x402] Warning:
|
|
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(
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
159
|
-
* Returns balance
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
//
|
|
192
|
-
|
|
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(
|
|
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:
|
|
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;
|