CexAccountTradeStatus
Owner context: cex-wallets | Entity: CexAccountTrade | Field: status | Values: 5
Status of a single trade executed on a CEX account (spot or margin). This is the upstream source of truth for PaymentRunTradeStatus in partners.
Effectively monomorphic in the current codebase. The enum declares 5 values, but the only write path —
CexAccountTrade.create()— hardcodesCexAccountTradeStatus.CLOSE(CexAccountTrade.ts:67). No service persists anopen,pending,failed, orcanceledtrade today. The other values exist in the enum for historical records (loaded viafromDto) and for future support of asynchronous trade lifecycles. Treat this as a 1-state machine in practice and a 5-state machine on the schema.
States
| State | Reachable via write path? | Description | Entry condition |
|---|---|---|---|
close | Yes — sole write path | Trade filled successfully; persisted only after the CEX confirmed the fill | CexAccountTrade.create() (CexAccountTrade.ts:44-73) |
open | No (historical / future) | Intent: trade record created locally, not yet submitted to the CEX | — |
pending | No (historical / future) | Intent: submitted to the CEX, awaiting fill | — |
failed | No (historical / future) | Intent: CEX rejected or errored the trade | — |
canceled | No (historical / future) | Intent: trade canceled before fill | — |
There are no helper methods beyond per-state predicates (isOpen(), isClose(), isPending(), isFailed(), isCanceled()), no isFinal(), no canTransition(), no TRANSITIONS matrix. See CexAccountTradeStatus.ts.
Transitions
Transition Table
There is no transition matrix. CexAccountTrade.updateStatus() exists (CexAccountTrade.ts:114-117) but is not called on a persisted trade by any current service. The only state change is at creation time, hardcoded to close.
| Transition | Driver | Reality |
|---|---|---|
(new) → close | CexAccountSpotTradeCreator.run(), CexAccountMarginTradeCreator.run(), ShiftTradesCreator.run() | The service calls cexSpotAdapter.createOrder() synchronously, waits for the CEX response, and persists the trade only if the order succeeded. A failed CEX order returns without persisting — no failed record is written. |
close → * | — | Never observed. updateStatus() has no caller. |
Why the state machine is so thin
The CEX adapter path is synchronous-effective: CexSpotAccountPort.createOrder() blocks until the CEX returns a final orderId with filled amounts, then the service persists the result. From the point of view of the system, a CexAccountTrade is born already settled. Errors are handled before persistence — if the CEX refuses the order, the service logs and returns without creating a record.
Two practical consequences:
- Trades that never happened are invisible. A failed order is not observable as a
failedtrade. If you need to audit attempts that did not result in a fill, read the service logs, not the trade repository. PaymentRunTradeStatusinherits the same reality. BecausePaymentRunTradeStatusis a read-only projection of this status, everyPaymentRunTradeaPaymentRunobserves is alsoclose. Thepending/failedbranches in thePaymentRunTradeconceptual diagram reflect the enum's potential, not its current behaviour.
Motive: shift vs paymentrun
A CexAccountTrade is used by two upstream contexts. The motive field (with values shift, paymentrun, funds, profit, user) disambiguates them. See CexAccountTradeMotive.ts.
motive | Upstream creator | Upstream consumer | Purpose |
|---|---|---|---|
shift | ShiftTradesCreator (triggered by ShiftReceivedDomainEvent, see shift-lifecycle) | ShiftSupervisor (via ShiftTradesCompletedDomainEvent) | Convert the user's deposit coin to the output coin during a shift's exchange leg |
paymentrun | PaymentRunTradesCreator (partners) | PaymentRunTradesSynchronizer | Rebalance coins before a partner payout |
funds, profit, user | Not currently written by the main flows | — | Reserved for other flows |
Invariant: motive and motiveId together identify which upstream aggregate owns the trade. A trade's motive never changes after creation.
Invariants
- Write path is monomorphic. Every persisted
CexAccountTradestarts (and currently remains) inclose. Anyone relying on a trade existing implies it succeeded. - No trade-level domain events. There is no
CexAccountTradeCreatedDomainEvent,CexAccountTradeClosedDomainEvent, or similar. The only related event isShiftTradesCompletedDomainEventat the aggregate level — it fires when all trades for a given shift have been created. originOrderIdis required at creation.CexAccountTrade.create()requiresoriginOrderId(the CEX's order identifier). If the adapter does not return one, no trade is persisted.fromDtotolerates any enum value. When loading historical records, any of the 5 status strings is accepted. If you find a non-closetrade in the database, it is legacy data from an older flow; treat it as read-only.- Failure handling is a known gap.
PaymentRunTradesSynchronizerhas aTODOfor failure handling (PaymentRunTradesSynchronizer.ts). If the CEX path ever evolves to persistfailedtrades, that downstream sync needs to be revisited. - No polling or webhook. There is no
CexAccountTradeUpdaterQueueorCexAccountTradeSubmitter. The synchronous create-and-persist pattern precludes it.
Code Pointers
- Value object: packages/cex-wallets/domain/valueObjects/CexAccountTradeStatus.ts
- Entity: packages/cex-wallets/domain/entities/CexAccountTrade.ts
- Motive enum: packages/cex-wallets/domain/valueObjects/CexAccountTradeMotive.ts
- Type enum (SPOT/MARGIN): packages/cex-wallets/domain/valueObjects/CexAccountTradeType.ts
- Spot creator: packages/cex-wallets/application/services/trades/CexAccountSpotTradeCreator.ts
- Margin creator: packages/cex-wallets/application/services/trades/CexAccountMarginTradeCreator.ts
- Shift-leg creator: packages/cex-wallets/application/services/trades/ShiftTradesCreator.ts
- Repository interface: packages/cex-wallets/domain/repositories/CexAccountTradeRepository.ts
- Spot account port: packages/cex-wallets/domain/ports/CexSpotAccountPort.ts
- Aggregate event: packages/cex-wallets/domain/events/ShiftTradesCompletedDomainEvent.ts
Related
- Downstream projection:
PaymentRunTradeStatus - Owner context:
cex-wallets - Upstream contexts that trigger trade creation:
shifts,partners - Flow that creates trades for shifts:
shift-lifecycle(step 4) - Future flow that creates trades for payouts:
payment-run(to be documented)
