Agara

Operations

The operational mechanics for running a quote loop on Agara — post-only, self-trade prevention, cancel-all, and how to wire the streaming surfaces into a robust loop.

This page covers the per-call mechanics that matter most when you're managing resting orders. None of it is MM-specific in the strict sense — the endpoints work the same way for any caller — but these are the patterns you'll actually use.

Quote loop shape

A minimal quote loop looks like this:

  1. Subscribe and buffer the orderbook stream and the account stream for your markets.
  2. Seed state with REST while buffering: /trade/v1/portfolio/summary for available capital, /trade/v1/portfolio/open-orders/list for any leftover resting orders.
  3. Place initial quotes using post-only limit orders.
  4. On each orderbook delta update (the channel sends a snapshot frame first, then delta frames), decide whether to re-quote — cancel stale orders, place fresh ones at the new desired prices.
  5. On each fill, update inventory and reconsider sizing.
  6. On sequence_reset or stream disconnect, resubscribe, buffer, and re-seed from REST before trusting local state again.

Post-only

Set post_only: true on any limit order you don't want to take liquidity. If your price would cross the book on arrival, the order is rejected rather than filled as a taker. Most of the time this comes back immediately as a 422 on the POST response; if the book moves after that check, it instead surfaces asynchronously as a REJECTED order — an order_rejected event, or poll /trade/v1/orders/{id}.

Use this on every quote in your loop. Without it, a fast move in the book between your decision and your submission silently turns you from maker to taker and makes the fill subject to taker fees.

{
  "token_id": "21742…36455",
  "side": "BUY",
  "type": "LIMIT",
  "time_in_force": "GTC",
  "price_micro": "300000",
  "shares_micro": "100000000",
  "post_only": true
}

See Orders → post-only for the full semantics.

Self-trade prevention

Two resting orders from the same account can't fill each other. By default the maker side is cancelled when an incoming taker would cross with your own resting order — your taker order lands as normal, and a order_cancelled frame fires for the cancelled maker with reason: "SELF_TRADE_PREVENTION".

This is usually what you want — you'd rather cancel an old stale quote than match against yourself and waste fees on a no-op trade. But it does mean your re-quote loop needs to account for it:

  • If you cancel-then-place, the new place may STP-cancel an older order you didn't realize was still resting. Log the SELF_TRADE_PREVENTION cancels so you can audit later.
  • If a new order crosses your own older resting order, the older maker order is cancelled. The new order continues and may match another account or rest on the book.

See Self-trade prevention for the mechanics.

Cancel-all

POST /trade/v1/orders/cancel-all requests cancellation of every resting order on your account. It returns 202; cancellation completes asynchronously. Use it as a kill switch:

  • Disconnects. If you cannot trust your local book, request cancellation, reconnect, then reconcile before resuming.
  • Process restart. Bot crashed, you don't trust your in-memory state — cancel everything from a fresh process, reconcile.
  • End of session. Stop quoting without leaving stale liquidity behind.

After the 202, wait for cancellation events or reconcile /trade/v1/portfolio/open-orders/list until the affected orders are terminal. Do not place replacement quotes under the assumption that the response made the book empty immediately.

Scope: orders:cancel_all. See Orders → cancel everything open.

There's no cancel-by-market or cancel-by-token-id endpoint today. If you need to clear one market's worth of orders, fetch POST /trade/v1/portfolio/open-orders/list with a token_ids array in the JSON body, then cancel each returned order by ID. Acceptable for low-N cases; if you're running many markets and need a faster path, let us know.

Cancel-on-disconnect

We don't auto-cancel resting orders on WebSocket disconnect today. This matters for market makers because a network blip can leave stale quotes resting in the book against a price that's moved.

Use heartbeat-driven recovery on your side:

  1. Track the last time you received any frame on the account stream.
  2. If it exceeds your tolerance (say, 30s), assume disconnect and call cancel-all.
  3. Reconnect, re-seed, resume.

The platform sends heartbeat frames every ~10s on both streams, so 30s of silence is a strong signal. See Streaming → reconnect strategy.

GTD (time-bounded orders)

time_in_force: "GTD" plus expiration_unix_seconds rests an order until it fills or expires. Useful for:

  • Orders you want to forget about (no need to cancel — they expire on their own).
  • Strategies where stale liquidity is worse than no liquidity (set a short TTL on every quote, rely on expiry rather than explicit cancel).

Expired GTD orders are cancelled server-side, and an order_cancelled frame fires so your state stays in sync.

See Orders → time in force.

Streaming for quote rebalancing

Polling the orderbook works but adds latency. For real-time rebalancing, subscribe to the streams:

ChannelUse it for
orderbookFull depth deltas — for fitting a curve, computing fair-value, or anything that depends on the shape of the book
best_quoteTop-of-book only — much smaller payload; sufficient for most spread-capture strategies
tradesPublic fill flow — to detect taker pressure and adjust sizing
account_eventsYour own order, fill, position, redemption, and collateral changes — use these for low-latency updates and reconcile with REST

A typical bot subscribes to best_quote + account_events for the hot path, and uses REST /trade/v1/orderbook/{token_id} lazily when it needs full depth (re-seeding after disconnect, sanity-checking on a slow loop).

Sizing against available capital

The /trade/v1/portfolio/summary response carries your collateral balance as cash_balance_micro. It is not reduced by resting BUY orders. Before sizing another BUY, calculate the remaining notional and worst-case fee requirement of your open BUY commitments from /trade/v1/portfolio/open-orders/list.

For each open order:

  1. Calculate its unfilled shares as original_size_micro - size_matched_micro.
  2. For a BUY, reserve its remaining notional plus the worst-case fee for that quantity. Do not size only from notional.
  3. For a SELL, reserve the unfilled shares of that specific outcome. Collateral cannot cover missing SELL inventory.

BUY commitments are checked per market. A conservative strategy should also cap aggregate exposure across every market it quotes.

When an order is rejected

We check your order against your live balance and positions before accepting it, so the rejections MM loops hit most come back synchronously as a 422 on the POST response, with the reason in the error field:

ReasonWhat it meansTypical reaction
Post-only would crossThe book moved against your quote before it restedRe-fetch top of book, re-quote
Insufficient balanceNot enough collateral to back a BUYCheck cash_balance_micro and your open orders, retry with smaller size
Insufficient sharesNot enough tokens to back a SELLReconcile inventory, retry with smaller size
Not fillableA FOK that couldn't fully fillRe-fetch the book, re-size or re-price

The check is best-effort. Your balance or the book can move between it and the match, so an order can still be accepted (202 / PENDING) and then land as a REJECTED order — poll /trade/v1/orders/{id}, or handle the order_rejected event on the account stream when it is delivered. A halted or resolved market is a separate 400 (market isn't accepting orders); the market_status channel tells you to stop quoting.

Request validation failures — malformed body, an order notional outside the 0.10–100,000 collateral-unit bounds, a bad time-in-force / post-only combination — are a 400, not a 422.

Don't blanket-retry — most of these need different action, not a re-attempt. See Conventions → error envelope.

Settlement considerations

This deployment settles AGARA fills on Base using USDC.

  • Settlement state. Keep matched and settled exposure separate. Do not depend on an undocumented confirmation time.
  • Resolution risk. Reduce or stop quoting when your strategy cannot price the approach to resolution safely.
  • Token rotation. PATs don't expire by default, but rotation is a good operational practice. See Authentication → TTL.
  • Withdrawals. Funds remain in the AGARA account until you withdraw them. A self-custody client can submit a supported same-chain withdrawal in an owner-signed account batch. Every path settles asynchronously.

The signed-in web flow can also quote supported cross-chain withdrawal routes. Request a fresh supported-assets response and quote before confirming.

On this page