Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/components/Payment/PaymentInfoRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,13 @@ export const PaymentInfoRow = ({
<Loading />
) : (
<div className="flex items-center justify-between">
<div className={twMerge('flex w-fit justify-end text-sm font-bold')}>
<span>{value}</span>
{/* 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. */}
<div className={twMerge('flex w-fit min-w-0 justify-end break-words text-sm font-bold')}>
<span className="min-w-0">{value}</span>
</div>
{allowCopy && typeof value === 'string' && (
<CopyToClipboard textToCopy={copyValue ?? value} fill="black" iconSize="4" />
Expand Down
11 changes: 5 additions & 6 deletions src/components/TransactionDetails/TransactionDetailsReceipt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -418,11 +417,11 @@ export const TransactionDetailsReceipt = ({
label={'To'}
value={
<div className="flex items-center gap-2">
<span>
{isAddress(transaction.userName)
? printableAddress(transaction.userName)
: transaction.userName}
</span>
{/* 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. */}
<span>{printableAddress(transaction.userName)}</span>
<CopyToClipboard textToCopy={transaction.userName} iconSize="4" />
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)', () => {
Expand Down
19 changes: 14 additions & 5 deletions src/utils/account-mask.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatIban } from './general.utils'
import { formatIban, shortenStringLong } from './general.utils'

/**
* Per-rail bank account masking for receipt display.
Expand Down Expand Up @@ -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 }

Expand Down
Loading