Best practices
The core habits a live Native Core integration should follow.
Orders & cancels
Give every action a unique
cloid. It is your only handle to reconcile an order after a network blip.Refresh a stale quote by cancel + re-place, not by chasing it with
modify.
Know the outcome
acceptedis not success — readresponse. An order that failed at execution still returnssubmission_status: "accepted"with no top-levelerror; the code is inresponse.status.error, orresponse.statuses[i].status.errorfor abatchitem. Branching onsubmission_statusalone records a rejected order as live.The fill state is already in the
/tradereply.response.statusisopen/filled/cancelled/error, and abatchcarries the same per item underresponse.statuses[]— you don't need anorderStatuspoll to learn which.Only
RateLimitedis safe to resend — back offerror.retry_after_msand resend the same signed action. Every other rejection needs a fresh action.On a
timeout, branch onerror.codebefore you decide. TheHandoff*family (HTTP 503) never reached a node — resubmit it, or you drop every write for the duration of a leadership handoff. Everything else may still land: reconcile bycloid, never resubmit under a new nonce. If a duplicate fill would be costly, reconcile before resubmitting even on aHandoff*. See Handle outcomes & timeouts.
Signing & nonces
One nonce source per API wallet — a monotonic millisecond clock. Never run two writers on one key; they collide. Shard across distinct API wallets.
Resolve
agent_epochlive fromuserAgents; don't hardcode it.
Numbers
Send price and quantity as strings, never floats — a binary float silently corrupts a price.
Snap to the market's precision before signing. Take
price_decimals/base_quantity_decimalsfrommarketsand the minimum fromquoteAssets.
Reads & rate limits
Query
/infoby the owner address, not the API-wallet address — the agent address returns nothing.Budget 1 request/second per IP on each endpoint. Reads and writes hold separate buckets, so polling never eats your order rate — but neither bucket gives you a second request. Cache static metadata, poll on a fixed interval, and back off on
429— see rate limits.Once you need more than one read per second, stream instead of polling. A WebSocket subscription costs nothing against the request budget.
Streaming
Quote off
bbo, notl2Book. On mainnetl2Bookis a five-second snapshot — read it for depth and shape, never for the price you act on.bbopushes on every change to the top of book.orderUpdatesis the event stream;openOrdersis the reconciliation.openOrdersis a full replacement at most every five seconds, so driving state off it silently drops every transition in between. Track lifecycle onorderUpdatesand useopenOrdersto catch drift.A partial fill arrives as
status: "open". Comparesz(remaining) againstorigSzon every update — branching onstatusalone misses partials entirely.Never wait on the stream to confirm a submission.
orderUpdatescarries only orders that reached the matching engine; a bad nonce or signature comes back on the/traderesponse and will never arrive as a frame.Deduplicate fills on
tid. After a reconnect theuserFillssnapshot overlaps the live stream by design. Resubscribe and rebuild from the snapshot packets rather than trying to patch the gap.POST /info userFillsreports the sametid, so a backfill merges in on it too.On a credit account, watch
pending_exposure, not justactual. It moves the moment an order rests or cancels, so it is your live risk ahead of settlement — seespotCreditState.Don't share the
postchannel between market data and trading. Onepostmay be in flight per IP, and a synchronous/tradeholds that slot for up to a block — a cheapinfoquery queued behind it just gets a429.
Degraded states
When suspended or frozen, keep cancelling. During
PlaceOrderSuspendedor on a frozen account onlycancel/cancelAllgo through — use them to reduce exposure; don't bundle a new order into the batch.
Last updated