SKILL.md
vregistry-c914171 · 22.2 KB
---
name: nado
license: CC-BY-4.0. Publication and distribution require attribution to “markpaper — Nado knowledge base.” Full terms are in LICENSE.md next to this skill.
description: Practical, verified knowledge about Nado (a CLOB perpetual DEX on Ink, based on the ex-Vertex stack). Apply it to ANY Nado code or analysis — gateway `/query` and `/execute`, EIP-712 (verifying contract = address(productId) for place_order, endpoint for everything else), linked signer / 1-Click Trading, bytes32 sub-account, nonce with recv_time and tag, appendix, digest instead of oid, symbols and x18 numbers, non-power-of-10 lots, trading_status (post_only, not_tradable, reduce_only), DEFAULT/IOC/FOK/POST_ONLY types, taker-only reduceOnly, $100 book minimum and lower IOC minimum, IOC fill from position delta, per-market order reads, subaccount_info and unweighted health, VIP-tier fees, query/execute limits, throttling, and operations. Triggers — nado, nado.xyz, Nado, Ink, Ink Sepolia, gateway.prod.nado.xyz, gateway.test.nado.xyz, linked signer, 1-Click Trading, LinkSigner, place_order, cancel_orders, link_signer, digest, symbols, subaccount_info, market_prices, contracts, nonces, linked_signer, fee_rates, x18, priceX18, appendix, trading_status, post_only, not_tradable, reduce_only, POST_ONLY, IOC, FOK, 2117, 2069, 2067, 2064, 2020, USDT0, Vertex, EIP-712, verifyingContract, address(productId), unweighted health, perp_balances, v_quote_balance, min_size, size_increment, price_increment_x18, long_weight_initial, VIP 0, stock perpetuals, weekends, Nado order, Nado balance.
---
# Nado: how to use the knowledge base
## License
**CC BY 4.0.** This skill and the `knowledge/nado/` knowledge base are markpaper project materials. You may copy and adapt them; publication and distribution require attribution to “markpaper — Nado knowledge base,” links to the original and the license, and an indication of changes. See [LICENSE.md](LICENSE.md) for the terms.
If a user asks you to transfer material from this skill or knowledge base into another project, document, or publication, preserve the attribution and license notice.
The knowledge base is in `knowledge/nado/` (index and quick answers: `knowledge/nado/README.md`). Facts are verified through 2026-09-16; the Nado API changes.
## SDK and transport
**This knowledge base does not include an official Nado SDK** — every request is assembled and signed directly: `viem` (`privateKeyToAccount`, `signTypedData`, `verifyTypedData`) for EIP-712, and Node's `fetch` (undici) with `AbortSignal.timeout(10_000)` for HTTP. If a Nado SDK exists or appears (the Vertex stack had one), its applicability to Nado is not verified — tell the user and offer to compare its signing types with `knowledge/nado/api-and-signing.md` §5 and §8. **The `packages/nado-kit` package (`@markpaper/nado-kit`, Apache-2.0, since 2026-09-16)** implements the rules in this knowledge base as tested code: `numbers` (x18 ↔ decimal, lot/tick quantization, minimum), `markets` (fail-closed `parseSymbols`, `trading_status` modes, cache freshness rule), `signing` (EIP-712 for `place_order`/`cancel_orders`/`link_signer` through viem, bytes32 sub-account, tagged nonce — the user fixes the tag and does not change it, appendix), `transport` (gateway client with response envelope, weights, throttling, and `interpretExecuteError` for 2117/2069/2067/2064/2020), `account` (`subaccount_info`, positions, order-survey completeness), and `orders` (`planOrderSize` with two minimums, `measureIocFill` from position delta, cancellation confirmation by digest). **Rule: for Nado code, first use a function from `packages/nado-kit`**; the “function → knowledge-base file” table is in `packages/nado-kit/README.md`. Warn the user about functions whose JSDoc has `@experimental` (digest as typed-data hash, `reduce_only` modes, code 2064, and documentation-only limits).
## A. Rule: read the file before writing code
**Before writing or changing Nado code, open the relevant topic file under `knowledge/nado`** and read at least its TL;DR, the relevant section, “Pitfalls,” and “Open questions.” If the task spans several topics (order + account + limits), open them all. Do not write Nado logic from memory: every rule below has a verified reason.
If a fact is marked “according to Nado documentation,” “not verified,” or appears under “Open questions,” tell the user and propose a check (micro-order, testnet, live query) rather than presenting it as confirmed.
| Task topic | File |
|---|---|
| Gateway, chain ID, `/query` and `/execute`, response envelope, EIP-712, domain and verifying contract, bytes32 sub-account, nonce, appendix, expiration, digest, linked signer, error classification | `knowledge/nado/api-and-signing.md` |
| `symbols`, product fields, x18 and BigInt, lots and ticks, quantization, leverage from weights, `trading_status`, listing, symbols cache and freeze, mids, coin names | `knowledge/nado/markets-and-numbers.md` |
| Order payload, types, taker-only reduceOnly, `post_only`/`not_tradable` modes, minimums, oversized RO, IOC fill from delta, reading orders, cancellation, retries, codes, smoke test | `knowledge/nado/orders.md` |
| `subaccount_info`, account value, unified margin, positions and PnL, order-survey completeness, stability fence, leverage, fees and tiers, `fee_rates`, link verification | `knowledge/nado/account-and-fees.md` |
| Query/execute limits, weights, local throttle, per-tick cost, execute budget, retries | `knowledge/nado/rate-limits.md` |
| Configuration and preflight, geoblocking, testnet and faucet, account and signer, rollout, diagnosis, manual orders, key rotation, shutdown | `knowledge/nado/ops.md` |
| Consolidated pitfalls table, error classes, antipatterns | `knowledge/nado/pitfalls.md` |
## B. Hard facts and invariants (always remember)
**API and signing**
1. Mainnet gateway `https://gateway.prod.nado.xyz/v1` (Ink, chain ID 57073); testnet `https://gateway.test.nado.xyz/v1` (Ink Sepolia, 763373). `POST …/query` is an unsigned read; `POST …/execute` is a signed action. A response always uses `{status:'success', data}` or `{status:'failure', error, error_code}`. Every request has a 10-second timeout. → `api-and-signing.md` §1–2
2. Compare the chain ID with the `contracts` query **before the first execute**; mismatch means refuse startup, not warn. Read `endpoint_addr` from the same response. → `api-and-signing.md` §1.3
3. EIP-712 domain `{name:'Nado', version:'0.0.1', chainId, verifyingContract}`: for `place_order`, `verifyingContract = address(productId)`; for `cancel_orders` and `link_signer`, use `endpoint_addr`. Domains are separated: a signature under another product ID does not verify. → `api-and-signing.md` §5
4. `sender` is the **master account's** bytes32 sub-account (20 address bytes + up to 12 ASCII bytes of the name; `default` is the UI sub-account), even when a linked signer signs. A different name means a different account, which looks valid and empty (`exists:false`, no errors). → `api-and-signing.md` §6
5. Order/cancellation nonce = `(recv_time_ms << 20) | client bits`; recv_time is a deadline (for example, now + 60 s; according to documentation, no farther than +100 s). The low 20 bits return in `orders`: the package stores an “ours / foreign” tag and neutral `reduceIntent` there; the bit does not select a TP strategy. **Do not change the tag scheme between deployments.** `link_signer` uses another nonce: `tx_nonce` from the `nonces` query. → `api-and-signing.md` §7
6. Appendix: `version` = 1, type in bits 10..9 (0 DEFAULT, 1 IOC, 2 FOK, 3 POST_ONLY), reduceOnly in bit 11. Live values: DEFAULT `1`, IOC `513`, IOC+RO `2561`, POST_ONLY `1537`. Expiration is `2^64 − 1` for every type. → `api-and-signing.md` §8–9
7. A `place_order` response contains only a `digest` (32 bytes), with no status or fill. Cancellation needs both digest and product ID — store them together; after restart, only a reread of the `orders` book recovers them. → `api-and-signing.md` §10, `ops.md` §9
8. Linked signer (“1-Click Trading”): the master signs `LinkSigner` in a browser popup, not a CLI on the server. The signer trades; withdrawal is only to the master; 50 link/revoke operations per 7 days; a new signer replaces the old one; revoke = link the zero address; the sub-account must hold ≥ 5 USDT0. Verify through `linked_signer`. → `api-and-signing.md` §11
9. A failure envelope on execute is a final sequencer rejection: the order was not applied; **do not retry**. Retry 429. A 5xx/timeout has unknown outcome: retry only `cancel_orders` and a full reduceOnly close. Nado returns 403 without compression negotiation — do not set `Accept-Encoding` manually. → `api-and-signing.md` §2, §13
**Markets and numbers**
10. `symbols` → an **object** `{'BTC-PERP': {...}}`; every number is an integer x18 string (`price_increment_x18`, `size_increment`, `min_size`, weights, fees). Read as `BigInt` and convert through an exact decimal string; response nonces around 1.87e18 > 2^53 must also remain strings. → `markets-and-numbers.md` §1–2
11. Lots are not powers of 10 (BTC 0.00005, XRP 5, PONS 2); ticks are fixed (BTC $1, ETH $0.1, XRP $0.0001). Quantize with BigInt functions and apply epsilon to the quotient; use `floorSz` for everything and `ceilSz` only for a full close; use the same function in planner and executor. → `markets-and-numbers.md` §3
12. Leverage is not configurable: `maxLeverage ≈ 1/(1 − long_weight_initial)` (0.98 → 50x, 0.9 → 10x). → `markets-and-numbers.md` §4
13. `trading_status`: `live` / `post_only` / `reduce_only` / `soft_reduce_only` / `not_tradable`. Listing progresses through `not_tradable → post_only → live`; **stock perpetuals switch to `post_only` on weekends**. A mode from the cache (5-minute TTL) is not a current fact: publish it only from a fresh cache, otherwise `undefined`. → `markets-and-numbers.md` §5
14. Nado is not crypto-only: 72–75 perpetuals, including stocks, ETFs, FX, and commodities. Always obtain the list from live `symbols`; never claim “not listed” from memory. → `markets-and-numbers.md` §6
15. Symbols cache: serve stale on failure, freshness = two TTLs, stability check (ID, lot, tick) that **does not** apply to `trading_status`. A market rename (CIRCLE → CRCL, 2026-08-10) freezes a strict cache until restart; remedy: restart after three control requests. → `markets-and-numbers.md` §7
**Orders**
16. Types: DEFAULT (GTC), IOC, FOK, POST_ONLY. There is no market-order type: use an IOC limit through the spread. **reduceOnly works only with IOC/FOK; resting RO → 2067.** A TP in the book is a plain limit order that reverses the position after a close. Safety: TP clamp — sum of resting orders on the reducing side ≤ position; before every IOC that changes the position, cancel all your resting orders for the coin (including TP), then replace them only from a fresh position read; set the real RO bit on every IOC reduction. → `orders.md` §2–3
17. A `post_only` market accepts **only** POST_ONLY (DEFAULT/IOC at any price → 2117); `not_tradable` → 2069. Resting orders there use POST_ONLY from a fresh cache; resend DEFAULT rejected with 2117 as POST_ONLY (new nonce, no duplicate); defer partial IOC (there is no taker flow); do not gate a full close. Do not substitute FOK/POST_ONLY for IOC, call the market unlisted, or gate a close from cache. → `orders.md` §4
18. `min_size` = $100 effectively applies to the book: no maker orders below $100 were observed, while IOC (including reduce-only) succeeded well below it. Keep two minimums: resting $100; IOC after measuring the floor with a buffer (default equals resting). Skip entries below minimum; never gate a full reduceOnly close. → `orders.md` §5
19. Oversized reduceOnly IOC: clipping or 2064 is not verified. Sequence: bump → on 2064 retry once with the exact position size → loud alert. → `orders.md` §6
20. IOC fill = position delta: read `subaccount_info` before the write (failure → do not send), place, then up to 3 reads at 150 ms intervals; use the order-direction delta capped at sent size; no delta → `REJECTED` (underestimation is safe; overestimation is not). Confirm GTC by its appearance in `orders` with your tag. → `orders.md` §7
21. `orders` is read for explicit `product_ids` (weight 2 per ID; choose chunk size for the configured query burst): a truncated read looks like an empty account. Only a complete survey of every market proves “there are no orders”; store a completeness indicator with the order snapshot. Do not touch foreign orders (without the tag), but count them as exposure. → `orders.md` §8, `account-and-fees.md` §3
22. Cancellation is confirmed only by a digest in `cancelled_orders`; everything else (transport, unknown digest after restart, `OrderNotFound` 2020) is unconfirmed, so do not place a replacement. Perform all cancellations before placements. → `orders.md` §9
**Account, fees, limits**
23. `subaccount_info` → account value = `healths[2].health` (unweighted health, x18, USDT0). Unified margin: free stablecoins outside equity = 0. A position is `perp_balances[].balance.amount` (sign = side); `entryPrice ≈ |v_quote/amount|`; uPnL = `amount × oracle + v_quote`. A position in an unknown product or without an oracle makes the read degraded: do not conclude “there is no position” from it. → `account-and-fees.md` §1–2
24. Account snapshot fence: positions → orders → positions. If any `amount` changes, a fill occurred between reads and the snapshot is inconsistent; do not make decisions from it, and reread. → `account-and-fees.md` §4
25. VIP 0 fees: maker 1.0 / taker 3.5 bps; tier by 30-day volume, maker 0 from $100M, rebate from $500M. Rolling window versus monthly epochs is uncertain — inspect `fee_rates`. → `account-and-fees.md` §6
26. Documentation limits: queries 2400 weight/min per IP, executes 600/min per wallet; not verified by hitting the live limit. Configure sustained rate, burst, concurrency, and chunk size explicitly: `nado-kit` has no tuned defaults. Reject a request heavier than configured capacity before the network. → `rate-limits.md` §1–4
27. Execute budget is consumed by replacements (2 execute per order) and by cancelling your resting orders for the coin before IOC (cancellations plus replacement). Worst case (calculated, not observed): retrying a full close in `post_only` markets every tick can exceed a local 500 weight/min budget. There is no separate hard write window beyond the execute limit; nevertheless, calculate action cost before the first cancellation. → `rate-limits.md` §5
**Operations**
28. One process — one sub-account; manual orders on it are forbidden (an untagged order blocks “account empty”). Geoblocking: Russia is prohibited; the United States and Canada are view-only. → `ops.md` §2, §7
29. Rollout: testnet checklist with live orders (or micro-orders on mainnet) → mainnet with a small deposit; increase only after resolving the checklist's open questions (oversized RO IOC, IOC floor). → `ops.md` §3–5
## C. Checklist before using real money
Live launch, switching dry-run → live, and sending orders require an **explicit user command**.
- [ ] The sub-account has no manual or foreign orders, and no second process is running on it. → `ops.md` §7
- [ ] Preflight: `contracts` confirmed the chain ID and returned `endpoint_addr`; mismatch = refuse startup. → `api-and-signing.md` §1.3
- [ ] The sub-account name and bytes32 were printed at startup and checked against the UI; `subaccount_info.exists = true`, account value > 0. → `api-and-signing.md` §6, `ops.md` §1
- [ ] The linked signer was linked through a browser; `linked_signer` = process-key address; the master key is absent from the server; the signer key comes from an environment variable without a trailing newline. → `api-and-signing.md` §11
- [ ] The server is in an allowed jurisdiction (geoblocking). → `ops.md` §2
- [ ] Price and size quantization use one BigInt-based function; size 0 after quantization = skip; minimums are checked after quantization; openings below minimum = skip; full close is not gated. → `markets-and-numbers.md` §3, `orders.md` §5
- [ ] The IOC minimum is not below the measured floor (or equals the resting minimum); the floor was measured by binary search. → `orders.md` §5
- [ ] Every IOC reduction has the RO bit; TP in the book has no RO; sum of resting orders on the reducing side ≤ position; before every IOC that changes the position, all your resting orders for the coin are cancelled; no resting order for the coin remains in the book during a full close. → `orders.md` §3
- [ ] `trading_status` is consumed: resting in `post_only` → POST_ONLY; DEFAULT rejected with 2117 is resent as POST_ONLY; partial IOC is deferred; `not_tradable` markets are not enabled for trading. → `orders.md` §4
- [ ] IOC fill is measured by position delta; failure of the “before” read means the order is not sent; no delta → `REJECTED`. → `orders.md` §7
- [ ] Cancellation is confirmed only by the digest in `cancelled_orders`; after restart, the `orders` book was reread before the first cancellation. → `orders.md` §9
- [ ] Irreversible decisions (“account empty”) require a complete order survey. → `account-and-fees.md` §3
- [ ] The local throttle and `orders` chunk size are configured explicitly for the load; execute has high priority; every fetch has a timeout. → `rate-limits.md` §3–4
- [ ] A failure envelope is not retried; openings and partial reductions are not repeated after a timeout. → `api-and-signing.md` §13
- [ ] Graceful shutdown waits for the current cycle to finish. → `ops.md` §9
- [ ] Smoke test passed: GTC place → appears in `orders` with tag → cancel → digest in `cancelled_orders`; IOC entry is read as a fill; full close reaches zero; in a `post_only` market, a resting order remains in the book while IOC receives 2117 and is not rewritten. → `orders.md` §12
- [ ] The list of items **not verified live** (oversized RO, IOC floor, `reduce_only` modes, trigger/stop orders, limits) was written down and shown to the user. → `ops.md` §5, `api-and-signing.md` §15
## D. “Place an order” checklist
- [ ] Product metadata comes from live `symbols` (fresh cache): `product_id`, tick, lot, `min_size`, `trading_status`. One metadata object supplies both `product_id` in the body and `verifyingContract` in the domain. → `markets-and-numbers.md` §1, `api-and-signing.md` §5
- [ ] Price → `pxToStr` (ticks × tickX18, exact string); size → `floorSz` (or `ceilSz` only for a full close); neither is 0. → `markets-and-numbers.md` §3
- [ ] Type: resting → DEFAULT, or POST_ONLY in a `post_only` market; market action → IOC. reduceOnly only with IOC/FOK; appendix construction rejects every other combination. → `orders.md` §2, `api-and-signing.md` §8
- [ ] Minimum by type: resting ≥ $100; IOC ≥ IOC minimum; full close has no gate (bump above the minimum; on 2064 use exact size). → `orders.md` §5–6
- [ ] Signed `amount` (+ buy, − sell), `expiration = 2^64 − 1`, nonce = recv_time (now + 60 s) + role tag; every number in the body is a string. → `orders.md` §1, `api-and-signing.md` §7, §9
- [ ] Before an IOC that changes the position, your resting orders for the coin were cancelled and the cancellations confirmed by digest. → `orders.md` §3, §9
- [ ] IOC: position was read before sending; after sending, read up to 3 times; result is `FILLED`/`REJECTED` from the delta. Resting: result is `RESTING` with a digest; confirmation is the next `orders` read. → `orders.md` §7
- [ ] Response parsed by envelope: `failure` → `REJECTED` with code (2117 on DEFAULT → resend POST_ONLY; 2064 on full close → exact size; 2069 → retry next tick; new code → log in full); transport → reconcile, no retry (except idempotent operations). → `api-and-signing.md` §13, `orders.md` §10–11
- [ ] Digest stored together with product ID for future cancellation. → `api-and-signing.md` §10
## E. “Investigate an incident” checklist
Start with exchange primary evidence, not alert wording: the same alert (“orders do not rest,” “account is empty”) can have different causes.
- [ ] Placement logs: grep codes `2117`, `2069`, `2067`, `2064` and `REJECTED`. If a code is present, use the code table and its handling branch. → `orders.md` §4, §11
- [ ] Live `symbols`: coin's `trading_status` (weekends for stock perpetuals, pre-listing), tick/lot/`min_size` unchanged, coin not renamed. Is the cache fresh? “serving stale cache” lines in logs → freeze; restart after three control requests. → `markets-and-numbers.md` §5, §7
- [ ] Live `subaccount_info`: `exists`, account value, `perp_balances`. `exists:false` → sub-account name. Position in an unknown product → degraded, not flat. → `account-and-fees.md` §1–2
- [ ] Live `orders` for **every** product: yours (tagged) and foreign (untagged). “Empty” without a complete survey proves nothing; foreign orders mean manual trading on the sub-account. → `account-and-fees.md` §3, `ops.md` §7
- [ ] Code considers IOC “filled,” but position differs: inspect before/after delta, cap at sent size, and whether the “before” read was missing. → `orders.md` §7
- [ ] Cancellation “succeeded,” but order is live: was the digest present in `cancelled_orders`; after restart, was the `orders` book reread before cancellation? → `orders.md` §9
- [ ] Reversed position: a TP limit without RO filled after close — were your resting orders for the coin cancelled before IOC, was their sum clamped to the position, did resting orders remain in the book during full close? → `orders.md` §3
- [ ] On weekends, IOC against a position receives `2117` — this is `post_only` mode, not an outage: partial IOC is deferred and full close is rejected; inspect execute-budget counters in the throttle. → `orders.md` §4.2, `rate-limits.md` §5
- [ ] Every execute fails after deployment: chain ID vs gateway; 403 on everything: `Accept-Encoding`. → `api-and-signing.md` §1.3, §2
- [ ] Use numbers only from live requests taken at one moment (equity + positions from one `subaccount_info`), not memory or two snapshots; never present a partial snapshot as complete.
- [ ] Add newly verified knowledge to the topic's knowledge-base file, anonymized and dated; explicitly resolve conflicts with an older entry (which conclusion is newer and why).