{"version":3,"file":"fundPeginTransaction-BLD8FRL9.cjs","sources":["../src/tbv/core/utils/fee/constants.ts","../src/tbv/core/utils/transaction/fundPeginTransaction.ts"],"sourcesContent":["/**\n * Fee calculation constants for Bitcoin transactions.\n * Based on btc-staking-ts values, adapted for vault peg-in transactions.\n */\n\n// P2TR input size in vbytes (42 vbytes non-witness + 16 vbytes witness)\nexport const P2TR_INPUT_SIZE = 58;\n\n// P2TR output size in bytes (largest non-legacy output type)\nexport const MAX_NON_LEGACY_OUTPUT_SIZE = 43;\n\n// Base transaction overhead (version, input/output counts, locktime, SegWit marker)\nexport const TX_BUFFER_SIZE_OVERHEAD = 11;\n\n// Dust threshold: outputs below this may not be relayed\nexport const BTC_DUST_SAT = 546;\n\n/** Pre-computed BigInt dust threshold to avoid repeated conversions in hot paths */\nexport const DUST_THRESHOLD = BigInt(BTC_DUST_SAT);\n\n// Buffer for low fee rate estimation accuracy (when feeRate <= 2 sat/vbyte)\nexport const LOW_RATE_ESTIMATION_ACCURACY_BUFFER = 30;\n\n// Wallet relay fee rate threshold - different buffer fees are used based on this\nexport const WALLET_RELAY_FEE_RATE_THRESHOLD = 2;\n\n/**\n * Adds a buffer to the transaction fee calculation if the fee rate is low.\n *\n * Some wallets have a relayer fee requirement. If the fee rate is <= 2 sat/vbyte,\n * there's a risk the fee might not be sufficient for transaction relay.\n * We add a buffer to ensure the transaction can be relayed.\n *\n * @param feeRate - Fee rate in satoshis per vbyte\n * @returns Buffer amount in satoshis to add to the transaction fee\n */\nexport function rateBasedTxBufferFee(feeRate: number): number {\n  return feeRate <= WALLET_RELAY_FEE_RATE_THRESHOLD\n    ? LOW_RATE_ESTIMATION_ACCURACY_BUFFER\n    : 0;\n}\n\n/**\n * Number of always-present fixed (non-HTLC) outputs in a Pre-PegIn\n * transaction. Currently this is 1 CPFP anchor output.\n */\nexport const PEGIN_FIXED_OUTPUTS = 1;\n\n/**\n * Size of the auth-anchor `OP_RETURN` output when committed into a\n * Pre-PegIn. The output carries `OP_RETURN <PUSH32 hash>` = 34 script\n * bytes, plus 8 bytes value + 1 byte scriptLen = ~43 bytes total —\n * same as {@link MAX_NON_LEGACY_OUTPUT_SIZE}. Counted as one output\n * toward the fee-estimation output budget.\n */\nexport const PEGIN_AUTH_ANCHOR_OUTPUTS = 1;\n\n/**\n * Compute the total number of outputs (before change) in a Pre-PegIn\n * transaction.\n *\n * A Pre-PegIn tx has: N HTLC outputs (one per vault) + optional\n * auth-anchor OP_RETURN output + fixed outputs (CPFP anchor). This\n * count is used for fee estimation only — the change output is handled\n * separately by `selectUtxosForPegin` when the change amount exceeds\n * the dust threshold.\n *\n * @param vaultCount     - Number of vaults in the batch (≥1).\n * @param hasAuthAnchor  - Whether the Pre-PegIn will carry an auth-anchor\n *                          OP_RETURN output. Pass the same value the\n *                          caller will hand to `buildPrePeginPsbt`'s\n *                          `authAnchorHash` (truthy ↔ true) so the fee\n *                          budget stays in lockstep with the output set.\n * @returns Total output count before change.\n * @throws If `vaultCount` is not a positive integer.\n */\nexport function peginOutputCount(\n  vaultCount: number,\n  hasAuthAnchor: boolean,\n): number {\n  if (!Number.isInteger(vaultCount) || vaultCount < 1) {\n    throw new Error(\n      `peginOutputCount: vaultCount must be a positive integer, got ${vaultCount}`,\n    );\n  }\n  return (\n    vaultCount +\n    PEGIN_FIXED_OUTPUTS +\n    (hasAuthAnchor ? PEGIN_AUTH_ANCHOR_OUTPUTS : 0)\n  );\n}\n\n/**\n * Safety multiplier for split transaction fee validation.\n * The signed PSBT's fee rate and absolute fee must not exceed this multiple\n * of the planned values. 5x accounts for witness estimation variance while\n * catching catastrophic wallet-side overpayment.\n */\nexport const SPLIT_TX_FEE_SAFETY_MULTIPLIER = 5;\n\n/**\n * Binary-independent cap on the implied per-HTLC reserve\n * (`htlcValue - peginAmount - depositorClaimValue`): the exact identity in\n * `assertWasmPeginSizing` is WASM-vs-WASM, so this pure-JS bound is what\n * limits a doctored binary. 100,000 vbytes = Bitcoin Core's relay ceiling\n * (MAX_STANDARD_TX_WEIGHT / 4) — ~19× the protocol-max PegIn (~5,150 vbytes\n * at 99 VKs + 99 UCs), so it can never false-positive.\n */\nexport const MAX_REASONABLE_PEGIN_VBYTES = 100_000n;\n","/**\n * Transaction Funding Utility for Peg-in Transactions\n *\n * This module funds an unfunded transaction template from the SDK by adding\n * UTXO inputs and change outputs, creating a transaction ready for wallet signing.\n *\n * Transaction Flow:\n * 1. SDK buildPrePeginPsbt() → unfunded Pre-PegIn tx (0 inputs, HTLC + CPFP outputs)\n * 2. selectUtxosForPegin() → select UTXOs and calculate fees\n * 3. fundPeginTransaction() → add inputs/change, create funded transaction\n *\n * Technical Note:\n * We manually extract the vault output from SDK hex instead of using bitcoinjs-lib\n * parsing because bitcoinjs-lib cannot parse 0-input transactions (even witness format).\n */\n\nimport * as bitcoin from \"bitcoinjs-lib\";\nimport { Buffer } from \"buffer\";\n\nimport { DUST_THRESHOLD } from \"../fee/constants\";\nimport type { UTXO } from \"../utxo/selectUtxos\";\n\nexport interface FundPeginTransactionParams {\n  /** Unfunded transaction hex from SDK (0 inputs, vault + depositor claim outputs) */\n  unfundedTxHex: string;\n  /** Selected UTXOs to use as inputs */\n  selectedUTXOs: UTXO[];\n  /** Change address (from wallet) */\n  changeAddress: string;\n  /** Change amount in satoshis */\n  changeAmount: bigint;\n  /** Bitcoin network */\n  network: bitcoin.Network;\n}\n\n/** A single parsed output from the unfunded WASM transaction */\nexport interface ParsedOutput {\n  value: number;\n  script: Buffer;\n}\n\n/** Parsed data from an unfunded WASM transaction */\ninterface ParsedUnfundedTx {\n  version: number;\n  locktime: number;\n  outputs: ParsedOutput[];\n}\n\n/**\n * Parses an unfunded transaction hex from WASM.\n *\n * WASM produces witness-format transactions with 0 inputs, which bitcoinjs-lib cannot parse.\n * This function manually extracts the transaction components.\n *\n * Format: [version:4bytes][marker:0x00][flag:0x01][inputs:1byte=0x00][outputCount:1byte]\n *         [output1: value:8bytes + scriptLen:1byte + script:N bytes]\n *         [output2: ...]\n *         [locktime:4bytes]\n *\n * @param unfundedTxHex - Raw transaction hex from WASM\n * @returns Parsed transaction components\n * @throws Error if transaction structure is invalid\n */\nexport function parseUnfundedWasmTransaction(\n  unfundedTxHex: string,\n): ParsedUnfundedTx {\n  // Check if witness markers are present (0x00 0x01 after version)\n  const hasWitnessMarkers = unfundedTxHex.substring(8, 12) === \"0001\";\n  const dataOffset = hasWitnessMarkers ? 12 : 8; // Skip version (8) + optional witness markers (4)\n\n  // Parse input/output counts\n  const inputCount = parseInt(\n    unfundedTxHex.substring(dataOffset, dataOffset + 2),\n    16,\n  );\n  const outputCount = parseInt(\n    unfundedTxHex.substring(dataOffset + 2, dataOffset + 4),\n    16,\n  );\n\n  if (inputCount !== 0) {\n    throw new Error(`Expected 0 inputs from WASM, got ${inputCount}`);\n  }\n  if (outputCount === 0) {\n    throw new Error(\"Expected at least 1 output from WASM, got 0\");\n  }\n\n  // Parse version (first 4 bytes, little-endian)\n  const version = Buffer.from(unfundedTxHex.substring(0, 8), \"hex\").readUInt32LE(0);\n\n  // Parse locktime (last 4 bytes, little-endian)\n  const locktime = Buffer.from(\n    unfundedTxHex.substring(unfundedTxHex.length - 8),\n    \"hex\",\n  ).readUInt32LE(0);\n\n  // Parse all outputs sequentially\n  const outputs: ParsedOutput[] = [];\n  let pos = dataOffset + 4; // position after input/output counts\n\n  for (let i = 0; i < outputCount; i++) {\n    const valueHex = unfundedTxHex.substring(pos, pos + 16);\n    const value = Number(Buffer.from(valueHex, \"hex\").readBigUInt64LE(0));\n    pos += 16;\n\n    const scriptLen = parseInt(unfundedTxHex.substring(pos, pos + 2), 16);\n    pos += 2;\n\n    const scriptHex = unfundedTxHex.substring(pos, pos + scriptLen * 2);\n    const script = Buffer.from(scriptHex, \"hex\");\n    pos += scriptLen * 2;\n\n    outputs.push({ value, script });\n  }\n\n  return { version, locktime, outputs };\n}\n\n/**\n * Funds an unfunded peg-in transaction by adding inputs and change output.\n *\n * Takes an unfunded transaction template (0 inputs, 1 vault output) from the SDK\n * and adds UTXO inputs and a change output to create a funded transaction ready\n * for wallet signing.\n *\n * @param params - Transaction funding parameters\n * @returns Transaction hex string ready for wallet signing\n */\nexport function fundPeginTransaction(\n  params: FundPeginTransactionParams,\n): string {\n  const { unfundedTxHex, selectedUTXOs, changeAddress, changeAmount, network } =\n    params;\n\n  // Parse the unfunded transaction from WASM\n  const { version, locktime, outputs } =\n    parseUnfundedWasmTransaction(unfundedTxHex);\n\n  // Create a new transaction with the extracted data\n  const tx = new bitcoin.Transaction();\n  tx.version = version;\n  tx.locktime = locktime;\n\n  // Add inputs from selected UTXOs\n  for (const utxo of selectedUTXOs) {\n    // Bitcoin uses reversed byte order for txid\n    const txHash = Buffer.from(utxo.txid, \"hex\").reverse();\n    tx.addInput(txHash, utxo.vout);\n  }\n\n  // Add all WASM outputs (vault output at index 0, depositor claim at index 1, etc.)\n  for (const output of outputs) {\n    tx.addOutput(output.script, output.value);\n  }\n\n  // Trust the selector's change decision: `selectUtxosForPegin` runs every\n  // candidate set through `applyChangeOutputPolicy` and returns\n  // `changeAmount = 0n` whenever the residual would be at-or-below dust\n  // after paying the change-output fee. Validate the contract at this\n  // boundary — a hand-built or stale `changeAmount` in (0, DUST_THRESHOLD]\n  // would produce a non-relayable dust output, and emitting one would also\n  // bypass the canonical fee policy that the selector applied.\n  if (changeAmount < 0n) {\n    throw new Error(\n      `fundPeginTransaction: changeAmount cannot be negative, got ${changeAmount}`,\n    );\n  }\n  if (changeAmount > 0n && changeAmount <= DUST_THRESHOLD) {\n    throw new Error(\n      `fundPeginTransaction: changeAmount must be 0 or strictly above DUST_THRESHOLD (${DUST_THRESHOLD}), got ${changeAmount}`,\n    );\n  }\n  if (changeAmount > 0n) {\n    const changeScript = bitcoin.address.toOutputScript(changeAddress, network);\n    tx.addOutput(changeScript, Number(changeAmount));\n  }\n\n  return tx.toHex();\n}\n\n// Re-export getNetwork from the canonical location in primitives\nexport { getNetwork } from \"../../primitives/utils/bitcoin\";\n"],"names":["P2TR_INPUT_SIZE","MAX_NON_LEGACY_OUTPUT_SIZE","TX_BUFFER_SIZE_OVERHEAD","BTC_DUST_SAT","DUST_THRESHOLD","LOW_RATE_ESTIMATION_ACCURACY_BUFFER","WALLET_RELAY_FEE_RATE_THRESHOLD","rateBasedTxBufferFee","feeRate","PEGIN_FIXED_OUTPUTS","PEGIN_AUTH_ANCHOR_OUTPUTS","peginOutputCount","vaultCount","hasAuthAnchor","SPLIT_TX_FEE_SAFETY_MULTIPLIER","MAX_REASONABLE_PEGIN_VBYTES","parseUnfundedWasmTransaction","unfundedTxHex","dataOffset","inputCount","outputCount","version","Buffer","locktime","outputs","pos","i","valueHex","value","scriptLen","scriptHex","script","fundPeginTransaction","params","selectedUTXOs","changeAddress","changeAmount","network","tx","bitcoin","utxo","txHash","output","changeScript"],"mappings":"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"}