UNPKG

bigblocks

Version:

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

17 lines 7.93 kB
{ "name": "market-hooks-usecancellisting", "type": "registry:hook", "dependencies": [ "@tanstack/react-query" ], "devDependencies": [], "registryDependencies": [], "files": [ { "path": "components/market/hooks/useCancelListing.ts", "type": "registry:hook", "content": "import {\n\tPrivateKey,\n\ttype Transaction,\n\ttype TransactionInput,\n\ttype TransactionOutput,\n} from \"@bsv/sdk\";\n// useCancelListing Hook - Cancel marketplace listings\nimport { useMutation } from \"@tanstack/react-query\";\nimport {\n\ttype CancelOrdListingsConfig,\n\ttype CancelOrdTokenListingsConfig,\n\ttype TokenType,\n\ttype TokenUtxo,\n\tcancelOrdListings,\n\tcancelOrdTokenListings,\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 UseCancelListingOptions {\n\tapp?: string;\n\tfeeRate?: number;\n\tonSuccess?: (result: BroadcastResult) => void;\n\tonError?: (error: MarketError) => void;\n}\n\nexport interface CancelListingResult {\n\ttxid: string;\n\tfee: number;\n\tcancelledPrice: number;\n\tassetType: AssetType;\n\ttokenAmount?: string;\n}\n\nexport function useCancelListing(options: UseCancelListingOptions = {}) {\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 cancelMutation = useMutation<\n\t\tCancelListingResult,\n\t\tMarketError,\n\t\tMarketListing\n\t>({\n\t\tmutationFn: async (listing: MarketListing) => {\n\t\t\tif (!user) {\n\t\t\t\tthrow {\n\t\t\t\t\tcode: \"UNAUTHORIZED\",\n\t\t\t\t\tmessage: \"User must be authenticated to cancel listings\",\n\t\t\t\t} as MarketError;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Get wallet properties with defaults\n\t\t\t\tconst walletProps = getWalletProps(user as WalletUser);\n\n\t\t\t\t// Get user's private keys\n\t\t\t\tconst paymentPk = walletProps.paymentKey\n\t\t\t\t\t? PrivateKey.fromWif(walletProps.paymentKey)\n\t\t\t\t\t: undefined;\n\t\t\t\tconst ordPk = walletProps.ordinalKey\n\t\t\t\t\t? PrivateKey.fromWif(walletProps.ordinalKey)\n\t\t\t\t\t: undefined;\n\n\t\t\t\tif (!ordPk) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"MISSING_KEYS\",\n\t\t\t\t\t\tmessage: \"Ordinal private key is required for cancelling listings\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\t// Get payment UTXOs\n\t\t\t\tconst paymentUtxos = walletProps.utxos;\n\t\t\t\tif (!paymentUtxos.length) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"INSUFFICIENT_FUNDS\",\n\t\t\t\t\t\tmessage: \"No UTXOs available for transaction fees\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\t// Create listing UTXO from the listing data\n\t\t\t\tconst listingUtxo = {\n\t\t\t\t\ttxid: listing.txid,\n\t\t\t\t\tvout: listing.vout,\n\t\t\t\t\tsatoshis:\n\t\t\t\t\t\tlisting.assetType === AssetType.Ordinals ? listing.priceSats : 1, // Token UTXOs always contain 1 satoshi\n\t\t\t\t\tscript: listing.ordAddress,\n\t\t\t\t};\n\n\t\t\t\tlet tx: Transaction;\n\t\t\t\tlet _spentOutpoints: string[];\n\n\t\t\t\tif (listing.assetType === AssetType.Ordinals) {\n\t\t\t\t\t// Cancel ordinal/NFT listing\n\t\t\t\t\tconst config: CancelOrdListingsConfig = {\n\t\t\t\t\t\tutxos: paymentUtxos,\n\t\t\t\t\t\tpaymentPk,\n\t\t\t\t\t\tordPk,\n\t\t\t\t\t\tlistingUtxos: [listingUtxo],\n\t\t\t\t\t\tchangeAddress: walletProps.fundingAddress,\n\t\t\t\t\t\tsatsPerKb: Math.floor(feeRate * 1000),\n\t\t\t\t\t\tadditionalPayments: [],\n\t\t\t\t\t};\n\n\t\t\t\t\tconst result = await cancelOrdListings(config);\n\t\t\t\t\ttx = result.tx;\n\t\t\t\t\t_spentOutpoints = result.spentOutpoints;\n\t\t\t\t} else {\n\t\t\t\t\t// Cancel token listing (BSV20/BSV21)\n\t\t\t\t\tif (!listing.tokenId || !listing.tokenAmount) {\n\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\tcode: \"INVALID_INPUT\",\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t\"Token ID and amount are required for token listing cancellation\",\n\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Convert AssetType to TokenType\n\t\t\t\t\tlet protocol: TokenType;\n\t\t\t\t\tif (listing.assetType === AssetType.BSV20) {\n\t\t\t\t\t\tprotocol = \"BSV20\" as TokenType;\n\t\t\t\t\t} else if (listing.assetType === AssetType.BSV21) {\n\t\t\t\t\t\tprotocol = \"BSV21\" as TokenType;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthrow {\n\t\t\t\t\t\t\tcode: \"INVALID_INPUT\",\n\t\t\t\t\t\t\tmessage: \"Invalid token type for cancellation\",\n\t\t\t\t\t\t} as MarketError;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst tokenListingUtxo: TokenUtxo = {\n\t\t\t\t\t\t...listingUtxo,\n\t\t\t\t\t\tsatoshis: 1 as const, // Token UTXOs always contain exactly 1 satoshi\n\t\t\t\t\t\tamt: listing.tokenAmount,\n\t\t\t\t\t\tid: listing.tokenId,\n\t\t\t\t\t};\n\n\t\t\t\t\tconst config: CancelOrdTokenListingsConfig = {\n\t\t\t\t\t\tprotocol,\n\t\t\t\t\t\ttokenID: listing.tokenId,\n\t\t\t\t\t\tutxos: paymentUtxos,\n\t\t\t\t\t\tpaymentPk,\n\t\t\t\t\t\tordPk,\n\t\t\t\t\t\tlistingUtxos: [tokenListingUtxo],\n\t\t\t\t\t\tordAddress: walletProps.ordinalAddress,\n\t\t\t\t\t\tchangeAddress: walletProps.fundingAddress,\n\t\t\t\t\t\tsatsPerKb: Math.floor(feeRate * 1000),\n\t\t\t\t\t\tadditionalPayments: [],\n\t\t\t\t\t};\n\n\t\t\t\t\tconst result = await cancelOrdTokenListings(config);\n\t\t\t\t\ttx = result.tx;\n\t\t\t\t\t_spentOutpoints = result.spentOutpoints;\n\t\t\t\t}\n\n\t\t\t\t// Broadcast transaction\n\t\t\t\tconst broadcastResult = await broadcastTransaction(tx);\n\n\t\t\t\tif (!broadcastResult.success) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"BROADCAST_FAILED\",\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\tbroadcastResult.error ||\n\t\t\t\t\t\t\t\"Failed to broadcast cancellation transaction\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\tif (!broadcastResult.txid) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"BROADCAST_FAILED\",\n\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\"Transaction broadcast succeeded but no transaction ID returned\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\tconst result: CancelListingResult = {\n\t\t\t\t\ttxid: broadcastResult.txid,\n\t\t\t\t\tfee:\n\t\t\t\t\t\ttx.inputs.reduce((sum: number, input: TransactionInput) => {\n\t\t\t\t\t\t\tconst sourceOutput =\n\t\t\t\t\t\t\t\tinput.sourceTransaction?.outputs?.[input.sourceOutputIndex];\n\t\t\t\t\t\t\treturn sum + (sourceOutput?.satoshis || 0);\n\t\t\t\t\t\t}, 0) -\n\t\t\t\t\t\ttx.outputs.reduce(\n\t\t\t\t\t\t\t(sum: number, output: TransactionOutput) =>\n\t\t\t\t\t\t\t\tsum + (output.satoshis || 0),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t),\n\t\t\t\t\tcancelledPrice: listing.priceSats,\n\t\t\t\t\tassetType: listing.assetType,\n\t\t\t\t\ttokenAmount: listing.tokenAmount,\n\t\t\t\t};\n\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tif ((error as MarketError).code) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\n\t\t\t\tthrow {\n\t\t\t\t\tcode: \"TRANSACTION_FAILED\",\n\t\t\t\t\tmessage: \"Failed to cancel listing\",\n\t\t\t\t\tdetails: error,\n\t\t\t\t} as MarketError;\n\t\t\t}\n\t\t},\n\t\tonSuccess: (data) => {\n\t\t\tonSuccess?.({ success: true, txid: data.txid });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"Cancel listing failed:\", error);\n\t\t\tonError?.(error);\n\t\t},\n\t});\n\n\treturn {\n\t\t// Mutation functions\n\t\tcancelListing: cancelMutation.mutate,\n\t\tcancelListingAsync: cancelMutation.mutateAsync,\n\n\t\t// State\n\t\tisLoading: cancelMutation.isPending,\n\t\tisError: cancelMutation.isError,\n\t\tisSuccess: cancelMutation.isSuccess,\n\t\terror: cancelMutation.error,\n\t\tdata: cancelMutation.data,\n\n\t\t// Reset\n\t\treset: cancelMutation.reset,\n\t};\n}\n", "target": "<%- config.aliases.hooks %>/usecancellisting.ts" } ] }