{
  "name": "market-hooks-usecreatelisting",
  "type": "registry:hook",
  "dependencies": [
    "@bsv/sdk",
    "@tanstack/react-query"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/market/hooks/useCreateListing.ts",
      "type": "registry:hook",
      "content": "import { PrivateKey } from \"@bsv/sdk\";\n// useCreateListing Hook - Create marketplace listings\nimport { useMutation } from \"@tanstack/react-query\";\nimport {\n\tTokenType,\n\tcreateOrdListings,\n\tcreateOrdTokenListings,\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 {\n\tCreateListingConfig,\n\tCreateTokenListingConfig,\n\tNewListing,\n\tNewTokenListing,\n} from \"../types/listings.js\";\nimport type { BroadcastResult, MarketError } from \"../types/market.js\";\n\ninterface UseCreateListingOptions {\n\tapp?: string;\n\tfeeRate?: number;\n\tonSuccess?: (result: BroadcastResult) => void;\n\tonError?: (error: MarketError) => void;\n}\n\nexport interface CreateListingResult {\n\ttxid: string;\n\tfee: number;\n\tsize: number;\n\tlistings: number;\n}\n\nexport interface CreateTokenListingParams {\n\tprotocol: \"bsv20\" | \"bsv21\";\n\ttokenID: string;\n\tdecimals: number;\n}\n\nexport function useCreateListing(options: UseCreateListingOptions = {}) {\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\t// Create ordinal/NFT listings\n\tconst createListingMutation = useMutation<\n\t\tCreateListingResult,\n\t\tMarketError,\n\t\tNewListing[]\n\t>({\n\t\tmutationFn: async (listings: NewListing[]) => {\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 create listings\",\n\t\t\t\t} as MarketError;\n\t\t\t}\n\n\t\t\tif (!listings.length) {\n\t\t\t\tthrow {\n\t\t\t\t\tcode: \"INVALID_INPUT\",\n\t\t\t\t\tmessage: \"At least one listing is required\",\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 listing\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\t// Get payment UTXOs for fees\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\tconst config: CreateListingConfig = {\n\t\t\t\t\tutxos: paymentUtxos,\n\t\t\t\t\tlistings,\n\t\t\t\t\tpaymentPk,\n\t\t\t\t\tordPk,\n\t\t\t\t\tchangeAddress: walletProps.fundingAddress,\n\t\t\t\t\tsatsPerKb: Math.floor(feeRate * 1000),\n\t\t\t\t};\n\n\t\t\t\tconst { tx, spentOutpoints: _spentOutpoints } =\n\t\t\t\t\tawait createOrdListings(config);\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: broadcastResult.error || \"Failed to broadcast transaction\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\tconst result: CreateListingResult = {\n\t\t\t\t\ttxid: broadcastResult.txid ?? \"\",\n\t\t\t\t\tfee:\n\t\t\t\t\t\ttx.inputs.reduce(\n\t\t\t\t\t\t\t(sum, input) =>\n\t\t\t\t\t\t\t\tsum +\n\t\t\t\t\t\t\t\t(input.sourceTransaction?.outputs[input.sourceOutputIndex]\n\t\t\t\t\t\t\t\t\t?.satoshis || 0),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t) -\n\t\t\t\t\t\ttx.outputs.reduce((sum, output) => sum + (output.satoshis || 0), 0),\n\t\t\t\t\tsize: tx.toBinary().length,\n\t\t\t\t\tlistings: listings.length,\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 create listing transaction\",\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(\"Create listing failed:\", error);\n\t\t\tonError?.(error);\n\t\t},\n\t});\n\n\t// Create token listings (BSV20/BSV21)\n\tconst createTokenListingMutation = useMutation<\n\t\tCreateListingResult,\n\t\tMarketError,\n\t\t{ listings: NewTokenListing[]; params: CreateTokenListingParams }\n\t>({\n\t\tmutationFn: async ({ listings, params }) => {\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 create token listings\",\n\t\t\t\t} as MarketError;\n\t\t\t}\n\n\t\t\tif (!listings.length) {\n\t\t\t\tthrow {\n\t\t\t\t\tcode: \"INVALID_INPUT\",\n\t\t\t\t\tmessage: \"At least one token listing is required\",\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 token listing\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\t// Get payment UTXOs and token UTXOs\n\t\t\t\tconst paymentUtxos = walletProps.utxos;\n\t\t\t\tconst tokenUtxos = walletProps.tokenUtxos.filter(\n\t\t\t\t\t(utxo: {\n\t\t\t\t\t\tid: string;\n\t\t\t\t\t\ttxid: string;\n\t\t\t\t\t\tvout: number;\n\t\t\t\t\t\tsatoshis: number;\n\t\t\t\t\t\tamount: string;\n\t\t\t\t\t}) => utxo.id === params.tokenID,\n\t\t\t\t);\n\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\tif (!tokenUtxos.length) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"INSUFFICIENT_TOKENS\",\n\t\t\t\t\t\tmessage: \"No tokens available for listing\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\tconst config: CreateTokenListingConfig = {\n\t\t\t\t\tutxos: paymentUtxos,\n\t\t\t\t\tlistings,\n\t\t\t\t\tpaymentPk,\n\t\t\t\t\tordPk,\n\t\t\t\t\tchangeAddress: walletProps.fundingAddress,\n\t\t\t\t\ttokenChangeAddress: walletProps.ordinalAddress,\n\t\t\t\t\tsatsPerKb: Math.floor(feeRate * 1000),\n\t\t\t\t\tprotocol:\n\t\t\t\t\t\tparams.protocol === \"bsv20\" ? TokenType.BSV20 : TokenType.BSV21,\n\t\t\t\t\ttokenID: params.tokenID,\n\t\t\t\t\tdecimals: params.decimals,\n\t\t\t\t\tinputTokens: tokenUtxos,\n\t\t\t\t};\n\n\t\t\t\tconst { tx, spentOutpoints: _spentOutpoints2 } =\n\t\t\t\t\tawait createOrdTokenListings(config);\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: broadcastResult.error || \"Failed to broadcast transaction\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\tconst result: CreateListingResult = {\n\t\t\t\t\ttxid: broadcastResult.txid || \"\",\n\t\t\t\t\tfee:\n\t\t\t\t\t\ttx.inputs.reduce(\n\t\t\t\t\t\t\t(sum, input) =>\n\t\t\t\t\t\t\t\tsum +\n\t\t\t\t\t\t\t\t(input.sourceTransaction?.outputs[input.sourceOutputIndex]\n\t\t\t\t\t\t\t\t\t?.satoshis || 0),\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t) -\n\t\t\t\t\t\ttx.outputs.reduce((sum, output) => sum + (output.satoshis || 0), 0),\n\t\t\t\t\tsize: tx.toBinary().length,\n\t\t\t\t\tlistings: listings.length,\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 create token listing transaction\",\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(\"Create token listing failed:\", error);\n\t\t\tonError?.(error);\n\t\t},\n\t});\n\n\treturn {\n\t\t// Mutations\n\t\tcreateListing: createListingMutation.mutate,\n\t\tcreateListingAsync: createListingMutation.mutateAsync,\n\t\tcreateTokenListing: (\n\t\t\tlistings: NewTokenListing[],\n\t\t\tparams: CreateTokenListingParams,\n\t\t) => {\n\t\t\tcreateTokenListingMutation.mutate({ listings, params });\n\t\t},\n\t\tcreateTokenListingAsync: (\n\t\t\tlistings: NewTokenListing[],\n\t\t\tparams: CreateTokenListingParams,\n\t\t) => {\n\t\t\treturn createTokenListingMutation.mutateAsync({ listings, params });\n\t\t},\n\n\t\t// State\n\t\tisLoading:\n\t\t\tcreateListingMutation.isPending || createTokenListingMutation.isPending,\n\t\tisError:\n\t\t\tcreateListingMutation.isError || createTokenListingMutation.isError,\n\t\tisSuccess:\n\t\t\tcreateListingMutation.isSuccess || createTokenListingMutation.isSuccess,\n\t\terror: createListingMutation.error || createTokenListingMutation.error,\n\n\t\t// Reset\n\t\treset: () => {\n\t\t\tcreateListingMutation.reset();\n\t\t\tcreateTokenListingMutation.reset();\n\t\t},\n\t};\n}\n",
      "target": "<%- config.aliases.hooks %>/usecreatelisting.ts"
    }
  ]
}