import { useState, useEffect, useCallback } from 'react';
import { useCashu } from '@/hooks/wallet/useCashu';
import { useMintManager } from '@/hooks/wallet/useMintManager';
import { useLightningOperations } from '@/hooks/wallet/useLightningOperations';
import { useTokenOperations } from '@/hooks/wallet/useTokenOperations';
import { useHistoryManager } from '@/hooks/wallet/useHistoryManager';
import { useSatsToUsd } from '@/hooks/useExchangeRate';
import { useLightningAddress } from '@/hooks/useLightningAddress';
import { generateQRCode } from '@/lib/wallet/qrCodeGenerator';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Skeleton } from '@/components/ui/skeleton';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Wallet,
ArrowDownCircle,
ArrowUpCircle,
History,
Copy,
ExternalLink,
AlertCircle,
Shield,
ScanLine,
Zap,
Nut,
} from 'lucide-react';
import { useToast } from '@/hooks/useToast';
import { QRScannerModal } from '@/components/wallet/QRScannerModal';
import { MintPrivacyManager } from '@/components/wallet/MintPrivacyManager';
import { RecoveryPhraseDialog } from '@/components/wallet/RecoveryPhraseDialog';
import { isNativePlatform } from '@/lib/platform';
import { Smartphone } from 'lucide-react';
/**
* The built-in Cashu (ecash) mint wallet only works where there's no browser
* CORS jail — i.e. in the native app. In a plain web browser most mints block
* requests (no Access-Control-Allow-Origin), so minting/melting fails. Rather
* than ship a browser feature that unreliably errors, we gate the full wallet
* to native and, in the browser, explain how to transact (zaps via a connected
* Lightning wallet, or use the native app).
*
* Zaps/tips — the app's actual money flow — keep working in the browser via
* NWC / WebLN / Lightning address, none of which touch a Cashu mint.
*/
export function CashuWalletCard() {
if (!isNativePlatform()) {
return ;
}
return ;
}
function CashuWalletBrowserNotice() {
return (
{/* Ambient glow */}
Built-in Ecash Wallet
App only
Self-custodial Cashu ecash, powered by Bitcoin Lightning
{/* Native-only explainer — framed as a premium capability, not a disabled feature */}
Unlock the full wallet in the SkateSpots app
The built-in ecash wallet lets you mint, send and redeem Cashu tokens right at the
spot. It lives in the native app, where it can talk to mints directly — browsers
sandbox those connections for your security.
{/* What works right here — the actual money flow */}
Zap & tip — works in your browser now
Sending sats to spots, events, coaches and crews works right here. Just connect a
Lightning wallet below — Nostr Wallet Connect or a browser extension like Alby. No
Cashu mint needed.
);
}
function CashuWalletCardNative() {
const {
coco,
repositories,
balances,
mints,
totalBalance,
isInitialized,
isLoading: walletLoading,
error: walletError,
mnemonic,
getEncodedToken,
refreshMints,
} = useCashu();
const usdValue = useSatsToUsd(totalBalance);
const { toast } = useToast();
const { getInvoice: getLightningInvoice, isLoading: isResolvingAddress } = useLightningAddress();
const {
activeMintUrl,
setActiveMintUrl,
handleAddMint,
handleRemoveMintDirect,
getCleanMintLabel,
} = useMintManager({ coco, mints, repositories, refreshMints });
const {
quoteAmount,
setQuoteAmount,
showInvoiceModal,
setShowInvoiceModal,
currentInvoice,
handleLightningReceive,
isGeneratingInvoice,
lightningInvoice,
setLightningInvoice,
isProcessingPayment,
handleLightningSend,
} = useLightningOperations({ coco, activeMintUrl, generateQRCode });
const {
tokenInput,
setTokenInput,
handleTokenReceive,
isReceivingToken,
sendAmount,
setSendAmount,
handleTokenSend,
isCreatingToken,
sentToken,
setSentToken,
sentTokenQRCode,
} = useTokenOperations({ coco, activeMintUrl, generateQRCode, getEncodedToken });
const { history, isLoading: historyLoading } = useHistoryManager({
coco,
repositories,
mints,
});
// QR Scanner state
const [showQRScanner, setShowQRScanner] = useState(false);
// Recovery-phrase reveal dialog
const [showRecoveryPhrase, setShowRecoveryPhrase] = useState(false);
// Lightning address state. Its amount is kept SEPARATE from the token-send
// amount (`sendAmount`) so switching between "Create Token" and "Pay Address"
// never carries a stale value between two unrelated intents.
const [lightningAddress, setLightningAddress] = useState('');
const [lightningAddressAmount, setLightningAddressAmount] = useState('');
// Set first mint as active if none selected
useEffect(() => {
if (mints.length > 0 && !activeMintUrl) {
setActiveMintUrl(mints[0].url);
}
}, [mints, activeMintUrl, setActiveMintUrl]);
// Handle QR scan result
const handleQRScanResult = useCallback(async (result: { type: string; value: string }) => {
setShowQRScanner(false);
switch (result.type) {
case 'cashu_token':
setTokenInput(result.value);
// Auto-receive
if (coco) {
try {
await coco.wallet.receive(result.value);
toast({ title: 'Token received', description: 'Cashu token redeemed successfully!' });
} catch (err) {
toast({
variant: 'destructive',
title: 'Receive failed',
description: err instanceof Error ? err.message : 'Unknown error'
});
}
}
break;
case 'lightning_invoice':
setLightningInvoice(result.value);
break;
case 'lightning_address':
setLightningAddress(result.value);
break;
case 'bitcoin_address':
// Recognized, but this is an ecash/Lightning wallet — no on-chain send.
toast({
variant: 'destructive',
title: 'On-chain not supported here',
description: 'That’s a Bitcoin address. This wallet only sends Cashu tokens and Lightning payments.',
});
break;
case 'npub':
toast({
variant: 'destructive',
title: 'That’s a Nostr profile',
description: 'Scan a Cashu token, Lightning invoice, or Lightning address to pay.',
});
break;
case 'clink_offer':
case 'clink_debit':
toast({
variant: 'destructive',
title: 'Payment code not supported yet',
description: 'This wallet can’t pay CLINK codes yet. Use a Lightning invoice or address instead.',
});
break;
default:
toast({
variant: 'destructive',
title: 'Unrecognized QR code',
description: 'Scan a Cashu token, Lightning invoice, or Lightning address.',
});
}
}, [coco, setTokenInput, setLightningInvoice, toast]);
// Handle paying a Lightning address
const [isPayingAddress, setIsPayingAddress] = useState(false);
const handlePayLightningAddress = useCallback(async () => {
if (!lightningAddress || !lightningAddressAmount || !coco || !activeMintUrl) return;
try {
const amount = parseInt(lightningAddressAmount);
if (isNaN(amount) || amount <= 0) {
toast({ variant: 'destructive', title: 'Invalid amount', description: 'Please enter a valid amount' });
return;
}
setIsPayingAddress(true);
// First resolve the Lightning address to get an invoice
const invoice = await getLightningInvoice({ lightningAddress, amountSats: amount });
// Then pay the invoice using Cashu
const meltQuote = await coco.quotes.createMeltQuote(activeMintUrl, invoice);
await coco.quotes.payMeltQuote(activeMintUrl, meltQuote.quote);
toast({ title: 'Payment sent', description: `Sent ${amount} sats to ${lightningAddress}` });
setLightningAddress('');
setLightningAddressAmount('');
} catch (err) {
toast({
variant: 'destructive',
title: 'Payment failed',
description: err instanceof Error ? err.message : 'Unknown error'
});
} finally {
setIsPayingAddress(false);
}
}, [lightningAddress, lightningAddressAmount, coco, activeMintUrl, getLightningInvoice, toast]);
// Copy to clipboard helper
const copyToClipboard = async (text: string, label: string) => {
try {
await navigator.clipboard.writeText(text);
toast({ title: 'Copied', description: `${label} copied to clipboard` });
} catch (err) {
console.error('Failed to copy:', err);
}
};
// Show wallet loading state
if (walletLoading) {
return (
Cashu Wallet
Initializing wallet...
);
}
// Show wallet error state
if (walletError) {
return (
Wallet Error
{walletError.message}
);
}
return (
<>
{/* Main Wallet Card */}
Cashu Wallet
Built-in ecash wallet — no external setup required
{/* Balance Display — compact on mobile since BalanceHero already shows it */}