bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
17 lines • 8.28 kB
JSON
{
"name": "market-hooks-usebuylisting",
"type": "registry:hook",
"dependencies": [
"@tanstack/react-query"
],
"devDependencies": [],
"registryDependencies": [],
"files": [
{
"path": "components/market/hooks/useBuyListing.ts",
"type": "registry:hook",
"content": "import {\n\tPrivateKey,\n\ttype Transaction,\n\ttype TransactionInput,\n\ttype TransactionOutput,\n} from \"@bsv/sdk\";\n// useBuyListing Hook - Purchase marketplace listings\nimport { useMutation } from \"@tanstack/react-query\";\nimport {\n\ttype PurchaseOrdListingConfig,\n\ttype PurchaseOrdTokenListingConfig,\n\tTokenType,\n\tpurchaseOrdListing,\n\tpurchaseOrdTokenListing,\n} from \"js-1sat-ord\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport { broadcastTransaction } from \"../../../lib/broadcast\";\nimport {\n\ttype WalletUser,\n\tgetWalletProps,\n} from \"../../wallet/types/extended-user.js\";\nimport type { MarketListing } from \"../types/listings.js\";\nimport {\n\tAssetType,\n\ttype BroadcastResult,\n\ttype MarketError,\n} from \"../types/market.js\";\n\ninterface UseBuyListingOptions {\n\tapp?: string;\n\tfeeRate?: number;\n\tonSuccess?: (result: BroadcastResult) => void;\n\tonError?: (error: MarketError) => void;\n}\n\nexport interface BuyListingResult {\n\ttxid: string;\n\tfee: number;\n\tprice: number;\n\tassetType: AssetType;\n\ttokenAmount?: string;\n}\n\nexport function useBuyListing(options: UseBuyListingOptions = {}) {\n\tconst {\n\t\tapp: _app = \"bigblocks\",\n\t\tfeeRate = 0.5,\n\t\tonSuccess,\n\t\tonError,\n\t} = options;\n\tconst { user } = useBitcoinAuth();\n\n\tconst buyMutation = useMutation<BuyListingResult, MarketError, MarketListing>(\n\t\t{\n\t\t\tmutationFn: async (listing: MarketListing) => {\n\t\t\t\tif (!user) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"UNAUTHORIZED\",\n\t\t\t\t\t\tmessage: \"User must be authenticated to buy listings\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\t// Get wallet properties with defaults\n\t\t\t\t\tconst walletProps = getWalletProps(user as WalletUser);\n\n\t\t\t\t\t// Get user's private keys\n\t\t\t\t\tconst paymentPk = walletProps.paymentKey\n\t\t\t\t\t\t? PrivateKey.fromWif(walletProps.paymentKey)\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst _ordPk = walletProps.ordinalKey\n\t\t\t\t\t\t? PrivateKey.fromWif(walletProps.ordinalKey)\n\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\tif (!paymentPk) {\n\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\tcode: \"MISSING_KEYS\",\n\t\t\t\t\t\t\tmessage: \"Payment private key is required for purchasing\",\n\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Get payment UTXOs\n\t\t\t\t\tconst paymentUtxos = walletProps.utxos;\n\t\t\t\t\tif (!paymentUtxos.length) {\n\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\tcode: \"INSUFFICIENT_FUNDS\",\n\t\t\t\t\t\t\tmessage: \"No UTXOs available for payment\",\n\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check available balance\n\t\t\t\t\tconst availableBalance = paymentUtxos.reduce(\n\t\t\t\t\t\t(sum: number, utxo: { satoshis: number }) => sum + utxo.satoshis,\n\t\t\t\t\t\t0,\n\t\t\t\t\t);\n\t\t\t\t\tconst estimatedFee = 500; // Basic fee estimate\n\t\t\t\t\tconst totalCost = listing.priceSats + estimatedFee;\n\n\t\t\t\t\tif (totalCost > availableBalance) {\n\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\tcode: \"INSUFFICIENT_FUNDS\",\n\t\t\t\t\t\t\tmessage: `Insufficient funds. Need ${totalCost} satoshis, have ${availableBalance}`,\n\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet tx: Transaction;\n\t\t\t\t\tlet _spentOutpoints: string[];\n\n\t\t\t\t\tif (listing.assetType === AssetType.Ordinals) {\n\t\t\t\t\t\t// Buy ordinal/NFT listing - need to convert to proper format\n\t\t\t\t\t\tconst config: PurchaseOrdListingConfig = {\n\t\t\t\t\t\t\tutxos: paymentUtxos,\n\t\t\t\t\t\t\tlisting: {\n\t\t\t\t\t\t\t\tpayout: listing.payAddress, // Assuming payAddress is the payout script\n\t\t\t\t\t\t\t\tlistingUtxo: {\n\t\t\t\t\t\t\t\t\ttxid: listing.txid,\n\t\t\t\t\t\t\t\t\tvout: listing.vout,\n\t\t\t\t\t\t\t\t\tsatoshis: listing.priceSats,\n\t\t\t\t\t\t\t\t\tscript: listing.ordAddress, // Assuming ordAddress contains script\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tpaymentPk,\n\t\t\t\t\t\t\tordAddress: walletProps.ordinalAddress,\n\t\t\t\t\t\t\tchangeAddress: walletProps.fundingAddress,\n\t\t\t\t\t\t\tsatsPerKb: Math.floor(feeRate * 1000),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst result = await purchaseOrdListing(config);\n\t\t\t\t\t\ttx = result.tx;\n\t\t\t\t\t\t_spentOutpoints = result.spentOutpoints;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Buy token listing (BSV20/BSV21)\n\t\t\t\t\t\tif (!listing.tokenId || !listing.tokenAmount) {\n\t\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\t\tcode: \"INVALID_INPUT\",\n\t\t\t\t\t\t\t\tmessage: \"Token ID and amount are required for token purchases\",\n\t\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst config: PurchaseOrdTokenListingConfig = {\n\t\t\t\t\t\t\tprotocol:\n\t\t\t\t\t\t\t\tlisting.assetType === AssetType.BSV20\n\t\t\t\t\t\t\t\t\t? TokenType.BSV20\n\t\t\t\t\t\t\t\t\t: TokenType.BSV21,\n\t\t\t\t\t\t\ttokenID: listing.tokenId,\n\t\t\t\t\t\t\tutxos: paymentUtxos,\n\t\t\t\t\t\t\tpaymentPk,\n\t\t\t\t\t\t\tlistingUtxo: {\n\t\t\t\t\t\t\t\ttxid: listing.txid,\n\t\t\t\t\t\t\t\tvout: listing.vout,\n\t\t\t\t\t\t\t\tsatoshis: 1, // Token UTXOs always contain exactly 1 satoshi in 1Sat Ordinals protocol\n\t\t\t\t\t\t\t\tscript: listing.ordAddress,\n\t\t\t\t\t\t\t\tamt: listing.tokenAmount,\n\t\t\t\t\t\t\t\tid: listing.tokenId,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tordAddress: walletProps.ordinalAddress,\n\t\t\t\t\t\t\tchangeAddress: walletProps.fundingAddress,\n\t\t\t\t\t\t\tsatsPerKb: Math.floor(feeRate * 1000),\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst result = await purchaseOrdTokenListing(config);\n\t\t\t\t\t\ttx = result.tx;\n\t\t\t\t\t\t_spentOutpoints = result.spentOutpoints;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Broadcast transaction\n\t\t\t\t\tconst broadcastResult = await broadcastTransaction(tx);\n\n\t\t\t\t\tif (!broadcastResult.success) {\n\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\tcode: \"BROADCAST_FAILED\",\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\tbroadcastResult.error ||\n\t\t\t\t\t\t\t\t\"Failed to broadcast purchase transaction\",\n\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!broadcastResult.txid) {\n\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\tcode: \"BROADCAST_FAILED\",\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\"Transaction broadcast succeeded but no transaction ID returned\",\n\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst result: BuyListingResult = {\n\t\t\t\t\t\ttxid: broadcastResult.txid,\n\t\t\t\t\t\tfee:\n\t\t\t\t\t\t\ttx.inputs.reduce((sum: number, input: TransactionInput) => {\n\t\t\t\t\t\t\t\tconst sourceOutput =\n\t\t\t\t\t\t\t\t\tinput.sourceTransaction?.outputs?.[input.sourceOutputIndex];\n\t\t\t\t\t\t\t\treturn sum + (sourceOutput?.satoshis || 0);\n\t\t\t\t\t\t\t}, 0) -\n\t\t\t\t\t\t\ttx.outputs.reduce(\n\t\t\t\t\t\t\t\t(sum: number, output: TransactionOutput) =>\n\t\t\t\t\t\t\t\t\tsum + (output.satoshis || 0),\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tprice: listing.priceSats,\n\t\t\t\t\t\tassetType: listing.assetType,\n\t\t\t\t\t\ttokenAmount: listing.tokenAmount,\n\t\t\t\t\t};\n\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif ((error as MarketError).code) {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"TRANSACTION_FAILED\",\n\t\t\t\t\t\tmessage: \"Failed to purchase listing\",\n\t\t\t\t\t\tdetails: error,\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonSuccess: (data) => {\n\t\t\t\tonSuccess?.({ success: true, txid: data.txid });\n\t\t\t},\n\t\t\tonError: (error) => {\n\t\t\t\tconsole.error(\"Buy listing failed:\", error);\n\t\t\t\tonError?.(error);\n\t\t\t},\n\t\t},\n\t);\n\n\treturn {\n\t\t// Mutation functions\n\t\tbuyListing: buyMutation.mutate,\n\t\tbuyListingAsync: buyMutation.mutateAsync,\n\n\t\t// State\n\t\tisLoading: buyMutation.isPending,\n\t\tisError: buyMutation.isError,\n\t\tisSuccess: buyMutation.isSuccess,\n\t\terror: buyMutation.error,\n\t\tdata: buyMutation.data,\n\n\t\t// Reset\n\t\treset: buyMutation.reset,\n\t};\n}\n",
"target": "<%- config.aliases.hooks %>/usebuylisting.ts"
}
]
}