Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.adipredictstreet.com/llms.txt

Use this file to discover all available pages before exploring further.

State machine

States

StateMeaningBalance effect
PENDINGOrder signed and submitted; balance locked; matcher call in flightavailable → locked for BUY notional
OPENResting in orderbooklocked remains
PARTIALSome quantity filled, remainder restinglocked reduced pro-rata at settlement
FILLEDTerminal — fully executedresidual locked refunded
CANCELLEDTerminal — user-initiated cancellocked → available (refund)
CANCELLED_BY_RESOLVETerminal — market was resolved; all open orders cleanedlocked → available (refund)
REJECTEDTerminal — rejected before matchinglocked → available (refund)
EXPIREDTerminal — expiration passedlocked → available (refund)
SETTLEMENT_FAILEDTerminal — every fill on this order reverted on-chain (MatchFailed for every leg)locked → available (refund). Rare; usually requires the maker book to have moved between off-chain match and on-chain submit.
PARTIAL is normalised to OPEN on GET /api/orders/open for client simplicity (the filledQty field carries the residual information). GET /api/orders/history returns the raw DB state, so a partially-filled-then-cancelled order surfaces there as PARTIAL (active leg) followed by CANCELLED (terminal). Code against both shapes when reconciling open vs historical views.

Filtering /api/orders/open

Both /open and /history accept the same filter shape — useful for MMs with thousands of resting orders who want to scope server- side instead of streaming the full open-set every poll:
Query paramTypeSemantics
marketIdstringRestrict to one market symbol
side"buy" / "sell" (case-insensitive)One side only
outcomeinteger ≥ 0One outcome index
type"limit" / "market" (case-insensitive)One order type
GET /api/orders/open?marketId=NCAA-FINAL-WINNER-93920000&side=buy&outcome=0
All four are optional; combine freely. Capped at 500 rows per response (the open-set is also bounded by per-wallet caps, so no cursor/pagination is exposed here — use /api/orders/history for keyset pagination over the terminal universe).

Trade-level settlement (after match)

Order state flips to FILLED / PARTIAL as soon as the matching engine returns fills — but the underlying trade rows then go through a separate on-chain settlement pipeline:
Trade stateMeaningLocked balance
matchedRecorded right after the matchRemains locked
settlement_pendingbatchMatchOrders tx broadcast on-chainRemains locked
settledOrderFilled event confirmed on-chainlocked → 0 (released)
settlement_failedTx reverted fully, or MatchFailed event for this specific leg (other legs in the batch can still settle)locked → available (refunded)
The most common cause of settlement_failed is MatchFailed(reason = 0xc56873ba) = OrderExpired() — a resting maker order whose expiration slipped between off-chain match and on-chain submit. Sign new orders with expiration = 0 to avoid this; see CTFExchange — MatchFailed reason codes. This is why PredictStreet uses a strict settlement model: partner-visible balances reflect only on-chain-confirmed state. Expect a few seconds (p99 < 30s in production) between order FILLED and the locked notional clearing. If settlement_pending exceeds 5 minutes, contact support — most likely chain congestion or a known stuck-trade case.

How partners observe settlement

The REST trade endpoints (/api/orders/{id}/fills, /api/me/trades) do not currently surface tx_hash or settlement state on the trade row — they always show the matched fill, not the on-chain status. The partner-recommended channels:
  • WebSocket /ws/user typed channels (recommended):
    • user_ordersorder_placed / order_cancelled. Use for orderbook UI updates.
    • user_fillsuser_fill fires when chain-watcher indexes the on-chain OrderFilled event. This is the balance-commit signal — available / locked totals from /api/me/balances reflect the trade after this event. Carries txHash, blockNumber, fee, vault addresses, matched amounts.
  • REST balance pollingavailable + locked totals derive from on-chain-confirmed state, so polling /api/me/balances is a backstop if WS is unavailable.
  • Settlement-leg failures are not surfaced on a WS channel today; to detect them, poll GET /api/orders/{id} and watch for status: SETTLEMENT_FAILED (locked balance is automatically refunded — the trade is treated as if it never settled).
See WebSocket — User events for the full payload shapes of each push.

What actually happens on POST /api/orders/place

  1. AuthnX-Api-Key is verified (partner lookup → SHA-256 hash compare → lifecycle + IP-allowlist checks). The canonical wallet is your key’s associatedWallet. orders:write scope is enforced before the handler runs — see API keys.
  2. Pre-verify enrichment — the platform resolves:
    • your vault via VaultFactory.vaultOf(signer)
    • the on-chain tokenId for the requested outcome
    • makerAmount / takerAmount from price × quantity per side
  3. EIP-712 verify — the full on-chain Order struct (11 fields) is reconstructed; the recovered signer must match the authenticated wallet, and vaultOf(signer) must equal maker. The body’s maker is then overridden to the resolved vault before storage.
  4. Market guard — the market’s status must be OPEN; otherwise 409 market_not_open.
  5. Balance / position lock — atomic:
    • BUY: available -= notional, locked += notional
    • SELL: position lookup is keyed by vault address (not EOA); fails with insufficient_position if the vault doesn’t hold enough of the outcome token
    • the order row is recorded as PENDING
  6. Match — sent to the matching engine. Returns {status, filledQty, trades[]} synchronously.
  7. Trade persistence — trade rows + fee ledger written, balance deltas applied for both sides of every trade — atomically.
  8. Settlement enqueue — matched trades are broadcast on-chain via batchMatchOrders on CTFExchange. See the trade-state table above.
  9. Response — shape documented below.
If step 6 fails after retries, step 5 is compensated: order status → REJECTED, locked balance refunded, call returns 503 exchange_unavailable.

Response shape

interface PlaceOrderResp {
  orderId: string;
  status:
    | 'PENDING'
    | 'OPEN'
    | 'FILLED'
    | 'CANCELLED'
    | 'REJECTED'
    | 'EXPIRED'
    | 'SETTLEMENT_FAILED';
  filledQty: string;
  remainingQty: string;
  trades: Trade[];
  code?: string;
  message?: string;
}

interface Trade {
  id: string;
  orderId: string;
  userWallet: string;        // EOA — the side's authenticated wallet
  marketId: string;
  price: string;             // decimal USDC
  quantity: string;          // outcome-token qty (decimal)
  fee: string;               // taker fee charged on this fill (decimal USDC)
  side: 'buy' | 'sell';      // from this wallet's perspective
  createdAt: string;
}
The synchronous trades[] carries the matched fill view — settlement state and txHash are not on this row. To observe on-chain confirmation, subscribe to the user_fills WebSocket channel and watch for user_fill events (they include txHash, blockNumber, and the matched amounts) — see How partners observe settlement above.

Idempotency

Include clientOrderId in the request body to make the call idempotent. If a network retry hits the server with the same (wallet, clientOrderId), the original response is replayed.

Cancellation

POST /api/orders/cancel
X-Api-Key: ps_live_<keyId>_<secret>  # integrators — needs `orders:write`
Content-Type: application/json

{ "orderId": "0x..." }
  • Matcher is called first (best-effort — NOT_FOUND is treated as idempotent success).
  • PG is the source of truth: on successful matcher.cancelOrder, we flip OPEN | PARTIAL → CANCELLED and refund residual locked balance in a single transaction.

Next

EIP-712 signing

Struct reference + signing examples.

Placing orders

Full request schema + validation.

Error codes

Complete code reference.