Wallet audit: warn about at-risk balance before removing a mint (1.2.39)

Audited the wallet surface (real money: Cashu proofs, Lightning, NWC).
Most of it is solid — the Cashu singleton init with browser-CORS mint
probing/auto-heal, the send/receive token flows, the Lightning receive
path (double-tap guard + redeemed-quote listener cleanup + actionable
CORS errors), and the melt-based send all check out and match the zap
path.

One real money-loss gap: removing a mint calls
mintRepository.deleteMint(), which drops that mint's proofs from the
device — so removing a mint you still hold sats in strands those funds.
There are two remove-mint dialogs and they disagreed:

  • MintPrivacyManager already warned with the concrete at-risk balance.
  • CashuWallet's dialog showed only a generic static "make sure you've
    withdrawn funds" line — no balance, no escalation — so a user could
    delete a mint holding sats with effectively no warning.

Brought CashuWallet up to (slightly past) parity: when the mint being
removed has a non-zero balance it now shows a loud red warning with the
exact sats at risk and relabels the button "Remove anyway"; empty mints
keep the mild yellow note. Deletion is still allowed (a permanently
offline mint must be removable even with a stuck balance) — it's now an
informed choice, not a silent one.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-09-08 00:15:07 +07:00
parent 56e2b17990
commit c6e74cc189
2 changed files with 24 additions and 8 deletions

View file

@ -1,7 +1,7 @@
{
"name": "skate-spots",
"private": true,
"version": "1.2.38",
"version": "1.2.39",
"type": "module",
"homepage": "https://skate-spots.com",
"scripts": {

View file

@ -663,12 +663,26 @@ export function CashuWallet() {
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<Alert className="bg-yellow-950/30 border-yellow-500/30">
<AlertCircle className="h-4 w-4 text-yellow-400" />
<AlertDescription className="text-sm text-gray-300">
Make sure you have withdrawn all funds from this mint before removing it.
</AlertDescription>
</Alert>
{mintToRemove && (balances[mintToRemove.url] || 0) > 0 ? (
// Real balance at risk — removing deletes the local proofs and
// strands these sats. Make the amount explicit and loud.
<Alert className="bg-red-950/40 border-red-500/50">
<AlertCircle className="h-4 w-4 text-red-400" />
<AlertDescription className="text-sm text-gray-200">
<span className="font-bold text-red-400">Warning:</span> This mint still holds{' '}
<span className="font-bold">{(balances[mintToRemove.url] || 0).toLocaleString()} sats</span>.
Removing it deletes those funds from this device — withdraw or send them first,
or they'll be lost.
</AlertDescription>
</Alert>
) : (
<Alert className="bg-yellow-950/30 border-yellow-500/30">
<AlertCircle className="h-4 w-4 text-yellow-400" />
<AlertDescription className="text-sm text-gray-300">
Make sure you have withdrawn all funds from this mint before removing it.
</AlertDescription>
</Alert>
)}
<div className="flex gap-3 justify-end">
<Button variant="outline" onClick={() => setShowRemoveMintModal(false)}>
Cancel
@ -677,7 +691,9 @@ export function CashuWallet() {
variant="destructive"
onClick={handleConfirmRemoveMint}
>
Remove Mint
{mintToRemove && (balances[mintToRemove.url] || 0) > 0
? 'Remove anyway'
: 'Remove Mint'}
</Button>
</div>
</div>