From 02c72d904bea447d0ddaa7f2ba7b346353d1cfdb Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Tue, 14 Jul 2026 16:23:05 +0200 Subject: [PATCH 1/4] fix(add-money): stop truncating Bridge deposit reference to 10 chars Users wired funds with the first 10 chars of the deposit reference, so Bridge could not match the incoming transfer and deposits sat in AWAITING_FUNDS until cancelled/returned. Display overflow (the original reason for the truncation) is handled with break-all instead, matching BridgeDepositInstructions.tsx. --- .../components/AddMoneyBankDetails.tsx | 12 +- .../__tests__/AddMoneyBankDetails.test.tsx | 131 ++++++++++++++++++ 2 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx diff --git a/src/components/AddMoney/components/AddMoneyBankDetails.tsx b/src/components/AddMoney/components/AddMoneyBankDetails.tsx index 7cbf2592d..8a1b1202f 100644 --- a/src/components/AddMoney/components/AddMoneyBankDetails.tsx +++ b/src/components/AddMoney/components/AddMoneyBankDetails.tsx @@ -236,7 +236,7 @@ ${routingLabel}: ${routingValue}` } bankDetails += ` -Deposit Reference: ${onrampData?.depositInstructions?.depositMessage?.slice(0, 10) || 'Loading...'} +Deposit Reference: ${onrampData?.depositInstructions?.depositMessage || 'Loading...'} Please use these details to complete your bank transfer.` @@ -273,12 +273,14 @@ Please use these details to complete your bank transfer.`

Deposit reference

-

- {onrampData?.depositInstructions?.depositMessage?.slice(0, 10) || 'Loading...'} + {/* Full reference always — Bridge can't reconcile a wire sent with a + truncated deposit message; break-all handles the visual overflow */} +

+ {onrampData?.depositInstructions?.depositMessage || 'Loading...'}

{onrampData?.depositInstructions?.depositMessage && ( @@ -416,7 +418,7 @@ Please use these details to complete your bank transfer.` title="Double check in your bank before sending:" items={[ `Amount: ${formattedCurrencyAmount} (exact)`, - `Reference: ${onrampData?.depositInstructions?.depositMessage?.slice(0, 10) || 'Loading...'} (included)`, + `Reference: ${onrampData?.depositInstructions?.depositMessage || 'Loading...'} (included)`, ]} /> diff --git a/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx new file mode 100644 index 000000000..ebc48a09b --- /dev/null +++ b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx @@ -0,0 +1,131 @@ +/** + * AddMoneyBankDetails — the Bridge bank-transfer instructions screen. + * + * Regression tests for the deposit-reference truncation bug: the reference was + * sliced to 10 chars in display AND copy, so users wired funds with a reference + * Bridge couldn't reconcile (deposit stuck AWAITING_FUNDS). Every surface that + * hands the reference to the user must carry the FULL depositMessage. Nested + * primitives are stubbed so only this component's own logic is under test. + */ +import React from 'react' +import { render, screen } from '@testing-library/react' + +jest.mock('next/navigation', () => ({ + useRouter: () => ({ push: jest.fn(), replace: jest.fn() }), + useParams: () => ({ country: 'germany' }), +})) +jest.mock('nuqs', () => ({ + useQueryState: () => ['100', jest.fn()], + parseAsString: {}, +})) +jest.mock('@/context/OnrampFlowContext', () => ({ + useOnrampFlow: () => mockUseOnrampFlow(), +})) +jest.mock('@/context/RequestFulfillmentFlowContext', () => ({ + RequestFulfillmentBankFlowStep: { BankCountryList: 'BankCountryList' }, + useRequestFulfillmentFlow: () => ({ + setFlowStep: jest.fn(), + onrampData: undefined, + selectedCountry: undefined, + }), +})) +jest.mock('@/hooks/useOnrampQuote', () => ({ + useOnrampQuote: () => ({ netRate: 1, isLoading: false }), +})) + +jest.mock('@/components/Global/NavHeader', () => ({ __esModule: true, default: () =>
})) +jest.mock('@/components/Global/Card', () => ({ + __esModule: true, + default: ({ children }: { children: React.ReactNode }) =>
{children}
, +})) +jest.mock('@/components/Global/CopyToClipboard', () => ({ + __esModule: true, + default: ({ textToCopy }: { textToCopy: string }) =>
, +})) +jest.mock('@/components/Global/InfoCard', () => ({ + __esModule: true, + default: ({ title, description, items }: { title?: string; description?: string; items?: string[] }) => ( +
+ {title} + {description} + {items?.map((item) =>

{item}

)} +
+ ), +})) +jest.mock('@/components/Payment/PaymentInfoRow', () => ({ + PaymentInfoRow: ({ label, value }: { label: React.ReactNode; value: React.ReactNode }) => ( +
+ {label}: {value} +
+ ), +})) +jest.mock('@/components/0_Bruddle/Button', () => ({ + Button: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => ( + + ), +})) +let capturedGenerateText: (() => Promise) | undefined +jest.mock('@/components/Global/ShareButton', () => ({ + __esModule: true, + default: (props: { generateText: () => Promise }) => { + capturedGenerateText = props.generateText + return
+ }, +})) + +const mockUseOnrampFlow = jest.fn() + +// eslint-disable-next-line import/first -- must come after jest.mock +import AddMoneyBankDetails from '../AddMoneyBankDetails' + +// 20 chars, the real shape of a Bridge deposit message — anything shorter than +// the full string is un-reconcilable by Bridge +const FULL_REFERENCE = 'BRGTESTREF1234567890' + +const baseOnrampData = { + transferId: 'transfer-123', + depositInstructions: { + amount: '100', + currency: 'EUR', + depositMessage: FULL_REFERENCE, + bankName: 'Deutsche Bank', + bankAddress: 'Frankfurt, Germany', + iban: 'DE89370400440532013000', + bic: 'COBADEFFXXX', + accountHolderName: 'Peanut Protocol', + }, +} + +beforeEach(() => { + capturedGenerateText = undefined + mockUseOnrampFlow.mockReturnValue({ onrampData: baseOnrampData }) +}) + +describe('AddMoneyBankDetails deposit reference', () => { + it('displays the full reference untruncated and copies the full reference', () => { + render() + + // display — the full 20-char reference, not the first 10 chars + expect(screen.getByText(FULL_REFERENCE)).toBeInTheDocument() + expect(screen.queryByText(FULL_REFERENCE.slice(0, 10))).not.toBeInTheDocument() + + // the reference copy button carries the full value + const copyTexts = screen.getAllByTestId('copy').map((el) => el.getAttribute('data-text')) + expect(copyTexts).toContain(FULL_REFERENCE) + expect(copyTexts).not.toContain(FULL_REFERENCE.slice(0, 10)) + }) + + it('includes the full reference in the "double check before sending" summary', () => { + render() + + expect(screen.getByText(`Reference: ${FULL_REFERENCE} (included)`)).toBeInTheDocument() + }) + + it('includes the full reference in the shareable bank-details text', async () => { + render() + + expect(capturedGenerateText).toBeDefined() + const details = await capturedGenerateText!() + expect(details).toContain(`Deposit Reference: ${FULL_REFERENCE}`) + }) +}) From dd4d59626da5c5a937545b7c6853fc71260ea752 Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Tue, 14 Jul 2026 16:23:42 +0200 Subject: [PATCH 2/4] style: prettier --- .../components/__tests__/AddMoneyBankDetails.test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx index ebc48a09b..cb2d4c9ae 100644 --- a/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx +++ b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx @@ -48,7 +48,9 @@ jest.mock('@/components/Global/InfoCard', () => ({
{title} {description} - {items?.map((item) =>

{item}

)} + {items?.map((item) => ( +

{item}

+ ))}
), })) From f58ddb3b7a05406d026a8d902244042c46c87275 Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Tue, 14 Jul 2026 16:42:00 +0200 Subject: [PATCH 3/4] fix(add-money): single depositReference source + gate share on reference presence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code-review findings: the literal 'Loading...' fallback could be shared as the deposit reference via ShareButton before onrampData resolved, and the reference expression was copy-pasted across four surfaces — the same structure that let the original truncation land on one surface only. --- .../components/AddMoneyBankDetails.tsx | 42 ++++++++++--------- .../__tests__/AddMoneyBankDetails.test.tsx | 16 +++++++ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/components/AddMoney/components/AddMoneyBankDetails.tsx b/src/components/AddMoney/components/AddMoneyBankDetails.tsx index 8a1b1202f..12e3bac39 100644 --- a/src/components/AddMoney/components/AddMoneyBankDetails.tsx +++ b/src/components/AddMoney/components/AddMoneyBankDetails.tsx @@ -105,6 +105,11 @@ export default function AddMoneyBankDetails(props: AddMoneyBankDetailsProps) { const amount = isAddMoneyFlow ? (amountFromUrl ?? '') : requestFulfilmentOnrampData?.depositInstructions?.amount const onrampData = isAddMoneyFlow ? onrampContext.onrampData : requestFulfilmentOnrampData + // The single source for every surface that hands the reference to the user + // (display, copy, share, summary). Bridge can't reconcile a wire sent with + // anything short of the full depositMessage — never truncate or format it. + const depositReference = onrampData?.depositInstructions?.depositMessage + const currencySymbolBasedOnCountry = useMemo(() => { // symbol of the detected onramp currency (e.g., €, $) return getCurrencySymbol(onrampCurrency) @@ -236,7 +241,7 @@ ${routingLabel}: ${routingValue}` } bankDetails += ` -Deposit Reference: ${onrampData?.depositInstructions?.depositMessage || 'Loading...'} +Deposit Reference: ${depositReference} Please use these details to complete your bank transfer.` @@ -273,17 +278,12 @@ Please use these details to complete your bank transfer.`

Deposit reference

- {/* Full reference always — Bridge can't reconcile a wire sent with a - truncated deposit message; break-all handles the visual overflow */} + {/* break-all handles the visual overflow of the full reference */}

- {onrampData?.depositInstructions?.depositMessage || 'Loading...'} + {depositReference || 'Loading...'}

- {onrampData?.depositInstructions?.depositMessage && ( - + {depositReference && ( + )}
@@ -418,7 +418,7 @@ Please use these details to complete your bank transfer.` title="Double check in your bank before sending:" items={[ `Amount: ${formattedCurrencyAmount} (exact)`, - `Reference: ${onrampData?.depositInstructions?.depositMessage || 'Loading...'} (included)`, + `Reference: ${depositReference || 'Loading...'} (included)`, ]} /> @@ -426,14 +426,18 @@ Please use these details to complete your bank transfer.` I've sent the transfer - - Share Details - + {/* No share until the reference exists — a shared blob with a + missing reference produces an unreconcilable wire */} + {depositReference && ( + + Share Details + + )}
) diff --git a/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx index cb2d4c9ae..1822a5765 100644 --- a/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx +++ b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx @@ -130,4 +130,20 @@ describe('AddMoneyBankDetails deposit reference', () => { const details = await capturedGenerateText!() expect(details).toContain(`Deposit Reference: ${FULL_REFERENCE}`) }) + + it('offers no copy or share while the reference is still loading', () => { + mockUseOnrampFlow.mockReturnValue({ + onrampData: { + transferId: 'transfer-123', + depositInstructions: { ...baseOnrampData.depositInstructions, depositMessage: undefined }, + }, + }) + render() + + // the 'Loading...' placeholder must never leave the screen — no share + // blob, no copyable value carrying the literal placeholder + expect(screen.queryByTestId('share-button')).not.toBeInTheDocument() + const copyTexts = screen.getAllByTestId('copy').map((el) => el.getAttribute('data-text')) + expect(copyTexts).not.toContain('Loading...') + }) }) From 92a392a6dbf1ddf50f5dafd64770777077440f0c Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Tue, 14 Jul 2026 16:49:37 +0200 Subject: [PATCH 4/4] chore: drop eslint-disable for a rule this config doesn't define --- .../AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx index 1822a5765..83ce2b2a4 100644 --- a/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx +++ b/src/components/AddMoney/components/__tests__/AddMoneyBankDetails.test.tsx @@ -77,7 +77,7 @@ jest.mock('@/components/Global/ShareButton', () => ({ const mockUseOnrampFlow = jest.fn() -// eslint-disable-next-line import/first -- must come after jest.mock +// import must come after jest.mock import AddMoneyBankDetails from '../AddMoneyBankDetails' // 20 chars, the real shape of a Bridge deposit message — anything shorter than