Skip to content

feat(cashout): route Cashout V1 wallets via cutover guard (ENG-357)#395

Open
islandbitcoin wants to merge 1 commit into
tmp/bridge-rebase-pr-readyfrom
eng-357-cashout-wallet
Open

feat(cashout): route Cashout V1 wallets via cutover guard (ENG-357)#395
islandbitcoin wants to merge 1 commit into
tmp/bridge-rebase-pr-readyfrom
eng-357-cashout-wallet

Conversation

@islandbitcoin
Copy link
Copy Markdown
Contributor

What

Cashout V1 always debited the user's legacy USD wallet and credited the Flash bank-owner's USD wallet. Post-cutover the user's funds live in an ETH-USDT cash wallet, so the offer must debit USDT and credit the bank-owner's USDT wallet. The bank-owner account holds both a USD and a USDT wallet, so the route selects the matching pair on each side — no cross-currency swap.

How

  • resolveCashoutWalletSelection (new, src/app/cash-wallet-cutover/cashout-routing.ts) reads the cutover config + the per-account migration record and runs the existing evaluateCashWalletCutoverGuard to pick the route:
    • legacy_usd → user's USD wallet + bank-owner USD wallet (unchanged).
    • usdt → the account's USDT wallet + the bank-owner's USDT wallet.
    • mid-migration / failed → the guard's error propagates and blocks the cashout.
    • Source and destination are resolved server-side from the guard, not from the client-supplied walletId (trusted only for wallet-level auth) — this protects old clients that still send the zeroed legacy USD walletId after migration.
  • CashoutManager.createOffer builds a USD or USDT invoice per route; the USD/JMD payout math is unchanged (1 USDT = 1 USD).
  • executeCashout now authorizes by account (provided walletId and the offer's settlement wallet must share an account) instead of an exact wallet-id match, so an old client presenting the legacy USD walletId can still execute a USDT-settled offer.
  • CashoutValidator and CashoutDetails.payment.amount are currency-aware (USDAmount | USDTAmount); the USD branches stay byte-identical.
  • ErpNext.draftCashout records the USDT amount (asNumber, since USDTAmount has no asDollars); wallet_id/flash_wallet already capture the USDT source/destination wallets for audit.

Acceptance criteria

  • ✅ Post-cutover Cashout V1 debits ETH-USDT (source + destination USDT wallets; USDT invoice).
  • ✅ Pre-cutover Cashout V1 still debits the legacy USD wallet — byte-identical behavior.

Builds on tmp/bridge-rebase-pr-ready (the cutover machinery lives there). blockedBy ENG-345 (Done).

Testing

  • tsc --noEmit: no new type errors (my files type-clean).
  • Added cashout-routing.spec.ts covering pre→legacy, complete→usdt, mid-migration→legacy, failed→blocked, and missing-USDT-wallet→error. (Note: the unit suite does not complete on my local host — a ts-jest cold-compile hang that also affects existing specs — so the spec is type-checked here and will run in CI.)

🤖 Generated with Claude Code

Cashout V1 always debited the legacy USD wallet and credited the bank-owner
USD wallet. Post-cutover the user's funds live in an ETH-USDT cash wallet, so
the offer must debit USDT and credit the bank-owner's USDT wallet. The Flash
bank-owner account holds both a USD and a USDT wallet, so the route simply
selects the matching pair on both sides — no cross-currency swap.

- Add resolveCashoutWalletSelection: reads the cutover config + per-account
  migration and runs evaluateCashWalletCutoverGuard to pick the route. Source
  and destination wallets are resolved server-side from the guard, NOT from the
  client-supplied walletId (trusted only for wallet-level auth). The guard
  blocks the cashout while a migration is in-flight or has failed.
- CashoutManager.createOffer builds a USD or USDT invoice per route; the
  USD/JMD payout math is unchanged (1 USDT = 1 USD).
- executeCashout authorizes by account, since an old client may still present
  the zeroed legacy USD walletId while the offer settles in USDT, instead of
  an exact wallet-id match.
- CashoutValidator and CashoutDetails.payment.amount are currency-aware; the
  USD path stays byte-identical. ErpNext.draftCashout records the USDT amount.

Adds unit coverage for the routing decision tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear Bot commented Jun 6, 2026

ENG-357

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants