From 9c3611956fd01a3526fdebf2bcb4b72ef64ad3b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 20:23:17 +0000 Subject: [PATCH] Send the selected destination rail as destination.paymentRail 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 Claude-Session: https://claude.ai/code/session_01MMnjJ8yWJsui7jLqqrDrL4 --- components/grid-visualizer/src/app/page.tsx | 5 ++ .../src/components/CodePanel/CodePanel.tsx | 8 +- .../CurrencyPicker/CurrencyPicker.tsx | 6 +- .../src/components/FlowPanel/FlowPanel.tsx | 4 +- .../src/components/InputCard/InputCard.tsx | 8 +- .../grid-visualizer/src/data/currencies.ts | 89 +++++++++++++------ .../src/hooks/useFlowBuilder.ts | 18 +++- .../grid-visualizer/src/lib/code-generator.ts | 15 +++- .../grid-visualizer/src/lib/flow-path.ts | 18 ++-- 9 files changed, 120 insertions(+), 51 deletions(-) diff --git a/components/grid-visualizer/src/app/page.tsx b/components/grid-visualizer/src/app/page.tsx index 14b950119..1826296b1 100644 --- a/components/grid-visualizer/src/app/page.tsx +++ b/components/grid-visualizer/src/app/page.tsx @@ -34,6 +34,7 @@ export default function Home() { setReceiveNetwork, setSourceFundingMode, setSourceRail, + setDestRail, setSettlementRail, swap, setSourceRegion, @@ -188,8 +189,10 @@ export default function Home() { openPicker('receive')} onNetworkChange={setReceiveNetwork} + onRailChange={setDestRail} /> @@ -259,6 +262,7 @@ export default function Home() { sourceRegion={state.sourceRegion} destRegion={state.destRegion} sourceRail={state.sourceRail} + destRail={state.destRail} settlementRail={state.settlementRail} expanded={flowExpanded} onToggle={toggleFlow} @@ -268,6 +272,7 @@ export default function Home() { receive={state.receive!} fundingModel={fundingModel} sourceRail={state.sourceRail} + destRail={state.destRail} audience={state.audience} onAudienceChange={setAudience} expanded={codeExpanded} diff --git a/components/grid-visualizer/src/components/CodePanel/CodePanel.tsx b/components/grid-visualizer/src/components/CodePanel/CodePanel.tsx index 02b66abdb..0686c145c 100644 --- a/components/grid-visualizer/src/components/CodePanel/CodePanel.tsx +++ b/components/grid-visualizer/src/components/CodePanel/CodePanel.tsx @@ -19,6 +19,7 @@ interface CodePanelProps { receive: CurrencySelection; fundingModel: 'jit' | 'pre-funded'; sourceRail?: string | null; + destRail?: string | null; audience: 'human' | 'agent'; onAudienceChange: (audience: 'human' | 'agent') => void; expanded: boolean; @@ -271,16 +272,17 @@ export function CodePanel({ receive, fundingModel, sourceRail, + destRail, audience, onAudienceChange, expanded, }: CodePanelProps) { const steps = useMemo( - () => generateSteps(send, receive, fundingModel, sourceRail), - [send, receive, fundingModel, sourceRail], + () => generateSteps(send, receive, fundingModel, sourceRail, destRail), + [send, receive, fundingModel, sourceRail, destRail], ); - const stepsKey = `${send.code}-${send.accountType}-${send.isInternal}-${receive.code}-${receive.accountType}-${receive.isInternal}-${fundingModel}-${sourceRail}-${audience}`; + const stepsKey = `${send.code}-${send.accountType}-${send.isInternal}-${receive.code}-${receive.accountType}-${receive.isInternal}-${fundingModel}-${sourceRail}-${destRail}-${audience}`; const isHuman = audience === 'human'; return ( diff --git a/components/grid-visualizer/src/components/CurrencyPicker/CurrencyPicker.tsx b/components/grid-visualizer/src/components/CurrencyPicker/CurrencyPicker.tsx index fe550d0ca..3e1669fbd 100644 --- a/components/grid-visualizer/src/components/CurrencyPicker/CurrencyPicker.tsx +++ b/components/grid-visualizer/src/components/CurrencyPicker/CurrencyPicker.tsx @@ -5,7 +5,7 @@ import { Command } from '@lightsparkdev/origin'; import type { CommandItem, CommandGroup } from '@lightsparkdev/origin'; import { IconMagnifyingGlass } from '@central-icons-react/round-outlined-radius-3-stroke-1.5/IconMagnifyingGlass'; import { useCommandNav } from '@/hooks/useCommandNav'; -import { currencies } from '@/data/currencies'; +import { currencies, railDisplayName } from '@/data/currencies'; import { cryptoAssets } from '@/data/crypto'; import type { CurrencySelection } from '@/lib/code-generator'; import styles from './CurrencyPicker.module.scss'; @@ -63,7 +63,7 @@ function lookupSelection(id: string): CurrencySelection | null { function getRailsText(sel: CurrencySelection | null): string { if (!sel) return ''; const fiat = currencies.find((c) => c.code === sel.code); - if (fiat) return fiat.allRails.join(', '); + if (fiat) return fiat.allRails.map(railDisplayName).join(', '); const crypto = cryptoAssets.find((a) => a.symbol === sel.code); if (crypto) return crypto.accountTypes.map((t) => t.network).join(', '); return ''; @@ -76,7 +76,7 @@ function buildFiatItem( return { id: c.code, label: c.name, - keywords: [c.code, c.accountType, ...c.allRails], + keywords: [c.code, c.accountType, ...c.allRails, ...c.allRails.map(railDisplayName)], icon: ( // eslint-disable-next-line @next/next/no-img-element void; @@ -267,11 +268,12 @@ export function FlowPanel({ sourceRegion, destRegion, sourceRail, + destRail, settlementRail, expanded, onToggle, }: FlowPanelProps) { - const path = useMemo(() => buildFlowPath(send, receive, sourceRegion, destRegion, sourceRail, settlementRail), [send, receive, sourceRegion, destRegion, sourceRail, settlementRail]); + const path = useMemo(() => buildFlowPath(send, receive, sourceRegion, destRegion, sourceRail, destRail, settlementRail), [send, receive, sourceRegion, destRegion, sourceRail, destRail, settlementRail]); const pathKey = path.nodes.map((n) => n.id + n.label + n.isInternal + n.actionCards.map((a) => a.text).join(',')).join('|'); const contentRef = useRef(null); const flowRef = useRef(null); diff --git a/components/grid-visualizer/src/components/InputCard/InputCard.tsx b/components/grid-visualizer/src/components/InputCard/InputCard.tsx index 4842fa8a1..37bf34cd7 100644 --- a/components/grid-visualizer/src/components/InputCard/InputCard.tsx +++ b/components/grid-visualizer/src/components/InputCard/InputCard.tsx @@ -4,7 +4,7 @@ import { useState, useRef, useCallback, useEffect } from 'react'; import { createPortal } from 'react-dom'; import type { CurrencySelection } from '@/lib/code-generator'; import { cryptoAssets, type CryptoAccountType } from '@/data/crypto'; -import { currencies } from '@/data/currencies'; +import { currencies, railDisplayName } from '@/data/currencies'; import { IconPlusLarge } from '@central-icons-react/round-outlined-radius-3-stroke-1.5/IconPlusLarge'; import { IconChevronBottom } from '@central-icons-react/round-outlined-radius-3-stroke-1.5/IconChevronBottom'; import { IconArrowsRepeat } from '@central-icons-react/round-outlined-radius-3-stroke-1.5/IconArrowsRepeat'; @@ -247,7 +247,7 @@ function RailDropdown({ > Rail - {selectedRail} + {railDisplayName(selectedRail)} @@ -285,7 +285,7 @@ function RailDropdown({ }} type="button" > - {rail} + {railDisplayName(rail)} ))} @@ -395,7 +395,7 @@ export function InputCard({
Rail - {fiatRail} + {railDisplayName(fiatRail)}
) : null} diff --git a/components/grid-visualizer/src/data/currencies.ts b/components/grid-visualizer/src/data/currencies.ts index 5b7ed99d0..0b8ed0ee6 100644 --- a/components/grid-visualizer/src/data/currencies.ts +++ b/components/grid-visualizer/src/data/currencies.ts @@ -14,6 +14,39 @@ export interface FiatCurrency { examplePerson: ExamplePerson; } +// instantRails/allRails hold PaymentRail enum members because they are sent to +// the API as-is. These are the human-readable forms for UI text only — never +// send a display name to the API. +const railDisplayNames: Record = { + ACH: 'ACH', + ACH_COLOMBIA: 'ACH Colombia', + BANK_TRANSFER: 'Bank Transfer', + BRE_B: 'Bre-B', + CIPS: 'CIPS', + FAST: 'FAST', + FASTER_PAYMENTS: 'Faster Payments', + FEDNOW: 'FedNow', + INSTAPAY: 'InstaPay', + MOBILE_MONEY: 'Mobile Money', + NEFT: 'NEFT', + PAYNOW: 'PayNow', + PESONET: 'PESONet', + PIX: 'PIX', + RTGS: 'RTGS', + RTP: 'RTP', + SEPA: 'SEPA', + SEPA_INSTANT: 'SEPA Instant', + SPEI: 'SPEI', + SWIFT: 'SWIFT', + UNIONPAY: 'UnionPay', + UPI: 'UPI', + WIRE: 'Wire', +}; + +export function railDisplayName(rail: string): string { + return railDisplayNames[rail] ?? rail.replace(/_/g, ' '); +} + export const currencies: FiatCurrency[] = [ { code: 'USD', @@ -21,8 +54,8 @@ export const currencies: FiatCurrency[] = [ countryCode: 'us', accountType: 'USD_ACCOUNT', accountLabel: 'US Bank Account', - instantRails: ['RTP', 'FedNow'], - allRails: ['ACH', 'Wire', 'RTP', 'FedNow'], + instantRails: ['RTP', 'FEDNOW'], + allRails: ['ACH', 'WIRE', 'RTP', 'FEDNOW'], examplePerson: { fullName: 'Jane Doe', nationality: 'US' }, }, { @@ -31,8 +64,8 @@ export const currencies: FiatCurrency[] = [ countryCode: 'eu', accountType: 'EUR_ACCOUNT', accountLabel: 'IBAN', - instantRails: ['SEPA Instant'], - allRails: ['SEPA', 'SEPA Instant'], + instantRails: ['SEPA_INSTANT'], + allRails: ['SEPA', 'SEPA_INSTANT'], examplePerson: { fullName: 'Anna Müller', nationality: 'DE' }, }, { @@ -41,8 +74,8 @@ export const currencies: FiatCurrency[] = [ countryCode: 'gb', accountType: 'GBP_ACCOUNT', accountLabel: 'UK Bank Account', - instantRails: ['Faster Payments'], - allRails: ['Faster Payments'], + instantRails: ['FASTER_PAYMENTS'], + allRails: ['FASTER_PAYMENTS'], examplePerson: { fullName: 'James Wilson', nationality: 'GB' }, }, { @@ -81,8 +114,8 @@ export const currencies: FiatCurrency[] = [ countryCode: 'dk', accountType: 'DKK_ACCOUNT', accountLabel: 'IBAN', - instantRails: ['SEPA Instant'], - allRails: ['SEPA', 'SEPA Instant'], + instantRails: ['SEPA_INSTANT'], + allRails: ['SEPA', 'SEPA_INSTANT'], examplePerson: { fullName: 'Lars Jensen', nationality: 'DK' }, }, { @@ -92,7 +125,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'NGN_ACCOUNT', accountLabel: 'Nigerian Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Chioma Okafor', nationality: 'NG' }, }, { @@ -102,7 +135,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'CAD_ACCOUNT', accountLabel: 'Canadian Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Sophie Tremblay', nationality: 'CA' }, }, { @@ -112,7 +145,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'PHP_ACCOUNT', accountLabel: 'Philippine Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Maria Santos', nationality: 'PH' }, }, { @@ -121,8 +154,8 @@ export const currencies: FiatCurrency[] = [ countryCode: 'sg', accountType: 'SGD_ACCOUNT', accountLabel: 'Singapore Bank Account', - instantRails: ['PayNow', 'FAST'], - allRails: ['PayNow', 'FAST', 'Bank Transfer'], + instantRails: ['PAYNOW', 'FAST'], + allRails: ['PAYNOW', 'FAST', 'BANK_TRANSFER'], examplePerson: { fullName: 'Wei Lin Tan', nationality: 'SG' }, }, { @@ -132,7 +165,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'HKD_ACCOUNT', accountLabel: 'Hong Kong Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Emily Chan', nationality: 'HK' }, }, { @@ -142,7 +175,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'IDR_ACCOUNT', accountLabel: 'Indonesian Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Siti Rahayu', nationality: 'ID' }, }, { @@ -152,7 +185,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'KES_ACCOUNT', accountLabel: 'M-Pesa', instantRails: [], - allRails: ['Mobile Money'], + allRails: ['MOBILE_MONEY'], examplePerson: { fullName: 'Wanjiku Kamau', nationality: 'KE' }, }, { @@ -162,7 +195,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'MYR_ACCOUNT', accountLabel: 'Malaysian Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Nurul Aisyah', nationality: 'MY' }, }, { @@ -172,7 +205,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'RWF_ACCOUNT', accountLabel: 'Mobile Money', instantRails: [], - allRails: ['Mobile Money'], + allRails: ['MOBILE_MONEY'], examplePerson: { fullName: 'Jean Uwimana', nationality: 'RW' }, }, { @@ -182,7 +215,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'THB_ACCOUNT', accountLabel: 'Thai Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Somchai Prasert', nationality: 'TH' }, }, { @@ -192,7 +225,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'TZS_ACCOUNT', accountLabel: 'Mobile Money', instantRails: [], - allRails: ['Mobile Money'], + allRails: ['MOBILE_MONEY'], examplePerson: { fullName: 'Halima Mwanga', nationality: 'TZ' }, }, { @@ -202,7 +235,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'VND_ACCOUNT', accountLabel: 'Vietnamese Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Nguyen Thi Lan', nationality: 'VN' }, }, { @@ -212,7 +245,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'ZAR_ACCOUNT', accountLabel: 'South African Bank Account', instantRails: [], - allRails: ['Bank Transfer'], + allRails: ['BANK_TRANSFER'], examplePerson: { fullName: 'Thabo Nkosi', nationality: 'ZA' }, }, { @@ -222,7 +255,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'ZMW_ACCOUNT', accountLabel: 'Mobile Money', instantRails: [], - allRails: ['Mobile Money'], + allRails: ['MOBILE_MONEY'], examplePerson: { fullName: 'Mwila Tembo', nationality: 'ZM' }, }, { @@ -232,7 +265,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'MWK_ACCOUNT', accountLabel: 'Mobile Money', instantRails: [], - allRails: ['Mobile Money'], + allRails: ['MOBILE_MONEY'], examplePerson: { fullName: 'Chimwemwe Banda', nationality: 'MW' }, }, { @@ -242,7 +275,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'UGX_ACCOUNT', accountLabel: 'Mobile Money', instantRails: [], - allRails: ['Mobile Money'], + allRails: ['MOBILE_MONEY'], examplePerson: { fullName: 'Grace Namugga', nationality: 'UG' }, }, { @@ -252,7 +285,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'XOF_ACCOUNT', accountLabel: 'Mobile Money', instantRails: [], - allRails: ['Mobile Money'], + allRails: ['MOBILE_MONEY'], examplePerson: { fullName: 'Amadou Diallo', nationality: 'SN' }, }, { @@ -262,7 +295,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'SLV_ACCOUNT', accountLabel: 'El Salvador Bank/Mobile', instantRails: [], - allRails: ['Bank Transfer', 'Mobile Money'], + allRails: ['BANK_TRANSFER', 'MOBILE_MONEY'], examplePerson: { fullName: 'Carlos Morales', nationality: 'SV' }, }, { @@ -272,7 +305,7 @@ export const currencies: FiatCurrency[] = [ accountType: 'CNY_ACCOUNT', accountLabel: 'Chinese Bank Account', instantRails: [], - allRails: ['Bank Transfer', 'Mobile Money'], + allRails: ['BANK_TRANSFER', 'MOBILE_MONEY'], examplePerson: { fullName: 'Wei Zhang', nationality: 'CN' }, }, ]; diff --git a/components/grid-visualizer/src/hooks/useFlowBuilder.ts b/components/grid-visualizer/src/hooks/useFlowBuilder.ts index b4e63dcba..7781388a1 100644 --- a/components/grid-visualizer/src/hooks/useFlowBuilder.ts +++ b/components/grid-visualizer/src/hooks/useFlowBuilder.ts @@ -15,6 +15,7 @@ export interface FlowBuilderState { destRegion: string | null; // fiat code — only used when destination is crypto sourceFundingMode: SourceFundingMode; sourceRail: string | null; // selected fiat rail e.g. 'RTP' — null for crypto + destRail: string | null; // selected fiat rail — sent as the quote's destination.paymentRail settlementRail: string; // asset Grid settles over between switches, e.g. 'BTC' | 'USDC' | 'USDB' audience: 'human' | 'agent'; pickerTarget: 'send' | 'receive' | null; @@ -27,6 +28,7 @@ type Action = | { type: 'SET_RECEIVE_NETWORK'; payload: CryptoAccountType } | { type: 'SET_SOURCE_FUNDING_MODE'; payload: SourceFundingMode } | { type: 'SET_SOURCE_RAIL'; payload: string } + | { type: 'SET_DEST_RAIL'; payload: string } | { type: 'SET_SETTLEMENT_RAIL'; payload: string } | { type: 'SET_SOURCE_REGION'; payload: string } | { type: 'SET_DEST_REGION'; payload: string } @@ -43,6 +45,7 @@ const initialState: FlowBuilderState = { destRegion: null, sourceFundingMode: 'external', sourceRail: null, + destRail: null, settlementRail: DEFAULT_SETTLEMENT_RAIL, audience: 'human', pickerTarget: null, @@ -70,7 +73,8 @@ function reducer(state: FlowBuilderState, action: Action): FlowBuilderState { const destRegion = receive.type === 'crypto' ? (receive.code === 'BTC' ? (state.destRegion ?? null) : 'USD') : null; - return { ...state, receive, destRegion, pickerTarget: null }; + const destRail = receive.type === 'fiat' ? getDefaultRail(receive.code) : null; + return { ...state, receive, destRegion, destRail, pickerTarget: null }; } case 'SET_SEND_NETWORK': { if (!state.send) return state; @@ -119,6 +123,8 @@ function reducer(state: FlowBuilderState, action: Action): FlowBuilderState { } case 'SET_SOURCE_RAIL': return { ...state, sourceRail: action.payload }; + case 'SET_DEST_RAIL': + return { ...state, destRail: action.payload }; case 'SET_SETTLEMENT_RAIL': return { ...state, settlementRail: action.payload }; case 'SET_SOURCE_REGION': @@ -133,16 +139,18 @@ function reducer(state: FlowBuilderState, action: Action): FlowBuilderState { return { ...state, pickerTarget: null }; case 'SWAP': { const newSend = state.receive; + const newReceive = state.send; const sourceFundingMode: SourceFundingMode = newSend?.type === 'crypto' ? 'realtime' : 'external'; return { ...state, send: newSend, - receive: state.send, + receive: newReceive, sourceRegion: state.destRegion, destRegion: state.sourceRegion, sourceRail: newSend?.type === 'fiat' ? getDefaultRail(newSend.code) : null, + destRail: newReceive?.type === 'fiat' ? getDefaultRail(newReceive.code) : null, sourceFundingMode, }; } @@ -228,6 +236,11 @@ export function useFlowBuilder() { [], ); + const setDestRail = useCallback( + (rail: string) => dispatch({ type: 'SET_DEST_RAIL', payload: rail }), + [], + ); + const setSettlementRail = useCallback( (asset: string) => dispatch({ type: 'SET_SETTLEMENT_RAIL', payload: asset }), [], @@ -271,6 +284,7 @@ export function useFlowBuilder() { setReceiveNetwork, setSourceFundingMode, setSourceRail, + setDestRail, setSettlementRail, setSourceRegion, setDestRegion, diff --git a/components/grid-visualizer/src/lib/code-generator.ts b/components/grid-visualizer/src/lib/code-generator.ts index c93930cdf..4c7c34161 100644 --- a/components/grid-visualizer/src/lib/code-generator.ts +++ b/components/grid-visualizer/src/lib/code-generator.ts @@ -1,5 +1,5 @@ import { accountTypeSpecs } from '@/data/account-types'; -import { currencies } from '@/data/currencies'; +import { currencies, railDisplayName } from '@/data/currencies'; export interface ExamplePerson { fullName: string; @@ -67,11 +67,11 @@ function getJitPaymentMethod(source: CurrencySelection, sourceRail: string | nul return 'crypto transfer'; } if (sourceRail) { - return `${sourceRail} transfer`; + return `${railDisplayName(sourceRail)} transfer`; } const fiat = currencies.find((c) => c.code === source.code); if (fiat && fiat.instantRails.length > 0) { - return `${fiat.instantRails[0]} transfer`; + return `${railDisplayName(fiat.instantRails[0])} transfer`; } return 'bank transfer'; } @@ -88,6 +88,7 @@ export function generateSteps( destination: CurrencySelection, fundingModel: 'jit' | 'pre-funded', sourceRail?: string | null, + destRail?: string | null, ): ApiStep[] { const jitFunding = fundingModel === 'jit'; const steps: ApiStep[] = []; @@ -267,6 +268,14 @@ export function generateSteps( destinationType: 'ACCOUNT', accountId: 'ExternalAccount:', }; + // Singular `paymentRail` is a request field on the quote destination and + // is optional — omitted, Grid picks a default. The plural `paymentRails` + // is response-only (it reports what an account supports) and must never + // be sent. Internal destinations are excluded: funds credited to a Grid + // balance never traverse an external rail. + if (destination.type === 'fiat' && destRail) { + quoteDest.paymentRail = destRail; + } } else { quoteDest = { destinationType: 'ACCOUNT', diff --git a/components/grid-visualizer/src/lib/flow-path.ts b/components/grid-visualizer/src/lib/flow-path.ts index 09222ef5e..e6b3b3775 100644 --- a/components/grid-visualizer/src/lib/flow-path.ts +++ b/components/grid-visualizer/src/lib/flow-path.ts @@ -1,5 +1,5 @@ import type { CurrencySelection } from './code-generator'; -import { currencies } from '@/data/currencies'; +import { currencies, railDisplayName } from '@/data/currencies'; import { getSettlementRail } from '@/data/settlement-rails'; export type ActionColor = 'fiat' | 'btc' | 'stable'; @@ -89,6 +89,7 @@ export function buildFlowPath( sourceRegion?: string | null, destRegion?: string | null, sourceRail?: string | null, + destRail?: string | null, settlementRail?: string | null, ): FlowPath { const nodes: FlowNode[] = []; @@ -266,15 +267,18 @@ export function buildFlowPath( if (curr.type === 'endpoint' && !curr.isInternal && next.type === 'switch') { const srcFiatData = source.type === 'fiat' ? currencies.find((c) => c.code === source.code) : null; - const srcRail = sourceRail - ?? (srcFiatData - ? (srcFiatData.instantRails[0] ?? srcFiatData.allRails[0] ?? source.accountLabel) - : (source.network ?? source.accountLabel)); + const srcRailValue = sourceRail + ?? (srcFiatData ? (srcFiatData.instantRails[0] ?? srcFiatData.allRails[0]) : null); + const srcRail = srcRailValue + ? railDisplayName(srcRailValue) + : (source.network ?? source.accountLabel); text = `Funds in via ${srcRail}`; } else if (curr.type === 'switch' && next.type === 'endpoint' && !next.isInternal) { const dstFiatData = destination.type === 'fiat' ? currencies.find((c) => c.code === destination.code) : null; - const dstRail = dstFiatData - ? (dstFiatData.instantRails[0] ?? dstFiatData.allRails[0] ?? destination.accountLabel) + const dstRailValue = destRail + ?? (dstFiatData ? (dstFiatData.instantRails[0] ?? dstFiatData.allRails[0]) : null); + const dstRail = dstRailValue + ? railDisplayName(dstRailValue) : (destination.network ?? destination.accountLabel); text = `Funds out via ${dstRail}`; } else if (curr.type === 'switch' && next.type === 'switch') {