One cockpit, every movement.
The console at /ops is the operating engine for the wallet core: every Fincra flow that touches a wallet or the ledger — pay-in, transfer, payout, FX, OTC, stablecoin, GPS, rolling reserve, corrections, treasury funding — executed from one server-rendered UI, through the exact same idempotent use cases the API calls. Nothing in it bypasses the ledger's guards.
Console map
A working replica of the /ops navigation. Click a section to see what it shows, what an operator can do there, and which use cases it writes through.
—
—
What it is
Not a separate app. The console is mounted inside the walletflow service itself, so it calls CoreWalletService, CoaService and ReportingService in-process — the same code path as the public API, with the same validation, flags and idempotency.
Pages are server-rendered hono/jsx; htmx 4 submits forms and swaps result fragments; styling is a Tailwind v4 design system built at deploy time — a dark terminal default with a light "ledger paper" theme, toggled from the user menu. The assets are vendored and served same-origin — the console works with no CDN and no client framework. The seventeen command forms are not hand-written: each is generated from src/application/commands/registry.ts, the same declarative entry that mounts the JSON API route, so a new operation is one service method plus one registry entry.
Every money-moving form carries a hidden, auto-generated idempotency key. Submitting twice replays the first result instead of double-posting; after each success the key rotates so the next submission is a fresh command. Server-side validation errors come back as inline field errors; ledger rejections (insufficient funds, currency unknown, flag denied) render as red result cards with the exact reason.
The console cannot overdraw a wallet or skip a gate: commands run the same Numscript templates, the same fail_closed flag checks, and the same (provider, reference) webhook dedupe as API traffic. An operator mistake degrades into a readable error, not a broken book.
Operating it
Sign in at /ops/login — operator token or Google — and work the loop: check the overview, keep the four gates green, clear manual review, watch the outbox.
Getting in
The console accepts three kinds of session. The operator token: the login form exchanges AUTH_TOKEN for an HttpOnly cookie (12 h), and scripts can send the same token as a Bearer header instead. Or Sign in with Google (better-auth), active when GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET are set: the account's email must be on the allowlist — seeded by OPS_ALLOWED_EMAILS / OPS_ADMIN_EMAILS, overridden by the lists saved on /ops/settings — and is re-checked on every request, so tightening the list locks existing sessions out within seconds. Admins may view and modify Controls and Settings; operators get everything else; token and Bearer sessions are always admin. Empty allowlists are open in dev but fail closed in production. The console is fully open only when neither AUTH_TOKEN nor Google OAuth is configured (local dev); the REST surface stays Bearer-gated either way.
# local
bun run dev # migrate + build assets + serve
open http://localhost:3000/ops
# first boot of a fresh environment (Ledger page has buttons for both)
POST /ops/ledger/rebuild-templates # load the 18 Numscript templates
POST /ops/ledger/rebuild-coa # seed COA + GL mappings The operating loop
Overview first: Formance health, posted/failed counts, outbox pending age, drift and template-mismatch counters. Then Reconciliation — the four gates from chapter 03 run live on the page, with ALM views beneath. Then Payouts: anything in manual_review is yours; anything long-reserved deserves a look. Every result card echoes status, reference, walletflow tx id, Formance tx ids, and the post-command balances — read it before moving on.
Playbooks
The handful of situations operators actually face, and the console path through each.
A payout is stuck in manual review
The saga exhausted its requeries and parked the hold. Verify the real provider state out-of-band, then expand the hold's row on Payouts: the prefilled Settle (provider paid) or Release (provider failed) form closes it. Money is never moved on a guess — that rule survives the UI.
A reconciliation gate is red
Holds drift → compare reserved rows against ledger hold balances in the gate detail. Wallet drift → Rebuild statement read model first (projection lag is the common cause), re-run, and only then consider a Correction — credit or debit with a mandatory reason and an optional bi-temporal effectiveAt.
An offramp or GPS payout fails with INSUFFICIENT_FUND
By design: the source bank or pool isn't funded. Record the treasury wire with Bank funding (or GPS pool top-up), confirm the balance on Overview, and re-run the original command.
Freeze a merchant — or everything
Controls → Set feature flag: disable core_wallet_enabled for a merchant, currency or provider scope. With CORE_WALLET_FLAG_MODE=fail_closed only enabled scopes transact. Saga completions still record by default — set CORE_WALLET_COMPLETION_GATING=gate for a true hard freeze (chapter 03 explains why that's the exception).
A delivered-then-disputed collection is a Correction (debit, reason chargeback …). A transaction posted in error is a Reversal by walletflow tx id — every leg reversed atomically. Don't hand-craft the opposite entries.
The full surface
Everything the console can do, and the use case each action writes through. If a flow in the Fincra product touches a wallet or the ledger, it is on this table.
| Page | You can | Writes through |
|---|---|---|
| /ops | watch health, counters, recent ledger transactions | read-only |
| /ops/wallets | create wallets · live balances · statements | createWallet |
| /ops/operations | pay-in · transfer · reserve payout · FX quote/deal · OTC | postPayin · transfer · reservePayout · bookFxDeal · bookCounterpartyTrade |
| stablecoin onramp/offramp · GPS top-up & cross-currency | stablecoinOnramp/Offramp · topUpGpsPool · gpsCrossCurrencyPayout | |
| bank funding · opening balance · rolling reserve · correction · reversal | fundBankAccount · postOpeningBalance · hold/releaseRollingReserve · postCorrection · reverse | |
| /ops/payouts | filter holds · settle · release · dead-letter to manual review | settleHold · releaseHold · markPayoutManualReview |
| /ops/ledger | browse COA/GL/templates · rebuild · drift check · CSV export | rebuildTemplates · rebuildFromSeed |
| /ops/recon | run all four gates · ALM views · rebuild statements | rebuildStatementReadModel |
| /ops/controls | set/list flags · outbox monitor · provider-event log | setFeatureFlag |
| /ops/settings | console access: allowed/admin email lists (admin only) | saveAccessLists (ops_auth.settings) |