Discover the market
market = "USDCcNGN-SPOT"contract_type = "spot"order_entry_spec = "usdc_cngn_spot_v1"
market symbol over asset_address and sub_id when you call the read endpoints. The
symbol is stable; the address pair can change between deployments.
Read the current price
spot_contract object. Read the price from it:
GET /v1/markets publishes the conversion rules inline on the spot entry, so you can assert them
at runtime rather than hardcoding them:
Book sides are named for the engine
bids and asks describe the engine’s view, and the engine side is always the inverse of the
trader’s. In trader terms the arrays read backwards:
Each order also carries
spot_contract.ui_intent.side, which states the trader-facing direction
directly. Branch on that rather than on the array name.
A mid price, with the sides read correctly:
Prefer the book over the last trade
GET /v1/trades returns the most recent fills, each with its own spot_contract. That is the right
source for a “last traded at” display, and every trade carries created_at so you can show its age.
It is the wrong source for a current rate. In a quiet period the last fill can be hours or days old,
and the endpoint returns it without any staleness signal — a stale price and a fresh one look
identical. The book quotes continuously, so a mid price stays current even when nothing is trading.
Check that
bids and asks are both non-empty before computing a mid. If no maker is quoting,
the arrays come back empty.Candles
spot_contract wrapper, so invert them yourself:
ui_price = 1 / engine_price. Buckets with no trades are absent rather than zero-filled.
Polling and streaming
No rate limit is enforced. Polling every one to five seconds is ample for a displayed rate; be considerate rather than aggressive. For anything latency-sensitive, subscribe to the websocket stream instead. Use thebook and trades channels with "market": "USDCcNGN-SPOT", seed from the
snapshot frame, and apply update deltas. Public channels need no authentication, and
server-side clients are unaffected by the browser origin allowlist.
Submitting spot orders
The same translation applies in reverse when you place an order. Send trader-facing values inui_intent, and the engine values in the signed action_json.data:
order_entry_spec and ui_intent are accepted on spot orders only — omit both on every other
market. See Authentication and signing for building the signed payload, and
Create order for the full request body.
Fees follow arrival order, not identity
GET /v1/markets reports the schedule for each market as taker_fee_bps and maker_fee_bps.
Read it from there — it is the only source, and the matcher charges exactly what it says.
The fee is paid by the taker, and taker is decided per fill by which of the two orders was
created later. It is not a property of an account, a strategy, or an order type.
The practical consequence is the bound you sign. worstFee caps what a fill may charge you, and
the contract reverts TM_FeeTooHigh rather than filling when the charge exceeds it. Signing 0
is safe only while a market charges nothing; on a market with a taker schedule it means every fill
where you arrive second reverts, silently and repeatedly, until the order expires.
Two details that decide the number:
worstFeeis per unit filled, not a total. The contract comparesfee / amountFilledagainst it, so the correct bound isfeeRate x price, and it changes with price. A single configured constant is right at exactly one price.- Leave headroom above the published schedule. Orders already resting were signed under the old number, and they cannot be re-signed atomically with a fee change. Our own clients sign 30 bps against a 25 bps schedule for this reason.

