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
| Bucket | What it means | Where it maps |
|---|---|---|
available | Withdrawable now | Seller's available balance at the payment provider (Pagar.me available, OPP merchant balance) |
held | Marketplace hold | Money 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
No diagram code provided
The two bucket-to-bucket transfers are:
| From → To | When |
|---|---|
held → available | Order completed / auto-accept released the hold |
available → held | Admin 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.