Skip to content

CexTransferStatus

Owner context: cex-wallets | Entity: none (DTO value object) | Field: status on CexDepositInfo / CexWithdrawInfo | Values: 4 (5 accepted on input)

Normalized representation of a CEX-reported transfer status (deposit or withdrawal) as it reaches the cex-wallets boundary. Unlike the other state machines in this catalog, there is no CexTransfer entity and no writer that mutates this value — the status is read from the CEX API by the adapter (CctxCexSpotAccountAdapter) and returned inside DTOs (CexDepositInfo, CexWithdrawInfo) from CexSpotAccountPort.

The value object's job is twofold:

  1. Vocabulary normalization — absorb the variety of status strings returned by different CEX APIs (via CCXT) and expose a fixed set of four domain values. The input verifying is coerced to pending to handle exchanges that surface a KYT/compliance step; any other unknown value throws.
  2. Bridge translation — convert the normalized CEX status to the neighboring state machines that actually drive work: TransactionStatus (via toTransactionStatus()) and TransferStatus (via toTransferStatus()).

Treat CexTransferStatus as a translation layer, not an owned lifecycle.

States

StateTerminalDescriptionEntry condition
pendingNoThe CEX has accepted the operation but has not completed itCEX returns pending or verifying
okYes (sink)The CEX reports the transfer as fully processedCEX returns ok
failedYes (sink)The CEX reports the transfer as failedCEX returns failed
canceledYes (sink)The CEX reports the transfer as canceledCEX returns canceled

There is a fifth input value, verifying, which fromString() accepts and coerces to pending at construction (CexTransferStatus.ts:22-24). There is no way to observe a stored verifying — the value object holds pending internally.

Helpers: isPending(), isConfirmed() (tests for ok), isFailed(), isCancelled(), and isSettled() which returns true for ok || failed (note: not canceled, matching the definition in TransactionStatus).

Transitions

All transitions happen inside the CEX. The adapter re-reads the value on every poll; the value object has no updateStatus or canTransition method.

"Transitions" in practice

There is no transition matrix because this system does not transition the value — it observes it.

ObservationProducerHow it enters the system
Deposit statusCexAccountDepositFinder polling the CEXReturned as CexDepositInfo.status from CexSpotAccountPort.deposits()
Withdrawal statusCexAccountWithdrawFinder polling the CEXReturned as CexWithdrawInfo.status from CexSpotAccountPort.withdrawals()
Internal sub-account transfer statusCexAccountTransferSenderUsed internally; emits CexAccountTransferSentDomainEvent when dispatched

No domain event carries a CexTransferStatus. When a deposit is confirmed, the context emits CexAccountDepositConfirmedDomainEvent, whose semantics are already "deposit is ok" — the raw status string does not travel on the event bus.

Bridge translations

The two mapper methods are the load-bearing part of this value object. They wire the CEX domain into the neighboring state machines.

toTransactionStatus()

Used when a Transaction on a custodial wallet in wallets is being driven by a CEX operation — today this does not happen in the main flow, but the method exists for alignment and is exercised by the adapter where the wallet side is backed by a CEX.

CexTransferStatusTransactionStatus
pendingpending
okconfirmed
failedfailed
canceledcancelled (note the double-l spelling in TransactionStatus)

toTransferStatus()

Used by ShiftTransferUpdater when the owning Transfer's sender is a CEX. The updater polls the CEX via CexAccountWithdrawFinder, receives a CexWithdrawInfo, calls toTransferStatus(), and uses the result to advance the shift's Transfer.

CexTransferStatusTransferStatus
pendingsending
okconfirmed
failedfailed
canceledcanceled

This mapping is the other half of the shift-lifecycle "pull" boundary documented in shift-lifecycle: when the transfer's sender is binance / htx / kucoin, this is how the external status becomes our status.

Invariants

  1. There is no CexTransfer entity. This is a value object on DTOs returned by the adapter; no repository persists it on its own. If you need to check a CEX operation's status, call the adapter — do not look for a database row.
  2. The cancelled static vs the canceled stored string. CexTransferStatus.cancelled (static, British spelling) constructs a value with stored string 'canceled' (American) (CexTransferStatus.ts:10). toString() returns 'canceled'. Code comparing against a string literal must use the American spelling.
  3. verifying is input-only. The enum tuple ['pending', 'ok', 'failed', 'canceled', 'verifying'] allows verifying in fromString, but the constructor stores pending instead. You will never see value === 'verifying' after construction.
  4. isSettled() excludes canceled. Matches TransactionStatus.isSettled(): settled means the CEX took an on-chain action (succeeded or failed). A cancellation means no action happened.
  5. No transition validation. The CEX is the authority. Our code does not assert that a transfer observed as ok was previously observed as pending; consecutive reads may skip straight to a terminal.
  6. ok is the only success path. For both mappers, ok is the only input that maps to a confirmed state downstream. Any other terminal (failed, canceled) is surfaced to the shift or transaction as failure.

Code Pointers