refactor: replace blur with fixed-width placeholders for privacy

Prevent balance size information leakage by using fixed-width
placeholder characters instead of blur effect. A blurred "1000000"
would still reveal it's a large balance vs "100" even when blurred.

Changes:
- Replace blur-sm class with conditional placeholder text
- Use "••••••" for main balances
- Use "••••" for transaction amounts in lists
- Use "•••••• sats" for detailed amounts with unit
- Use "•••• sats" for smaller amounts like fees

Security improvement: No information about balance size is leaked
when privacy mode is enabled. All hidden amounts appear identical.
This commit is contained in:
Claude
2026-01-18 22:05:36 +00:00
parent 57fbe5cdd3
commit 2a3da97b49
2 changed files with 30 additions and 31 deletions

View File

@@ -1064,8 +1064,8 @@ export default function WalletViewer() {
className="text-4xl font-bold font-mono hover:opacity-70 transition-opacity cursor-pointer flex items-center gap-3"
title="Click to toggle privacy blur"
>
<span className={state.walletBalancesBlurred ? "blur-sm" : ""}>
{formatSats(balance)}
<span>
{state.walletBalancesBlurred ? "••••••" : formatSats(balance)}
</span>
{state.walletBalancesBlurred ? (
<EyeOff className="size-6 text-muted-foreground" />
@@ -1161,10 +1161,10 @@ export default function WalletViewer() {
<TransactionLabel transaction={tx} />
</div>
<div className="flex-shrink-0 ml-4">
<p
className={`text-sm font-semibold font-mono ${state.walletBalancesBlurred ? "blur-sm" : ""}`}
>
{formatSats(tx.amount)}
<p className="text-sm font-semibold font-mono">
{state.walletBalancesBlurred
? "••••"
: formatSats(tx.amount)}
</p>
</div>
</div>
@@ -1251,10 +1251,10 @@ export default function WalletViewer() {
? "Received"
: "Sent"}
</p>
<p
className={`text-2xl font-bold font-mono ${state.walletBalancesBlurred ? "blur-sm" : ""}`}
>
{formatSats(selectedTransaction.amount)} sats
<p className="text-2xl font-bold font-mono">
{state.walletBalancesBlurred
? "•••••• sats"
: `${formatSats(selectedTransaction.amount)} sats`}
</p>
</div>
</div>
@@ -1287,10 +1287,10 @@ export default function WalletViewer() {
<Label className="text-xs text-muted-foreground">
Fees Paid
</Label>
<p
className={`text-sm font-mono ${state.walletBalancesBlurred ? "blur-sm" : ""}`}
>
{formatSats(selectedTransaction.fees_paid)} sats
<p className="text-sm font-mono">
{state.walletBalancesBlurred
? "•••• sats"
: `${formatSats(selectedTransaction.fees_paid)} sats`}
</p>
</div>
)}
@@ -1446,21 +1446,20 @@ export default function WalletViewer() {
{invoiceDetails?.amount && !sendAmount && (
<div className="flex justify-between">
<span className="text-muted-foreground">Amount:</span>
<span
className={`font-semibold font-mono ${state.walletBalancesBlurred ? "blur-sm" : ""}`}
>
{Math.floor(invoiceDetails.amount).toLocaleString()}{" "}
sats
<span className="font-semibold font-mono">
{state.walletBalancesBlurred
? "•••••• sats"
: `${Math.floor(invoiceDetails.amount).toLocaleString()} sats`}
</span>
</div>
)}
{sendAmount && (
<div className="flex justify-between">
<span className="text-muted-foreground">Amount:</span>
<span
className={`font-semibold font-mono ${state.walletBalancesBlurred ? "blur-sm" : ""}`}
>
{parseInt(sendAmount).toLocaleString()} sats
<span className="font-semibold font-mono">
{state.walletBalancesBlurred
? "•••••• sats"
: `${parseInt(sendAmount).toLocaleString()} sats`}
</span>
</div>
)}

View File

@@ -197,10 +197,10 @@ export default function UserMenu() {
className="text-lg font-semibold hover:opacity-70 transition-opacity cursor-pointer flex items-center gap-1.5"
title="Click to toggle privacy blur"
>
<span
className={state.walletBalancesBlurred ? "blur-sm" : ""}
>
{formatBalance(balance ?? nwcConnection.balance)}
<span>
{state.walletBalancesBlurred
? "••••••"
: formatBalance(balance ?? nwcConnection.balance)}
</span>
{state.walletBalancesBlurred ? (
<EyeOff className="size-3.5 text-muted-foreground" />
@@ -348,10 +348,10 @@ export default function UserMenu() {
<Wallet className="size-4 text-muted-foreground" />
{balance !== undefined ||
nwcConnection.balance !== undefined ? (
<span
className={`text-sm ${state.walletBalancesBlurred ? "blur-sm" : ""}`}
>
{formatBalance(balance ?? nwcConnection.balance)}
<span className="text-sm">
{state.walletBalancesBlurred
? "••••"
: formatBalance(balance ?? nwcConnection.balance)}
</span>
) : null}
</div>