Skip to content

feat(grid-wallet-prod): fork the wallet demo into a production demo (phone + API panel, passkey-only) - #730

Merged
pengying merged 13 commits into
mainfrom
07-21-feat_grid-wallet-prod_fork_grid-wallet-demo_as_the_production_demo_app
Jul 27, 2026
Merged

feat(grid-wallet-prod): fork the wallet demo into a production demo (phone + API panel, passkey-only)#730
pengying merged 13 commits into
mainfrom
07-21-feat_grid-wallet-prod_fork_grid-wallet-demo_as_the_production_demo_app

Conversation

@pengying

@pengying pengying commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Forks components/grid-wallet-democomponents/grid-wallet-prod per the spec in the parent PR:

  • Fork: byte-faithful copy; package renamed, dev/start on port 4001 (demo keeps 4000), dev:apple dropped, new README, HANDOFF.md removed. The demo and its docs embed are untouched.
  • Sidebar removed: page.tsx rewritten to phone + resizable API panel only; ConfigurePanel deleted; mobile configure⇄playground chrome (back pill, explore CTA, blur tray) removed from TSX and SCSS.
  • Hardcoded flow: useWalletDemoLogic pins persona: 'fintech' and methods: ['passkey']; setPersona/useCase/setUseCase/toggleMethod pruned. The phone's sign-in offers only Continue with Passkey.
  • Fix: useColumnResize no longer reserves the removed 400px configure column, so the API panel can use the full width (verified by live drag test: cap 880px → 1280px at a 1600px viewport).

Deferred to Phase 2 (live API integration spec): embed-path default-width math, orphaned UseCasePicker/USE_CASES pruning, layout.tsx metadata copy.

Verification

npm run build + tsc clean at every step; Playwright smoke test on :4001 confirmed no sidebar, passkey-only sign-in, and the API panel appending calls as the phone flow runs.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 22, 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 27, 2026 9:41pm
grid-wallet-demo Ignored Ignored Preview Jul 27, 2026 9:41pm

Request Review

pengying commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@pengying pengying changed the title feat(grid-wallet-prod): fork grid-wallet-demo as the production demo app feat(grid-wallet-prod): fork the wallet demo into a production demo (phone + API panel, passkey-only) Jul 23, 2026
@pengying
pengying marked this pull request as ready for review July 23, 2026 00:00
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (613 files found, 100 file limit)

mariano-macri
mariano-macri previously approved these changes Jul 27, 2026

pengying commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

@pengying
pengying force-pushed the 07-21-feat_grid-wallet-prod_fork_grid-wallet-demo_as_the_production_demo_app branch from 5d67e3a to 70931d5 Compare July 27, 2026 21:17
@github-actions github-actions Bot added the breaking-change Introduces a breaking change to the OpenAPI spec label Jul 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Breaking OpenAPI changes detected

This PR introduces breaking changes to openapi.yaml:

API Changelog 2025-10-13 vs. 2025-10-13

API Changes

GET /agents/approvals

  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

GET /agents/me/actions

  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property data/items/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

GET /agents/me/actions/{actionId}

  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

POST /agents/me/external-accounts

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /agents/me/quotes/{quoteId}/execute

  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

GET /agents/me/transactions

  • ⚠️ removed the optional property data/items/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

GET /agents/me/transactions/{transactionId}

  • ⚠️ removed the optional property oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

POST /agents/me/transfer-in

  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 201 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 201 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 201 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 201 status

POST /agents/me/transfer-out

  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 201 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 201 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 201 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 201 status

POST /agents/{agentId}/actions/{actionId}/approve

  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /agents/{agentId}/actions/{actionId}/reject

  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /agents/{agentId}/device-codes

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /auth/delegated-keys

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

PATCH /cards/{id}

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/external-accounts

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/external-accounts/{externalAccountId}/trust

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/external-accounts/{externalAccountId}/trust/confirm

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/external-accounts/{externalAccountId}/untrust

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/external-accounts/{externalAccountId}/untrust/confirm

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

PATCH /customers/{customerId}

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/{customerId}/kyc-link

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/{customerId}/verify-email

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/{customerId}/verify-email/confirm

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/{customerId}/verify-phone

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /customers/{customerId}/verify-phone/confirm

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

DELETE /documents/{documentId}

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /platform/external-accounts

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /quotes/{quoteId}/authorize

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /quotes/{quoteId}/authorize/resend

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /quotes/{quoteId}/execute

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sandbox/send

  • ⚠️ added #/components/schemas/IncomingTransaction, #/components/schemas/OutgoingTransaction, #/components/schemas/CardTransaction to the response body oneOf list for the response status 200

POST /sandbox/uma/receive

  • ⚠️ removed the optional property allOf[subschema #2]/rateDetails from the response with the 200 status

GET /sca/factors

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sca/factors

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sca/factors/confirm

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sca/factors/reset

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

GET /sca/factors/reset/{resetId}

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sca/factors/reset/{resetId}/complete

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

DELETE /sca/factors/{credentialId}

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sca/login/complete

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sca/login/start

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /sca/record-event

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /stablecoin-provider-accounts

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

GET /transactions

  • ⚠️ removed the optional property data/items/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property data/items/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

GET /transactions/{transactionId}

  • ⚠️ removed the optional property oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

POST /transactions/{transactionId}/approve

  • ⚠️ removed the optional property allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /transactions/{transactionId}/confirm

  • ⚠️ removed the optional property oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 200 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 200 status

POST /transactions/{transactionId}/reject

  • ⚠️ removed the optional property allOf[subschema #2]/rateDetails from the response with the 200 status
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST /transfer-in

  • ⚠️ removed the optional property oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 201 status

POST /transfer-out

  • ⚠️ removed the optional property oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId from the response with the 201 status
  • ⚠️ removed the optional property oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund from the response with the 201 status

POST webhook:agent-action

  • ⚠️ removed the request property allOf[subschema #2]/data/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails
  • ⚠️ removed the request property allOf[subschema #2]/data/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/exchangeRate
  • ⚠️ removed the request property allOf[subschema #2]/data/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/quoteId
  • ⚠️ removed the request property allOf[subschema #2]/data/transaction/allOf[#/components/schemas/TransactionOneOf]/oneOf[subschema #2: Outgoing Transaction]/allOf[subschema #2]/refund
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:bulk-upload

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:card-funding-source-change

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:card-state-change

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:card-transaction

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:customer-update

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:incoming-payment

  • ⚠️ removed the request property allOf[subschema #2]/data/allOf[subschema #1: Incoming Transaction]/allOf[subschema #2]/rateDetails
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:internal-account-status

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:invitation-claimed

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:outgoing-payment

  • ⚠️ removed the request property allOf[subschema #2]/data/allOf[subschema #2]/exchangeRate
  • ⚠️ removed the request property allOf[subschema #2]/data/allOf[subschema #2]/quoteId
  • ⚠️ removed the request property allOf[subschema #2]/data/allOf[subschema #2]/refund
  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:test-webhook

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

POST webhook:verification-update

  • ⚠️ added the new TRANSACTION_NOT_CANCELLABLE enum value to the code response property for the response status 409

Detected by oasdiff. This PR will need approval from an API reviewer before merge.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

No changes were made to the SDKs.


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-07-27 21:42:06 UTC

@pengying
pengying changed the base branch from 07-21-docs_design_spec_for_grid-wallet-prod_fork_phase_1_remove_config_sidebar_ to main July 27, 2026 21:37
@pengying
pengying dismissed mariano-macri’s stale review July 27, 2026 21:37

The base branch was changed.

pengying and others added 12 commits July 27, 2026 14:41
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…API panel

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he layout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…case

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… resize math

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the "Save a passkey?" interstitial sheet — tapping Continue with
passkey now starts the WebAuthn create ceremony directly, which also keeps
the tap's user activation for the system dialog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…screen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nd signed cash-out (Phase 2) (#740)

## Summary

Wires `components/grid-wallet-prod` from fully-scripted to the **real
Grid API** (public sandbox), per the Phase 2 spec
(`docs/superpowers/specs/2026-07-22-grid-wallet-prod-live-api-design.md`).
The API panel now renders the requests/responses that actually went over
the wire.

**The full money loop is live-verified end to end:** passkey sign-in →
Add $20 (real platform on-ramp, `COMPLETED`) → Cash out $5 (quote →
browser-signed `Grid-Wallet-Signature` execute → polled to `COMPLETED`
in ~62s), with the displayed balance tracking the real account
throughout.

### What's in here

- **Server proxy** (`/api/grid/[...path]`): allow-listed paths only,
Basic-auth injected server-side, `{request,response}` envelopes with
redacted auth — the panel logs real traffic.
- **Browser crypto** (`gridCrypto.ts`): TEK keygen, HPKE
encrypt/decrypt, Turnkey stamps via `@turnkey/*` — session keys never
leave the client; cross-checked against
`scripts/embedded-wallet-sign.js`.
- **Real auth** (`gridSession.ts`): first-run EMAIL_OTP bootstrap (magic
`000000` in sandbox) → registers a real Grid `PASSKEY` credential via
WebAuthn → returning sign-ins are pure passkey (challenge → `.get()` →
verify).
- **Webhook receiver** (`/api/webhooks`): `X-Grid-Signature` P-256
verification against `GRID_WEBHOOK_PUBKEY`, event ring buffer + poll
route.
- **Live money**: balance/activity reads, Add money via the real
platform on-ramp (the customer-wallet sandbox fund endpoint mints
unspendable book balance — discovered live), outbound
quote/stamp/execute/poll with completion checkmarks gated on
`COMPLETED`.
- 38 vitest tests across the pure modules; every task browser-verified
against the live sandbox.

### Env contract

`GRID_CLIENT_ID/SECRET`, `GRID_API_BASE_URL`, `GRID_CUSTOMER_ID`,
`GRID_WEBHOOK_PUBKEY` in `.env.local` (git-ignored). Prod cutover = swap
those values; first tap re-runs passkey registration against the
production customer.

### Tracked pre-prod follow-ups (not in this PR)

- ~~Gate `GET /api/webhooks/events`~~ — route removed; deliveries are
pushed over SSE instead of polled.
- Proxy customer-scoping before production keys (today it's a
single-customer sandbox gateway).
- ~~Wire real activity rows onto the phone~~ — done below.
- Phone-side failure states for execute errors (panel-only today).
- Report upstream: `POST /quotes` returns undocumented 200 for
platform-sourced quotes.

---

## Update — funding flows, webhook-driven arrivals, sandbox gating

A second pass over the same component. Everything below was verified
against the live sandbox in a browser; the API panel now shows **only**
real traffic.

### Auth follows the documented bootstrap order

- Sign-in leads with the credential a Global Account is actually born
with (`EMAIL_OTP`), with the entry step prefilled from the credential's
own `nickname` — no invented address.
- A passkey is added **later**, from the wallet, as its own signed
action: one device ceremony authorized by the live session (it used to
register *and* immediately authenticate, so a single tap asked for
approval twice).
- The CTA advertises the credential `signIn` will really use. Passkeys
are device-bound, so a passkey listed on the account but registered
elsewhere no longer sends this browser into an assertion it can't
satisfy.
- A lapsed session (15 min) can now re-authenticate in place: the OTP
sheet has a surface on the wallet screen, and the WebAuthn call runs
inside the tap's user activation instead of behind the re-auth
round-trip.

### Funding uses the endpoints Grid actually supports

- **Add money → country → deposit instructions**: the customer's own
account/routing/rails/reference, read from `GET
/customers/internal-accounts`. (The EUR block is a clearly-marked
placeholder IBAN in `data/placeholderDeposit.ts` until the customer has
an EUR account — the read already returns one section per fiat account,
so a real one appears with no code change.)
- **…or "add an account"** to pull from. A pull quote sources the
**ExternalAccount** — the previous body pointed at the platform's
internal account. Grid rejects `/execute` for such a quote (*"funds must
be pushed … use POST /sandbox/send"*), so the quote is created and left
pending, and `POST /sandbox/send` stands in for the push. Amounts here
are USD cents, not USDB micro-units.
- **Add from crypto**: Base / Solana / Ethereum, network → amount → the
real Grid-provisioned deposit address for that chain.
- Saved banks are seeded from `GET /customers/external-accounts`
(filtered to USD/EUR, ACTIVE); selecting one quotes against that account
instead of re-creating it — which also fixes the stale-recipient gap
noted in #740's original follow-ups.

### Webhooks

- **`X-Grid-Signature` parsing was rejecting every real delivery.** Grid
sends `{"v":"1","s":"…"}` (or a bare base64 header); we required
`{"version":1,"signature":"…"}`, so verification never ran and
everything 401'd. The earlier tests signed using our own assumed shape,
so they passed — they now assert the documented formats.
- Verified deliveries **stream to the panel over SSE**
(`/api/webhooks/stream`); nothing polls. The bus is pinned to
`globalThis` because Next gives each route handler its own module
instance — the publisher and subscriber were otherwise in different
registries.
- **An arrival, not a tap, is what says money landed**:
`INCOMING_PAYMENT.COMPLETED` drives the toast and Activity row from its
payload and re-reads the balance from the API. Confirming an add no
longer claims anything.

### Panel, wallet, packaging

- The panel shows only real requests: every synthesized call and
response body is gone (including a fabricated `POST
https://your-app.com/webhooks/grid` and ~330 lines of stubbed
quote/transaction/session responses). Flows with no client call behind
them log nothing.
- Activity renders the real `GET /transactions` history (EXPIRED quotes
filtered out); the balance hero shows **available** with the account
**total** beneath, since sandbox book balance runs ahead of spendable.
- Bank picker trimmed to the US + euro area (ACH + SEPA); APY 3%.
- `Dockerfile` + `.dockerignore` on `output: 'standalone'`.
**`NEXT_PUBLIC_GRID_SANDBOX` is a build arg** (NEXT_PUBLIC_* is inlined
at build time) and gates the sandbox-only "simulate funding" affordance
— a production image must be built without it.
- `scripts/ensure-business-customer.mjs`: idempotent `BUSINESS` customer
provisioning, writes `GRID_BUSINESS_CUSTOMER_ID` to `.env.local`.

60 vitest tests (was 38).

### Still open

- A received webhook only drives the *add* path;
`INCOMING_PAYMENT.PENDING` and `OUTGOING_PAYMENT.*` are logged but don't
touch the wallet.
- The sandbox funding stand-in for the *instructions* screen still runs
the platform on-ramp (no quote exists there to `/sandbox/send`).
- USDB-native funding over Spark works (`REALTIME_FUNDING` +
`cryptoNetwork: SPARK`, rate 1.0, verified) but isn't offered in the UI
— both current paths convert.
- `docker build` unverified: the daemon wasn't running here. The
standalone server itself was run and exercised (page, webhook, SSE,
proxy → Grid all responding).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
….example

The README still described sign-in as "passkey only" and the live API as "Phase 2,
planned" — both contradicted by the code in this branch. It now documents what the
app actually does: EMAIL_OTP-first auth with a passkey added later, the push/pull
split for funding (and why a pull quote can't be executed), webhook-driven arrival
reporting over SSE with the ngrok setup, the sandbox flag and Docker build, and the
sandbox quirks worth knowing (skipped email delivery, book-vs-spendable balance).

.env.example documents the env contract the README refers to; .env.local stays
git-ignored and out of the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pjqydq34z8DBK1kBrioXK2
@pengying
pengying force-pushed the 07-21-feat_grid-wallet-prod_fork_grid-wallet-demo_as_the_production_demo_app branch from 70931d5 to f482893 Compare July 27, 2026 21:41
@pengying
pengying merged commit 6bd06b6 into main Jul 27, 2026
7 of 8 checks passed
@pengying
pengying deleted the 07-21-feat_grid-wallet-prod_fork_grid-wallet-demo_as_the_production_demo_app branch July 27, 2026 21:49
pengying added a commit that referenced this pull request Jul 27, 2026
…ents (#742)

## Summary

Fan-out to multiple clients already worked; **replay on reconnect didn't**. This adds it.

The demo drives one hardcoded customer, so every connected panel is watching the same account and should see every delivery. There is no per-client filtering — `pushEvent` walks all subscribers — and that part is verified below. The gap was that a client only ever saw events that arrived *while it was connected*, so an `EventSource` auto-reconnect after a network blip silently dropped whatever landed in the gap.

### What changed

- Every frame now carries an `id:` (a per-process sequence number). `EventSource` echoes the last one back as `Last-Event-ID` when it reconnects, and the stream replays events after that point.
- **A fresh client replays nothing.** It presents no id, and replaying history would render old deliveries as if they had just arrived — the panel timestamps entries on receipt.
- **Subscribe happens before replay**, so a delivery landing mid-replay queues behind it rather than slipping between the two steps.
- The connect handshake comment reports `clients=N`. `EventSource` ignores comment frames; it shows up in `curl -N .../api/webhooks/stream` when you want to confirm every panel is attached.

### Verified

Three concurrent clients, one delivery each time, with a disconnect and a reconnect in the middle:

```
A handshake: : connected clients=1     B: clients=2     C: clients=3
deliver #1 → A [1] | B [1] | C [1]                    ← every client, no filtering

B disconnects; #2 and #3 land while it is away
  A [1,2,3] | C [1,2,3]                               ← survivors unaffected
B reconnects with Last-Event-ID: 1
  B replayed: [2,3]                                   ← exactly what it missed
D connects fresh: []                                  ← no invented history
deliver #4 → A [1,2,3,4] | C [1,2,3,4] | B [2,3,4] | D [4]
```

Signature verification is untouched; the replay path only re-sends events that already passed it.

### Limits (documented in the module)

- The bus is **per server process**. With multiple replicas, only the instance that received a delivery can fan it out to its own clients — a shared channel (Redis pub/sub) would be needed.
- The ring buffer holds the **last 50** events, so a client away longer than that loses the overflow.

Stacked on #730.

### Update: catch-up must not duplicate either

Self-review caught a flaw in the first push. Subscribe-then-replay avoids gaps but **double-sends**: an event landing between `subscribe` and the replay loop goes out live *and* again from the backlog. Replay-then-subscribe has the opposite bug (it loses that event). The stream now subscribes immediately but **holds** live events, replays the backlog, then flushes the held ones the replay didn't cover.

Verified by reconnecting from `Last-Event-ID: 1` while deliveries stream in:

```
client saw: 2,3,4,5,6
duplicates: none
gaps      : none (2..6 as expected)
```

Also added `webhookEvents.test.ts` covering the bus contract: fan-out to every subscriber, survival of a subscriber that throws, the `seq`/`eventsSince` replay windows, and the ring dropping the overflow a long-gone client can no longer catch up on. **69 tests** total (was 60).

### Update: the stream was readable by any site

Second self-review finding, and a more serious one. `/api/webhooks/stream` is unauthenticated and carries whatever Grid delivered — transaction ids, amounts, and `counterpartyInformation`, which per Grid's schema can include a counterparty's **name, birth date and nationality**. That is the same exposure `GET /api/webhooks/events` was deleted for earlier in this stack; the SSE replacement inherited it.

An `EventSource` opened by another site is now refused (`Sec-Fetch-Site: cross-site` → 403), so a page on someone else's origin can't attach to a panel running on your machine:

```
same-origin (our own panel)        → 200
no header (curl / scripts)         → 200
cross-site (another website)       → 403
```

**This is not access control**, and the code comment says so: anyone who can reach the host can still read the stream, which behind an ngrok tunnel means anyone with the URL. Gating it properly — a session cookie, or not exposing it past localhost — belongs on the pre-prod list next to proxy customer-scoping.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01Pjqydq34z8DBK1kBrioXK2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Introduces a breaking change to the OpenAPI spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants