nansen-cli 1.34.0 → 1.36.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 +98 -0
- package/README.md +48 -4
- package/package.json +1 -1
- package/scripts/postinstall.js +13 -3
- package/skills/nansen-trading/SKILL.md +49 -1
- package/src/api.js +59 -6
- package/src/bridge.js +1102 -0
- package/src/cli.js +139 -7
- package/src/hl-action.js +528 -0
- package/src/hl-client.js +168 -0
- package/src/hl-env.js +37 -0
- package/src/keychain.js +6 -2
- package/src/limit-order.js +18 -4
- package/src/perp.js +835 -0
- package/src/response-meta.js +123 -0
- package/src/rpc-urls.js +18 -11
- package/src/schema.json +415 -6
- package/src/trading.js +162 -17
- package/src/wallet-signing.js +87 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,103 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`54386c0`](https://github.com/nansen-ai/nansen-cli/commit/54386c0f3280fcc06c8bb7a5d18956d45b2d3d63) Thanks [@kome12](https://github.com/kome12)! - **Output shape change:** every command failure now serializes through the same error envelope — `{success: false, error, code, status, details}`. Previously a `CommandError` printed its structured payload at the top level instead, so errors from `trade`, `limit-order` and the API-key flows (`NOT_A_TTY`, `API_KEY_REQUIRED`, `INVALID_API_KEY`, `VERIFICATION_FAILED`) came back in a different shape from everything else.
|
|
8
|
+
|
|
9
|
+
Nothing is lost — the previous top-level payload is preserved verbatim under `details` — but anything parsing those errors positionally needs to read `details` instead of the root object. The new `perp` and `bridge` commands raise `CommandError` throughout, so without this they would have been the third distinct error shape in the CLI.
|
|
10
|
+
|
|
11
|
+
One deliberate exception: a missing-argument usage banner (`MISSING_PARAM`, `MISSING_ARGS`) prints as plain text when stdout is an interactive terminal and no output format was requested, because those messages are multi-line help written to be read and serializing them renders every newline as a literal `\n`. Piped output, and any run with `--pretty`, `--table`, `--format csv` or `--stream`, still gets the envelope — so nothing consuming the CLI programmatically sees a different shape.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`8ab8bb4`](https://github.com/nansen-ai/nansen-cli/commit/8ab8bb41a1fc89b7dfa804f5c2d601e6ae38c89b) Thanks [@kome12](https://github.com/kome12)! - `bridge execute` now re-screens the wallet against the compliance blocklist immediately before signing, and fails closed if the check can't be completed — matching what the perp commands already did. Bridge quotes stay valid for an hour and the EVM deposit leg broadcasts straight to a public RPC, so previously nothing re-checked the wallet between the quote and the transaction that moves funds.
|
|
16
|
+
|
|
17
|
+
It also refuses to execute a quote with a wallet other than the one the quote was created for. Previously the signing wallet was resolved from `--wallet` or the current default independently of the quote, so a changed default (or an explicit `--wallet`) could sign with a different wallet than the one screened.
|
|
18
|
+
|
|
19
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`44805e2`](https://github.com/nansen-ai/nansen-cli/commit/44805e2cd0a002b38b32bd2818da89548c4974f0) Thanks [@kome12](https://github.com/kome12)! - `nansen schema` now describes the `bridge` command group — its three subcommands, their options, and the supported routes — so agents driving the CLI off the schema can discover it.
|
|
20
|
+
|
|
21
|
+
The mutating `perp` subcommands (`order`, `cancel`, `close`, `leverage`, `transfer`, `approve-builder-fee`) now declare `submitsTo: "https://api.hyperliquid.xyz/exchange"` in place of an `endpoint`, which is where they actually send a signed action, alongside an `apiEndpoints` list of the Nansen routes each one reads for compliance screening, market metadata and builder-fee status. Read-only subcommands keep their `endpoint` unchanged.
|
|
22
|
+
|
|
23
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`7185359`](https://github.com/nansen-ai/nansen-cli/commit/7185359d3ce657df849c371b34f6d045a4dac653) Thanks [@kome12](https://github.com/kome12)! - `nansen bridge` supports `base -> hyperliquid` for deposits, and `hyperliquid -> base`, `hyperliquid -> ethereum`, `hyperliquid -> arbitrum` for withdrawals. Any other combination is rejected at quote time with the supported routes listed.
|
|
24
|
+
|
|
25
|
+
The route set is asymmetric because the two directions need different things from the client: a deposit broadcasts an EVM transaction and so needs a locally signable origin chain, while a withdrawal signs a Hyperliquid action and never touches the destination chain.
|
|
26
|
+
|
|
27
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`19738dd`](https://github.com/nansen-ai/nansen-cli/commit/19738dd252dca12ce0e41d40fe4023ac96ff0213) Thanks [@kome12](https://github.com/kome12)! - EVM transactions are now signed as EIP-1559 (type 2) when the quote supplies fee caps, instead of being flattened into a legacy (type 0) transaction with a single gas price.
|
|
28
|
+
|
|
29
|
+
A legacy transaction pays exactly its `gasPrice`, so once the base fee rises above that value it is not merely slow — it can never be included at that nonce. A type-2 transaction pays base fee plus priority up to its cap, so it tolerates the fee moving between signing and inclusion. This affects `trade execute` and `bridge execute`, which share the signer.
|
|
30
|
+
|
|
31
|
+
`bridge execute` also stops discarding the fee fields the bridge quote provides. It previously overwrote them with a bare `eth_gasPrice` reading, producing a transaction priced at roughly the current base fee with almost no priority fee — which is what it takes to sit unmined on Base. Quoted fees are now kept, with the priority fee raised to a floor that Base will actually schedule and the cap lifted to cover both that and base-fee movement.
|
|
32
|
+
|
|
33
|
+
Two related robustness changes: signing now refuses outright when a quote carries no gas information at all, rather than falling back to a 1 wei gas price that produces a permanently unmineable transaction; and the receipt wait is longer, because by the time it runs the transaction is already broadcast, so giving up early reports a failure without undoing anything.
|
|
34
|
+
|
|
35
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`f649eea`](https://github.com/nansen-ai/nansen-cli/commit/f649eeaf90b5666d51bc623aafdeaf4087bfe9f8) Thanks [@kome12](https://github.com/kome12)! - Add a client-side Hyperliquid action builder (`src/hl-action.js`), the groundwork for submitting perp trades straight to Hyperliquid instead of round-tripping through the Nansen backend to build them.
|
|
36
|
+
|
|
37
|
+
- msgpack encoder that reproduces the reference `msgpack.packb` output byte-for-byte (insertion-ordered maps, smallest-width ints, utf-8 strings), so an action's `connectionId` hash matches the known-good path.
|
|
38
|
+
- `actionHash` + `l1Eip712` reproduce the phantom-agent EIP-712 payload (Exchange domain, mainnet `source: "a"`) that the existing signer already knows how to sign.
|
|
39
|
+
- Order-wire assembly for market/limit orders, cancels, closes and leverage updates, including TP/SL (`normalTpsl`) grouping and the builder-code attachment.
|
|
40
|
+
- Price/size rounding (`roundPrice`/`roundSize`) ported with Python-parity banker's rounding, so over-precise values that Hyperliquid would reject are rounded identically to the server path.
|
|
41
|
+
- `approveBuilderFee` and `usdClassTransfer` user-signed payload builders.
|
|
42
|
+
|
|
43
|
+
Pinned against the live prepare endpoints with golden-vector tests that assert both the built action and its `connectionId` match byte-for-byte.
|
|
44
|
+
|
|
45
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`3bd3909`](https://github.com/nansen-ai/nansen-cli/commit/3bd3909155d89b926ceabd506aaea44858d905f6) Thanks [@kome12](https://github.com/kome12)! - Add `src/hl-client.js`, the single direct-to-Hyperliquid submission path: `submitExchange()` POSTs a signed action straight to `api.hyperliquid.xyz/exchange` from the user's machine instead of routing it through the Nansen backend. Reads and market-data stay on the proxy.
|
|
46
|
+
|
|
47
|
+
It reproduces the backend proxy's failure handling that it replaces — throwing on a top-level `status: "err"` and on a per-action error nested in `response.data.statuses[].error` (a rejected order that Hyperliquid otherwise reports under a top-level `"ok"`) — so a rejected order can never be mistaken for a fill. The submit is deliberately not retried, since each carries a unique nonce and is not idempotent. The HL base URL is overridable via `NANSEN_HL_API_URL` (for testnet / tests).
|
|
48
|
+
|
|
49
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`b5d6946`](https://github.com/nansen-ai/nansen-cli/commit/b5d6946b0380b30d5dc1a4bcc33dcbc18ad0f8ad) Thanks [@kome12](https://github.com/kome12)! - Harden perp, swap, and bridge command safety:
|
|
50
|
+
|
|
51
|
+
- `perp order`/`close` now reject an invalid `--side` instead of silently opening the opposite direction, and `perp leverage` rejects an invalid `--margin-type` instead of silently switching to isolated.
|
|
52
|
+
- Perp numeric args (`--size`, `--price`, `--leverage`, `--oid`) are validated as positive numbers, with specific error messages instead of a generic usage banner.
|
|
53
|
+
- Perp commands now require an EVM wallet, with a clear error instead of querying for an `"undefined"` address.
|
|
54
|
+
- `trade quote` validates `--quote-index`, `--slippage`, and `--max-auto-slippage`, rejecting out-of-range values (e.g. a percent-vs-decimal slippage mix-up).
|
|
55
|
+
- `bridge quote` now validates `--slippage` client-side (whole basis points in `[0, 10000]`), rejecting non-numeric or out-of-range values with a clear message instead of forwarding them to an opaque backend 422 (matching how `perp` validates `--slippage`).
|
|
56
|
+
- `perp order`/`close` warn before signing when `--size` (or `--price` for `order`) is finer than the asset's Hyperliquid precision, which the exchange silently rounds.
|
|
57
|
+
- `perp order`/`close` now report the size and price the order actually executes at (post-rounding, slippage-adjusted for market orders) instead of echoing the raw input, so the printed values match the fill.
|
|
58
|
+
- `limit-order list` no longer aborts the whole render when one order has a non-integer amount.
|
|
59
|
+
- Quote loaders reject a cross-type quote (a bridge quote sent to `trade execute`, or a swap quote sent to `bridge execute`).
|
|
60
|
+
- `bridge execute` now refuses a quote that has already been executed, preventing an accidental double-bridge on retry.
|
|
61
|
+
- `bridge quote` accepts human amounts via `--amount-unit token|usd` (default stays base units), resolving token decimals per chain so the same `5` isn't 100x off between chains (USDC is 6 decimals on EVM, 8 on Hyperliquid). Hyperliquid USDC is floored to the bridge's 6-decimal precision to avoid a round-up-past-balance rejection.
|
|
62
|
+
- `perp meta` supports `--all` and `--filter <text>` so assets past the first 20 (e.g. HYPE) are listable.
|
|
63
|
+
- Deprecated top-level aliases now print a deprecation notice on stderr when run, not only in `--help`.
|
|
64
|
+
- `limit-order` rejects a zero-duration or past expiry instead of creating an order that expires immediately.
|
|
65
|
+
- A password with leading/trailing whitespace is no longer mangled when read back from the OS keychain.
|
|
66
|
+
- Nested backend error messages containing an apostrophe are no longer truncated.
|
|
67
|
+
|
|
68
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`b577954`](https://github.com/nansen-ai/nansen-cli/commit/b5779543c5a5bb714a3428bb80e3d8f5e3eb865e) Thanks [@kome12](https://github.com/kome12)! - `perp` and `bridge` no longer serve any API response from the `--cache` store, and `bridge execute` no longer retries its submission.
|
|
69
|
+
|
|
70
|
+
- **Compliance screening** is always a live check. Every mutating command re-screens the signing wallet immediately before signing; with `--cache` that verdict could previously come from a cache written up to five minutes earlier, which is exactly the window the check exists to close.
|
|
71
|
+
- **`bridge execute`** sets `retry: false`. It proxies to Relay's `/authorize` and Hyperliquid's `/exchange`, neither of which is idempotent, so an automatic re-send on a 500 or 502 could submit the same signed action twice.
|
|
72
|
+
- **Bridge status polling** now observes progress under `--cache`. The cache key is endpoint plus body, so every poll for a given request id hit the same key and the loop would re-read one stale verdict for the whole TTL.
|
|
73
|
+
- **Perp reads** (`positions`, `orders`, `account`, `meta`) and **bridge quotes** bypass the cache too: they either report live balances to the user or feed a signing decision — `close` sizes its order from the positions read, and asset ids come from `meta`.
|
|
74
|
+
|
|
75
|
+
`bridge execute` also refuses to sign a step whose EIP-712 type definition is missing or whose `primaryType` matches no entry in it. With an empty field list the digest is still well-formed but commits to none of the action's contents, so it would have produced a valid-looking signature over nothing.
|
|
76
|
+
|
|
77
|
+
- [#467](https://github.com/nansen-ai/nansen-cli/pull/467) [`4c1d3aa`](https://github.com/nansen-ai/nansen-cli/commit/4c1d3aa16874b2d994ba0e57f52131fdc2dd0748) Thanks [@kome12](https://github.com/kome12)! - Close three residual safety gaps on the perp/bridge signing paths:
|
|
78
|
+
|
|
79
|
+
- `perp order` now rejects a take-profit or stop-loss price that rounds to zero at the asset's precision, instead of encoding a `triggerPx` of `0` and resting a dead protective order while the parent position opens unprotected. This extends the existing zero-price/size guard (which only covered the parent leg) to the TP/SL trigger legs.
|
|
80
|
+
- The Privy bridge signing path now refuses to sign an EIP-712 action whose primary type has no field definitions, matching the guard the local signing path already had. An empty type list produces a valid-looking signature that commits to none of the action's contents.
|
|
81
|
+
- The EVM bridge deposit leg resolves its nonce from the signing wallet's own address rather than the server-returned `txData.from`. The transaction is signed with the local key regardless of `from`, so the nonce must come from that account — and this stays correct even if a quote omits `from`.
|
|
82
|
+
|
|
83
|
+
## 1.35.0
|
|
84
|
+
|
|
85
|
+
### Minor Changes
|
|
86
|
+
|
|
87
|
+
- [#469](https://github.com/nansen-ai/nansen-cli/pull/469) [`85b1934`](https://github.com/nansen-ai/nansen-cli/commit/85b1934ae25ed02d1726de8ebe92ea41a98f4454) Thanks [@gulshngill](https://github.com/gulshngill)! - Surface the API's credit and rate-limit response headers.
|
|
88
|
+
|
|
89
|
+
Failed calls now report quota state in their error details: an out-of-credits error carries your actual remaining balance, and a rate-limited error carries the limit, what is left, and how long the window needs to drain. Previously the only credit figure the CLI could show was the static per-endpoint estimate published in the API reference — a quote, not what you were charged.
|
|
90
|
+
|
|
91
|
+
A warning goes to stderr when your balance will not cover another call of the size just made, so it never interferes with the JSON on stdout.
|
|
92
|
+
|
|
93
|
+
Successful responses carry the same numbers under an exported `RESPONSE_META` symbol, and the client exposes `lastResponseMeta`. Both are additive: the JSON each command prints is unchanged.
|
|
94
|
+
|
|
95
|
+
- [#470](https://github.com/nansen-ai/nansen-cli/pull/470) [`8159300`](https://github.com/nansen-ai/nansen-cli/commit/81593008f829cc83f1d4a6ee9e5c1a10237a9553) Thanks [@gulshngill](https://github.com/gulshngill)! - Surface the API's request id.
|
|
96
|
+
|
|
97
|
+
Failed calls now carry `details.requestId` — the value that identifies the call end to end. Quote it when reporting a problem; previously nothing identifying a failed request ever reached the user, which made server errors effectively unreportable. Successful responses expose it alongside the credit and rate-limit figures under the `RESPONSE_META` symbol.
|
|
98
|
+
|
|
99
|
+
Absent on deployments that do not send the header yet, in which case the field is simply omitted.
|
|
100
|
+
|
|
3
101
|
## 1.34.0
|
|
4
102
|
|
|
5
103
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -93,6 +93,38 @@ nansen trade limit-order update --order <orderId> --trigger-price 85
|
|
|
93
93
|
|
|
94
94
|
For EVM chains, there's no native limit-order surface — pair an external venue's resting order with a `common-token-transfer` smart alert on the settlement wallet as a best-effort fill signal. See the `nansen-limit-orders` skill for details.
|
|
95
95
|
|
|
96
|
+
## Perpetuals
|
|
97
|
+
|
|
98
|
+
Hyperliquid perpetual trading via `nansen perp`. Uses the same wallet and `NANSEN_WALLET_PASSWORD` as DEX trading (requires an EVM wallet). Select the asset with `--coin` (`--symbol` is accepted as an alias).
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
nansen perp meta --filter ETH # list assets + max leverage
|
|
102
|
+
nansen perp order --coin ETH --side buy --size 0.1 --price 1600 --type limit
|
|
103
|
+
nansen perp order --coin BTC --side sell --size 0.001 --price 95000 --type market \
|
|
104
|
+
--take-profit 90000 --stop-loss 98000
|
|
105
|
+
nansen perp close --coin ETH --size 0.1 --price 1600 --side sell # sell closes a long
|
|
106
|
+
nansen perp cancel --coin ETH --oid <orderId>
|
|
107
|
+
nansen perp leverage --coin ETH --leverage 5 --margin-type cross # or isolated
|
|
108
|
+
nansen perp transfer --direction spot-to-perp --amount 25 # Spot<->Perps (or perp-to-spot)
|
|
109
|
+
nansen perp positions
|
|
110
|
+
nansen perp account # value, unrealized PnL, margin, spot USDC
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`--side` is `buy`/`long` or `sell`/`short` to open (`buy`/`sell` to close); `--tif` is `Gtc`/`Ioc`/`Alo`; `--slippage` is a decimal in `[0,1]`; `--leverage` is a whole integer capped at the asset max. Perp orders are irreversible once signed. USDC sent to a wallet via Hyperliquid's **Send** lands in the **Spot** balance (shown as `Spot USDC`); move it to Perps with `perp transfer` before trading. See the `nansen-trading` skill for details.
|
|
114
|
+
|
|
115
|
+
## Bridge
|
|
116
|
+
|
|
117
|
+
Move USDC between EVM chains and Hyperliquid via `nansen bridge`. Uses the same wallet and `NANSEN_WALLET_PASSWORD` as perps and DEX trading. Quotes are written to a local file and executed by id; execution signs and broadcasts.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
nansen bridge quote --from-chain base --to-chain hyperliquid --from-token USDC --amount 1000000
|
|
121
|
+
nansen bridge execute --quote <quoteId>
|
|
122
|
+
nansen bridge execute --quote <quoteId> --nonce 20 --priority-fee 5 # replace a stuck EVM deposit
|
|
123
|
+
nansen bridge status --request-id <id>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Supported routes: `base → hyperliquid` (deposit), and `hyperliquid → base`/`ethereum`/`arbitrum` (withdraw). Deposits broadcast an EVM transaction locally, so only Base is offered on the deposit side; run `nansen bridge help` for the current list. `--amount` is a base-unit integer by default; pass `--amount-unit token` for a human amount. `--recipient` defaults to the wallet's own EVM address. `--priority-fee`/`--max-fee` (gwei) and `--nonce` apply only to EVM deposit legs and let a stuck transaction be replaced. Bridge transfers are irreversible once signed.
|
|
127
|
+
|
|
96
128
|
## Wallet
|
|
97
129
|
|
|
98
130
|
```bash
|
|
@@ -177,17 +209,28 @@ nansen research smart-money netflow --chain solana --fields token_symbol,net_flo
|
|
|
177
209
|
|
|
178
210
|
```json
|
|
179
211
|
{ "success": true, "data": <api_response> }
|
|
180
|
-
{ "success": false, "error": "message", "code": "ERROR_CODE", "status": 401 }
|
|
212
|
+
{ "success": false, "error": "message", "code": "ERROR_CODE", "status": 401, "details": { ... } }
|
|
181
213
|
```
|
|
182
214
|
|
|
183
215
|
**Critical error codes:**
|
|
184
216
|
|
|
185
217
|
| Code | Action |
|
|
186
218
|
|------|--------|
|
|
187
|
-
| `CREDITS_EXHAUSTED` | Stop all API calls immediately.
|
|
219
|
+
| `CREDITS_EXHAUSTED` | Stop all API calls immediately. `details.credits.remaining` is your actual balance. Top up at [app.nansen.ai/api](https://app.nansen.ai/api). |
|
|
188
220
|
| `UNAUTHORIZED` | Wrong or missing key. Re-auth. |
|
|
189
|
-
| `RATE_LIMITED` | Auto-retried by CLI. |
|
|
221
|
+
| `RATE_LIMITED` | Auto-retried by CLI. `details.rateLimit.resetSeconds` is how long the window needs to drain. |
|
|
190
222
|
| `UNSUPPORTED_FILTER` | Remove the filter and retry. |
|
|
223
|
+
| `SERVER_ERROR` | Not your fault. Quote `details.requestId` when reporting it. |
|
|
224
|
+
|
|
225
|
+
**Error metadata.** When the API reports them, `details` carries:
|
|
226
|
+
|
|
227
|
+
| Field | Meaning |
|
|
228
|
+
|-------|---------|
|
|
229
|
+
| `requestId` | Identifies this call end to end. Quote it in any support report. Opaque — do not parse it. |
|
|
230
|
+
| `credits` | `used`, `remaining` |
|
|
231
|
+
| `rateLimit` | `limit`, `remaining`, `resetSeconds` |
|
|
232
|
+
|
|
233
|
+
Any field may be absent or `null`, meaning unknown — never assume zero. A low-balance warning goes to **stderr**, so stdout stays pure JSON.
|
|
191
234
|
|
|
192
235
|
## Troubleshooting
|
|
193
236
|
|
|
@@ -195,7 +238,8 @@ nansen research smart-money netflow --chain solana --fields token_symbol,net_flo
|
|
|
195
238
|
|---------|-----|
|
|
196
239
|
| `command not found` | `npm install -g nansen-cli` |
|
|
197
240
|
| `UNAUTHORIZED` after login | `cat ~/.nansen/config.json` or set `NANSEN_API_KEY` |
|
|
198
|
-
| Empty perp results | Use `--symbol BTC`, not `--token`. Perps are Hyperliquid-only. |
|
|
241
|
+
| Empty perp _research_ results | Use `--symbol BTC`, not `--token`. Perps are Hyperliquid-only. |
|
|
242
|
+
| `perp` _trading_ prints the usage banner | Trading needs `--coin BTC` (`--symbol` also works); see the Perpetuals section. |
|
|
199
243
|
| `UNSUPPORTED_FILTER` on token holders | Remove `--smart-money` — not all tokens have that data. |
|
|
200
244
|
| Huge JSON response | Use `--fields` to select columns. |
|
|
201
245
|
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -41,9 +41,19 @@ function hasTTY() {
|
|
|
41
41
|
return process.stdin.isTTY && process.stderr.isTTY;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// npx is a .cmd shim on Windows, and Node refuses to spawn .cmd/.bat without a
|
|
45
|
+
// shell (CVE-2024-27980). Go through cmd.exe explicitly rather than enabling
|
|
46
|
+
// `shell: true`, which would hand the whole command line to the shell parser.
|
|
47
|
+
const IS_WIN = process.platform === "win32";
|
|
48
|
+
|
|
49
|
+
function npxInvocation(args) {
|
|
50
|
+
return IS_WIN ? ["cmd.exe", ["/c", "npx", ...args]] : ["npx", args];
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
function hasNpx() {
|
|
45
54
|
try {
|
|
46
|
-
|
|
55
|
+
const [cmd, cmdArgs] = npxInvocation(["--version"]);
|
|
56
|
+
execFileSync(cmd, cmdArgs, { stdio: "ignore", shell: false });
|
|
47
57
|
return true;
|
|
48
58
|
} catch {
|
|
49
59
|
return false;
|
|
@@ -86,7 +96,7 @@ function prompt(question) {
|
|
|
86
96
|
|
|
87
97
|
function runCommand(cmd, args) {
|
|
88
98
|
return new Promise((resolve) => {
|
|
89
|
-
const child = spawn(cmd, args, { stdio: "inherit", shell:
|
|
99
|
+
const child = spawn(cmd, args, { stdio: "inherit", shell: false });
|
|
90
100
|
child.on("close", (code) => resolve(code === 0));
|
|
91
101
|
child.on("error", () => resolve(false));
|
|
92
102
|
});
|
|
@@ -113,7 +123,7 @@ async function installSkill() {
|
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
log(`Installing Nansen skill...`);
|
|
116
|
-
const ok = await runCommand(
|
|
126
|
+
const ok = await runCommand(...npxInvocation(["-y", "skills", "add", SKILL_REPO]));
|
|
117
127
|
if (!ok) {
|
|
118
128
|
log(`${YELLOW}Skill installation failed. You can retry with: npx skills add ${SKILL_REPO}${RESET}`);
|
|
119
129
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nansen-trading
|
|
3
|
-
description: Execute DEX swaps on Solana or Base
|
|
3
|
+
description: Execute DEX swaps on Solana or Base (including cross-chain bridges) and Hyperliquid perpetual trades. Use when buying or selling a token, getting a swap quote, executing a trade, or opening/closing/managing a perp position.
|
|
4
4
|
metadata:
|
|
5
5
|
openclaw:
|
|
6
6
|
requires:
|
|
@@ -191,6 +191,54 @@ If the user says "$20 worth of X", use `--amount-unit usd` directly — no manua
|
|
|
191
191
|
- A wallet is required even for quotes (the API builds sender-specific transactions).
|
|
192
192
|
- ERC-20 swaps may require an approval step — execute handles this automatically.
|
|
193
193
|
|
|
194
|
+
# Perp Trading
|
|
195
|
+
|
|
196
|
+
Use `nansen perp` for Hyperliquid perpetual trading. Uses the same wallet and `NANSEN_WALLET_PASSWORD` as DEX trading; requires an **EVM** wallet. **Perp orders are irreversible once signed.**
|
|
197
|
+
|
|
198
|
+
Subcommands: `order`, `cancel`, `close`, `leverage`, `positions`, `orders`, `account`, `meta`.
|
|
199
|
+
|
|
200
|
+
The asset is selected with `--coin` (e.g. `BTC`, `ETH`); `--symbol` is accepted as an alias. List tradable assets and their max leverage with `nansen perp meta` (use `--filter <text>` or `--all` to see beyond the first 20).
|
|
201
|
+
|
|
202
|
+
## Open a position
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Limit long: 0.1 ETH at $1600
|
|
206
|
+
nansen perp order --coin ETH --side buy --size 0.1 --price 1600 --type limit
|
|
207
|
+
|
|
208
|
+
# Market short with optional take-profit / stop-loss
|
|
209
|
+
nansen perp order --coin BTC --side sell --size 0.001 --price 95000 --type market \
|
|
210
|
+
--take-profit 90000 --stop-loss 98000
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- `--side`: `buy`/`long` to open a long, `sell`/`short` to open a short.
|
|
214
|
+
- `--size`: position size in base asset units (positive number).
|
|
215
|
+
- `--price`: limit price (or mark price for market orders).
|
|
216
|
+
- `--type`: `limit` (default) or `market`. `--tif`: `Gtc` (default), `Ioc`, `Alo`.
|
|
217
|
+
- `--slippage`: decimal in `[0,1]` for market orders (default `0.03` = 3%).
|
|
218
|
+
|
|
219
|
+
## Close / cancel
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# Close: sell to close a long, buy to close a short (validated against your open position)
|
|
223
|
+
nansen perp close --coin ETH --size 0.1 --price 1600 --side sell
|
|
224
|
+
|
|
225
|
+
# Cancel a resting order by id
|
|
226
|
+
nansen perp cancel --coin ETH --oid 123456
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Leverage, transfers & account
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
nansen perp leverage --coin ETH --leverage 5 --margin-type cross # or isolated
|
|
233
|
+
nansen perp transfer --direction spot-to-perp --amount 25 # or perp-to-spot
|
|
234
|
+
nansen perp positions
|
|
235
|
+
nansen perp account # account value, unrealized PnL, margin used, withdrawable, spot USDC
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`--leverage` must be a whole integer and is capped at the asset's maximum (see `perp meta`).
|
|
239
|
+
|
|
240
|
+
**Spot vs Perps:** perp trading draws from the **Perps** balance, but USDC sent to a wallet via Hyperliquid's **Send** lands in **Spot** (and shows as `Spot USDC` in `perp account`). Move it across with `perp transfer --direction spot-to-perp --amount <usdc>` before trading. (Deposits via the bridge land in Perps directly.)
|
|
241
|
+
|
|
194
242
|
## Source
|
|
195
243
|
|
|
196
244
|
- npm: https://www.npmjs.com/package/nansen-cli
|
package/src/api.js
CHANGED
|
@@ -8,6 +8,16 @@ import path from 'path';
|
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { EVM_CHAINS } from './chain-ids.js';
|
|
10
10
|
import { getAnonymousId, TELEMETRY_DISABLED } from './telemetry.js';
|
|
11
|
+
import { readResponseMeta } from './response-meta.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Key for the credit/rate-limit metadata attached to a successful response.
|
|
15
|
+
*
|
|
16
|
+
* A symbol on purpose: JSON.stringify and Object.keys both skip it, so the JSON
|
|
17
|
+
* every command prints is byte-for-byte unchanged while callers that want the
|
|
18
|
+
* numbers can still read them off the returned object.
|
|
19
|
+
*/
|
|
20
|
+
export const RESPONSE_META = Symbol('nansenResponseMeta');
|
|
11
21
|
|
|
12
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
23
|
|
|
@@ -465,6 +475,15 @@ export class NansenAPI {
|
|
|
465
475
|
ttl: options.cache?.ttl ?? DEFAULT_CACHE_TTL
|
|
466
476
|
};
|
|
467
477
|
this.defaultHeaders = options.defaultHeaders || {};
|
|
478
|
+
/**
|
|
479
|
+
* Credit/rate-limit metadata from the most recent response, or null.
|
|
480
|
+
*
|
|
481
|
+
* Survives any reshaping a command handler does to the response body, which
|
|
482
|
+
* the RESPONSE_META symbol on the returned object does not. Last write wins
|
|
483
|
+
* when a handler makes several calls — the freshest balance, which is what a
|
|
484
|
+
* low-credit warning wants.
|
|
485
|
+
*/
|
|
486
|
+
this.lastResponseMeta = null;
|
|
468
487
|
}
|
|
469
488
|
|
|
470
489
|
static cleanBody(body) {
|
|
@@ -520,7 +539,11 @@ export class NansenAPI {
|
|
|
520
539
|
}
|
|
521
540
|
} catch { /* balance check is best-effort */ }
|
|
522
541
|
}
|
|
523
|
-
|
|
542
|
+
const data = await paidResponse.json();
|
|
543
|
+
const meta = readResponseMeta(paidResponse);
|
|
544
|
+
this.lastResponseMeta = meta;
|
|
545
|
+
if (meta && data !== null && typeof data === 'object') data[RESPONSE_META] = meta;
|
|
546
|
+
return data;
|
|
524
547
|
}
|
|
525
548
|
|
|
526
549
|
async request(endpoint, body = {}, options = {}) {
|
|
@@ -581,11 +604,17 @@ export class NansenAPI {
|
|
|
581
604
|
data = await response.json();
|
|
582
605
|
} catch (_err) {
|
|
583
606
|
// Non-JSON response (rare, usually server errors)
|
|
607
|
+
const meta = readResponseMeta(response);
|
|
608
|
+
this.lastResponseMeta = meta;
|
|
584
609
|
const error = new NansenError(
|
|
585
610
|
`Invalid response from API (status ${response.status})`,
|
|
586
611
|
response.status >= 500 ? ErrorCode.SERVER_ERROR : ErrorCode.UNKNOWN,
|
|
587
612
|
response.status,
|
|
588
|
-
{
|
|
613
|
+
{
|
|
614
|
+
body: await response.text().catch(() => null),
|
|
615
|
+
attempt: attempt + 1,
|
|
616
|
+
...(meta?.requestId && { requestId: meta.requestId })
|
|
617
|
+
}
|
|
589
618
|
);
|
|
590
619
|
|
|
591
620
|
if (shouldRetry && attempt < maxRetries && response.status >= 500) {
|
|
@@ -603,7 +632,9 @@ export class NansenAPI {
|
|
|
603
632
|
|| `API error: ${response.status}`;
|
|
604
633
|
// nansen-api proxy stringifies nested error dicts via Python str(), producing
|
|
605
634
|
// "{'message': 'actual error', ...}". Extract the inner message if present.
|
|
606
|
-
|
|
635
|
+
// Require the closing quote to be followed by a comma or closing brace so
|
|
636
|
+
// an apostrophe inside the message (e.g. "can't") doesn't truncate it.
|
|
637
|
+
const nestedMatch = typeof message === 'string' && message.match(/['"]message['"]\s*:\s*['"](.*?)['"]\s*[,}]/s);
|
|
607
638
|
if (nestedMatch) message = nestedMatch[1];
|
|
608
639
|
const code = statusToErrorCode(response.status, data);
|
|
609
640
|
const retryAfterMs = parseRetryAfter(response.headers.get('retry-after'));
|
|
@@ -702,10 +733,23 @@ export class NansenAPI {
|
|
|
702
733
|
}
|
|
703
734
|
}
|
|
704
735
|
|
|
736
|
+
// Quota state and the request id belong on the error above all: an
|
|
737
|
+
// out-of-credits or rate-limited failure is exactly when the caller
|
|
738
|
+
// needs the numbers, and a 5xx is worthless to support without the id.
|
|
739
|
+
// formatError() surfaces details, so this needs no plumbing.
|
|
740
|
+
//
|
|
741
|
+
// On a retried call this is the LAST attempt's id — each attempt gets
|
|
742
|
+
// its own server-side id, and the last one is the failure worth
|
|
743
|
+
// reporting.
|
|
744
|
+
const meta = readResponseMeta(response);
|
|
745
|
+
this.lastResponseMeta = meta;
|
|
705
746
|
lastError = new NansenError(message, code, response.status, {
|
|
706
747
|
...data,
|
|
707
748
|
attempt: attempt + 1,
|
|
708
|
-
retryAfterMs
|
|
749
|
+
retryAfterMs,
|
|
750
|
+
...(meta?.requestId && { requestId: meta.requestId }),
|
|
751
|
+
...(meta?.credits && { credits: meta.credits }),
|
|
752
|
+
...(meta?.rateLimit && { rateLimit: meta.rateLimit })
|
|
709
753
|
});
|
|
710
754
|
|
|
711
755
|
// Retry on specific status codes
|
|
@@ -722,12 +766,21 @@ export class NansenAPI {
|
|
|
722
766
|
if (attempt > 0) {
|
|
723
767
|
data._meta = { ...(data._meta || {}), retriedAttempts: attempt };
|
|
724
768
|
}
|
|
725
|
-
|
|
769
|
+
|
|
726
770
|
// Cache successful response
|
|
727
771
|
if (useCache) {
|
|
728
772
|
setCachedResponse(endpoint, body, data);
|
|
729
773
|
}
|
|
730
|
-
|
|
774
|
+
|
|
775
|
+
// Attach after caching so the cache stores the payload alone — quota
|
|
776
|
+
// numbers are per-response and would be stale on a cache hit.
|
|
777
|
+
// Guarded: a response body can be a primitive, which cannot take a property.
|
|
778
|
+
const meta = readResponseMeta(response);
|
|
779
|
+
this.lastResponseMeta = meta;
|
|
780
|
+
if (meta) {
|
|
781
|
+
if (data !== null && typeof data === 'object') data[RESPONSE_META] = meta;
|
|
782
|
+
}
|
|
783
|
+
|
|
731
784
|
return data;
|
|
732
785
|
}
|
|
733
786
|
|