UNPKG

@btc-vision/transaction

Version:

OPNet transaction library allows you to create and sign transactions for the OPNet network.

728 lines 32.6 kB
import { toHex, toSatoshi, Transaction, } from '@btc-vision/bitcoin'; import { CustomScriptTransaction } from './builders/CustomScriptTransaction.js'; import { DeploymentTransaction } from './builders/DeploymentTransaction.js'; import { FundingTransaction } from './builders/FundingTransaction.js'; import { InteractionTransaction } from './builders/InteractionTransaction.js'; import { TransactionBuilder } from './builders/TransactionBuilder.js'; import { TransactionType } from './enums/TransactionType.js'; import { P2WDADetector } from '../p2wda/P2WDADetector.js'; import { InteractionTransactionP2WDA } from './builders/InteractionTransactionP2WDA.js'; import { Address } from '../keypair/Address.js'; import { BitcoinUtils } from '../utils/BitcoinUtils.js'; import { CancelTransaction } from './builders/CancelTransaction.js'; import { ConsolidatedInteractionTransaction } from './builders/ConsolidatedInteractionTransaction.js'; export class TransactionFactory { debug = false; DUMMY_PUBKEY = new Uint8Array(32).fill(1); P2TR_SCRIPT = Uint8Array.from([0x51, 0x20, ...this.DUMMY_PUBKEY]); INITIAL_FUNDING_ESTIMATE = 2000n; MAX_ITERATIONS = 10; /** * @description Creates a cancellable transaction. * @param {ICancelTransactionParameters | ICancelTransactionParametersWithoutSigner} params - The cancel transaction parameters * @returns {Promise<CancelledTransaction>} - The cancelled transaction result */ async createCancellableTransaction(params) { if (!params.to) { throw new Error('Field "to" not provided.'); } if (!params.from) { throw new Error('Field "from" not provided.'); } if (!params.utxos[0]) { throw new Error('Missing at least one UTXO.'); } const opWalletCancel = await this.detectCancelOPWallet(params); if (opWalletCancel) { return opWalletCancel; } if (!('signer' in params)) { throw new Error('Field "signer" not provided, OP_WALLET not detected.'); } const cancel = new CancelTransaction(params); const signed = await cancel.signTransaction(); const rawTx = signed.toHex(); return { transaction: rawTx, nextUTXOs: this.getUTXOAsTransaction(signed, params.from, 0), inputUtxos: params.utxos, }; } /** * @description Generate a transaction with a custom script. * @param {ICustomTransactionParameters | ICustomTransactionWithoutSigner} interactionParameters - The custom transaction parameters * @returns {Promise<[string, string, UTXO[], UTXO[]]>} - The signed transaction tuple [fundingTx, customTx, nextUTXOs, inputUtxos] */ async createCustomScriptTransaction(interactionParameters) { if (!interactionParameters.to) { throw new Error('Field "to" not provided.'); } if (!interactionParameters.from) { throw new Error('Field "from" not provided.'); } if (!interactionParameters.utxos[0]) { throw new Error('Missing at least one UTXO.'); } if (!('signer' in interactionParameters)) { throw new Error('Field "signer" not provided, OP_WALLET not detected.'); } const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs); const { finalTransaction, estimatedAmount } = await this.iterateFundingAmount({ ...interactionParameters, optionalInputs: inputs }, CustomScriptTransaction, async (tx) => { const fee = await tx.estimateTransactionFees(); const priorityFee = this.getPriorityFee(interactionParameters); const optionalValue = tx.getOptionalOutputValue(); return fee + priorityFee + optionalValue; }, 'CustomScript'); const parameters = await finalTransaction.getFundingTransactionParameters(); parameters.utxos = interactionParameters.utxos; parameters.amount = estimatedAmount; const feeEstimationFunding = await this.createFundTransaction({ ...parameters, optionalOutputs: [], optionalInputs: [], }); if (!feeEstimationFunding) { throw new Error('Could not sign funding transaction.'); } parameters.estimatedFees = feeEstimationFunding.estimatedFees; const signedTransaction = await this.createFundTransaction({ ...parameters, optionalOutputs: [], optionalInputs: [], }); if (!signedTransaction) { throw new Error('Could not sign funding transaction.'); } const newParams = { ...interactionParameters, utxos: this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.to, 0), randomBytes: finalTransaction.getRndBytes(), nonWitnessUtxo: signedTransaction.tx.toBuffer(), estimatedFees: finalTransaction.estimatedFees, compiledTargetScript: finalTransaction.exportCompiledTargetScript(), optionalInputs: inputs, }; const customTransaction = new CustomScriptTransaction(newParams); const outTx = await customTransaction.signTransaction(); return [ signedTransaction.tx.toHex(), outTx.toHex(), this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.from, 1), interactionParameters.utxos, ]; } /** * @description Generates the required transactions. * @param {IInteractionParameters | InteractionParametersWithoutSigner} interactionParameters - The interaction parameters * @returns {Promise<InteractionResponse>} - The signed transaction */ async signInteraction(interactionParameters) { if (!interactionParameters.to) { throw new Error('Field "to" not provided.'); } if (!interactionParameters.from) { throw new Error('Field "from" not provided.'); } if (!interactionParameters.utxos[0]) { throw new Error('Missing at least one UTXO.'); } const opWalletInteraction = await this.detectInteractionOPWallet(interactionParameters); if (opWalletInteraction) { return opWalletInteraction; } if (!('signer' in interactionParameters)) { throw new Error('Field "signer" not provided, OP_WALLET not detected.'); } const useP2WDA = this.hasP2WDAInputs(interactionParameters.utxos); if (useP2WDA) { return this.signP2WDAInteraction(interactionParameters); } const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs); const { finalTransaction, estimatedAmount, challenge } = await this.iterateFundingAmount({ ...interactionParameters, optionalInputs: inputs }, InteractionTransaction, async (tx) => { const fee = await tx.estimateTransactionFees(); const outputsValue = tx.getTotalOutputValue(); const total = fee + outputsValue; if (interactionParameters.subtractExtraUTXOFromAmountRequired && interactionParameters.optionalInputs && interactionParameters.optionalInputs.length > 0) { const optionalInputValue = interactionParameters.optionalInputs.reduce((sum, input) => sum + input.value, 0n); const adjusted = total > optionalInputValue ? total - optionalInputValue : 0n; return adjusted < TransactionBuilder.MINIMUM_DUST ? TransactionBuilder.MINIMUM_DUST : adjusted; } return total; }, 'Interaction'); if (!challenge) { throw new Error('Failed to get challenge from interaction transaction'); } const parameters = await finalTransaction.getFundingTransactionParameters(); parameters.utxos = interactionParameters.utxos; parameters.amount = estimatedAmount; const feeEstimationFunding = await this.createFundTransaction({ ...parameters, optionalOutputs: [], optionalInputs: [], }); if (!feeEstimationFunding) { throw new Error('Could not sign funding transaction.'); } parameters.estimatedFees = feeEstimationFunding.estimatedFees; const signedTransaction = await this.createFundTransaction({ ...parameters, optionalOutputs: [], optionalInputs: [], }); if (!signedTransaction) { throw new Error('Could not sign funding transaction.'); } const fundingUTXO = this.getUTXOAsTransaction(signedTransaction.tx, finalTransaction.getScriptAddress(), 0); const newParams = { ...interactionParameters, utxos: fundingUTXO, randomBytes: finalTransaction.getRndBytes(), challenge: challenge, compiledTargetScript: finalTransaction.exportCompiledTargetScript(), nonWitnessUtxo: signedTransaction.tx.toBuffer(), estimatedFees: finalTransaction.estimatedFees, optionalInputs: inputs, }; const interactionTx = new InteractionTransaction(newParams); const outTx = await interactionTx.signTransaction(); return { interactionAddress: finalTransaction.getScriptAddress(), fundingTransaction: signedTransaction.tx.toHex(), interactionTransaction: outTx.toHex(), estimatedFees: interactionTx.transactionFee, nextUTXOs: this.getUTXOAsTransaction(signedTransaction.tx, interactionParameters.from, 1), challenge: challenge.toRaw(), fundingUTXOs: [...fundingUTXO, ...inputs], fundingInputUtxos: interactionParameters.utxos, compiledTargetScript: toHex(interactionTx.exportCompiledTargetScript()), }; } /** * @description Generates a consolidated interaction transaction (CHCT system). * * Drop-in replacement for signInteraction that bypasses BIP110/Bitcoin Knots censorship. * Uses P2WSH with HASH160 commitments instead of Tapscript (which uses OP_IF and gets censored). * * Returns two transactions: * - Setup: Creates P2WSH outputs with hash commitments to data chunks * - Reveal: Spends those outputs, revealing data in witnesses * * Data integrity is consensus-enforced - if data is stripped/modified, * HASH160(data) != committed_hash and the transaction is INVALID. * * @param {IConsolidatedInteractionParameters} interactionParameters - Same parameters as signInteraction * @returns {Promise<ConsolidatedInteractionResponse>} - Both setup and reveal transactions */ async signConsolidatedInteraction(interactionParameters) { if (!interactionParameters.to) { throw new Error('Field "to" not provided.'); } if (!interactionParameters.from) { throw new Error('Field "from" not provided.'); } if (!interactionParameters.utxos[0]) { throw new Error('Missing at least one UTXO.'); } if (!('signer' in interactionParameters)) { throw new Error('Field "signer" not provided.'); } if (!interactionParameters.challenge) { throw new Error('Field "challenge" not provided.'); } const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs); const consolidatedTx = new ConsolidatedInteractionTransaction({ ...interactionParameters, optionalInputs: inputs, }); const result = await consolidatedTx.build(); return { setupTransaction: result.setup.txHex, revealTransaction: result.reveal.txHex, setupTxId: result.setup.txId, revealTxId: result.reveal.txId, totalFees: result.totalFees, chunkCount: result.setup.chunkCount, dataSize: result.setup.totalDataSize, challenge: consolidatedTx.getChallenge().toRaw(), inputUtxos: interactionParameters.utxos, compiledTargetScript: toHex(consolidatedTx.exportCompiledTargetScript()), }; } /** * @description Generates the required transactions. * @param {IDeploymentParameters} deploymentParameters - The deployment parameters * @returns {Promise<DeploymentResult>} - The signed transaction */ async signDeployment(deploymentParameters) { const opWalletDeployment = await this.detectDeploymentOPWallet(deploymentParameters); if (opWalletDeployment) { return opWalletDeployment; } if (!('signer' in deploymentParameters)) { throw new Error('Field "signer" not provided, OP_WALLET not detected.'); } const inputs = this.parseOptionalInputs(deploymentParameters.optionalInputs); const { finalTransaction, estimatedAmount, challenge } = await this.iterateFundingAmount({ ...deploymentParameters, optionalInputs: inputs }, DeploymentTransaction, async (tx) => { const fee = await tx.estimateTransactionFees(); const priorityFee = this.getPriorityFee(deploymentParameters); const optionalValue = tx.getOptionalOutputValue(); return fee + priorityFee + optionalValue; }, 'Deployment'); if (!challenge) { throw new Error('Failed to get challenge from deployment transaction'); } const parameters = await finalTransaction.getFundingTransactionParameters(); parameters.utxos = deploymentParameters.utxos; parameters.amount = estimatedAmount; const feeEstimationFunding = await this.createFundTransaction({ ...parameters, optionalOutputs: [], optionalInputs: [], }); if (!feeEstimationFunding) { throw new Error('Could not sign funding transaction.'); } parameters.estimatedFees = feeEstimationFunding.estimatedFees; const fundingTransaction = new FundingTransaction({ ...parameters, optionalInputs: [], optionalOutputs: [], }); const signedTransaction = await fundingTransaction.signTransaction(); if (!signedTransaction) { throw new Error('Could not sign funding transaction.'); } const out = signedTransaction.outs[0]; const newUtxo = { transactionId: signedTransaction.getId(), outputIndex: 0, scriptPubKey: { hex: toHex(out.script), address: finalTransaction.getScriptAddress(), }, value: BigInt(out.value), }; const newParams = { ...deploymentParameters, utxos: [newUtxo], randomBytes: finalTransaction.getRndBytes(), compiledTargetScript: finalTransaction.exportCompiledTargetScript(), challenge: challenge, nonWitnessUtxo: signedTransaction.toBuffer(), estimatedFees: finalTransaction.estimatedFees, optionalInputs: inputs, }; const deploymentTx = new DeploymentTransaction(newParams); const outTx = await deploymentTx.signTransaction(); const out2 = signedTransaction.outs[1]; const refundUTXO = { transactionId: signedTransaction.getId(), outputIndex: 1, scriptPubKey: { hex: toHex(out2.script), address: deploymentParameters.from, }, value: BigInt(out2.value), }; return { transaction: [signedTransaction.toHex(), outTx.toHex()], contractAddress: deploymentTx.getContractAddress(), contractPubKey: deploymentTx.contractPubKey, utxos: [refundUTXO], challenge: challenge.toRaw(), inputUtxos: deploymentParameters.utxos, }; } /** * @description Creates a funding transaction. * @param {IFundingTransactionParameters} parameters - The funding transaction parameters * @returns {Promise<BitcoinTransferBase>} - The signed transaction */ async createBTCTransfer(parameters) { if (!parameters.to) { throw new Error('Field "to" not provided.'); } if (!parameters.from) { throw new Error('Field "from" not provided.'); } const opWalletInteraction = await this.detectFundingOPWallet(parameters); if (opWalletInteraction) { return opWalletInteraction; } if (!('signer' in parameters)) { throw new Error('Field "signer" not provided, OP_WALLET not detected.'); } const resp = await this.createFundTransaction(parameters); return { estimatedFees: resp.estimatedFees, tx: resp.tx.toHex(), nextUTXOs: this.getAllNewUTXOs(resp.original, resp.tx, parameters.from), inputUtxos: parameters.utxos, }; } /** * Get all new UTXOs of a generated transaction. * @param {TransactionBuilder<TransactionType>} original - The original transaction * @param {Transaction} tx - The transaction * @param {string} to - The address to filter * @returns {UTXO[]} - The new UTXOs belonging to the specified address */ getAllNewUTXOs(original, tx, to) { const outputs = original.getOutputs(); const utxos = []; for (let i = 0; i < tx.outs.length; i++) { const output = outputs[i]; if ('address' in output) { if (output.address !== to) continue; } else { continue; } utxos.push(...this.getUTXOAsTransaction(tx, to, i)); } return utxos; } /** * Parse optional inputs and normalize nonWitnessUtxo format. * @param {UTXO[]} optionalInputs - The optional inputs to parse * @returns {UTXO[]} - The parsed inputs with normalized nonWitnessUtxo */ parseOptionalInputs(optionalInputs) { return (optionalInputs || []).map((input) => { let nonWitness = input.nonWitnessUtxo; if (nonWitness && !(nonWitness instanceof Uint8Array) && typeof nonWitness === 'object') { nonWitness = Uint8Array.from(Object.values(nonWitness)); } return { ...input, nonWitnessUtxo: nonWitness, }; }); } /** * Detect and use OP_WALLET for funding transactions if available. * * @param {IFundingTransactionParameters | IFundingTransactionParametersWithoutSigner} fundingParams - The funding transaction parameters * @return {Promise<BitcoinTransferBase | null>} - The funding transaction response or null if OP_WALLET not available */ async detectFundingOPWallet(fundingParams) { if (typeof window === 'undefined') { return null; } const _window = window; if (!_window || !_window.opnet || !_window.opnet.web3) { return null; } const opnet = _window.opnet.web3; const result = await opnet.sendBitcoin({ ...fundingParams, // @ts-expect-error signer is stripped by the wallet signer: undefined, }); if (!result) { throw new Error('Could not sign funding transaction.'); } return { ...result, inputUtxos: result.inputUtxos ?? fundingParams.utxos, }; } /** * Detect and use OP_WALLET for cancel transactions if available. * @param {ICancelTransactionParameters | ICancelTransactionParametersWithoutSigner} interactionParameters - The cancel parameters * @returns {Promise<CancelledTransaction | null>} - The cancelled transaction or null if OP_WALLET not available */ async detectCancelOPWallet(interactionParameters) { if (typeof window === 'undefined') { return null; } const _window = window; if (!_window || !_window.opnet || !_window.opnet.web3) { return null; } const opnet = _window.opnet.web3; const interaction = await opnet.cancelTransaction({ ...interactionParameters, // @ts-expect-error no, this is ok signer: undefined, }); if (!interaction) { throw new Error('Could not sign interaction transaction.'); } return { ...interaction, inputUtxos: interaction.inputUtxos ?? interactionParameters.utxos, }; } /** * Detect and use OP_WALLET for interaction transactions if available. * @param {IInteractionParameters | InteractionParametersWithoutSigner} interactionParameters - The interaction parameters * @returns {Promise<InteractionResponse | null>} - The interaction response or null if OP_WALLET not available */ async detectInteractionOPWallet(interactionParameters) { if (typeof window === 'undefined') { return null; } const _window = window; if (!_window || !_window.opnet || !_window.opnet.web3) { return null; } const opnet = _window.opnet.web3; const interaction = await opnet.signInteraction({ ...interactionParameters, // @ts-expect-error no, this is ok signer: undefined, }); if (!interaction) { throw new Error('Could not sign interaction transaction.'); } return { ...interaction, fundingInputUtxos: interaction.fundingInputUtxos ?? interactionParameters.utxos, }; } /** * Detect and use OP_WALLET for deployment transactions if available. * @param {IDeploymentParameters | IDeploymentParametersWithoutSigner} deploymentParameters - The deployment parameters * @returns {Promise<DeploymentResult | null>} - The deployment result or null if OP_WALLET not available */ async detectDeploymentOPWallet(deploymentParameters) { if (typeof window === 'undefined') { return null; } const _window = window; if (!_window || !_window.opnet || !_window.opnet.web3) { return null; } const opnet = _window.opnet.web3; const deployment = await opnet.deployContract({ ...deploymentParameters, // @ts-expect-error no, this is ok signer: undefined, }); if (!deployment) { throw new Error('Could not sign interaction transaction.'); } return { ...deployment, inputUtxos: deployment.inputUtxos ?? deploymentParameters.utxos, }; } /** * Create and sign a funding transaction. * @param {IFundingTransactionParameters} parameters - The funding transaction parameters * @returns {Promise<FundingTransactionResponse>} - The funding transaction response */ async createFundTransaction(parameters) { if (!parameters.to) throw new Error('Field "to" not provided.'); const fundingTransaction = new FundingTransaction(parameters); const signedTransaction = await fundingTransaction.signTransaction(); if (!signedTransaction) { throw new Error('Could not sign funding transaction.'); } return { tx: signedTransaction, original: fundingTransaction, estimatedFees: fundingTransaction.estimatedFees, nextUTXOs: this.getUTXOAsTransaction(signedTransaction, parameters.to, 0), inputUtxos: parameters.utxos, }; } /** * Check if the UTXOs contain any P2WDA inputs * * This method examines both main UTXOs and optional inputs to determine * if any of them are P2WDA addresses. P2WDA detection is based on the * witness script pattern: (OP_2DROP * 5) <pubkey> OP_CHECKSIG * * @param {UTXO[]} utxos - The main UTXOs to check * @returns {boolean} - true if any UTXO is P2WDA, false otherwise */ hasP2WDAInputs(utxos) { return utxos.some((utxo) => P2WDADetector.isP2WDAUTXO(utxo)); } /** * Sign a P2WDA interaction transaction * * P2WDA interactions are fundamentally different from standard OP_NET interactions. * Instead of using a two-transaction model (funding + interaction), P2WDA embeds * the operation data directly in the witness field of a single transaction. * This achieves significant cost savings through the witness discount. * * Key differences: * - Single transaction instead of two * - Operation data in witness field instead of taproot script * - 75% cost reduction for data storage * - No separate funding transaction needed * * @param {IInteractionParameters | InteractionParametersWithoutSigner} interactionParameters - The interaction parameters * @returns {Promise<InteractionResponse>} - The signed P2WDA interaction response */ async signP2WDAInteraction(interactionParameters) { if (!interactionParameters.from) { throw new Error('Field "from" not provided.'); } if (!('signer' in interactionParameters)) { throw new Error('P2WDA interactions require a signer. OP_WALLET is not supported for P2WDA.'); } const inputs = this.parseOptionalInputs(interactionParameters.optionalInputs); const p2wdaTransaction = new InteractionTransactionP2WDA({ ...interactionParameters, optionalInputs: inputs, }); const signedTx = await p2wdaTransaction.signTransaction(); const txHex = signedTx.toHex(); return { interactionAddress: null, fundingTransaction: null, interactionTransaction: txHex, estimatedFees: p2wdaTransaction.estimatedFees, nextUTXOs: this.getUTXOAsTransaction(signedTx, interactionParameters.from, signedTx.outs.length - 1), fundingUTXOs: [...interactionParameters.utxos, ...inputs], fundingInputUtxos: interactionParameters.utxos, challenge: interactionParameters.challenge.toRaw(), compiledTargetScript: null, }; } /** * Get the priority fee from transaction parameters. * @param {ITransactionParameters} params - The transaction parameters * @returns {bigint} - The priority fee, minimum dust if below threshold */ getPriorityFee(params) { const totalFee = params.priorityFee + params.gasSatFee; if (totalFee < TransactionBuilder.MINIMUM_DUST) { return TransactionBuilder.MINIMUM_DUST; } return totalFee; } /** * Common iteration logic for finding the correct funding amount. * * This method iteratively estimates the required funding amount by simulating * transactions until the amount converges or max iterations is reached. * * @param {P extends IInteractionParameters | IDeploymentParameters | ICustomTransactionParameters} params - The transaction parameters * @param {new (params: P) => T} TransactionClass - The transaction class constructor * @param {(tx: T extends InteractionTransaction | DeploymentTransaction | CustomScriptTransaction) => Promise<bigint>} calculateAmount - Function to calculate required amount * @param {string} debugPrefix - Prefix for debug logging * @returns {Promise<{finalTransaction: T extends InteractionTransaction | DeploymentTransaction | CustomScriptTransaction, estimatedAmount: bigint, challenge: IChallengeSolution | null}>} - The final transaction and estimated amount */ async iterateFundingAmount(params, TransactionClass, calculateAmount, debugPrefix) { const randomBytes = 'randomBytes' in params ? (params.randomBytes ?? BitcoinUtils.rndBytes()) : BitcoinUtils.rndBytes(); const dummyAddress = Address.dead().p2tr(params.network); let estimatedFundingAmount = this.INITIAL_FUNDING_ESTIMATE; let previousAmount = 0n; let iterations = 0; let finalPreTransaction = null; let challenge = null; while (iterations < this.MAX_ITERATIONS && estimatedFundingAmount !== previousAmount) { previousAmount = estimatedFundingAmount; const dummyTx = new Transaction(); dummyTx.addOutput(this.P2TR_SCRIPT, toSatoshi(estimatedFundingAmount)); const simulatedFundedUtxo = { transactionId: toHex(new Uint8Array(32)), outputIndex: 0, scriptPubKey: { hex: toHex(this.P2TR_SCRIPT), address: dummyAddress, }, value: estimatedFundingAmount, nonWitnessUtxo: dummyTx.toBuffer(), }; let txParams; if ('challenge' in params && params.challenge) { const withChallenge = { ...params, utxos: [simulatedFundedUtxo], randomBytes: randomBytes, challenge: challenge ?? params.challenge, }; txParams = withChallenge; } else { const withoutChallenge = { ...params, utxos: [simulatedFundedUtxo], randomBytes: randomBytes, }; txParams = withoutChallenge; } const preTransaction = new TransactionClass(txParams); try { await preTransaction.generateTransactionMinimalSignatures(); estimatedFundingAmount = await calculateAmount(preTransaction); } catch (error) { if (error instanceof Error) { const match = error.message.match(/need (\d+) sats but only have (\d+) sats/); if (match) { estimatedFundingAmount = BigInt(match[1]); if (this.debug) { console.log(`${debugPrefix}: Caught insufficient funds, updating to ${estimatedFundingAmount}`); } } else { throw error; } } else { throw new Error('Unknown error during fee estimation', { cause: error }); } } finalPreTransaction = preTransaction; if ('getChallenge' in preTransaction && typeof preTransaction.getChallenge === 'function') { challenge = preTransaction.getChallenge(); } iterations++; if (this.debug) { console.log(`${debugPrefix} Iteration ${iterations}: Previous=${previousAmount}, New=${estimatedFundingAmount}`); } } if (!finalPreTransaction) { throw new Error(`Failed to converge on ${debugPrefix} funding amount`); } if (estimatedFundingAmount === 0n) { throw new Error(`Impossible. Transaction cant be free.`); } return { finalTransaction: finalPreTransaction, estimatedAmount: estimatedFundingAmount, challenge, }; } /** * Convert a transaction output to a UTXO. * @param {Transaction} tx - The transaction * @param {string} to - The address * @param {number} index - The output index * @returns {UTXO[]} - The UTXO array (empty if output doesn't exist) */ getUTXOAsTransaction(tx, to, index) { if (!tx.outs[index]) return []; const out = tx.outs[index]; const newUtxo = { transactionId: tx.getId(), outputIndex: index, scriptPubKey: { hex: toHex(out.script), address: to, }, value: BigInt(out.value), }; return [newUtxo]; } } //# sourceMappingURL=TransactionFactory.js.map