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.
This commit is contained in:
parent
15901976ef
commit
c37e21e860
1 changed files with 46 additions and 0 deletions
|
|
@ -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
|
|||
</p>
|
||||
)}
|
||||
|
||||
{/* Buyer: reclaim escrow after the locktime if the seller didn't deliver */}
|
||||
{escrow.available && myEscrowRefundable && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleRefundEscrow}
|
||||
className="w-full gap-2 border-[#ccff00]/40 text-[#ccff00] hover:bg-[#ccff00]/10"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
Refund escrow
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Contact button */}
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
|
|||
Loading…
Reference in a new issue