Skip to main content
The batched-market service is the front door for market execution in relayer mode: one endpoint for every supported market order type (opens, closes, and size increases). You sign an EIP-712 intent off-chain; the service picks a fresh price, prices the spread, builds the calldata, submits it on-chain, and streams the order lifecycle back over Server-Sent Events (SSE) on the POST response. This is the transport behind client.trade.market_open / market_close / increase_position in the SDK, and — with a locally built intent — the market-maker fast path: local build + sign → one POST, zero API round-trips before submission.

Endpoints

Request body

Why eip7702 exists at all. The server decides which mechanism to execute from its own configuration, never from the request — so operations can move traffic between EIP-712 and EIP-7702 (or roll back) without a client release. The SDK’s high-level calls send both legs to keep that flip free. A client that only signs intents may omit eip7702 — that is the market-maker fast path — with the trade-off that its requests start returning 400 if the deployment is ever switched to EIP-7702 mode.

Supported order types

Any value outside this set is rejected with 400. Notes:
  • PnL vs non-PnL is a property of the pair — use the _PNL types on PnL pairs only; mismatches are rejected.
  • Coin-exposure variants size the order in the base asset (e.g. exactly 0.5 ETH) instead of USDC notional.
  • Increases have no PnL variant.
  • The intent structs, field meanings, and units are documented on the corresponding tx-builder pages (/v2/intents/open, /v2/intents/close, /v2/intents/increase, and their -coin variants) — the message shown there is exactly what you sign, and the returned encodedIntent is the userIntent this service consumes. Remember: deadlines are unix milliseconds, nonces are unordered 256-bit bitmap values, and closes bind the position’s on-chain timestamp (PositionMismatch on drift).

The SSE stream

Read the POST response as a stream (fetch + reader; the browser EventSource API is GET-only). Every request produces one accepted event, zero or more non-terminal AttemptFailed events, one initiation event, and exactly one terminal event:
Either flow can instead end with:
  • MarketOrderCanceled — the transaction succeeded but the protocol declined the fill (e.g. slippage). A market outcome, not a system error.
  • Error — execution failed (bad intent, no price, retries exhausted) or the stream view timed out.
AttemptFailed (non-terminal, persisted) reports one execution attempt that hit a retryable condition — payload {attempt, code, message, willRetry} — while the service keeps working the request. Treat unknown event types as non-terminal noise: the server adds informational types without a version bump. AttemptFailed and Error payloads carry a machine-readable code next to the human message: a bare Avantis contract error name (WrongSl, HighSlippage, …) when the failure decoded to a specific revert, or a synthetic service code (NO_PRICE, SPREAD_BLOCKED, SPREAD_UNAVAILABLE, SUBMISSION_FAILED, ATTEMPTS_EXHAUSTED, TX_NOT_EXECUTED, STREAM_TIMEOUT, ENQUEUE_FAILED, plus RELAY_FAILED / TX_REVERTED / RELAY_TIMEOUT in EIP-7702 mode). Branch on code, not on the message. Wire format: each event is an event: line (type), an id: line (the seq, monotonic per trackingId), and a data: line (JSON payload), terminated by a blank line. Lines starting with : are keep-alive comments (sent every ~15s) — a spec-compliant parser never surfaces them. The stream has a hard lifetime cap (60s in EIP-712 mode, 180s in EIP-7702 mode); a stream that ends without a terminal event does not mean failure — replay by seq (below).
An Error with code: "STREAM_TIMEOUT" (also recognizable by its missing id: line — it is not persisted) is not the request’s outcome: only this connection’s view of it timed out, and the order may still execute. Fall back to the status endpoint. Every other Error is a real terminal failure; ENQUEUE_FAILED means the request never reached the execution queue.

Event payloads

All uint values are strings in raw on-chain units (USDC 1e6, prices/leverage/exposure 1e10) — many exceed Number.MAX_SAFE_INTEGER. MarketOrderAccepted (seq 0) — the request was accepted and a trackingId minted. Nothing is on-chain yet; save it for reconnects.
MarketOrderInitiated — the order is registered on-chain with an orderId.
IncreasePositionRequested — the increase is registered on-chain.
MarketOrderExecuted (terminal — opens/closes) — the final fill. t is the stored Trade tuple (initialPosToken = final collateral, plus leverage, openPrice, tp/sl). On closes, percentProfit and usdcSentToTrader carry the realized outcome.
PositionSizeIncreased (terminal — increases) — note the shape differs from a fill: no price / percentProfit / usdcSentToTrader, and t is the blended resulting position.
MarketOrderCanceled (terminal) — the protocol declined the fill.
AttemptFailed (non-terminal) — one retryable attempt failed; the service is still working the request.
Error (terminal — except STREAM_TIMEOUT, which is this connection’s view expiring).

Replaying by trackingId

Every streamed event is persisted with its seq, so a dropped stream is fully recoverable:
  • afterSeq is optional — omit it for the full history.
  • 404 = unknown trackingId. An empty events array = accepted but nothing has happened yet; poll again (~1–2s cadence) until a terminal event lands.

Error handling summary

Because intents carry unordered nonces, the service retries execution internally without double-execution risk — never re-POST an accepted order; only retry if the POST itself returned 4xx.

Using it from the SDK

on_event is available on every layer of the path — trade.market_* / increase_position*, engine.submit_intent_batch, and BatchedMarketClient.execute / .wait — sync or async callables; see Track the order lifecycle. See MM fast path for the full zero-I/O signing loop.