Skip to main content
Normal SDK calls fetch the order’s EIP-712 intent from the tx-builder API. For market makers, local_intents() removes that: the LocalIntentBuilder mirrors the on-chain EIP-712 schemas (proven byte-for-byte by the golden-vector test suite), so intents are built and signed in microseconds with no I/O. The batched-market service’s EIP-7702 leg is optional: a signed intent alone executes. The hot path is therefore local build + sign → batched-market POST, with zero API round-trips before submission. (High-level SDK calls like trade.market_open still attach a pre-signed EIP-7702 transaction alongside the intent, letting the server pick the execution mechanism; pass calldata= to submit_intent_batch if you want that from the fast path too.) The example below sizes the order in coin units (exactly 0.5 ETH), the usual shape for market-making flow; the fill leverage floats within [min_leverage, max_leverage]. For a USDC-sized open, use builder.open_trade(...) with /v2/trade/open and AggregatorOrderType.MARKET_OPEN instead.
Global TP/SL updates (UpdateTpSlReq) are also built and signed fully locally, but ride a different rail: the signed intent goes to the core API price-triggers endpoint (PUT /price-triggers/global-...), which executes the operator entry point itself. They settle by re-reading the position (what trade.update_tp_sl(wait=True) does), not by tracking_id.

Builder surface

Payloads are IntentPayload objects identical to what the tx-builder would return, and go through the same digest-verified signer. Since the builder never touches the network, prices are always caller-supplied: coin/increase helpers take an explicit open_price/wanted_price (the tx-builder route would resolve these from the feed). Note partial_tp_sl and update_tp_sl only build intents; the signed payloads still have to be submitted to the core API /price-triggers (what client.trade.partial_tp_sl / client.trade.update_tp_sl do), not to the batched-market endpoint. Likewise twap_* intents go to the TWAP API (what client.trade.twap_* does).

Settling

wait=False returns as soon as the order is accepted, but an accepted order can still fail (declined fill, on-chain revert). You own the settlement check, off the hot path. Market orders (batched-market route) settle by tracking_id:
The terminal MarketOrderExecuted event carries the full fill — execution price, positionSizeUSDC, percentProfit, usdcSentToTrader, and the stored trade tuple t with the final collateral (initialPosToken), leverage, open price and TP/SL — so reconciliation needs no follow-up positions query. Numerics are strings in raw on-chain units (1e6 USDC / 1e10 prices-leverage); see the payload reference. engine.batched_market.status(tracking_id, after_seq=n) is the non-blocking single poll; every event carries a seq, so a crashed process can resume replay without missing or double-counting events. Prefer the SDK’s settle logic but want the journey as it happens — e.g. AttemptFailed diagnostics as debug logs? Pass on_event= to submit_intent_batch (with wait=True) or to batched_market.wait(...): the hook fires per lifecycle event, terminal included even when the call raises, while settlement still returns/raises through the SDK. See Track the order lifecycle. Blitz relayer routes (passthrough actions: limit orders, margin, approvals, …) settle by request_id:
engine.relayer.status(request_id) is the single non-blocking poll returning a RelayStatus (settled, success, tx_hash, error_message). This is exactly what the default wait=True path runs internally.

Pair with

  • wait=False on submission, confirming fills via the settlement calls above or the order event stream.
  • The Lazer SSE price stream for the freshest open_price.