For the complete documentation index, see llms.txt. This page is also available as Markdown.

Executions & Trade History

Confirmed fills are delivered two ways: a real-time stream for live trading, and REST history endpoints for backfill and reconciliation. Both cover spot and perp. This page explains which channel is authoritative, how to identify and de-duplicate a fill, and exactly how the history pagination works.

Real-time: the executions channels are the source of truth

A fill is only real once it has settled on-chain, and that is precisely what the executions channels report:

  • /v2/wallet/{address}/spotExecutions — confirmed spot fills

  • /v2/wallet/{address}/perpExecutions — confirmed perp fills

  • /v2/wallet/{address}/executionBusts — failed settlements, unified across spot and perp

Executions are split per product; busts are a single unified channel. A fill appears on your executions channel only after on-chain settlement succeeds, and once it appears it is final — it never reverses. A match that fails settlement appears on executionBusts instead. A given match produces exactly one event, on exactly one of the two — never both, so there is nothing to double-count.

This is the read side of the lifecycle described in Trade Settlement; for the failure path and why the order-submission response and orderChanges are not trade confirmations, see Trade Busts.

Drive your trade ledger and position accounting from your executions channel (spotExecutions / perpExecutions) — not from the order-submission response and not from orderChanges, both of which are matching-engine-level signals that fire before settlement.

Reading an execution

Each fill's execution price is reported in price. For the cumulative matching-engine average across an order's fills, see avgFillPx (introduced in API 3.5.4). This order-level field does not change the per-fill execution format or confirm settlement.

An ORDER_MATCH perp execution is a single match record carrying both sides. You receive it on your wallet channel whether you were the taker or the maker — determine your side from takerAccountId vs makerAccountId, and read your economics from the corresponding taker* / maker* fee and PnL fields. Treat maker fields as optional because legacy V2 rows may not carry the full counterparty shape. ADL and MARKET_CLOSE executions are always single-sided: they have no maker and omit every maker* field rather than returning null, account 0, or another placeholder. A spot execution uses the same taker/maker naming: takerAccountId, makerAccountId, takerFee, and maker-side order fields where available.

  • Unique identity & de-duplication — sequenceNumber. Every execution carries a monotonic sequenceNumber that increases by 1 for each execution on its stream. Use (channel, sequenceNumber) as the idempotency key to drop duplicates and to detect gaps after a reconnect. The same applies to executionBusts, which carries its own sequenceNumber.

  • Timestamps are milliseconds. All timestamp fields are the on-chain block timestamp of the settlement, in POSIX milliseconds (block_timestamp × 1000) — for an execution, the block in which the fill settled on-chain; for a bust, the block in which settlement failed. It is chain-side time, not the matching-engine match time. Because block timestamps have second-level granularity, several fills in the same block share one timestamp (see pagination below).

Fee breakdown fields

Under the current fee model, takerFee is the perp taker's gross signed rUSD debit. The execution also carries the four settlement buckets it was split into (API 3.3.0, additive):

Field
Meaning

protocolFeeCredit

Portion of takerFee credited to the protocol fee collector

referrerFeeCredit

Portion credited to the taker's referrer (0 when there is no referrer)

takerRebateCredit

Portion routed to the taker-rebate settlement bucket

poolFeeCredit

Portion credited to the passive pool

All four are rUSD amounts and, whenever they are present, they sum exactly:

takerRebateCredit is part of the gross taker debit: takerFee is what the fill charged. The rebate appears as a separate credit to the taker, which shows on the wallet's transfer history as a PERP_TAKER_REBATE entry, so report takerFee as the fee of the fill and take the net cost from the ledger rather than computing takerFee − takerRebateCredit here.

The four component fields are present together or absent together. Executions settled under the legacy fee model carry takerFee without this breakdown; the components are never synthesized for them. makerFee is omitted under the current fee model because there is no maker fee or rebate leg. It appears only on legacy executions that recorded a maker debit or credit. Do not require either field or infer that a missing field is an indexing failure.

The full field lists are in the WebSocket Info API Reference under the perpExecutions, spotExecutions, and executionBusts channels.

Mapping an order to its fills

A current PerpOB ORDER_MATCH execution carries a fillId — a stable identifier for that individual fill — plus the order ids of the orders that produced it. Both spot and perp order matches carry takerOrderId and makerOrderId; legacy V2 executions can omit fillId and these order-id fields. Non-order executions such as ADL and MARKET_CLOSE are not backed by an ME fill, so fillId and non-meaningful order ids are omitted.

To reconcile an order with its settled fills, match executions by takerOrderId / makerOrderId and use fillId as the unique key for each individual fill (the same fillId appears on an executionBust if that fill is later busted). When an order response or orderChanges update includes firstFillId and fillCount, that range identifies the contiguous fill ids produced by that order update.

Order-side fill ranges are contiguous: [firstFillId, firstFillId + fillCount − 1]. Use the executions channel for full per-fill economics and settlement status.

Historical: the REST endpoints

To backfill or reconcile, pull history per product:

  • GET /v2/wallet/{address}/perpExecutions

  • GET /v2/wallet/{address}/spotExecutions

  • GET /v2/wallet/{address}/executionBusts (unified spot + perp)

Each returns executions where the wallet participated in, or was affected by, the execution. Results are capped at 100 per request and ordered newest-first. The response is an envelope:

meta.startTime is the timestamp of the newest row in the page and meta.endTime the oldest — i.e. the time range this page covers (startTime >= endTime, since the page is newest-first).

Pagination — startTime / endTime

  • Omit both to get the most recent 100.

  • startTime only → executions at or after that time (still newest-first, capped at 100).

  • endTime only → executions at or before that time.

  • Both → the inclusive window between them.

To page backward through history, walk the window using the oldest timestamp you've received:

  1. Request with no startTime/endTime (or your desired upper bound) → newest 100.

  2. Take meta.endTime (the oldest timestamp in the page) and issue the next request with endTime = meta.endTime.

  3. Repeat until a page returns fewer than 100 rows — you've reached the end of the window.

Because the bound is inclusive, consecutive pages can overlap on rows sharing the boundary timestamp (block timestamps have second-level granularity, so several fills can share one). De-duplicate across pages by sequenceNumber — that's what makes the walk robust. (You can also set the next endTime to meta.endTime - 1 to skip the boundary, but de-duping by sequenceNumber is the safer habit since it also covers same-timestamp ties.)

Where the money went

Executions describe the trade; the collateral it moved is on the wallet's transfer history. A perp fill under the current fee model appears there as signed entries per account — PERP_TAKER_FEE for the gross debit, PERP_TAKER_REBATE for what came back, PERP_REFERRER_REBATE and PERP_POOL_REBATE on the referrer's and pool's accounts. Use fillId to link entries to executions; historical perp fee entries may omit fillId and symbol.

Keeping your trade ledger in sync

The goal is a local trade ledger that matches the authoritative on-chain executions. Three cases cover it:

  • Steady state: consume spotExecutions / perpExecutions live and key your ledger on sequenceNumber, processing each execution once.

  • After a disconnect: you may have missed events while offline. Backfill the gap from the REST history endpoint (windowed by time) and merge by sequenceNumber — already-seen fills de-dupe away, missed fills fill the gap.

  • Failures: a match that busted never appears on your executions channel, so there is nothing to reverse. If you track busts, read them from the unified executionBusts (stream or REST). A fill is on executions xor busts, never both.

Last updated