UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

170 lines (169 loc) 7.58 kB
import { PrivateKey, } from "@bsv/sdk"; // useBuyListing Hook - Purchase marketplace listings import { useMutation } from "@tanstack/react-query"; import { TokenType, purchaseOrdListing, purchaseOrdTokenListing, } 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 useBuyListing(options = {}) { const { app: _app = "bigblocks", feeRate = 0.5, onSuccess, onError, } = options; const { user } = useBitcoinAuth(); const buyMutation = useMutation({ mutationFn: async (listing) => { if (!user) { throw { code: "UNAUTHORIZED", message: "User must be authenticated to buy 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 (!paymentPk) { throw { code: "MISSING_KEYS", message: "Payment private key is required for purchasing", }; } // Get payment UTXOs const paymentUtxos = walletProps.utxos; if (!paymentUtxos.length) { throw { code: "INSUFFICIENT_FUNDS", message: "No UTXOs available for payment", }; } // Check available balance const availableBalance = paymentUtxos.reduce((sum, utxo) => sum + utxo.satoshis, 0); const estimatedFee = 500; // Basic fee estimate const totalCost = listing.priceSats + estimatedFee; if (totalCost > availableBalance) { throw { code: "INSUFFICIENT_FUNDS", message: `Insufficient funds. Need ${totalCost} satoshis, have ${availableBalance}`, }; } let tx; let _spentOutpoints; if (listing.assetType === AssetType.Ordinals) { // Buy ordinal/NFT listing - need to convert to proper format const config = { utxos: paymentUtxos, listing: { payout: listing.payAddress, // Assuming payAddress is the payout script listingUtxo: { txid: listing.txid, vout: listing.vout, satoshis: listing.priceSats, script: listing.ordAddress, // Assuming ordAddress contains script }, }, paymentPk, ordAddress: walletProps.ordinalAddress, changeAddress: walletProps.fundingAddress, satsPerKb: Math.floor(feeRate * 1000), }; const result = await purchaseOrdListing(config); tx = result.tx; _spentOutpoints = result.spentOutpoints; } else { // Buy token listing (BSV20/BSV21) if (!listing.tokenId || !listing.tokenAmount) { throw { code: "INVALID_INPUT", message: "Token ID and amount are required for token purchases", }; } const config = { protocol: listing.assetType === AssetType.BSV20 ? TokenType.BSV20 : TokenType.BSV21, tokenID: listing.tokenId, utxos: paymentUtxos, paymentPk, listingUtxo: { txid: listing.txid, vout: listing.vout, satoshis: 1, // Token UTXOs always contain exactly 1 satoshi in 1Sat Ordinals protocol script: listing.ordAddress, amt: listing.tokenAmount, id: listing.tokenId, }, ordAddress: walletProps.ordinalAddress, changeAddress: walletProps.fundingAddress, satsPerKb: Math.floor(feeRate * 1000), }; const result = await purchaseOrdTokenListing(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 purchase 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), price: listing.priceSats, assetType: listing.assetType, tokenAmount: listing.tokenAmount, }; return result; } catch (error) { if (error.code) { throw error; } throw { code: "TRANSACTION_FAILED", message: "Failed to purchase listing", details: error, }; } }, onSuccess: (data) => { onSuccess?.({ success: true, txid: data.txid }); }, onError: (error) => { console.error("Buy listing failed:", error); onError?.(error); }, }); return { // Mutation functions buyListing: buyMutation.mutate, buyListingAsync: buyMutation.mutateAsync, // State isLoading: buyMutation.isPending, isError: buyMutation.isError, isSuccess: buyMutation.isSuccess, error: buyMutation.error, data: buyMutation.data, // Reset reset: buyMutation.reset, }; }