Skip to content

fix(grid-wallet-prod): activity list reflects the real ledger, not the UI#745

Draft
pengying wants to merge 1 commit into
07-25-feat_grid-wallet-prod_payouts_reach_every_corridor_only_funding_is_rail-limitedfrom
07-25-fix_grid-wallet-prod_activity_list_reflects_the_real_ledger_not_the_ui
Draft

fix(grid-wallet-prod): activity list reflects the real ledger, not the UI#745
pengying wants to merge 1 commit into
07-25-feat_grid-wallet-prod_payouts_reach_every_corridor_only_funding_is_rail-limitedfrom
07-25-fix_grid-wallet-prod_activity_list_reflects_the_real_ledger_not_the_ui

Conversation

@pengying

@pengying pengying commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Money moved but the activity list didn't. Three causes, all real:

  • Inbound rows only ever came from the arrival webhook, which never reaches localhost without a tunnel. Every completion site now calls refreshLedger (balance and GET /transactions together), so rows come from Grid rather than from the UI.
  • Grid records an inbound pull as a DEBIT (it debits the external source), so keying the row off direction labelled arriving money "Money sent". transactionToRow now derives direction from which side the wallet is on, falling back to direction/type when the wallet id isn't known.
  • Funding arrives in two legs (fiat credit, then conversion), so fetchActivity lists only the legs that touch the wallet — the leg the balance moved on.

The push stand-in produced no row at all, for a different reason: it funded the platform's USD account and on-ramped from it. That lands spendable balance, but the transaction belongs to the platform and never appears in GET /transactions?customerId=… — measured 0 of 20 rows in the customer's list. It's now a REALTIME_FUNDING quote settled with POST /sandbox/send: the same settlement a pushed wire gets, and the customer's own transaction.

Fidelity note: a real wire to those deposit instructions lands USD in the fiat account and the sweep converts it (two legs); the stand-in settles straight into the wallet (one). Both produce a customer transaction and a row.

That removed the platform on-ramp helpers entirely, and with them GET /platform/internal-accounts and POST /sandbox/internal-accounts/{id}/fund from the proxy allowlist. The proxy injects the platform's own client credentials, so a narrower allowlist is a real reduction in what a compromised browser session can reach.

Also in this pass

  • Outbound is ledger-driven too — a pending row goes in on confirm and settles from the transaction's terminal status, so a failed quote no longer leaves a phantom "Withdrawn from balance" row.
  • Webhook cards show a static Payload label instead of a Response tab (there is no response to show), vertically centred in the tab group.
  • Panel filter chips for Requests vs Webhooks. Action cards are controls, not traffic, so they're never filtered out.
  • After USD arrives, quote+execute USD internal → USDB automatically.
  • External accounts dedupe on routing + account number (or IBAN), ignoring spacing/case. Collapses nothing today — the demo randomizes account digits on every add, so each run leaves a new real account at Grid — but it stops true repeats, which is what production sees.
  • accountLabel() shows handles whole and masks only numbers (no more •••• @upi).
  • detectAddressNetworks() + 15 tests — groundwork for pasting from the real clipboard outside sandbox. Deliberately not wired to the UI yet; the ambiguous-EVM case still needs a chain prompt.

Verification

Sandbox-probed before wiring, then end-to-end in a browser:

  • REALTIME_FUNDING + /sandbox/sendCOMPLETED, listed with the wallet as destination, spendable (not book-only) balance.
  • Push flow in the UI: balance $751 → $776, top row Money added · Added to balance · Just now · +$25.00.
  • Pull flow: +$100.00 inbound now reads as added, not sent.
  • EUR real-time funding is rejected by this platform (INVALID_INPUT: Sending currency 'EUR' is not configured), so the stand-in deposits USD and the action card says so rather than implying euros arrived.
  • 101 unit tests pass; tsc --noEmit clean.

Known, not addressed here

The push flow is hard to reach: on "Select bank" the 13 saved accounts fill the screen, and the country picker that leads to deposit instructions sits behind the small + in the top-right. Fixing it means either capping the saved list or giving the instructions path its own row on the source step — both visible design changes, so they're left for a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Pjqydq34z8DBK1kBrioXK2

…e UI

Money moved but the activity list didn't. Three causes, all real:

- Inbound rows only ever came from the arrival webhook, which never reaches
  localhost without a tunnel. Every completion site now calls refreshLedger
  (balance AND GET /transactions together), so rows come from Grid.
- Grid records an inbound pull as a DEBIT (it debits the external source), so
  keying the row off `direction` labelled arriving money "Money sent".
  transactionToRow now derives direction from which side the wallet is on and
  falls back to direction/type when the wallet id isn't known.
- Funding arrives in two legs (fiat credit, then conversion), so fetchActivity
  lists only the legs that touch the wallet — the leg the balance moved on.

The push stand-in produced no row at all, because it funded the PLATFORM's USD
account and on-ramped from it: that lands spendable balance, but the transaction
belongs to the platform and never appears in GET /transactions?customerId=...
(measured: 0 of 20 rows). It's now a REALTIME_FUNDING quote settled with
POST /sandbox/send — the same settlement a pushed wire gets, and the customer's
own transaction. Verified in the sandbox: COMPLETED, wallet as destination,
spendable balance. That drops the platform on-ramp helpers entirely, and with
them two endpoints from the proxy allowlist (the proxy injects the platform's
own credentials, so a narrower allowlist is a real reduction in reach).

Also in this pass:
- Outbound is ledger-driven too: a pending row goes in on confirm and settles
  from the transaction's terminal status, so a failed quote no longer leaves a
  phantom "Withdrawn from balance" row.
- Webhook cards show a static Payload label instead of a Response tab (there is
  no response to show), vertically centred in the tab group.
- Panel filter chips for Requests vs Webhooks; action cards are never filtered.
- After USD arrives, quote+execute USD internal -> USDB automatically.
- External accounts dedupe on routing+account number (or IBAN). Collapses
  nothing today — the demo randomizes digits per add — but stops true repeats.
- accountLabel() shows handles whole and masks only numbers (no more "•••• @upi").
- detectAddressNetworks() + tests: groundwork for pasting from the real
  clipboard outside sandbox. Not wired to the UI yet.
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jul 26, 2026 1:32am
grid-wallet-demo Ignored Ignored Preview Jul 26, 2026 1:32am

Request Review

Copy link
Copy Markdown
Contributor Author

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.

1 participant