diff --git a/src/components/Payment/PaymentInfoRow.tsx b/src/components/Payment/PaymentInfoRow.tsx index ebbe0d35f2..a290645751 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 79d631c459..4742da64c6 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)}
} diff --git a/src/components/TransactionDetails/__tests__/transaction-details.utils.test.ts b/src/components/TransactionDetails/__tests__/transaction-details.utils.test.ts index 84cce5d026..2edd63430b 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 d77de633f1..10a5c5b37c 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 }