bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
173 lines (172 loc) • 7.46 kB
JavaScript
import { PrivateKey, } from "@bsv/sdk";
// useCancelListing Hook - Cancel marketplace listings
import { useMutation } from "@tanstack/react-query";
import { cancelOrdListings, cancelOrdTokenListings, } from "js-1sat-ord";
import { useBitcoinAuth } from "../../../hooks/useBitcoinAuth.js";
import { broadcastTransaction } from "../../../lib/broadcast";
import { getWalletProps, } from "../../wallet/types/extended-user.js";
import { AssetType, } from "../types/market.js";
export function useCancelListing(options = {}) {
const { app: _app = "bigblocks", feeRate = 0.5, onSuccess, onError, } = options;
const { user } = useBitcoinAuth();
const cancelMutation = useMutation({
mutationFn: async (listing) => {
if (!user) {
throw {
code: "UNAUTHORIZED",
message: "User must be authenticated to cancel listings",
};
}
try {
// Get wallet properties with defaults
const walletProps = getWalletProps(user);
// Get user's private keys
const paymentPk = walletProps.paymentKey
? PrivateKey.fromWif(walletProps.paymentKey)
: undefined;
const ordPk = walletProps.ordinalKey
? PrivateKey.fromWif(walletProps.ordinalKey)
: undefined;
if (!ordPk) {
throw {
code: "MISSING_KEYS",
message: "Ordinal private key is required for cancelling listings",
};
}
// Get payment UTXOs
const paymentUtxos = walletProps.utxos;
if (!paymentUtxos.length) {
throw {
code: "INSUFFICIENT_FUNDS",
message: "No UTXOs available for transaction fees",
};
}
// Create listing UTXO from the listing data
const listingUtxo = {
txid: listing.txid,
vout: listing.vout,
satoshis: listing.assetType === AssetType.Ordinals ? listing.priceSats : 1, // Token UTXOs always contain 1 satoshi
script: listing.ordAddress,
};
let tx;
let _spentOutpoints;
if (listing.assetType === AssetType.Ordinals) {
// Cancel ordinal/NFT listing
const config = {
utxos: paymentUtxos,
paymentPk,
ordPk,
listingUtxos: [listingUtxo],
changeAddress: walletProps.fundingAddress,
satsPerKb: Math.floor(feeRate * 1000),
additionalPayments: [],
};
const result = await cancelOrdListings(config);
tx = result.tx;
_spentOutpoints = result.spentOutpoints;
}
else {
// Cancel token listing (BSV20/BSV21)
if (!listing.tokenId || !listing.tokenAmount) {
throw {
code: "INVALID_INPUT",
message: "Token ID and amount are required for token listing cancellation",
};
}
// Convert AssetType to TokenType
let protocol;
if (listing.assetType === AssetType.BSV20) {
protocol = "BSV20";
}
else if (listing.assetType === AssetType.BSV21) {
protocol = "BSV21";
}
else {
throw {
code: "INVALID_INPUT",
message: "Invalid token type for cancellation",
};
}
const tokenListingUtxo = {
...listingUtxo,
satoshis: 1, // Token UTXOs always contain exactly 1 satoshi
amt: listing.tokenAmount,
id: listing.tokenId,
};
const config = {
protocol,
tokenID: listing.tokenId,
utxos: paymentUtxos,
paymentPk,
ordPk,
listingUtxos: [tokenListingUtxo],
ordAddress: walletProps.ordinalAddress,
changeAddress: walletProps.fundingAddress,
satsPerKb: Math.floor(feeRate * 1000),
additionalPayments: [],
};
const result = await cancelOrdTokenListings(config);
tx = result.tx;
_spentOutpoints = result.spentOutpoints;
}
// Broadcast transaction
const broadcastResult = await broadcastTransaction(tx);
if (!broadcastResult.success) {
throw {
code: "BROADCAST_FAILED",
message: broadcastResult.error ||
"Failed to broadcast cancellation transaction",
};
}
if (!broadcastResult.txid) {
throw {
code: "BROADCAST_FAILED",
message: "Transaction broadcast succeeded but no transaction ID returned",
};
}
const result = {
txid: broadcastResult.txid,
fee: tx.inputs.reduce((sum, input) => {
const sourceOutput = input.sourceTransaction?.outputs?.[input.sourceOutputIndex];
return sum + (sourceOutput?.satoshis || 0);
}, 0) -
tx.outputs.reduce((sum, output) => sum + (output.satoshis || 0), 0),
cancelledPrice: listing.priceSats,
assetType: listing.assetType,
tokenAmount: listing.tokenAmount,
};
return result;
}
catch (error) {
if (error.code) {
throw error;
}
throw {
code: "TRANSACTION_FAILED",
message: "Failed to cancel listing",
details: error,
};
}
},
onSuccess: (data) => {
onSuccess?.({ success: true, txid: data.txid });
},
onError: (error) => {
console.error("Cancel listing failed:", error);
onError?.(error);
},
});
return {
// Mutation functions
cancelListing: cancelMutation.mutate,
cancelListingAsync: cancelMutation.mutateAsync,
// State
isLoading: cancelMutation.isPending,
isError: cancelMutation.isError,
isSuccess: cancelMutation.isSuccess,
error: cancelMutation.error,
data: cancelMutation.data,
// Reset
reset: cancelMutation.reset,
};
}