Add Cashu escrow settings + a "require escrow" listing flag
- useEscrowPrefs (device-local): default lock window, preferred escrow mint,
require-escrow default for new listings.
- EscrowSettingsCard on the Wallet (native-only, beside EscrowKeyCard).
- EscrowRequestDialog now defaults its lock window + funding mint from the prefs.
- Listings gain an optional ['escrow','required'] tag: a toggle on
CreateListingPage (defaulted from the pref) and a 🔒 badge on the card.
This commit is contained in:
parent
d82ecf4adf
commit
fb42f4f0d1
9 changed files with 171 additions and 3 deletions
|
|
@ -10,9 +10,10 @@ import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { useCashuEscrow } from '@/hooks/wallet/useCashuEscrow';
|
import { useCashuEscrow } from '@/hooks/wallet/useCashuEscrow';
|
||||||
|
import { useEscrowPrefs } from '@/hooks/wallet/useEscrowPrefs';
|
||||||
import { useTradeAttestationRequest } from '@/hooks/useTradeAttestations';
|
import { useTradeAttestationRequest } from '@/hooks/useTradeAttestations';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { ESCROW_DEFAULT_LOCK_DAYS, ESCROW_MIN_LOCK_DAYS } from '@/lib/cashuEscrow';
|
import { ESCROW_MIN_LOCK_DAYS } from '@/lib/cashuEscrow';
|
||||||
import { Loader2, Lock } from 'lucide-react';
|
import { Loader2, Lock } from 'lucide-react';
|
||||||
import { useTranslation } from '@/contexts/language';
|
import { useTranslation } from '@/contexts/language';
|
||||||
|
|
||||||
|
|
@ -39,11 +40,12 @@ export function EscrowRequestDialog({
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const escrow = useCashuEscrow();
|
const escrow = useCashuEscrow();
|
||||||
|
const { prefs } = useEscrowPrefs();
|
||||||
const { publishRequest, isPending: publishing } = useTradeAttestationRequest();
|
const { publishRequest, isPending: publishing } = useTradeAttestationRequest();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [amount, setAmount] = useState('');
|
const [amount, setAmount] = useState('');
|
||||||
const [days, setDays] = useState(String(ESCROW_DEFAULT_LOCK_DAYS));
|
const [days, setDays] = useState(String(prefs.defaultLockDays));
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
|
@ -61,7 +63,7 @@ export function EscrowRequestDialog({
|
||||||
|
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
const { token } = await escrow.createEscrow({ amountSats, sellerPubkey, lockDays });
|
const { token } = await escrow.createEscrow({ amountSats, sellerPubkey, lockDays, mintUrl: prefs.preferredMintUrl || undefined });
|
||||||
await publishRequest({
|
await publishRequest({
|
||||||
listing,
|
listing,
|
||||||
attestor: sellerPubkey,
|
attestor: sellerPubkey,
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,11 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa
|
||||||
: `📍📦 ${t('marketUi.gearSwap.pickupOrShip')}`}
|
: `📍📦 ${t('marketUi.gearSwap.pickupOrShip')}`}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
{item.escrow === 'required' && (
|
||||||
|
<Badge variant="outline" className="text-xs border-[#ff6600]/50 bg-[#ff6600]/10 text-[#ff6600]">
|
||||||
|
🔒 {t('marketUi.create.requireEscrow')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
{distanceKm != null && (
|
{distanceKm != null && (
|
||||||
<Badge variant="outline" className="text-xs border-gray-600 bg-gray-800/50">
|
<Badge variant="outline" className="text-xs border-gray-600 bg-gray-800/50">
|
||||||
{distanceKm < 1
|
{distanceKm < 1
|
||||||
|
|
|
||||||
87
src/components/wallet/EscrowSettingsCard.tsx
Normal file
87
src/components/wallet/EscrowSettingsCard.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { Settings2 } from 'lucide-react';
|
||||||
|
import { useTranslation } from '@/contexts/language';
|
||||||
|
import { useEscrowPrefs } from '@/hooks/wallet/useEscrowPrefs';
|
||||||
|
import { useCashu } from '@/hooks/wallet/useCashu';
|
||||||
|
import { isNativePlatform } from '@/lib/platform';
|
||||||
|
import { ESCROW_MIN_LOCK_DAYS } from '@/lib/cashuEscrow';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cashu escrow preferences (device-local): the default lock window offered to
|
||||||
|
* buyers, the mint funds come from, and whether new listings default to
|
||||||
|
* requiring escrow. Native-only, like the rest of the Cashu wallet.
|
||||||
|
*/
|
||||||
|
export function EscrowSettingsCard() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { prefs, setPrefs } = useEscrowPrefs();
|
||||||
|
const { mints, isInitialized } = useCashu();
|
||||||
|
|
||||||
|
if (!isNativePlatform() || !isInitialized) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="bg-black/90 border-2 border-[#ff6600]/25">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-lg text-[#ff6600]">
|
||||||
|
<Settings2 className="h-5 w-5" />
|
||||||
|
{t('walletUi.escrowSettings.title')}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>{t('walletUi.escrowSettings.desc')}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{/* Default lock window */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="escrow-lock-days">{t('walletUi.escrowSettings.lockWindow')}</Label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
id="escrow-lock-days"
|
||||||
|
type="number"
|
||||||
|
inputMode="numeric"
|
||||||
|
min={ESCROW_MIN_LOCK_DAYS}
|
||||||
|
value={String(prefs.defaultLockDays)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const n = parseInt(e.target.value, 10);
|
||||||
|
setPrefs({ ...prefs, defaultLockDays: Number.isFinite(n) ? Math.max(ESCROW_MIN_LOCK_DAYS, n) : ESCROW_MIN_LOCK_DAYS });
|
||||||
|
}}
|
||||||
|
className="max-w-24 bg-white/5 border-white/10"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-muted-foreground">{t('walletUi.escrowSettings.days')}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">{t('walletUi.escrowSettings.lockWindowHint')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Preferred mint */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>{t('walletUi.escrowSettings.preferredMint')}</Label>
|
||||||
|
<Select
|
||||||
|
value={prefs.preferredMintUrl || '__active__'}
|
||||||
|
onValueChange={(v) => setPrefs({ ...prefs, preferredMintUrl: v === '__active__' ? '' : v })}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="bg-white/5 border-white/10">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__active__">{t('walletUi.escrowSettings.activeMint')}</SelectItem>
|
||||||
|
{mints.map((m) => (
|
||||||
|
<SelectItem key={m.url} value={m.url}>{m.url.replace(/^https?:\/\//, '')}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-xs text-muted-foreground">{t('walletUi.escrowSettings.preferredMintHint')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Require escrow on new listings */}
|
||||||
|
<div className="flex items-center justify-between gap-3 rounded-lg border border-white/10 p-3">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<Label className="text-sm">{t('walletUi.escrowSettings.requireListings')}</Label>
|
||||||
|
<p className="text-xs text-muted-foreground">{t('walletUi.escrowSettings.requireListingsHint')}</p>
|
||||||
|
</div>
|
||||||
|
<Switch checked={prefs.requireEscrow} onCheckedChange={(v) => setPrefs({ ...prefs, requireEscrow: v })} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
src/hooks/wallet/useEscrowPrefs.ts
Normal file
25
src/hooks/wallet/useEscrowPrefs.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||||
|
import { ESCROW_DEFAULT_LOCK_DAYS } from '@/lib/cashuEscrow';
|
||||||
|
|
||||||
|
export interface EscrowPrefs {
|
||||||
|
/** Default lock window (days) offered when a buyer opens the escrow dialog. */
|
||||||
|
defaultLockDays: number;
|
||||||
|
/** Preferred mint URL for funding escrow ('' = the active/first mint). */
|
||||||
|
preferredMintUrl: string;
|
||||||
|
/** Default the "require escrow" flag on new listings. */
|
||||||
|
requireEscrow: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_PREFS: EscrowPrefs = {
|
||||||
|
defaultLockDays: ESCROW_DEFAULT_LOCK_DAYS,
|
||||||
|
preferredMintUrl: '',
|
||||||
|
requireEscrow: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const KEY = 'skatespots:escrow:prefs';
|
||||||
|
|
||||||
|
/** Device-local Cashu escrow preferences (see ESCROW.md). */
|
||||||
|
export function useEscrowPrefs() {
|
||||||
|
const [prefs, setPrefs] = useLocalStorage<EscrowPrefs>(KEY, DEFAULT_PREFS);
|
||||||
|
return { prefs, setPrefs };
|
||||||
|
}
|
||||||
|
|
@ -1510,6 +1510,8 @@ marketUi: {
|
||||||
bothHint: 'Either',
|
bothHint: 'Either',
|
||||||
addressNote: 'We never collect your address — for shipping, share it privately in the trade chat.',
|
addressNote: 'We never collect your address — for shipping, share it privately in the trade chat.',
|
||||||
postageLabel: 'Who pays postage?',
|
postageLabel: 'Who pays postage?',
|
||||||
|
requireEscrow: 'Require escrow',
|
||||||
|
requireEscrowHint: 'Ask the buyer to settle through Cashu escrow.',
|
||||||
postageBuyer: 'Buyer pays',
|
postageBuyer: 'Buyer pays',
|
||||||
postageSeller: 'I pay',
|
postageSeller: 'I pay',
|
||||||
postageSplit: 'Split',
|
postageSplit: 'Split',
|
||||||
|
|
@ -5759,6 +5761,18 @@ walletUi: {
|
||||||
publishedDesc: 'Buyers can now lock funds to you for a trade.',
|
publishedDesc: 'Buyers can now lock funds to you for a trade.',
|
||||||
failed: 'Could not turn on escrow',
|
failed: 'Could not turn on escrow',
|
||||||
},
|
},
|
||||||
|
escrowSettings: {
|
||||||
|
title: 'Escrow settings',
|
||||||
|
desc: 'Defaults for Cashu escrow trades. Device-local.',
|
||||||
|
lockWindow: 'Default lock window',
|
||||||
|
lockWindowHint: 'How long funds stay locked before the buyer can reclaim them. Longer is safer for the seller.',
|
||||||
|
days: 'days',
|
||||||
|
preferredMint: 'Mint for escrow',
|
||||||
|
activeMint: 'Active mint',
|
||||||
|
preferredMintHint: 'Which mint funds escrow. Use a trustworthy one.',
|
||||||
|
requireListings: 'Require escrow on new listings',
|
||||||
|
requireListingsHint: 'Pre-selects “Require escrow” when you list an item.',
|
||||||
|
},
|
||||||
privacy: {
|
privacy: {
|
||||||
title: 'Privacy Tips',
|
title: 'Privacy Tips',
|
||||||
subtitle: 'Learn how to protect your financial privacy',
|
subtitle: 'Learn how to protect your financial privacy',
|
||||||
|
|
|
||||||
|
|
@ -1510,6 +1510,8 @@ marketUi: {
|
||||||
bothHint: 'Cualquiera',
|
bothHint: 'Cualquiera',
|
||||||
addressNote: 'Nunca recogemos tu dirección — para envíos, compártela en privado en el chat del trato.',
|
addressNote: 'Nunca recogemos tu dirección — para envíos, compártela en privado en el chat del trato.',
|
||||||
postageLabel: '¿Quién paga el envío?',
|
postageLabel: '¿Quién paga el envío?',
|
||||||
|
requireEscrow: 'Exigir depósito',
|
||||||
|
requireEscrowHint: 'Pide al comprador liquidar mediante depósito Cashu.',
|
||||||
postageBuyer: 'Paga el comprador',
|
postageBuyer: 'Paga el comprador',
|
||||||
postageSeller: 'Pago yo',
|
postageSeller: 'Pago yo',
|
||||||
postageSplit: 'A medias',
|
postageSplit: 'A medias',
|
||||||
|
|
@ -5759,6 +5761,18 @@ walletUi: {
|
||||||
publishedDesc: 'Los compradores ya pueden bloquear fondos para ti en un trato.',
|
publishedDesc: 'Los compradores ya pueden bloquear fondos para ti en un trato.',
|
||||||
failed: 'No se pudo activar el depósito',
|
failed: 'No se pudo activar el depósito',
|
||||||
},
|
},
|
||||||
|
escrowSettings: {
|
||||||
|
title: 'Ajustes del depósito',
|
||||||
|
desc: 'Valores por defecto para tratos con depósito Cashu. Local del dispositivo.',
|
||||||
|
lockWindow: 'Plazo de bloqueo por defecto',
|
||||||
|
lockWindowHint: 'Cuánto tiempo quedan bloqueados los fondos antes de que el comprador pueda recuperarlos. Más largo es más seguro para el vendedor.',
|
||||||
|
days: 'días',
|
||||||
|
preferredMint: 'Mint para el depósito',
|
||||||
|
activeMint: 'Mint activo',
|
||||||
|
preferredMintHint: 'Qué mint financia el depósito. Usa uno de confianza.',
|
||||||
|
requireListings: 'Exigir depósito en anuncios nuevos',
|
||||||
|
requireListingsHint: 'Preselecciona «Exigir depósito» al publicar un artículo.',
|
||||||
|
},
|
||||||
privacy: {
|
privacy: {
|
||||||
title: 'Consejos de privacidad',
|
title: 'Consejos de privacidad',
|
||||||
subtitle: 'Aprende a proteger tu privacidad financiera',
|
subtitle: 'Aprende a proteger tu privacidad financiera',
|
||||||
|
|
|
||||||
|
|
@ -3702,6 +3702,8 @@ export interface MarketplaceItem {
|
||||||
fulfilment?: 'pickup' | 'ships' | 'both';
|
fulfilment?: 'pickup' | 'ships' | 'both';
|
||||||
/** Who pays postage, when the deal ships. */
|
/** Who pays postage, when the deal ships. */
|
||||||
shipping?: 'buyer' | 'seller' | 'split';
|
shipping?: 'buyer' | 'seller' | 'split';
|
||||||
|
/** Seller asks the buyer to settle via Cashu escrow. */
|
||||||
|
escrow?: 'required';
|
||||||
/** NIP-13 proof-of-work difficulty (leading zero bits) on the event id. */
|
/** NIP-13 proof-of-work difficulty (leading zero bits) on the event id. */
|
||||||
powDifficulty?: number;
|
powDifficulty?: number;
|
||||||
expiration?: number; // Unix timestamp
|
expiration?: number; // Unix timestamp
|
||||||
|
|
@ -3785,6 +3787,7 @@ export function parseMarketplaceItem(event: NostrEvent): MarketplaceItem | null
|
||||||
const listingType = event.tags.find(([name]) => name === 'listing_type')?.[1] as MarketplaceItem['listingType'];
|
const listingType = event.tags.find(([name]) => name === 'listing_type')?.[1] as MarketplaceItem['listingType'];
|
||||||
const fulfilment = event.tags.find(([name]) => name === 'fulfilment')?.[1] as MarketplaceItem['fulfilment'];
|
const fulfilment = event.tags.find(([name]) => name === 'fulfilment')?.[1] as MarketplaceItem['fulfilment'];
|
||||||
const shipping = event.tags.find(([name]) => name === 'shipping')?.[1] as MarketplaceItem['shipping'];
|
const shipping = event.tags.find(([name]) => name === 'shipping')?.[1] as MarketplaceItem['shipping'];
|
||||||
|
const escrow = event.tags.find(([name]) => name === 'escrow')?.[1] as MarketplaceItem['escrow'];
|
||||||
const powDifficulty = event.tags.some(([name]) => name === 'nonce')
|
const powDifficulty = event.tags.some(([name]) => name === 'nonce')
|
||||||
? countLeadingZeroBits(event.id)
|
? countLeadingZeroBits(event.id)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
@ -3823,6 +3826,7 @@ export function parseMarketplaceItem(event: NostrEvent): MarketplaceItem | null
|
||||||
listingType,
|
listingType,
|
||||||
fulfilment,
|
fulfilment,
|
||||||
shipping,
|
shipping,
|
||||||
|
escrow,
|
||||||
powDifficulty,
|
powDifficulty,
|
||||||
size,
|
size,
|
||||||
expiration,
|
expiration,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { useUploadFile } from '@/hooks/useUploadFile';
|
import { useUploadFile } from '@/hooks/useUploadFile';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { useEscrowPrefs } from '@/hooks/wallet/useEscrowPrefs';
|
||||||
import { useSkateSpot, useSkateSpots } from '@/hooks/useSkateSpots';
|
import { useSkateSpot, useSkateSpots } from '@/hooks/useSkateSpots';
|
||||||
import { useListingRestrictions, recordListingCreation } from '@/hooks/useListingRestrictions';
|
import { useListingRestrictions, recordListingCreation } from '@/hooks/useListingRestrictions';
|
||||||
import { LoginArea } from '@/components/auth/LoginArea';
|
import { LoginArea } from '@/components/auth/LoginArea';
|
||||||
|
|
@ -65,6 +67,8 @@ export default function CreateListingPage() {
|
||||||
const [listingType, setListingType] = useState<'selling' | 'wtb'>('selling');
|
const [listingType, setListingType] = useState<'selling' | 'wtb'>('selling');
|
||||||
const [fulfilment, setFulfilment] = useState<'pickup' | 'ships' | 'both'>('pickup');
|
const [fulfilment, setFulfilment] = useState<'pickup' | 'ships' | 'both'>('pickup');
|
||||||
const [shippingWho, setShippingWho] = useState<'buyer' | 'seller' | 'split'>('buyer');
|
const [shippingWho, setShippingWho] = useState<'buyer' | 'seller' | 'split'>('buyer');
|
||||||
|
const { prefs: escrowPrefs } = useEscrowPrefs();
|
||||||
|
const [requireEscrow, setRequireEscrow] = useState(escrowPrefs.requireEscrow);
|
||||||
const [category, setCategory] = useState(searchParams.get('category') || '');
|
const [category, setCategory] = useState(searchParams.get('category') || '');
|
||||||
const [title, setTitle] = useState(searchParams.get('title') || '');
|
const [title, setTitle] = useState(searchParams.get('title') || '');
|
||||||
const [description, setDescription] = useState(searchParams.get('description') || '');
|
const [description, setDescription] = useState(searchParams.get('description') || '');
|
||||||
|
|
@ -282,6 +286,7 @@ export default function CreateListingPage() {
|
||||||
if (listingType === 'selling') {
|
if (listingType === 'selling') {
|
||||||
tags.push(['fulfilment', fulfilment]);
|
tags.push(['fulfilment', fulfilment]);
|
||||||
if (fulfilment !== 'pickup') tags.push(['shipping', shippingWho]);
|
if (fulfilment !== 'pickup') tags.push(['shipping', shippingWho]);
|
||||||
|
if (requireEscrow) tags.push(['escrow', 'required']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size with optional modifiers
|
// Size with optional modifiers
|
||||||
|
|
@ -755,6 +760,16 @@ export default function CreateListingPage() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{listingType === 'selling' && (
|
||||||
|
<div className="flex items-center justify-between gap-3 rounded-lg border border-gray-700 p-3">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<Label className="text-sm">{t('marketUi.create.requireEscrow')}</Label>
|
||||||
|
<p className="text-xs text-gray-500">{t('marketUi.create.requireEscrowHint')}</p>
|
||||||
|
</div>
|
||||||
|
<Switch checked={requireEscrow} onCheckedChange={setRequireEscrow} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Size */}
|
{/* Size */}
|
||||||
{currentSizeOptions.length > 0 && (
|
{currentSizeOptions.length > 0 && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { useTranslation } from '@/contexts/language';
|
||||||
import { LoginArea } from '@/components/auth/LoginArea';
|
import { LoginArea } from '@/components/auth/LoginArea';
|
||||||
import { CashuWalletCard } from '@/components/wallet/CashuWalletCard';
|
import { CashuWalletCard } from '@/components/wallet/CashuWalletCard';
|
||||||
import { EscrowKeyCard } from '@/components/wallet/EscrowKeyCard';
|
import { EscrowKeyCard } from '@/components/wallet/EscrowKeyCard';
|
||||||
|
import { EscrowSettingsCard } from '@/components/wallet/EscrowSettingsCard';
|
||||||
import { NwcWalletCard } from '@/components/wallet/NwcWalletCard';
|
import { NwcWalletCard } from '@/components/wallet/NwcWalletCard';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
|
|
@ -263,6 +264,7 @@ export default function WalletPage() {
|
||||||
{/* Cashu wallet — native app, self-custodial ecash */}
|
{/* Cashu wallet — native app, self-custodial ecash */}
|
||||||
<CashuWalletCard />
|
<CashuWalletCard />
|
||||||
<EscrowKeyCard />
|
<EscrowKeyCard />
|
||||||
|
<EscrowSettingsCard />
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to="/mints"
|
to="/mints"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue