Wallet & buckets

Wallet is intentionally NOT a lifecycle FSM. Each user's user_balances row has two independent integer columns — 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, Stripe connected-account 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 and 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