Skip to main content
The USDC/cNGN spot market reports its prices in two different units. Read this before integrating against spot — a price read from the wrong field is off by a factor of roughly 1,370.

Discover the market

Look for:
  • market = "USDCcNGN-SPOT"
  • contract_type = "spot"
  • order_entry_spec = "usdc_cngn_spot_v1"
Prefer the 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

Every order in the response carries a spot_contract object. Read the price from it:
Read spot_contract.ui_intent.price, not the top-level price or limit_price.The engine trades cNGN against internal USDC cash, so its native price is USDC per cNGN (0.000728875…). ui_intent.price is the reciprocal — cNGN per USDC (1371.977018) — which is the number a person expects to see. Reading the raw field yields a price near zero, which usually looks like a broken response rather than a unit mismatch.
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

Candle prices are raw engine values with no 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 the book 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 in ui_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.
Resting a quote does not make you the maker. If your order arrives after the one it matches against, you are the taker on that fill and you pay the taker fee — even if you have been sitting on the book for hours, and even if you think of yourself as a market maker.A bot that requotes frequently is the later order most of the time, so it is usually the taker.
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:
  • worstFee is per unit filled, not a total. The contract compares fee / amountFilled against it, so the correct bound is feeRate 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.
See Authentication and signing for how to compute and encode it.