Withdraw (GBP / EUR back to user's bank)
Send the user's Infinia balance back to their own UK / EU bank account. Same currency in and out — no FX, no on-chain step.
Sequence
Payload detail for the calls above:
POST /bank-details→{ currency, accountHolderName, accountNumber + sortCode | iban + bic }POST /withdraws→{ amount }(currency inferred from user's home currency)- Infinia
payoutsusesoriginId = "grng-wd-" + txId,sourceAccountId= user's GBP or EUR Infinia account,destinationType= UK CHAPS/FPS or Europe SEPA.
Total time: ~10s for FPS; same-day for SEPA (bounded by the receiving bank's settlement window).
Screens
apps/mobile/app/withdraw/index.tsx— entry.apps/mobile/app/withdraw/bank-details.tsx— one-time capture per currency.apps/mobile/app/withdraw/amount.tsx— amount input, shows current balance ceiling.apps/mobile/app/withdraw/confirm.tsx— review + Pay button.apps/mobile/app/withdraw/receipt.tsx— polls status.
Fees & timing
- Infinia payout fee: TBC (part of the open Infinia fee schedule question).
- GrinGo fee: 0% on withdrawals for MVP.
- FPS timing: near-instant, typically seconds.
- SEPA timing: same-day or next-day depending on the receiving bank.
Bank details
- We cache bank details on our side (
user_bank_details, one row per (user, currency)). User enters once; every subsequent withdraw reuses. If they withdraw to both GBP and EUR banks, that's two rows. - No pre-registration is needed at Infinia — unlike the previous Bridge/liquidation-address model where we cached a per-bank liquidation address id, Infinia payouts take the bank details inline on every
POST /v2/payoutscall. There's no beneficiary token to save. - Sensitive columns (
account_number,sort_code,iban,bic) are encrypted at rest viapgcrypto. Full values are shown in the UI only during in-app edit; elsewhere we mask to****1234. See privacy. - Updating bank details is trivial — replace the row, next payout uses the new details.
- Bank details are our data, not Infinia's. They're passed through on each payout request.
What we're storing
transactions row (type=withdraw):
source_currency= user's home currency (matches the payout destination)source_amount= amount debited (in pence / cent)dest_currency= same as source (no FX)dest_amount= same as sourceprovider = "infinia"provider_payout_id= Infinia's payout idstatusprogressespending → settling → completed | failed
Failure modes
| What broke | Where it surfaces | Recovery |
|---|---|---|
| No bank details for the requested currency | POST /withdraws → 409 | User adds details, retries |
| Balance insufficient | POST /withdraws → 402 (or 409) | Mobile disables the button when amount > balance |
| Infinia payout rejected synchronously | POST /withdraws → 502; transactions row is failed immediately | Manual retry; user's balance is untouched |
| Payout webhook says FAILED | transactions.status = failed; failure_reason stored; balance restored (debit reverses on failed) | User sees failure reason on receipt |
| No payout webhook arrives | Payment stuck in settling. Reconciliation cron polls GET /v2/payouts/{payout_id}/ to detect. | Automatic resolution |
| Recipient bank rejects the transfer | Infinia surfaces failure via payout.updated FAILED | Same as above |
Not supported at MVP
- Cross-currency withdrawals (e.g. holding GBP but withdrawing as EUR). Home currency is fixed; withdrawals match. If a user wants EUR out, they'd need a EUR home currency — currently a support ticket.
- Partial withdrawals with FX (e.g. converting £45 to EUR before payout). Same reason as above.
- Scheduled / recurring withdrawals. Manual only.