{
  "name": "wallet-hooks-usesendbsv",
  "type": "registry:hook",
  "dependencies": [
    "@bsv/sdk",
    "@tanstack/react-query"
  ],
  "devDependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/wallet/hooks/useSendBSV.ts",
      "type": "registry:hook",
      "content": "import { P2PKH, PrivateKey, Script, Transaction, Utils } from \"@bsv/sdk\";\n// useSendBSV Hook - Send BSV transactions\nimport { useMutation } from \"@tanstack/react-query\";\nimport { useBitcoinAuth } from \"../../../hooks/useBitcoinAuth.js\";\nimport { useBlockchainService } from \"../../../hooks/useBlockchainService.js\";\nimport type { MarketError } from \"../../market/types/market.js\";\nimport type { WalletUserExtension } from \"../types/extended-user.js\";\nimport type { SendResult } from \"../types/wallet.js\";\n\nexport interface UseSendBSVOptions {\n\tfeeRate?: number;\n\tapp?: string;\n\twalletData?: WalletUserExtension;\n\tonSuccess?: (result: SendResult) => void;\n\tonError?: (error: MarketError) => void;\n}\n\nexport interface SendBSVParams {\n\ttoAddress: string;\n\tsatoshis: number;\n\tmessage?: string;\n}\n\nexport function useSendBSV(options: UseSendBSVOptions = {}) {\n\tconst {\n\t\tfeeRate = 0.5,\n\t\tapp: _app = \"bigblocks\",\n\t\twalletData,\n\t\tonSuccess,\n\t\tonError,\n\t} = options;\n\tconst { user } = useBitcoinAuth();\n\tconst blockchainService = useBlockchainService();\n\n\t// Use provided wallet data or fall back to auth user (which may not have wallet props)\n\tconst currentWalletData = walletData || (user as WalletUserExtension);\n\n\tconst sendMutation = useMutation<SendResult, MarketError, SendBSVParams>({\n\t\tmutationFn: async ({ toAddress, satoshis, message }) => {\n\t\t\tif (!user || !currentWalletData?.paymentKey) {\n\t\t\t\tthrow {\n\t\t\t\t\tcode: \"UNAUTHORIZED\",\n\t\t\t\t\tmessage: \"User must be authenticated with wallet data to send BSV\",\n\t\t\t\t} as MarketError;\n\t\t\t}\n\n\t\t\t// Validate inputs\n\t\t\tif (!toAddress || toAddress.length < 26) {\n\t\t\t\tthrow {\n\t\t\t\t\tcode: \"INVALID_INPUT\",\n\t\t\t\t\tmessage: \"Invalid recipient address\",\n\t\t\t\t} as MarketError;\n\t\t\t}\n\n\t\t\tif (satoshis <= 0) {\n\t\t\t\tthrow {\n\t\t\t\t\tcode: \"INVALID_INPUT\",\n\t\t\t\t\tmessage: \"Amount must be greater than 0\",\n\t\t\t\t} as MarketError;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Get user's private key\n\t\t\t\tconst privateKey = currentWalletData.paymentKey\n\t\t\t\t\t? PrivateKey.fromWif(currentWalletData.paymentKey)\n\t\t\t\t\t: undefined;\n\t\t\t\tif (!privateKey) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"MISSING_KEYS\",\n\t\t\t\t\t\tmessage: \"Payment private key is required\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\t// Get available UTXOs - prefer fresh data from blockchain service\n\t\t\t\tlet utxos = currentWalletData.utxos || [];\n\n\t\t\t\t// If no cached UTXOs, try to fetch them\n\t\t\t\tif (!utxos.length && currentWalletData.fundingAddress) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst fetchedUtxos = await blockchainService.getUTXOs(\n\t\t\t\t\t\t\tcurrentWalletData.fundingAddress,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tutxos = fetchedUtxos;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t\"Failed to fetch UTXOs from blockchain service:\",\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!utxos.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\",\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\t// Calculate total available\n\t\t\t\tconst totalAvailable = utxos.reduce(\n\t\t\t\t\t(sum: number, utxo: { satoshis: number }) => sum + utxo.satoshis,\n\t\t\t\t\t0,\n\t\t\t\t);\n\n\t\t\t\t// Estimate transaction size and fee\n\t\t\t\tconst estimatedSize = 148 + 34 + 10; // input + output + overhead (simplified)\n\t\t\t\tconst fee = Math.ceil(estimatedSize * feeRate);\n\t\t\t\tconst totalNeeded = satoshis + fee;\n\n\t\t\t\tif (totalNeeded > totalAvailable) {\n\t\t\t\t\tthrow {\n\t\t\t\t\t\tcode: \"INSUFFICIENT_FUNDS\",\n\t\t\t\t\t\tmessage: `Insufficient funds. Need ${totalNeeded} satoshis, have ${totalAvailable}`,\n\t\t\t\t\t} as MarketError;\n\t\t\t\t}\n\n\t\t\t\t// Create transaction using BSV SDK\n\t\t\t\tconst tx = new Transaction();\n\t\t\t\tconst p2pkh = new P2PKH();\n\n\t\t\t\t// Add recipient output\n\t\t\t\ttx.addOutput({\n\t\t\t\t\tlockingScript: p2pkh.lock(toAddress),\n\t\t\t\t\tsatoshis: satoshis,\n\t\t\t\t});\n\n\t\t\t\t// Select UTXOs and add inputs\n\t\t\t\tlet inputTotal = 0;\n\t\t\t\tconst usedUtxos = [];\n\n\t\t\t\tfor (const utxo of utxos) {\n\t\t\t\t\tif (inputTotal >= totalNeeded) break;\n\n\t\t\t\t\t// Note: In a real implementation, you would need to fetch the full transaction\n\t\t\t\t\t// For now, we create a minimal transaction reference for the BSV SDK\n\t\t\t\t\t// This is a common pattern where the source transaction details are fetched separately\n\t\t\t\t\t// Create minimal source transaction for BSV SDK\n\t\t\t\t\t// Note: The SDK expects a Transaction object, but we can provide a minimal implementation\n\t\t\t\t\tconst sourceTransaction = Object.create(Transaction.prototype);\n\t\t\t\t\tsourceTransaction.id = () => utxo.txid;\n\t\t\t\t\tsourceTransaction.outputs = [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tsatoshis: utxo.satoshis,\n\t\t\t\t\t\t\tlockingScript:\n\t\t\t\t\t\t\t\tutxo.script || new P2PKH().lock(privateKey.toAddress()),\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\n\t\t\t\t\ttx.addInput({\n\t\t\t\t\t\tsourceTransaction,\n\t\t\t\t\t\tsourceOutputIndex: utxo.vout,\n\t\t\t\t\t\tunlockingScriptTemplate: p2pkh.unlock(privateKey),\n\t\t\t\t\t});\n\n\t\t\t\t\tinputTotal += utxo.satoshis;\n\t\t\t\t\tusedUtxos.push(utxo);\n\t\t\t\t}\n\n\t\t\t\t// Add change output if needed\n\t\t\t\tconst change = inputTotal - satoshis - fee;\n\t\t\t\tif (change > 546) {\n\t\t\t\t\t// Dust limit\n\t\t\t\t\ttx.addOutput({\n\t\t\t\t\t\tlockingScript: p2pkh.lock(\n\t\t\t\t\t\t\tcurrentWalletData.fundingAddress || user.address,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tsatoshis: change,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Add OP_RETURN message if provided\n\t\t\t\tif (message) {\n\t\t\t\t\tconst messageBytes = Utils.toArray(message, \"utf8\");\n\t\t\t\t\tconst opReturnScript = new Script([\n\t\t\t\t\t\t{ op: 106 }, // OP_RETURN\n\t\t\t\t\t\t{ op: 0, data: messageBytes },\n\t\t\t\t\t]);\n\n\t\t\t\t\ttx.addOutput({\n\t\t\t\t\t\tlockingScript: opReturnScript,\n\t\t\t\t\t\tsatoshis: 0,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Sign transaction\n\t\t\t\tawait tx.sign();\n\n\t\t\t\t// Broadcast transaction using blockchain service\n\t\t\t\tconst broadcastResult =\n\t\t\t\t\tawait blockchainService.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: SendResult = {\n\t\t\t\t\ttxid: broadcastResult.txid ?? \"\",\n\t\t\t\t\tfee: fee,\n\t\t\t\t\tamount: satoshis,\n\t\t\t\t\trecipient: toAddress,\n\t\t\t\t\tmessage: message,\n\t\t\t\t\tsize: tx.toHex().length / 2, // Convert hex length to bytes\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 or send 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?.(data);\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"Send BSV failed:\", error);\n\t\t\tonError?.(error);\n\t\t},\n\t});\n\n\t// Calculate estimated fee for current inputs\n\tconst estimatedFee = currentWalletData?.utxos?.length\n\t\t? Math.ceil((148 + 34 + 10) * feeRate) // Basic estimation\n\t\t: 500; // Default fallback\n\n\treturn {\n\t\t// Mutation functions\n\t\tsendBSV: sendMutation.mutate,\n\t\tsendBSVAsync: sendMutation.mutateAsync,\n\n\t\t// State\n\t\tisLoading: sendMutation.isPending,\n\t\tisError: sendMutation.isError,\n\t\tisSuccess: sendMutation.isSuccess,\n\t\terror: sendMutation.error,\n\t\tdata: sendMutation.data,\n\t\testimatedFee,\n\n\t\t// Reset\n\t\treset: sendMutation.reset,\n\t};\n}\n",
      "target": "<%- config.aliases.hooks %>/usesendbsv.ts"
    }
  ]
}