From c37e21e860b9d963239d0baba535d7d3d2951b80 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sat, 26 Sep 2026 21:10:59 +0700 Subject: [PATCH] feat(marketplace): buyer escrow refund after locktime Completes the escrow safety loop: a buyer whose escrow token is past its locktime can reclaim it from the listing card ('Refund escrow'), which redeems the P2PK token with their refund key. Native-only, same as the rest of escrow. --- src/components/MarketplaceItemCard.tsx | 46 ++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/components/MarketplaceItemCard.tsx b/src/components/MarketplaceItemCard.tsx index ca93862..59da861 100644 --- a/src/components/MarketplaceItemCard.tsx +++ b/src/components/MarketplaceItemCard.tsx @@ -35,6 +35,7 @@ import { useMarkListingSold } from '@/hooks/useMarkListingSold'; import { MarkSoldDialog } from '@/components/MarkSoldDialog'; import { EscrowRequestDialog } from '@/components/EscrowRequestDialog'; import { useCashuEscrow } from '@/hooks/wallet/useCashuEscrow'; +import { parseEscrowToken } from '@/lib/cashuEscrow'; import { useListingTrade, useTradeAttestation, useTradeReputation, useListingTradeRequest, useTradeAttestationRequest } from '@/hooks/useTradeAttestations'; import { DeleteEntityDialog } from '@/components/DeleteEntityDialog'; import { genUserName } from '@/lib/genUserName'; @@ -224,6 +225,38 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa !sellerNamedMe && !iRequestedConfirmation; + // The buyer's own escrow token (from their published 31872), refundable after + // the locktime if the seller never delivered. + const myEscrowToken = requestState?.requests + .find((e) => e.pubkey === user?.pubkey) + ?.tags.find(([t]) => t === 'cashu_token')?.[1]; + const myEscrowRefundable = (() => { + if (!myEscrowToken) return false; + try { + const info = parseEscrowToken(myEscrowToken); + return info.locked && info.refundable; + } catch { + return false; + } + })(); + + const handleRefundEscrow = async () => { + if (!myEscrowToken) return; + try { + await escrow.redeemEscrow(myEscrowToken); + toast({ + title: 'Escrow refunded', + description: 'The funds are back in your Cashu wallet.', + }); + } catch (e) { + toast({ + title: 'Could not refund escrow', + description: e instanceof Error ? e.message : 'Something went wrong.', + variant: 'destructive', + }); + } + }; + const handleConfirmTrade = async () => { if (!user) return; try { @@ -576,6 +609,19 @@ export function MarketplaceItemCard({ item, compact = false }: MarketplaceItemCa

)} + {/* Buyer: reclaim escrow after the locktime if the seller didn't deliver */} + {escrow.available && myEscrowRefundable && ( + + )} + {/* Contact button */}