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 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
- Mainnet gateway
https://gateway.prod.nado.xyz/v1(Ink, chain ID 57073); testnethttps://gateway.test.nado.xyz/v1(Ink Sepolia, 763373).POST …/queryis an unsigned read;POST …/executeis 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 - Compare the chain ID with the
contractsquery before the first execute; mismatch means refuse startup, not warn. Readendpoint_addrfrom the same response. →api-and-signing.md§1.3 - EIP-712 domain
{name:'Nado', version:'0.0.1', chainId, verifyingContract}: forplace_order,verifyingContract = address(productId); forcancel_ordersandlink_signer, useendpoint_addr. Domains are separated: a signature under another product ID does not verify. →api-and-signing.md§5 senderis the master account's bytes32 sub-account (20 address bytes + up to 12 ASCII bytes of the name;defaultis 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- 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 inorders: the package stores an “ours / foreign” tag and neutralreduceIntentthere; the bit does not select a TP strategy. Do not change the tag scheme between deployments.link_signeruses another nonce:tx_noncefrom thenoncesquery. →api-and-signing.md§7 - Appendix:
version= 1, type in bits 10..9 (0 DEFAULT, 1 IOC, 2 FOK, 3 POST_ONLY), reduceOnly in bit 11. Live values: DEFAULT1, IOC513, IOC+RO2561, POST_ONLY1537. Expiration is2^64 − 1for every type. →api-and-signing.md§8–9 - A
place_orderresponse contains only adigest(32 bytes), with no status or fill. Cancellation needs both digest and product ID — store them together; after restart, only a reread of theordersbook recovers them. →api-and-signing.md§10,ops.md§9 - Linked signer (“1-Click Trading”): the master signs
LinkSignerin 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 throughlinked_signer. →api-and-signing.md§11 - 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_ordersand a full reduceOnly close. Nado returns 403 without compression negotiation — do not setAccept-Encodingmanually. →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:
contractsconfirmed the chain ID and returnedendpoint_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_statusis consumed: resting inpost_only→ POST_ONLY; DEFAULT rejected with 2117 is resent as POST_ONLY; partial IOC is deferred;not_tradablemarkets 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, theordersbook 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
orderschunk 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
orderswith tag → cancel → digest incancelled_orders; IOC entry is read as a fill; full close reaches zero; in apost_onlymarket, 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_onlymodes, 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 bothproduct_idin the body andverifyingContractin the domain. →markets-and-numbers.md§1,api-and-signing.md§5 - Price →
pxToStr(ticks × tickX18, exact string); size →floorSz(orceilSzonly for a full close); neither is 0. →markets-and-numbers.md§3 - Type: resting → DEFAULT, or POST_ONLY in a
post_onlymarket; 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/REJECTEDfrom the delta. Resting: result isRESTINGwith a digest; confirmation is the nextordersread. →orders.md§7 - Response parsed by envelope:
failure→REJECTEDwith 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,2064andREJECTED. If a code is present, use the code table and its handling branch. →orders.md§4, §11 - Live
symbols: coin'strading_status(weekends for stock perpetuals, pre-listing), tick/lot/min_sizeunchanged, 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
ordersfor 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 theordersbook 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 ispost_onlymode, 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).