Skip to content

PaymentRunTradeStatus

Owner context: partners (value object) — projected from cex-wallets CexAccountTradeStatus | Entity: PaymentRunTrade (value object embedded in PaymentRun) | Field: status | Values: 5

Status of a single CEX spot trade executed as part of a PaymentRun (partner payout). Unlike the other state machines documented so far, this is not a state machine the partners context owns — it is a read-only projection of CexAccountTradeStatus in cex-wallets. The values are the same strings; the entity in partners never mutates them, it just mirrors the CEX trade's status whenever PaymentRun.updateTrades() is called.

If you need to document or change the transition semantics, the authoritative source is CexAccountTradeStatus (to be documented), not this value object.

States

StateTerminalDescriptionEntry condition
openNoTrade created locally, not yet submitted to the CEXPaymentRunTradesCreator creates the trade record
pendingNoSubmitted to the CEX; awaiting fillCEX accepted the order; reported back on polling
closeYes (sink)Trade filled and closed successfullyCEX reports fill
failedYes (sink)Trade rejected or errored at the CEXCEX reports failure
canceledYes (sink)Trade canceled (manually or by CEX)Cancellation confirmed

There are no helper methods beyond the per-state predicates (isOpen(), isPending(), isClose(), isFailed(), isCanceled()). No isFinal(), no canTransition(). See PaymentRunTradeStatus.ts.

Transitions

The arrows show what is observed in practice from the CEX, not what PaymentRunTradeStatus enforces — it enforces nothing. The source state machine in cex-wallets is where transitions are validated.

Transition Table

PaymentRunTradeStatus has no transition matrix and no guards. The projection is refreshed by PaymentRun.updateTrades() using PaymentRun.mapCexTradesToPaymentRunTrades(), which builds PaymentRunTrade value objects from the current CexAccountTrade records on every call.

Observed transitionDriven by (in cex-wallets)How it reaches partners
open → pendingCexAccountSpotTradeCreator submits the order to the CEXPaymentRunTradesSynchronizer reloads trades by motive and calls PaymentRun.updateTrades()
pending → closeCEX fills the orderSame sync path
pending → failedCEX rejects the orderSame sync path
pending → canceledManual cancel or CEX cancelSame sync path
open → {canceled, failed}Pre-submission cancel/errorSame sync path

No domain events are emitted by transitions of PaymentRunTradeStatus in the partners context. Events of interest live on the CEX side (CexAccountTrade*DomainEvent, to be catalogued) or at the PaymentRun aggregate level (PaymentRunWithdrawInitiatedDomainEvent, PaymentRunInsufficientFundsForTradesDomainEvent).

Invariants

  1. Read-only in partnersPaymentRunTrade is a value object. No method mutates its status. The only way the value changes is by replacing the whole trades array on the parent PaymentRun via updateTrades().
  2. Source of truth is CexAccountTradeStatus — the enum values are type-aliased from cex-wallets (the internal tuple is literally named CexAccountTradeStatuses, PaymentRunTradeStatus.ts:1). Any divergence between the two enums is a bug.
  3. True sinks are close, failed, canceled — once the CEX reports one of these, the trade does not move. This is enforced by CexAccountTradeStatus in cex-wallets, not here.
  4. open vs pending is the submission boundaryopen means "record exists locally, order not yet sent"; pending means "CEX has accepted the order and has not filled it yet". Treat them as both "not settled" for reporting, but only pending guarantees the order is in the CEX's book.
  5. close vs canceled is the settlement semanticsclose means the trade was filled (successful settlement, coins moved on the CEX); canceled means the order was withdrawn without filling (nothing happened).
  6. No event emissions tied to status changes — if you need to react to a close, subscribe to the CEX trade event (in cex-wallets) or poll via PaymentRunTradesSynchronizer. Do not expect a PaymentRunTradeStatusChangedDomainEvent.

PaymentRunTrade vs Due vs PaymentRun

These three are often confused; the distinctions matter:

Value object / entityContextStatus fieldLifecycle
Due (entity)partnersDueStatus (open → settling → paid)One commission owed to one partner, created from a ShiftConfirmedDomainEvent
PaymentRun (aggregate root)partnersPaymentRunStatus (created → rated → withdrawing → paid)Container that groups many Dues for one payout cycle
PaymentRunTrade (value object)partnersPaymentRunTradeStatus (this doc)One spot trade executed to rebalance funds before the payout; projection of CexAccountTrade
CexAccountTrade (entity)cex-walletsCexAccountTradeStatusThe real trade record — source of truth for this projection

Advancing a PaymentRun from rated to paid requires all its PaymentRunTrades to reach close and the resulting blockchain transfer to confirm. The trade-level state does not directly drive the PaymentRun state — PaymentRunTransferCompletionChecker owns that coordination.

Code Pointers

  • Owner context: partners
  • Upstream context and source of truth: cex-walletsCexAccountTradeStatus (to be documented)
  • Sibling status in the same aggregate: DueStatus (to be documented), PaymentRunStatus (to be documented)
  • Cross-context flow that consumes this projection: payment-run (to be documented)
  • Upstream flow that creates the Dues this payout aggregates: shift-lifecycle