@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
747 lines (589 loc) • 28 kB
Markdown
# TransactionFactory
The `TransactionFactory` class is the **main entry point** for creating all OPNet transaction types. It manages the complete transaction lifecycle including fee estimation, the two-transaction model for contract operations, and automatic detection of browser wallet extensions.
**Source:** `src/transaction/TransactionFactory.ts`
## Architecture
```mermaid
flowchart TB
TF["TransactionFactory<br/>(Main Entry Point)"]
TF -->|"createBTCTransfer()"| FT["FundingTransaction<br/>Simple BTC transfers"]
TF -->|"signDeployment()"| DT["DeploymentTransaction<br/>Deploy smart contracts"]
TF -->|"signInteraction()"| IT["InteractionTransaction<br/>Call contract functions"]
TF -->|"signInteraction() + P2WDA"| P2["InteractionTransactionP2WDA<br/>Witness-data interactions"]
TF -->|"signConsolidatedInteraction()"| CI["ConsolidatedInteractionTransaction<br/>CHCT censorship bypass"]
TF -->|"createCustomScriptTransaction()"| CS["CustomScriptTransaction<br/>Arbitrary Bitcoin scripts"]
TF -->|"createCancellableTransaction()"| CT["CancelTransaction<br/>Recover stuck funds"]
subgraph Builders["Transaction Builders"]
FT
DT
IT
P2
CI
CS
CT
end
subgraph Base["Base Classes"]
TB["TransactionBuilder<T><br/>UTXO management, fees, outputs"]
TW["TweakedTransaction<br/>Signing, PSBT, Taproot"]
end
Builders --> TB --> TW
```
## Quick Start
```typescript
import { TransactionFactory, EcKeyPair } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';
const network = networks.bitcoin;
const factory = new TransactionFactory();
// Backend: provide a signer
const signer = EcKeyPair.fromWIF('your-private-key-wif', network);
// Browser: signer is null (wallet extension handles signing)
// const signer = null;
```
## Methods
### createBTCTransfer
Creates and signs a simple BTC transfer (funding) transaction.
```typescript
public async createBTCTransfer(
parameters: IFundingTransactionParameters,
): Promise<BitcoinTransferResponse>
```
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `signer` | `Signer \| UniversalSigner` | Yes | The signing key for the transaction |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | Yes | ML-DSA quantum signer, or `null` |
| `network` | `Network` | Yes | Bitcoin network (`networks.bitcoin`, `networks.testnet`, etc.) |
| `utxos` | `UTXO[]` | Yes | Unspent outputs to fund the transaction |
| `from` | `string` | Yes | Sender address |
| `to` | `string` | Yes | Recipient address |
| `feeRate` | `number` | Yes | Fee rate in sat/vB |
| `priorityFee` | `bigint` | Yes | OPNet priority fee in satoshis |
| `gasSatFee` | `bigint` | Yes | OPNet gas fee in satoshis |
| `amount` | `bigint` | Yes | Amount to send in satoshis |
| `splitInputsInto` | `number` | No | Split the payment into N equal outputs (default: 1) |
| `autoAdjustAmount` | `boolean` | No | Deduct fees from amount (send-max mode) |
| `feeUtxos` | `UTXO[]` | No | Separate UTXOs used exclusively for fees |
**Returns:** [`BitcoinTransferResponse`](#bitcointransferresponse)
---
### signDeployment
Deploys a smart contract using the two-transaction model. Produces a funding transaction and a deployment transaction.
```typescript
public async signDeployment(
deploymentParameters: IDeploymentParameters,
): Promise<DeploymentResult>
```
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `signer` | `Signer \| UniversalSigner` | Browser: omit | The signing key (omit in browser for OP_WALLET) |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | Browser: omit | ML-DSA quantum signer |
| `network` | `Network` | Yes | Bitcoin network |
| `utxos` | `UTXO[]` | Yes | Unspent outputs to fund the deployment |
| `from` | `string` | Yes | Deployer address |
| `feeRate` | `number` | Yes | Fee rate in sat/vB |
| `priorityFee` | `bigint` | Yes | OPNet priority fee in satoshis |
| `gasSatFee` | `bigint` | Yes | OPNet gas fee in satoshis |
| `bytecode` | `Uint8Array` | Yes | Compiled contract bytecode |
| `calldata` | `Uint8Array` | No | Constructor parameters (ABI-encoded) |
| `challenge` | `IChallengeSolution` | Yes | Epoch challenge solution |
| `randomBytes` | `Uint8Array` | No | 32 random bytes for script uniqueness (auto-generated if omitted) |
**Returns:** [`DeploymentResult`](#deploymentresult)
---
### signInteraction
Calls a smart contract function using the two-transaction model. Produces a funding transaction and an interaction transaction. Automatically uses P2WDA mode when P2WDA UTXOs are detected.
```typescript
public async signInteraction(
interactionParameters: IInteractionParameters | InteractionParametersWithoutSigner,
): Promise<InteractionResponse>
```
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `signer` | `Signer \| UniversalSigner` | Browser: omit | The signing key (omit in browser for OP_WALLET) |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | Browser: omit | ML-DSA quantum signer |
| `network` | `Network` | Yes | Bitcoin network |
| `utxos` | `UTXO[]` | Yes | Unspent outputs to fund the interaction |
| `from` | `string` | Yes | Caller address |
| `to` | `string` | Yes | Contract address to call |
| `feeRate` | `number` | Yes | Fee rate in sat/vB |
| `priorityFee` | `bigint` | Yes | OPNet priority fee in satoshis |
| `gasSatFee` | `bigint` | Yes | OPNet gas fee in satoshis |
| `calldata` | `Uint8Array` | Yes | ABI-encoded function call data |
| `challenge` | `IChallengeSolution` | Yes | Epoch challenge solution |
| `contract` | `string` | No | Alternative contract identifier |
| `randomBytes` | `Uint8Array` | No | 32 random bytes (auto-generated if omitted) |
| `disableAutoRefund` | `boolean` | No | Disable automatic refund output |
| `loadedStorage` | `LoadedStorage` | No | Pre-loaded contract storage for optimization |
**Returns:** [`InteractionResponse`](#interactionresponse)
---
### signConsolidatedInteraction
Drop-in replacement for `signInteraction` that bypasses BIP110/Bitcoin Knots censorship. Uses P2WSH with HASH160 commitments (the CHCT system) instead of Tapscript. Produces a setup transaction and a reveal transaction.
```typescript
public async signConsolidatedInteraction(
interactionParameters: IConsolidatedInteractionParameters,
): Promise<ConsolidatedInteractionResponse>
```
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `signer` | `Signer \| UniversalSigner` | Yes | The signing key (no browser wallet support) |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | Yes | ML-DSA quantum signer |
| `network` | `Network` | Yes | Bitcoin network |
| `utxos` | `UTXO[]` | Yes | Unspent outputs |
| `from` | `string` | Yes | Caller address |
| `to` | `string` | Yes | Contract address to call |
| `feeRate` | `number` | Yes | Fee rate in sat/vB |
| `priorityFee` | `bigint` | Yes | OPNet priority fee in satoshis |
| `gasSatFee` | `bigint` | Yes | OPNet gas fee in satoshis |
| `calldata` | `Uint8Array` | Yes | ABI-encoded function call data |
| `challenge` | `IChallengeSolution` | Yes | Epoch challenge solution |
| `maxChunkSize` | `number` | No | Max bytes per stack item (default: 80, P2WSH policy limit) |
**Returns:** [`ConsolidatedInteractionResponse`](#consolidatedinteractionresponse)
> **Note:** Unlike `signInteraction`, consolidated interactions do **not** support browser wallet extensions (OP_WALLET). A signer must always be provided.
---
### createCustomScriptTransaction
Generates a transaction with an arbitrary Bitcoin script. Follows the two-transaction model (funding + custom script).
```typescript
public async createCustomScriptTransaction(
interactionParameters: ICustomTransactionParameters | ICustomTransactionWithoutSigner,
): Promise<[string, string, UTXO[], UTXO[]]>
```
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `signer` | `Signer \| UniversalSigner` | Yes | The signing key |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | Yes | ML-DSA quantum signer |
| `network` | `Network` | Yes | Bitcoin network |
| `utxos` | `UTXO[]` | Yes | Unspent outputs |
| `from` | `string` | Yes | Sender address |
| `to` | `string` | Yes | Recipient address |
| `feeRate` | `number` | Yes | Fee rate in sat/vB |
| `priorityFee` | `bigint` | Yes | OPNet priority fee in satoshis |
| `gasSatFee` | `bigint` | Yes | OPNet gas fee in satoshis |
| `script` | `(Uint8Array \| Stack)[]` | Yes | The custom Bitcoin script to execute |
| `witnesses` | `Uint8Array[]` | Yes | Witness data for the script |
| `annex` | `Uint8Array` | No | Optional Taproot annex payload (without the `0x50` prefix) |
**Returns:** `[string, string, UTXO[], UTXO[]]` - Tuple of `[fundingTxHex, customTxHex, nextUTXOs, inputUtxos]`
---
### createCancellableTransaction
Cancels or recovers funds from a stuck transaction. Uses the compiled target script to spend the locked UTXO back to the sender.
```typescript
public async createCancellableTransaction(
params: ICancelTransactionParameters | ICancelTransactionParametersWithoutSigner,
): Promise<CancelledTransaction>
```
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `signer` | `Signer \| UniversalSigner` | Browser: omit | The signing key (omit in browser for OP_WALLET) |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | Browser: omit | ML-DSA quantum signer |
| `network` | `Network` | Yes | Bitcoin network |
| `utxos` | `UTXO[]` | Yes | The stuck UTXOs to recover |
| `from` | `string` | Yes | Original sender address (receives refund) |
| `to` | `string` | Yes | Recovery destination address |
| `feeRate` | `number` | Yes | Fee rate in sat/vB |
| `compiledTargetScript` | `string \| Uint8Array` | Yes | The compiled script from the original transaction |
**Returns:** [`CancelledTransaction`](#cancelledtransaction)
> **Note:** Cancel transactions do **not** use `priorityFee` or `gasSatFee`. These fields are omitted from the parameter interface.
---
### MultiSignTransaction (Direct Constructor)
Multi-signature transactions are created directly through the `MultiSignTransaction` class rather than the factory.
```typescript
import { MultiSignTransaction } from '@btc-vision/transaction';
const multiSigTx = new MultiSignTransaction({
network,
utxos: vaultUtxos,
feeRate: 10,
mldsaSigner: null,
pubkeys: [pubkeyA, pubkeyB, pubkeyC],
minimumSignatures: 2,
receiver: recipientAddress,
requestedAmount: 100000n,
refundVault: vaultAddress,
});
const psbt = await multiSigTx.signPSBT();
```
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `network` | `Network` | Yes | Bitcoin network |
| `utxos` | `UTXO[]` | Yes | Vault UTXOs to spend |
| `feeRate` | `number` | Yes | Fee rate in sat/vB |
| `mldsaSigner` | `QuantumBIP32Interface \| null` | Yes | ML-DSA quantum signer |
| `pubkeys` | `Uint8Array[]` | Yes | All N public keys for the M-of-N scheme |
| `minimumSignatures` | `number` | Yes | Minimum required signatures (M) |
| `receiver` | `string` | Yes | Address to receive the requested amount |
| `requestedAmount` | `bigint` | Yes | Amount to send to the receiver in satoshis |
| `refundVault` | `string` | Yes | Address to receive leftover funds |
| `psbt` | `Psbt` | No | Existing PSBT to continue signing |
---
### Unsigned Transaction Builders
These methods on `TransactionFactory` create transactions without signing, useful for estimation or offline workflows. They are **private** methods used internally by the factory but document the internal two-step process.
| Internal Method | Creates | Used By |
|----------------|---------|---------|
| `createFundTransaction(params)` | `FundingTransactionResponse` | `createBTCTransfer`, `signInteraction`, `signDeployment`, `createCustomScriptTransaction` |
| `iterateFundingAmount(params, TxClass, calcFn, prefix)` | Iteratively estimates and converges on the correct funding amount | `signInteraction`, `signDeployment`, `createCustomScriptTransaction` |
## Response Types
### BitcoinTransferResponse
Returned by `createBTCTransfer`.
```typescript
interface BitcoinTransferResponse {
readonly tx: string; // Signed transaction hex
readonly estimatedFees: bigint; // Total fees paid in satoshis
readonly nextUTXOs: UTXO[]; // Change UTXOs (yours to spend next)
readonly inputUtxos: UTXO[]; // UTXOs consumed by this transaction
readonly original: FundingTransaction; // The underlying transaction builder
}
```
### DeploymentResult
Returned by `signDeployment`.
```typescript
interface DeploymentResult {
readonly transaction: [string, string]; // [fundingTxHex, deploymentTxHex]
readonly contractAddress: string; // The deployed contract's address
readonly contractPubKey: string; // The contract's public key
readonly challenge: RawChallenge; // Serialized epoch challenge
readonly utxos: UTXO[]; // Refund UTXOs (change from funding)
readonly inputUtxos: UTXO[]; // UTXOs consumed
}
```
### InteractionResponse
Returned by `signInteraction`.
```typescript
interface InteractionResponse {
readonly fundingTransaction: string | null; // Funding tx hex (null for P2WDA)
readonly interactionTransaction: string; // Interaction tx hex
readonly estimatedFees: bigint; // Fees for the interaction tx
readonly nextUTXOs: UTXO[]; // Refund UTXOs (change)
readonly fundingUTXOs: UTXO[]; // UTXOs created by funding tx
readonly fundingInputUtxos: UTXO[]; // Original UTXOs consumed
readonly challenge: RawChallenge; // Serialized epoch challenge
readonly interactionAddress: string | null; // Script address (null for P2WDA)
readonly compiledTargetScript: string | null; // Compiled script hex (null for P2WDA)
}
```
### ConsolidatedInteractionResponse
Returned by `signConsolidatedInteraction`.
```typescript
interface ConsolidatedInteractionResponse {
readonly setupTransaction: string; // Setup tx hex (creates P2WSH commitments)
readonly revealTransaction: string; // Reveal tx hex (spends commitments)
readonly setupTxId: string; // Setup transaction ID
readonly revealTxId: string; // Reveal transaction ID
readonly totalFees: bigint; // Total fees across both transactions
readonly chunkCount: number; // Number of data chunks
readonly dataSize: number; // Total compiled data size in bytes
readonly challenge: RawChallenge; // Serialized epoch challenge
readonly inputUtxos: UTXO[]; // UTXOs consumed
readonly compiledTargetScript: string; // Compiled script hex
}
```
### CancelledTransaction
Returned by `createCancellableTransaction`.
```typescript
interface CancelledTransaction {
readonly transaction: string; // Signed cancel tx hex
readonly nextUTXOs: UTXO[]; // Recovered UTXOs
readonly inputUtxos: UTXO[]; // UTXOs consumed
}
```
## The Two-Transaction Model
Standard contract operations (deployment, interaction) use a **two-transaction model**. The first transaction funds a temporary script address; the second spends from that address to execute the contract operation.
```mermaid
flowchart LR
subgraph TX1["Transaction 1: Funding"]
U1["User UTXO 1"]
U2["User UTXO 2"]
SO["Script Output<br/>(to temporary address)"]
CO["Change Output<br/>(back to sender)"]
U1 --> SO
U2 --> SO
U1 --> CO
end
subgraph TX2["Transaction 2: Operation"]
SI["Script Input<br/>(from TX1 output 0)"]
ER["Epoch Reward<br/>(330 sat minimum)"]
RF["Refund Output"]
OD["OP_RETURN Data<br/>(optional note)"]
SI --> ER
SI --> RF
SI --> OD
end
SO -->|"creates input for"| SI
```
### Why Two Transactions?
1. **Script isolation** - The contract operation script is committed to a specific Taproot address. Funding that address in a separate transaction ensures the script hash is locked before spending.
2. **Fee accuracy** - The factory iteratively estimates the funding amount needed for the second transaction, accounting for fees, priority fees, and optional outputs.
3. **Cancellation support** - If the second transaction fails to confirm, the first transaction's output can be recovered using `createCancellableTransaction` with the `compiledTargetScript`.
### P2WDA Exception
When the factory detects P2WDA (Pay-to-Witness-Data-Authentication) UTXOs in the inputs, it switches to a **single-transaction model**. P2WDA embeds operation data directly in the witness field, avoiding the need for a separate funding transaction and achieving approximately 75% cost reduction.
In P2WDA mode, the `InteractionResponse` will have:
- `fundingTransaction: null`
- `interactionAddress: null`
- `compiledTargetScript: null`
### CHCT System (Consolidated Interactions)
The `signConsolidatedInteraction` method uses a different two-transaction model called CHCT (Commitment-Hash-Commitment-Transaction):
```mermaid
flowchart LR
subgraph Setup["Setup Transaction"]
UI["User UTXOs"]
P1["P2WSH Output 1<br/>HASH160 commitments"]
P2["P2WSH Output 2<br/>HASH160 commitments"]
PN["P2WSH Output N<br/>..."]
CH["Change"]
UI --> P1
UI --> P2
UI --> PN
UI --> CH
end
subgraph Reveal["Reveal Transaction"]
S1["Spend P2WSH 1<br/>Reveal data chunks"]
S2["Spend P2WSH 2<br/>Reveal data chunks"]
SN["Spend P2WSH N<br/>..."]
RW["Epoch Reward"]
RF["Refund"]
S1 --> RW
S2 --> RW
SN --> RF
end
P1 -->|"witness reveals data"| S1
P2 -->|"witness reveals data"| S2
PN -->|"witness reveals data"| SN
```
This bypasses BIP110/Bitcoin Knots censorship by avoiding Tapscript `OP_IF` opcodes entirely. Data integrity is consensus-enforced: if any data is modified, `HASH160(data) != committed_hash` and the transaction is invalid.
## Browser vs. Backend Environments
The `TransactionFactory` supports both browser and backend environments. The key difference is how signing is handled.
### Backend Environment
In a backend (Node.js) environment, you provide a signer object directly:
```typescript
import { EcKeyPair } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';
const signer = EcKeyPair.fromWIF(privateKeyWIF, networks.bitcoin);
const result = await factory.signInteraction({
signer: signer, // Provided signer
mldsaSigner: mldsaKey, // Or null if no quantum signing
network: networks.bitcoin,
// ... other parameters
});
```
### Browser Environment (Wallet Extensions)
In a browser environment, set `signer` and `mldsaSigner` to `null`. The factory automatically detects the OP_WALLET browser extension (`window.opnet.web3`) and delegates signing to it:
```typescript
// Browser - wallet extension handles signing
const result = await factory.signInteraction({
// signer is OMITTED (or set to null via the WithoutSigner type)
// mldsaSigner is OMITTED
network: networks.bitcoin,
utxos,
from: walletAddress,
to: contractAddress,
feeRate: 10,
// priorityFee and gasSatFee are still required
priorityFee: 1000n,
gasSatFee: 500n,
calldata: encodedCall,
// challenge is OMITTED for browser
});
```
The `WithoutSigner` type variants (`InteractionParametersWithoutSigner`, `IDeploymentParametersWithoutSigner`, etc.) automatically omit `signer`, `mldsaSigner`, and `challenge` from the parameter types.
### Detection Flow
```mermaid
flowchart TB
Start["Method Called"] --> CheckWindow{"typeof window<br/>!== 'undefined'?"}
CheckWindow -->|"No (Backend)"| RequireSigner["Require signer parameter"]
CheckWindow -->|"Yes (Browser)"| CheckOPWallet{"window.opnet.web3<br/>exists?"}
CheckOPWallet -->|"Yes"| Delegate["Delegate to OP_WALLET"]
CheckOPWallet -->|"No"| RequireSigner
RequireSigner --> CheckParam{"'signer' in params?"}
CheckParam -->|"Yes"| SignLocally["Sign with provided signer"]
CheckParam -->|"No"| ThrowError["Throw Error:<br/>signer not provided"]
```
## Complete Examples
### Example 1: Simple BTC Transfer
```typescript
import { TransactionFactory, EcKeyPair, type UTXO } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';
async function sendBitcoin() {
const network = networks.bitcoin;
const factory = new TransactionFactory();
const signer = EcKeyPair.fromWIF(process.env.PRIVATE_KEY!, network);
const address = EcKeyPair.getTaprootAddress(signer, network);
const utxos: UTXO[] = [
{
transactionId: 'abcd1234...'.padEnd(64, '0'),
outputIndex: 0,
value: 100_000n,
scriptPubKey: {
hex: '5120...',
address: address,
},
},
];
const result = await factory.createBTCTransfer({
signer,
mldsaSigner: null,
network,
utxos,
from: address,
to: 'bc1p...recipient',
feeRate: 10,
priorityFee: 0n,
gasSatFee: 0n,
amount: 50_000n,
});
console.log('Transaction hex:', result.tx);
console.log('Fees paid:', result.estimatedFees, 'satoshis');
console.log('Change UTXOs:', result.nextUTXOs);
// Broadcast result.tx to the Bitcoin network
// Save result.nextUTXOs for the next transaction
}
```
### Example 2: Contract Deployment
```typescript
import { TransactionFactory, EcKeyPair } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';
async function deployContract(
bytecode: Uint8Array,
constructorCalldata: Uint8Array,
challenge: IChallengeSolution,
) {
const network = networks.bitcoin;
const factory = new TransactionFactory();
const signer = EcKeyPair.fromWIF(process.env.PRIVATE_KEY!, network);
const address = EcKeyPair.getTaprootAddress(signer, network);
const utxos = await fetchUTXOs(address);
const result = await factory.signDeployment({
signer,
mldsaSigner: null,
network,
utxos,
from: address,
feeRate: 15,
priorityFee: 1000n,
gasSatFee: 500n,
bytecode: bytecode,
calldata: constructorCalldata,
challenge: challenge,
});
// Broadcast BOTH transactions in order
await broadcastTransaction(result.transaction[0]); // Funding tx first
await broadcastTransaction(result.transaction[1]); // Then deployment tx
console.log('Contract deployed at:', result.contractAddress);
console.log('Contract public key:', result.contractPubKey);
console.log('Refund UTXOs:', result.utxos);
}
```
### Example 3: Contract Interaction
```typescript
import { TransactionFactory, EcKeyPair } from '@btc-vision/transaction';
import { networks } from '@btc-vision/bitcoin';
async function callContract(
contractAddress: string,
calldata: Uint8Array,
challenge: IChallengeSolution,
) {
const network = networks.bitcoin;
const factory = new TransactionFactory();
const signer = EcKeyPair.fromWIF(process.env.PRIVATE_KEY!, network);
const address = EcKeyPair.getTaprootAddress(signer, network);
const utxos = await fetchUTXOs(address);
const result = await factory.signInteraction({
signer,
mldsaSigner: null,
network,
utxos,
from: address,
to: contractAddress,
feeRate: 10,
priorityFee: 1000n,
gasSatFee: 500n,
calldata: calldata,
challenge: challenge,
});
// For standard interactions: broadcast both transactions
if (result.fundingTransaction) {
await broadcastTransaction(result.fundingTransaction);
}
await broadcastTransaction(result.interactionTransaction);
console.log('Interaction address:', result.interactionAddress);
console.log('Estimated fees:', result.estimatedFees, 'satoshis');
console.log('Change UTXOs:', result.nextUTXOs);
// Save compiledTargetScript in case cancellation is needed
if (result.compiledTargetScript) {
saveCancelScript(result.compiledTargetScript);
}
}
```
### Example 4: Cancel a Stuck Transaction
```typescript
async function cancelStuckTransaction(
stuckUtxos: UTXO[],
compiledTargetScript: string,
) {
const factory = new TransactionFactory();
const result = await factory.createCancellableTransaction({
signer,
mldsaSigner: null,
network: networks.bitcoin,
utxos: stuckUtxos,
from: myAddress,
to: myAddress,
feeRate: 20, // Higher fee to ensure confirmation
compiledTargetScript: compiledTargetScript,
});
await broadcastTransaction(result.transaction);
console.log('Funds recovered! UTXOs:', result.nextUTXOs);
}
```
### Example 5: Send-Max with autoAdjustAmount
```typescript
// Send the entire UTXO balance minus fees
const result = await factory.createBTCTransfer({
signer,
mldsaSigner: null,
network,
utxos: allMyUtxos,
from: address,
to: 'bc1p...recipient',
feeRate: 10,
priorityFee: 0n,
gasSatFee: 0n,
amount: totalUtxoValue, // Set amount to total value
autoAdjustAmount: true, // Fees deducted from amount automatically
});
```
## Best Practices
### UTXO Management
Always track and reuse the `nextUTXOs` returned by each transaction method. These are your change outputs and represent your available balance for subsequent transactions.
```typescript
let availableUtxos = await fetchUTXOs(address);
const result1 = await factory.createBTCTransfer({ utxos: availableUtxos, ... });
availableUtxos = result1.nextUTXOs; // Update for next transaction
const result2 = await factory.signInteraction({ utxos: availableUtxos, ... });
availableUtxos = result2.nextUTXOs; // Update again
```
### Fee Rate Selection
Use realistic fee rates from mempool data. The factory uses the fee rate for vSize-based fee calculation. Setting the fee too low risks the transaction not confirming; setting it too high wastes satoshis.
### Save compiledTargetScript
For deployment and interaction transactions, always save the `compiledTargetScript` from the response. If the second transaction fails to confirm (e.g., due to network congestion), you will need this script to cancel and recover funds.
### Error Handling
```typescript
try {
const result = await factory.signInteraction(params);
} catch (error) {
if (error.message.includes('not provided')) {
// Missing required parameter
} else if (error.message.includes('Missing at least one UTXO')) {
// Empty UTXO array
} else if (error.message.includes('Insufficient funds')) {
// Not enough satoshis to cover amount + fees
} else if (error.message.includes('Failed to converge')) {
// Fee estimation did not stabilize (rare edge case)
}
}
```
### Debug Mode
Enable debug logging to trace the iterative fee estimation process:
```typescript
const factory = new TransactionFactory();
factory.debug = true;
// Console will show iteration logs:
// "Interaction Iteration 1: Previous=2000, New=3456"
// "Interaction Iteration 2: Previous=3456, New=3456"
```
## Navigation
- **Previous:** [Transaction Building Guide](../transaction-building.md)
- **Next:** [Transaction Factory Interfaces](./transaction-factory-interfaces.md)
- **Related:** [Offline Transaction Signing](../offline-transaction-signing.md)