From cd9c9c964f7b8164a4088a015da7b54dc7dbfc41 Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Mon, 27 Jul 2026 17:43:16 +0200 Subject: [PATCH 1/2] fix(receipt): shorten crypto destination address so it can't overflow the card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 44-char Solana address (and 42-char EVM / 34-char Tron) rendered verbatim in the receipt's Address row is one unbreakable token — it blows the drawer card out of the viewport on mobile (Crisp session_779df6b2, TASK-20889). Verbatim display dates from the Tron formatIban-corruption fix, which correctly stopped IBAN-chunking but left no shortening at all. Shorten by wire type in maskAccountIdentifier (start...end, same shape as the drawer header and TX ID rows) instead of printableAddress, whose viem isAddress re-validation would silently fall back to the full overflowing string for wrong-checksum mixed-case addresses. Copy is untouched: getAccountCopyValue still yields the verbatim address. --- .../transaction-details.utils.test.ts | 18 ++++++++++++++---- src/utils/account-mask.utils.ts | 19 ++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/components/TransactionDetails/__tests__/transaction-details.utils.test.ts b/src/components/TransactionDetails/__tests__/transaction-details.utils.test.ts index 84cce5d02..2edd63430 100644 --- a/src/components/TransactionDetails/__tests__/transaction-details.utils.test.ts +++ b/src/components/TransactionDetails/__tests__/transaction-details.utils.test.ts @@ -55,12 +55,22 @@ describe('getAccountCopyValue — copy path', () => { }) describe('maskAccountIdentifier — display path', () => { - test("Tron address with a digit 3rd char (wire type 'address') displays VERBATIM, not IBAN-chunked", () => { - expect(maskAccountIdentifier(TRON_ADDR_DIGIT, 'address')).toBe(TRON_ADDR_DIGIT) + // Shortened (start...end), never IBAN-chunked — the full 34/44-char + // address is one unbreakable token that overflows the receipt card on + // mobile (Crisp session_779df6b2, TASK-20889). Copy stays verbatim — + // see the getAccountCopyValue suite above. + test("Tron address with a digit 3rd char (wire type 'address') displays shortened, not IBAN-chunked", () => { + expect(maskAccountIdentifier(TRON_ADDR_DIGIT, 'address')).toBe('TN9RRa...zxLQPP') }) - test("Solana address (wire type 'address') displays VERBATIM", () => { - expect(maskAccountIdentifier(SOLANA_ADDR, 'address')).toBe(SOLANA_ADDR) + test("Solana address (wire type 'address') displays shortened", () => { + expect(maskAccountIdentifier(SOLANA_ADDR, 'address')).toBe('6PqX5b...U9iM6b') + }) + + test("EVM address (wire type 'evm-address') displays shortened", () => { + expect(maskAccountIdentifier('0xCfB0eA7Ba06EffC1534f232736c31F69aD03F91b', 'evm-address')).toBe( + '0xCfB0...03F91b' + ) }) test('IBAN rail still masks to last-4 groups (no regression)', () => { diff --git a/src/utils/account-mask.utils.ts b/src/utils/account-mask.utils.ts index d77de633f..10a5c5b37 100644 --- a/src/utils/account-mask.utils.ts +++ b/src/utils/account-mask.utils.ts @@ -1,4 +1,4 @@ -import { formatIban } from './general.utils' +import { formatIban, shortenStringLong } from './general.utils' /** * Per-rail bank account masking for receipt display. @@ -87,10 +87,19 @@ export function maskAccountIdentifier( accountType: string | null | undefined ): string { if (!identifier) return '' - // Crypto wallet addresses are shown verbatim — never routed through the - // 'plain' branch's IBAN-shape heuristic, which mangles base58 addresses - // whose 2nd char is a letter and 3rd a digit (e.g. Tron `TN9R…`). - if (isCryptoAddressType(accountType)) return identifier + // Crypto wallet addresses are shortened for display ("BfbXuD...NKNb19") — + // a full 44-char Solana address is one unbreakable token that overflows + // the receipt card on mobile. Copy still yields the verbatim address via + // getAccountCopyValue. Shorten by wire type, NOT via printableAddress's + // shape re-validation (viem isAddress rejects wrong-checksum mixed-case + // strings and would fall back to the full overflowing address). Never + // route these through the 'plain' branch's IBAN-shape heuristic, which + // mangles case-sensitive base58 (Tron `TN9R…`). The length guard keeps + // degenerate short identifiers intact — shortenStringLong would garble + // anything shorter than its 6+6 window; real addresses are 32+ chars. + if (isCryptoAddressType(accountType)) { + return identifier.length <= 16 ? identifier : shortenStringLong(identifier) + } const rail = (accountType ?? '').toUpperCase() const rule = MASK_RULES[rail] ?? { mode: 'plain' as MaskMode } From 3f7b54c2ac1cd8aab9fde6cb1ddcf32ea1c12256 Mon Sep 17 00:00:00 2001 From: Aleksandar Balinda Date: Mon, 27 Jul 2026 17:51:46 +0200 Subject: [PATCH 2/2] fix(receipt): contain long values in PaymentInfoRow; shorten Solana counterparty in To row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Belt-and-braces for the same overflow class: PaymentInfoRow's value wrapper gets min-w-0 + break-words so any future unbreakable token wraps inside the card instead of stretching the row (flex min-width: auto resolves to the token's full width). The To row dropped its viem isAddress pre-guard — it's EVM-only, so Solana/Tron counterparties rendered full-length; printableAddress already passes usernames through untouched. --- src/components/Payment/PaymentInfoRow.tsx | 9 +++++++-- .../TransactionDetails/TransactionDetailsReceipt.tsx | 11 +++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/components/Payment/PaymentInfoRow.tsx b/src/components/Payment/PaymentInfoRow.tsx index ebbe0d35f..a29064575 100644 --- a/src/components/Payment/PaymentInfoRow.tsx +++ b/src/components/Payment/PaymentInfoRow.tsx @@ -49,8 +49,13 @@ export const PaymentInfoRow = ({ ) : (
-
- {value} + {/* min-w-0 + break-words: a single unbreakable token (wallet + address, tx hash) must wrap inside the card, not stretch + the row to the token's full width and escape the layout. + break-word only activates when a word can't fit, so + normal values render unchanged. */} +
+ {value}
{allowCopy && typeof value === 'string' && ( diff --git a/src/components/TransactionDetails/TransactionDetailsReceipt.tsx b/src/components/TransactionDetails/TransactionDetailsReceipt.tsx index 79d631c45..4742da64c 100644 --- a/src/components/TransactionDetails/TransactionDetailsReceipt.tsx +++ b/src/components/TransactionDetails/TransactionDetailsReceipt.tsx @@ -39,7 +39,6 @@ import { TransactionDetailsHeaderCard } from './TransactionDetailsHeaderCard' import CopyToClipboard from '../Global/CopyToClipboard' import CancelSendLinkModal from '../Global/CancelSendLinkModal' import { twMerge } from 'tailwind-merge' -import { isAddress } from 'viem' import { getAccountCopyValue, getBankAccountLabel } from './transaction-details.utils' import { useModalsContext } from '@/context/ModalsContext' import { useRouter } from 'next/navigation' @@ -418,11 +417,11 @@ export const TransactionDetailsReceipt = ({ label={'To'} value={
- - {isAddress(transaction.userName) - ? printableAddress(transaction.userName) - : transaction.userName} - + {/* printableAddress shortens Solana/Tron/EVM and + passes usernames through — no viem isAddress + pre-guard, which is EVM-only and let 44-char + Solana counterparties render full-length. */} + {printableAddress(transaction.userName)}
}