> For the complete documentation index, see [llms.txt](https://docs.reya.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.reya.xyz/developers/devnet/executions-and-settlement/trade-busts.md).

# Trade Busts

Reya is a hybrid system where the matching engine runs off-chain and the chain independently validates and settles each trade. If a trade that the matching engine matched fails on-chain validation, it results in a **trade bust** — the match is rolled back, both sides are released back to their previous state, and no trade occurred.

Busts are **uncommon**, but your integration should account for them. A matched fill becomes a confirmed trade only after it settles successfully.

This page explains why busts happen, when in the trade lifecycle they happen, and how a client should handle them. It applies to **both spot and perpetual markets** — they go through the identical match-then-settle lifecycle.

## The Two-Stage Trade Lifecycle

Every match on Reya goes through **two distinct stages**:

1. **Off-chain match.** The matching engine (ME) finds a counterparty and produces a fill record. The order response reports this matching outcome.
2. **On-chain settlement.** Reya validates and settles the fill on-chain, checking signatures, balances, margin requirements and price limits. The result is reported as a confirmed execution or a bust.

These stages have independent outcomes. **A successful off-chain match does not guarantee on-chain settlement.** When settlement fails, the result is a bust.

Different WebSocket channels surface different stages of this lifecycle, which is what makes bust handling subtle — but also what makes it manageable, once you know which channel reflects which stage.

## Spot and perp: the channels

The lifecycle is identical for spot and perp, but the channel layout is worth getting right up front:

| Concern                         | Channels                                                                    |
| ------------------------------- | --------------------------------------------------------------------------- |
| **Order status** (ME-level)     | `orderChanges` — one channel, covers both spot and perp                     |
| **Confirmed trades** (on-chain) | **Split per product**: `spotExecutions` for spot, `perpExecutions` for perp |
| **Busts** (on-chain failures)   | **Unified**: a single `executionBusts` channel covers both spot and perp    |

Executions are split per product; busts are not. On the unified `executionBusts` channel you distinguish spot from perp by the **symbol suffix** — spot symbols end in `RUSD` (e.g. `ETHRUSD`), perp symbols end in `RUSDPERP` (e.g. `BTCRUSDPERP`).

Throughout this page, "your executions channel" means `spotExecutions` if you're trading spot and `perpExecutions` if you're trading perp.

## Why Busts Happen

Balances, prices and account or market permissions can change between matching and settlement. Every fill must pass settlement checks against the **current** state.

Common examples of settlement failures include:

* **Price guard.** A fill is refused if it falls outside the allowed deviation band around the reference price. The reference differs by product: **perpetuals** are checked against the on-chain **mark price** (not the live oracle — see [Mark Price](/developers/devnet/pricing-and-funding/mark-price.md)), while **spot** fills are checked against the **live oracle (index) price** directly. Reya also enforces the **price limit you signed** as the worst acceptable execution price.
* **GTT order expired.** Only GTT orders carry a non-zero signed `expiresAfter` lifetime. If on-chain settlement for a GTT runs after that timestamp (e.g. because of network congestion or settlement backlog), the contract refuses the fill. The separate request `deadline` is checked at entry and may already be past by settlement time.
* **Account, signature, or market state changed.** Settlement also re-checks signatures, permissions, nonces, margin, balances, oracle/mark freshness, market state, spacing, reduce-only intent, and other risk or settlement rules against the current on-chain state.

## Timeline of a Trade

The matching and settlement lifecycle is covered in [Trade Settlement](/developers/devnet/executions-and-settlement/settlement.md). What matters for busts is **which channel reflects which stage**:

1. The match surfaces on [`orderChanges`](/developers/devnet/api-reference/websocket-api-reference.md) (e.g. `FILLED`, or `OPEN` with an updated cumulative quantity) — **before** on-chain settlement.
2. The fill is then submitted on-chain and re-validated independently of the match — signatures, balances, margin, oracle staleness, market state, and so on.
3. The outcome is published **after** settlement: a confirmed trade on your executions channel ([`spotExecutions`](/developers/devnet/api-reference/websocket-api-reference.md) / [`perpExecutions`](/developers/devnet/api-reference/websocket-api-reference.md)), or a **bust** on the unified [`executionBusts`](/developers/devnet/api-reference/websocket-api-reference.md) channel — never both.

The key consequence: a `FILLED` on `orderChanges` can still be followed by a bust, and `orderChanges` is **not** corrected when that happens.

## The Channels — What Each One Tells You

| Channel                                                                      | What it shows                                                                                                                                                                                             | Reflects on-chain settlement?                                      |
| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `/v2/wallet/{address}/orderChanges`                                          | ME-level order status changes (`OPEN`, `FILLED`, `CANCELLED`), plus updated cumulative-fill quantity. Covers spot and perp. Request-level rejects are returned as errors instead of order-status updates. | **No** — fires immediately after the ME matches, before settlement |
| `/v2/wallet/{address}/spotExecutions`, `/v2/wallet/{address}/perpExecutions` | Confirmed on-chain trades, per product                                                                                                                                                                    | **Yes** — only emitted after settlement succeeds                   |
| `/v2/wallet/{address}/executionBusts`                                        | Failed on-chain settlements (spot and perp)                                                                                                                                                               | **Yes** — only emitted after settlement fails                      |

The critical asymmetry: **`orderChanges` reports ME-level state and never retroactively corrects itself.** If a fill briefly appeared as `FILLED` on `orderChanges` and then the on-chain settlement attempt busted, no correction event is published on `orderChanges`. The order's state on that channel will reflect what the ME observed, not what happened on-chain.

## Recommended Pattern

For most clients, the cleanest model is to treat **your executions channel as the single source of truth for confirmed trades** — `spotExecutions` for spot, `perpExecutions` for perp.

* If a trade appears on your executions channel, it has settled on-chain and is final. It can never be busted after that.
* If a trade does not appear, either it never matched, or it matched but busted on settlement. Either way, your position state is unaffected and there is nothing to reconcile.

Under this model:

* **Your executions channel** (`spotExecutions` / `perpExecutions`) drives your trade ledger and position accounting.
* **`orderChanges`** is useful for surfacing intent and ME-level workflow (e.g. "my order is now resting", "my order has been cancelled"), but its `FILLED` status (or any non-zero cumulative-fill quantity on an `OPEN` order) is not a trade confirmation — those signals fire before on-chain settlement.
* **`executionBusts`** is optional. You don't need to subscribe to it to be correct — busted trades simply never appear on your executions channel in the first place. Subscribe only if you specifically need awareness of failed settlements, e.g. for monitoring, alerting on systematic failure rates, or surfacing diagnostic information to end users.

## What Not to Do

* **Don't treat the order-submission response as a trade confirmation.** The response from `POST /v2/createOrder` (REST) or `createOrder` over WebSocket Order Entry reports what the matching engine did with your order — it carries no information about whether any resulting fill settled on-chain. Treat it the same way you'd treat an `orderChanges` update. See the dedicated section below.
* **Don't treat `FILLED` on `orderChanges` as a trade confirmation.** It is an ME-level status that fires before on-chain settlement. A fill seen here may still be busted later, and you will not be notified of the bust on this channel.
* **Don't try to "match" `orderChanges` events against your executions channel to detect busts.** It's possible to do, but it's strictly harder than just listening to `executionBusts` directly (or ignoring busts entirely and relying on your executions channel as the source of truth).
* **Don't double-count.** A trade that settles on-chain produces exactly one event on your executions channel. A trade that busts produces exactly one event on `executionBusts`. Never both.

## The Order-Submission Response Is an ME-Level Signal

When you submit an order — whether over REST (`POST /v2/createOrder`, `POST /v2/cancelOrder`, `POST /v2/cancelAll`) or the equivalent WebSocket Order Entry operations — the response you get back reports what the **matching engine** did with your order. It does **not** report on-chain settlement.

Treat the order-submission response the same way you'd treat an update on the `orderChanges` channel:

* **Success response with fills in the body.** The matching engine matched your order and produced fill records. Those fills are ME-level fills; they have not yet been settled on-chain and may still bust during settlement. The presence of fills in the response is not a guarantee that any of them settle.
* **Success response without fills.** The matching engine accepted your order — it rested on the book (GTC/GTT), armed as a trigger, or didn't find a match (IOC). No settlement is implied either way.
* **Request rejection.** A validation, signature, permission, risk or rate-limit rejection means the request was not accepted. A missing response or transport error does not establish whether it was accepted; reconcile before retrying.

In none of these cases does the response carry settlement information. To learn whether a specific fill settled, listen to your executions channel ([`spotExecutions`](/developers/devnet/api-reference/websocket-api-reference.md) / [`perpExecutions`](/developers/devnet/api-reference/websocket-api-reference.md)). That channel is the only authoritative source for confirmed trades — if a fill appears there, it has settled on-chain and is final; if it never appears, either it never matched or it busted.

This rule is the same regardless of which transport you use to submit the order. REST and WebSocket Order Entry affect latency and framing; they don't change the finality semantics. **Finality is on-chain, and finality is reported only on your executions channel.**

The same applies in the partial-fill case. If you submit an aggressive order that produces ten fills, you will see references to those fills in the submission response (and on `orderChanges`), but each of the ten is independently subject to on-chain settlement. Some may settle, some may bust. The submission response and `orderChanges` will not reflect which is which — only your executions channel (for the ones that settled) and `executionBusts` (for the ones that busted) will.

## Reading a Bust Event

A bust event on `executionBusts` carries enough information to identify which match failed and why. The wire shape and field semantics are documented in the channel reference under [`/v2/market/{symbol}/executionBusts`](/developers/devnet/api-reference/websocket-api-reference.md). Key fields:

* `symbol` — identifies the market, and tells you whether the bust was spot (`…RUSD`) or perp (`…RUSDPERP`).
* `takerAccountId`, `makerAccountId`, `takerOrderId`, `makerOrderId`, `qty`, `side`, `price` — identify the failed match (taker and maker sides).
* `exchangeId` — the exchange the order was placed on.
* `reason` — a **machine-readable object** describing why settlement failed, keyed by `reasonName`. Known contract errors have typed fields, such as `{ reasonName: "AccountBelowIM", accountId, delta, shortfall }`; decoded but unmodeled contract errors include string-valued `args`; legacy decoded strings and undecodable bytes use `DecodedReason` / `UnknownReason` fallback objects. You do not need to ABI-decode anything yourself.
* `timestamp` — block timestamp in milliseconds.
* `sequenceNumber` — a monotonic execution-bust sequence number, increasing by 1 per bust, useful for ordering and gap detection.

For most monitoring needs, the high-level information (which order, which counterparty, why) is sufficient.
