Send the selected destination rail as destination.paymentRail - #776
Merged
Conversation
The Grid Visualizer's rail selector was decorative: picking a rail only changed UI captions and never reached a request body. The quote destination accepts an optional paymentRail, so the user's choice can be real. Three parts, in dependency order: Convert currencies.ts instantRails/allRails to PaymentRail enum members. This is the prerequisite, not a cosmetic rename - 'SEPA Instant' is not a value the API accepts, so no rail could be sent while the data held display strings. Add railDisplayName() so the four render sites (JIT payment note, currency picker rail list, rail dropdown, flow-path captions) keep reading as prose. The enum value is the data; the display name is presentation only. Add destRail state mirroring sourceRail, and pass rail/onRailChange to the Destination InputCard. The spec puts paymentRail on the destination, but the only rail selector was on the Source card, so the destination needed its own selection rather than a reuse of the source's. Options stay constrained to the currency's allRails, since the rail must be one the destination account supports. Emit it in the generated POST /quotes body, and use it for the "Funds out via X" caption instead of defaulting to instantRails[0]. Supersedes #351, which injected the plural, response-only paymentRails into external-account create bodies. Singular paymentRail on a quote destination is a request field; plural paymentRails only ever appears in *AccountInfo response wrappers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMnjJ8yWJsui7jLqqrDrL4
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
Contributor
Greptile SummaryThis PR makes the destination payment-rail selector functional throughout the Grid Visualizer.
Confidence Score: 5/5The PR appears safe to merge, with destination-rail state, presentation, and generated quote output consistently wired to the documented API contract. The selectable values match the PaymentRail enum, normal destination transitions keep the selected rail compatible with the current currency, and paymentRail is emitted only for external fiat account destinations.
|
| Filename | Overview |
|---|---|
| components/grid-visualizer/src/data/currencies.ts | Replaces display-form rail strings with valid PaymentRail enum values and centralizes presentation labels. |
| components/grid-visualizer/src/hooks/useFlowBuilder.ts | Adds destination-rail state and consistently initializes or recomputes it when selecting or swapping destinations. |
| components/grid-visualizer/src/lib/code-generator.ts | Threads the destination rail into external fiat quote destinations using the schema-compatible singular paymentRail field. |
| components/grid-visualizer/src/lib/flow-path.ts | Uses the selected destination rail in flow captions while formatting enum values for display. |
| components/grid-visualizer/src/app/page.tsx | Connects destination-rail state to the destination card, flow panel, and code panel. |
| components/grid-visualizer/src/components/CodePanel/CodePanel.tsx | Regenerates and remounts generated steps when the destination rail changes. |
| components/grid-visualizer/src/components/FlowPanel/FlowPanel.tsx | Recomputes the visualized flow whenever the destination rail changes. |
| components/grid-visualizer/src/components/InputCard/InputCard.tsx | Displays readable rail labels while preserving enum values in selection callbacks. |
| components/grid-visualizer/src/components/CurrencyPicker/CurrencyPicker.tsx | Presents readable rail names and retains both enum and display forms as search keywords. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Destination rail dropdown] --> B[useFlowBuilder destRail]
B --> C[FlowPanel]
B --> D[CodePanel]
C --> E[Funds out via display label]
D --> F[generateSteps]
F --> G{External fiat destination?}
G -- Yes --> H[destination.paymentRail enum value]
G -- No --> I[Omit paymentRail]
Reviews (1): Last reviewed commit: "Send the selected destination rail as de..." | Re-trigger Greptile
jklein24
approved these changes
Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 /quotesrequest asdestination.paymentRail.Supersedes #351.
Why the
currencies.tsrename is a prerequisite, not a cosmetic changeinstantRails/allRailscurrently 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 toPaymentRailenum members (FEDNOW,SEPA_INSTANT,BANK_TRANSFER, …) is what makes the feature possible.Because the enum values would otherwise render as
SEPA_INSTANTat users, this adds an exportedrailDisplayName()covering all 23 enum members (with an underscores-to-spaces fallback) and applies it at the four sites that surface a rail:code-generator.ts—getJitPaymentMethod(), the "sending a FedNow transfer" prose noteCurrencyPicker.tsx—getRailsText()and thekeywordsarray inbuildFiatItem(keywords keep both enum and display forms, so search still matches either)InputCard.tsx— dropdown button label, dropdown menu items, staticfiatRailrowflow-path.ts— the "Funds in via X" / "Funds out via X" captionsThe enum value is the data;
railDisplayName()is presentation only. No display string reaches a request body oronSelect/state.The rename + display map is carried over from the closing PR on
docs/sync-20260418(e3191b4). Two currencies added tomainsince that branch (El SalvadorSLV_ACCOUNT,CNY_ACCOUNT) were converted here as well.paymentRailvspaymentRails— the distinction #351 got wrongpaymentRail(singular)quotes/AccountDestination.yaml). Optional — omitted, Grid selects a default.paymentRails(plural)*AccountInfo.yamlwrappers, reporting what an account supports. Absent from*ExternalAccountCreateInfo/*AccountInfoBase.#351 injected the plural into
POST /customers/external-accountsbodies — a field that endpoint does not accept. This PR introduces nopaymentRailsanywhere (grep -rn paymentRails src/in the visualizer returns nothing), andbuildAccountInfoBodyhas no rail logic at all. A short WHY comment at the emission site records the distinction.The source/destination asymmetry
The spec puts
paymentRailon 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:destRailstate,SET_DEST_RAILaction,setDestRailcallback — mirroringsourceRailexactly. Defaults via the existinggetDefaultRail()onSET_RECEIVE, and recomputed onSWAP(matching howsourceRailalready behaves there).page.tsx: DestinationInputCardnow getsrail/onRailChange.InputCardalready supported these generically, so no component changes were needed beyond the display-name calls.allRailsvia the existinggetFiatRailOptions, 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 toinstantRails[0]— same user choice, so it should reflect it.Judgment call worth a second opinion
paymentRailis 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 howflow-path.tsalready 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 ciincomponents/grid-visualizerfails because@central-icons-reactrequires aCENTRAL_LICENSE_KEYenv var that is not available in this environment. The Vercel preview build on this PR is the real check —next build/next lintand all.tsxtypechecking are unverified here.What I was able to verify locally:
code-generator.ts,flow-path.ts,currencies.ts,settlement-rails.ts,account-types.ts,crypto.tsunderstrict: truewith the@/*path mapping: clean. The.tsxfiles could not be checked (missingreact/next/ icon types).paymentRailappears in the quote body with the enum value, and changing the selection changes it (SEPA_INSTANT→SEPA)PaymentRailmembers are covered by the display map, and every rail value incurrencies.tsis a valid enum memberopenapi/components/schemas/quotes/AccountDestination.yaml,common/PaymentRail.yaml, and the*AccountInfo/*ExternalAccountCreateInfosplit.There is no test suite in
grid-visualizer(notestscript, no test files), so no tests were added or updated.Not verified: actual rendering, dropdown interaction, and that
CodePanel'sstepsKeyremount behaves as intended in the browser (destRailwas 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—destRailstate/action/settersrc/lib/code-generator.ts—destRailparam,paymentRailemission, display names in the JIT notesrc/lib/flow-path.ts—destRailparam, "Funds out via" uses it, display namessrc/app/page.tsx— wiresdestRailto the Destination card and both panelssrc/components/CodePanel/CodePanel.tsx—destRailprop, memo deps,stepsKeysrc/components/FlowPanel/FlowPanel.tsx—destRailprop, memo depssrc/components/InputCard/InputCard.tsx— display namessrc/components/CurrencyPicker/CurrencyPicker.tsx— display namesGenerated by Claude Code