-
{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 }