Wallet & buckets

Wallet is intentionally NOT a lifecycle FSM. Each user's user_balances row has two independent integer columns plus a freeze flag — money moves between buckets via two narrowly scoped transfers, while deposits/withdrawals enter or leave either bucket directly.

Buckets

BucketWhat it meansWhere it maps
availableWithdrawable nowSeller's available balance at the payment provider (Pagar.me available, OPP merchant balance)
heldMarketplace holdMoney the provider has paid the seller's recipient but cüte conceptually withholds until the order completes

There is no freeze flag on the wallet. A member's standing is users.account_status, and deriveEligibility reads it before any money moves: a suspended seller can neither list nor withdraw. In-flight withdrawals are NOT a bucket either; the payouts table is the source of truth for "money mid-transfer to bank."

Allowed moves

LegendWaitingCompleted

The two bucket-to-bucket transfers are:

From → ToWhen
held → availableOrder completed / auto-accept released the hold
available → heldAdmin places funds on hold pending review

Everything else (deposit, refund, withdrawal, held-funds inbound) is a direct credit or debit. The transfer map in packages/commerce/wallet/wallet-machine.ts only constrains bucket-to-bucket moves, not these direct movements.

Why this isn't a state machine

State machines model lifecycles — an entity in exactly one state at a time, advancing through transitions. The wallet has two simultaneous bucket totals plus a freeze flag, with no "current state" to advance. The trace UI accordingly renders this as a bucket diagram + ledger table, not a lane on the swim-lane canvas.

On this page