Skip to content

Sync Grid Visualizer with PaymentRail enum - #351

Closed
claude[bot] wants to merge 2 commits into
mainfrom
docs/sync-20260418
Closed

Sync Grid Visualizer with PaymentRail enum#351
claude[bot] wants to merge 2 commits into
mainfrom
docs/sync-20260418

Conversation

@claude

@claude claude Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Summary

Syncs the Grid Visualizer with the new PaymentRail enum added in #347:

  • currencies.ts: Update payment rail values to use SCREAMING_SNAKE_CASE format matching the OpenAPI PaymentRail enum (e.g., FEDNOW instead of FedNow, FASTER_PAYMENTS instead of Faster Payments, SEPA_INSTANT instead of SEPA Instant, BANK_TRANSFER instead of Bank Transfer, MOBILE_MONEY instead of Mobile Money)
  • code-generator.ts: Add paymentRails array to generated external account creation requests, matching the documented workflow examples in .claude/skills/grid-api/references/workflows.md

Trigger

OpenAPI schema changes in commit 4d89702:

  • openapi/components/schemas/common/PaymentRail.yaml (new enum)
  • openapi/components/schemas/exchange_rates/ExchangeRate.yaml (added destinationPaymentRail)
  • openapi/components/schemas/quotes/AccountDestination.yaml (added optional paymentRail)

Test plan

  • Verify generated API code in Grid Visualizer uses correct enum values
  • Test external account creation flow shows paymentRails in request body

🤖 Generated with Claude Code

Update currencies.ts to use SCREAMING_SNAKE_CASE payment rail values
that match the PaymentRail enum in the OpenAPI spec (e.g., FEDNOW,
FASTER_PAYMENTS, SEPA_INSTANT, BANK_TRANSFER, MOBILE_MONEY).

Add paymentRails field to generated external account creation requests
in code-generator.ts to match documented workflow examples.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
claude Bot requested review from pengying and shreyav April 18, 2026 08:22
@vercel

vercel Bot commented Apr 18, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
grid-flow-builder Ready Ready Preview Jul 30, 2026 7:55pm
grid-wallet-demo Error Error Jul 30, 2026 7:55pm

Request Review

@greptile-apps

greptile-apps Bot commented Apr 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR syncs the Grid Visualizer with the new PaymentRail enum from PR #347, correctly updating all rail string values in currencies.ts to SCREAMING_SNAKE_CASE. However, the code-generator.ts change also injects paymentRails inside accountInfo in external account creation requests, and that field is defined only in response-side schemas (*AccountInfo) — not in the create-request schemas (*AccountInfoBase) used by POST /customers/external-accounts. This would produce example API calls that include an undocumented field, potentially causing 400 errors or misleading developers who copy the generated code.

Confidence Score: 4/5

The currencies.ts enum update is safe, but code-generator.ts injects a response-only field into create request bodies which would produce incorrect example code.

One P1 finding: paymentRails is injected into accountInfo for external account creation requests, but the field is absent from all create-request schemas (*AccountInfoBase). This generates misleading or broken example API calls, which is the core purpose of this visualizer. The P2 (enum values in prose) is minor. Score is 4 until the P1 is resolved.

components/grid-visualizer/src/lib/code-generator.ts — paymentRails field placement in buildAccountInfoBody

Important Files Changed

Filename Overview
components/grid-visualizer/src/data/currencies.ts Rail values updated to SCREAMING_SNAKE_CASE matching the PaymentRail enum; all new values (FEDNOW, WIRE, SEPA_INSTANT, FASTER_PAYMENTS, BANK_TRANSFER, MOBILE_MONEY, PAYNOW) are valid enum members.
components/grid-visualizer/src/lib/code-generator.ts Adds paymentRails to accountInfo in external account create requests, but this field exists only in response schemas (*AccountInfo), not create request schemas (*AccountInfoBase) — the generated example code would send an undocumented field to the API.

Sequence Diagram

sequenceDiagram
    participant UI as Grid Visualizer UI
    participant CG as code-generator.ts
    participant CUR as currencies.ts (PaymentRail enum)
    participant API as Grid API

    UI->>CG: generateSteps(source, destination)
    CG->>CUR: find(c => c.code === sel.code)
    CUR-->>CG: fiat.allRails (SCREAMING_SNAKE_CASE)
    CG->>CG: buildAccountInfoBody() sets info.paymentRails = allRails
    CG-->>UI: ApiStep with accountInfo.paymentRails
    UI->>API: POST /customers/external-accounts { accountInfo: { ..., paymentRails: [...] } }
    Note over API: paymentRails not in create schema, only in response schema
Loading

Comments Outside Diff (1)

  1. components/grid-visualizer/src/lib/code-generator.ts, line 309 (link)

    P2 Raw enum values in user-facing note text

    getJitPaymentMethod() now returns strings like "FEDNOW transfer" or "SEPA_INSTANT transfer" from fiat.instantRails[0], which appear directly in the step note shown to the user: "Trigger the payment by sending a SEPA_INSTANT transfer to the deposit address...". The previous human-readable names (FedNow, SEPA Instant) were better suited for this prose context.

    Consider either keeping a separate display-name map for prose use or adding a formatting helper that converts SEPA_INSTANTSEPA Instant for the note text.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: components/grid-visualizer/src/lib/code-generator.ts
    Line: 309
    
    Comment:
    **Raw enum values in user-facing note text**
    
    `getJitPaymentMethod()` now returns strings like `"FEDNOW transfer"` or `"SEPA_INSTANT transfer"` from `fiat.instantRails[0]`, which appear directly in the step note shown to the user: *"Trigger the payment by sending a SEPA_INSTANT transfer to the deposit address..."*. The previous human-readable names (`FedNow`, `SEPA Instant`) were better suited for this prose context.
    
    Consider either keeping a separate display-name map for prose use or adding a formatting helper that converts `SEPA_INSTANT``SEPA Instant` for the note text.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
This is a comment left during a code review.
Path: components/grid-visualizer/src/lib/code-generator.ts
Line: 51-57

Comment:
**`paymentRails` is a response-only field**

`paymentRails` is not part of the external account creation request schema. Every `*ExternalAccountCreateInfo` schema (e.g. `UsdExternalAccountCreateInfo`) extends `*AccountInfoBase`, and none of those base schemas include `paymentRails`. The field only appears in the response-side `*AccountInfo` schemas (e.g. `UsdAccountInfo`) that the API returns after creation.

Sending `paymentRails` inside `accountInfo` will either cause a 400 validation error or be silently ignored — either way it generates misleading example code for developers using this visualizer as a reference. The OpenAPI spec examples in `customers_external_accounts.yaml` also omit it from create request bodies.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: components/grid-visualizer/src/lib/code-generator.ts
Line: 309

Comment:
**Raw enum values in user-facing note text**

`getJitPaymentMethod()` now returns strings like `"FEDNOW transfer"` or `"SEPA_INSTANT transfer"` from `fiat.instantRails[0]`, which appear directly in the step note shown to the user: *"Trigger the payment by sending a SEPA_INSTANT transfer to the deposit address..."*. The previous human-readable names (`FedNow`, `SEPA Instant`) were better suited for this prose context.

Consider either keeping a separate display-name map for prose use or adding a formatting helper that converts `SEPA_INSTANT``SEPA Instant` for the note text.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Sync Grid Visualizer with PaymentRail en..." | Re-trigger Greptile

Comment on lines +51 to +57
// Add paymentRails for fiat currencies
if (sel.type === 'fiat') {
const fiat = currencies.find((c) => c.code === sel.code);
if (fiat && fiat.allRails.length > 0) {
info.paymentRails = fiat.allRails;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 paymentRails is a response-only field

paymentRails is not part of the external account creation request schema. Every *ExternalAccountCreateInfo schema (e.g. UsdExternalAccountCreateInfo) extends *AccountInfoBase, and none of those base schemas include paymentRails. The field only appears in the response-side *AccountInfo schemas (e.g. UsdAccountInfo) that the API returns after creation.

Sending paymentRails inside accountInfo will either cause a 400 validation error or be silently ignored — either way it generates misleading example code for developers using this visualizer as a reference. The OpenAPI spec examples in customers_external_accounts.yaml also omit it from create request bodies.

Prompt To Fix With AI
This is a comment left during a code review.
Path: components/grid-visualizer/src/lib/code-generator.ts
Line: 51-57

Comment:
**`paymentRails` is a response-only field**

`paymentRails` is not part of the external account creation request schema. Every `*ExternalAccountCreateInfo` schema (e.g. `UsdExternalAccountCreateInfo`) extends `*AccountInfoBase`, and none of those base schemas include `paymentRails`. The field only appears in the response-side `*AccountInfo` schemas (e.g. `UsdAccountInfo`) that the API returns after creation.

Sending `paymentRails` inside `accountInfo` will either cause a 400 validation error or be silently ignored — either way it generates misleading example code for developers using this visualizer as a reference. The OpenAPI spec examples in `customers_external_accounts.yaml` also omit it from create request bodies.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude fix this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e3191b4 — the paymentRails injection is removed, and code-generator.ts is now byte-identical to the base commit.

Confirmed the finding against current main before removing it. UsdExternalAccountCreateInfo and UsdAccountInfo both extend the same UsdAccountInfoBase; paymentRails is added only in the response wrapper:

# UsdAccountInfo.yaml  (response)
allOf:
- $ref: ./UsdAccountInfoBase.yaml
- required: [paymentRails]        # ← only here

# UsdExternalAccountCreateInfo.yaml  (create request)
allOf:
- $ref: ./BaseExternalAccountInfo.yaml
- $ref: ../common/UsdAccountInfoBase.yaml
- required: [beneficiary]         # ← no paymentRails

Worth noting the PR description justified this hunk by citing workflows.md. That reference no longer says so — main now states the opposite in two places, so there's nothing left to fix upstream:

  • .claude/skills/grid-api/SKILL.md: "Do not send paymentRails — Grid selects the rail and returns it on the created account."
  • .claude/skills/grid-api/references/account-types.md: "The rail is chosen by Grid and returned on the created account (the response paymentRails field) — it is not a request input."

The currencies.ts half is unaffected and still needed: main continues to carry 'FedNow' / 'SEPA Instant' / 'Bank Transfer' while the spec has the PaymentRail enum. I checked every value in the diff against PaymentRail.yamlACH, WIRE, RTP, FEDNOW, SEPA, SEPA_INSTANT, FASTER_PAYMENTS, BANK_TRANSFER, MOBILE_MONEY, PAYNOW, FAST are all valid members.


Generated by Claude Code

The visualizer was injecting paymentRails into the body of
POST /customers/external-accounts. That field is response-only:
*ExternalAccountCreateInfo and *AccountInfo both extend the same
*AccountInfoBase, and paymentRails is added only in the response
wrapper. Generated example calls therefore sent a field the create
endpoint does not accept.

The skill reference now states this explicitly ("Do not send
paymentRails - Grid selects the rail and returns it on the created
account"), so the workflow examples this change was originally
justified by no longer say what it claimed.

Also add railDisplayName() for rendering rails as prose. Moving
currencies.ts to PaymentRail enum values is correct for the data, but
those values reach the UI in four places - the JIT payment note, the
currency picker rail list, the rail dropdown, and the flow-path
captions - which would otherwise read "SEPA_INSTANT transfer". The
enum value stays the data; the display name is presentation only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMnjJ8yWJsui7jLqqrDrL4

shreyav commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Also addressed the P2 from the Greptile summary (raw enum values in prose), which had no review thread to reply to.

The scope was slightly wider than the original comment described. getJitPaymentMethod() was one of four places the rail value reaches the UI, so moving currencies.ts to enum values would have surfaced SEPA_INSTANT in all of them:

Site Before Would have become
code-generator.ts JIT note "sending a SEPA Instant transfer" SEPA_INSTANT
CurrencyPicker rail list "PayNow, FAST, Bank Transfer" PAYNOW, FAST, BANK_TRANSFER
InputCard rail dropdown "FedNow" FEDNOW
flow-path captions "Funds in via SEPA Instant" SEPA_INSTANT

Took the display-map approach suggested in the comment: railDisplayName() in currencies.ts, covering all 23 PaymentRail members, with an underscores-to-spaces fallback so a newly added rail degrades readably instead of breaking. Title-casing generically wasn't viable — it yields "Fednow", "Rtp", "Sepa Instant".

The enum value remains the data everywhere; railDisplayName() is applied only at render. Rail values passed to onSelect and into generated request bodies are untouched.

One thing I could not verify locally: npm ci fails in my environment because @central-icons-react requires CENTRAL_LICENSE_KEY, so I could not run the Next build. currencies.ts typechecks clean standalone under --strict, and the other four files are import-and-call-site changes only, but the Vercel preview build is the real check here.


Generated by Claude Code

@shreyav

shreyav commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

no longer relevant -- rail is not specified on create anymore

@shreyav shreyav closed this Jul 30, 2026
shreyav added a commit that referenced this pull request Jul 31, 2026
## What

Makes the Grid Visualizer's payment-rail selection functional. Today the
rail is decorative — the user picks one from a dropdown and it only
changes UI captions. This threads the choice into the generated `POST
/quotes` request as `destination.paymentRail`.

**Supersedes #351.**

## Why the `currencies.ts` rename is a prerequisite, not a cosmetic
change

`instantRails` / `allRails` currently hold human-readable strings
(`'FedNow'`, `'SEPA Instant'`, `'Bank Transfer'`, …). Those are not
values the API accepts, so **no rail could be sent at all** while the
data held display strings. Converting them to `PaymentRail` enum members
(`FEDNOW`, `SEPA_INSTANT`, `BANK_TRANSFER`, …) is what makes the feature
possible.

Because the enum values would otherwise render as `SEPA_INSTANT` at
users, this adds an exported `railDisplayName()` covering all 23 enum
members (with an underscores-to-spaces fallback) and applies it at the
four sites that surface a rail:

1. `code-generator.ts` — `getJitPaymentMethod()`, the "sending a FedNow
transfer" prose note
2. `CurrencyPicker.tsx` — `getRailsText()` and the `keywords` array in
`buildFiatItem` (keywords keep **both** enum and display forms, so
search still matches either)
3. `InputCard.tsx` — dropdown button label, dropdown menu items, static
`fiatRail` row
4. `flow-path.ts` — the "Funds in via X" / "Funds out via X" captions

**The enum value is the data; `railDisplayName()` is presentation
only.** No display string reaches a request body or `onSelect`/state.

The rename + display map is carried over from the closing PR on
`docs/sync-20260418` (`e3191b4`). Two currencies added to `main` since
that branch (El Salvador `SLV_ACCOUNT`, `CNY_ACCOUNT`) were converted
here as well.

## `paymentRail` vs `paymentRails` — the distinction #351 got wrong

| | |
|---|---|
| **`paymentRail`** (singular) | **Request** field, on quote
destinations only (`quotes/AccountDestination.yaml`). Optional —
omitted, Grid selects a default. |
| **`paymentRails`** (plural) | **Response**-only. Lives in
`*AccountInfo.yaml` wrappers, reporting what an account supports. Absent
from `*ExternalAccountCreateInfo` / `*AccountInfoBase`. |

#351 injected the plural into `POST /customers/external-accounts` bodies
— a field that endpoint does not accept. This PR introduces **no**
`paymentRails` anywhere (`grep -rn paymentRails src/` in the visualizer
returns nothing), and `buildAccountInfoBody` has no rail logic at all. A
short WHY comment at the emission site records the distinction.

## The source/destination asymmetry

The spec puts `paymentRail` on the **destination**, but the visualizer's
only rail selector was on the **Source** card. So this adds a
destination-side selection rather than reusing the source's:

- `useFlowBuilder.ts`: `destRail` state, `SET_DEST_RAIL` action,
`setDestRail` callback — mirroring `sourceRail` exactly. Defaults via
the existing `getDefaultRail()` on `SET_RECEIVE`, and recomputed on
`SWAP` (matching how `sourceRail` already behaves there).
- `page.tsx`: Destination `InputCard` now gets `rail` / `onRailChange`.
`InputCard` already supported these generically, so no component changes
were needed beyond the display-name calls.
- Options stay constrained to the currency's `allRails` via the existing
`getFiatRailOptions`, per "must be one of the rails supported by the
destination account."

The existing source rail is untouched and keeps its display purposes.

`flow-path.ts`'s "Funds out via X" caption now uses the selected
destination rail instead of always defaulting to `instantRails[0]` —
same user choice, so it should reflect it.

## Judgment call worth a second opinion

**`paymentRail` is emitted only for external destinations**, in addition
to the "fiat and a rail selected" conditions. Funds credited to a Grid
internal balance never traverse an external payment rail, so sending one
there would generate an example the API would likely reject. This also
matches how `flow-path.ts` already treats rails (the "Funds out via"
caption only renders for `!next.isInternal`). A destination can become
internal via **Swap**. Happy to drop the gate if reviewers disagree.

Note the UI still shows a Rail row on an internal fiat destination card
even though it no longer affects the body — consistent with how the
Source card already behaves for internal sources. Hiding it felt like
scope creep; flagging rather than fixing.

## Verification — please read

**I could not run the real build.** `npm ci` in
`components/grid-visualizer` fails because `@central-icons-react`
requires a `CENTRAL_LICENSE_KEY` env var that is not available in this
environment. **The Vercel preview build on this PR is the real check** —
`next build` / `next lint` and all `.tsx` typechecking are unverified
here.

What I *was* able to verify locally:

- **Strict typecheck of every dependency-free module** —
`code-generator.ts`, `flow-path.ts`, `currencies.ts`,
`settlement-rails.ts`, `account-types.ts`, `crypto.ts` under `strict:
true` with the `@/*` path mapping: clean. The `.tsx` files could not be
checked (missing `react` / `next` / icon types).
- **Behavioral smoke test** of the compiled pure logic:
- `paymentRail` appears in the quote body with the enum value, and
changing the selection changes it (`SEPA_INSTANT` → `SEPA`)
- key **omitted entirely** (not null/undefined) when no rail is
selected, when the destination is crypto, and when the destination is
internal
- across every rail of every currency, no non-enum-looking value is ever
emitted
- "Funds out via" follows the selection, falls back to the default, and
renders display names
- all 23 `PaymentRail` members are covered by the display map, and every
rail value in `currencies.ts` is a valid enum member
- **Spec claims re-verified** against
`openapi/components/schemas/quotes/AccountDestination.yaml`,
`common/PaymentRail.yaml`, and the `*AccountInfo` /
`*ExternalAccountCreateInfo` split.

There is no test suite in `grid-visualizer` (no `test` script, no test
files), so no tests were added or updated.

Not verified: actual rendering, dropdown interaction, and that
`CodePanel`'s `stepsKey` remount behaves as intended in the browser
(`destRail` was added to both the memo deps and the key, so stale code
should be impossible, but this is untested at runtime).

## Files changed

- `src/data/currencies.ts` — enum rename + `railDisplayName()`
- `src/hooks/useFlowBuilder.ts` — `destRail` state/action/setter
- `src/lib/code-generator.ts` — `destRail` param, `paymentRail`
emission, display names in the JIT note
- `src/lib/flow-path.ts` — `destRail` param, "Funds out via" uses it,
display names
- `src/app/page.tsx` — wires `destRail` to the Destination card and both
panels
- `src/components/CodePanel/CodePanel.tsx` — `destRail` prop, memo deps,
`stepsKey`
- `src/components/FlowPanel/FlowPanel.tsx` — `destRail` prop, memo deps
- `src/components/InputCard/InputCard.tsx` — display names
- `src/components/CurrencyPicker/CurrencyPicker.tsx` — display names

---
_Generated by [Claude
Code](https://claude.ai/code/session_01MMnjJ8yWJsui7jLqqrDrL4)_

Co-authored-by: Claude <noreply@anthropic.com>
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.

3 participants