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 */}