@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
728 lines • 32.6 kB
JavaScript
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