Skip to content

Instantly share code, notes, and snippets.

@ezynda3
Created July 27, 2025 13:32
Show Gist options
  • Select an option

  • Save ezynda3/f9e7d16b7468e62fbd68bd70f9a8f155 to your computer and use it in GitHub Desktop.

Select an option

Save ezynda3/f9e7d16b7468e62fbd68bd70f9a8f155 to your computer and use it in GitHub Desktop.
Creating Bitcoin Transactions with Bigmi and bitcoinjs-lib

Creating Bitcoin Transactions with Bigmi and bitcoinjs-lib

The bigmi library doesn't include functions for generating raw transactions. It focuses on blockchain data retrieval and transaction broadcasting. For transaction creation, use bitcoinjs-lib which bigmi already depends on.

Recommended Approach: Using bitcoinjs-lib

1. Basic Transaction Creation

import * as bitcoin from 'bitcoinjs-lib';
import { getUTXOs } from '@bigmi/core';

async function createTransaction(
  client: Client,
  fromAddress: string,
  toAddress: string,
  amount: number,
  privateKey: Buffer
) {
  // 1. Get UTXOs for the address
  const utxos = await getUTXOs(client, { address: fromAddress });
  
  // 2. Create a new transaction
  const psbt = new bitcoin.Psbt();
  
  // 3. Add inputs from UTXOs
  let inputValue = 0;
  for (const utxo of utxos) {
    psbt.addInput({
      hash: utxo.txId,
      index: utxo.vout,
      witnessUtxo: {
        script: Buffer.from(utxo.scriptHex, 'hex'),
        value: utxo.value,
      },
    });
    inputValue += utxo.value;
    if (inputValue >= amount + estimatedFee) break;
  }
  
  // 4. Add outputs
  psbt.addOutput({
    address: toAddress,
    value: amount,
  });
  
  // 5. Add change output if needed
  const fee = 1000; // Calculate appropriate fee
  const change = inputValue - amount - fee;
  if (change > 0) {
    psbt.addOutput({
      address: fromAddress,
      value: change,
    });
  }
  
  // 6. Sign the transaction
  psbt.signAllInputs(bitcoin.ECPair.fromPrivateKey(privateKey));
  psbt.finalizeAllInputs();
  
  // 7. Get the raw transaction hex
  const rawTx = psbt.extractTransaction().toHex();
  
  return rawTx;
}

2. Using PSBT (Partially Signed Bitcoin Transaction)

import * as bitcoin from 'bitcoinjs-lib';

function createPSBT(
  utxos: UTXO[],
  outputs: { address: string; value: number }[]
): bitcoin.Psbt {
  const psbt = new bitcoin.Psbt();
  
  // Add inputs
  for (const utxo of utxos) {
    psbt.addInput({
      hash: utxo.txId,
      index: utxo.vout,
      witnessUtxo: {
        script: Buffer.from(utxo.scriptHex, 'hex'),
        value: utxo.value,
      },
    });
  }
  
  // Add outputs
  for (const output of outputs) {
    psbt.addOutput({
      address: output.address,
      value: output.value,
    });
  }
  
  return psbt;
}

3. Complete Flow with bigmi

import * as bitcoin from 'bitcoinjs-lib';
import { createClient, getUTXOs, sendUTXOTransaction, waitForTransaction } from '@bigmi/core';

async function sendBitcoin(
  client: Client,
  fromAddress: string,
  toAddress: string,
  amount: number,
  privateKey: Buffer
) {
  // 1. Get UTXOs using bigmi
  const utxos = await getUTXOs(client, { 
    address: fromAddress,
    minValue: amount + 1000 // amount + estimated fee
  });
  
  // 2. Create transaction with bitcoinjs-lib
  const psbt = new bitcoin.Psbt();
  
  // Add inputs and calculate total
  let totalInput = 0;
  for (const utxo of utxos) {
    psbt.addInput({
      hash: utxo.txId,
      index: utxo.vout,
      witnessUtxo: {
        script: Buffer.from(utxo.scriptHex, 'hex'),
        value: utxo.value,
      },
    });
    totalInput += utxo.value;
  }
  
  // Add output
  psbt.addOutput({
    address: toAddress,
    value: amount,
  });
  
  // Add change output
  const fee = 1000; // Use fee estimation
  const change = totalInput - amount - fee;
  if (change > 0) {
    psbt.addOutput({
      address: fromAddress,
      value: change,
    });
  }
  
  // Sign
  const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey);
  psbt.signAllInputs(keyPair);
  psbt.finalizeAllInputs();
  
  // Get raw transaction
  const rawTx = psbt.extractTransaction();
  const txHex = rawTx.toHex();
  
  // 3. Broadcast using bigmi
  const txId = await sendUTXOTransaction(client, { hex: txHex });
  
  // 4. Wait for confirmation using bigmi
  const confirmedTx = await waitForTransaction(client, {
    txId,
    txHex,
    senderAddress: fromAddress,
    confirmations: 1,
  });
  
  return confirmedTx;
}

4. Fee Estimation

async function estimateFee(
  client: Client,
  numInputs: number,
  numOutputs: number
): Promise<number> {
  // Get recent block stats for fee estimation
  const blockStats = await getBlockStats(client, {
    blockNumber: await getBlockCount(client),
    stats: ['avgfeerate']
  });
  
  // Estimate transaction size
  // P2WPKH: ~68 bytes per input, ~31 bytes per output, ~10 bytes overhead
  const estimatedSize = (numInputs * 68) + (numOutputs * 31) + 10;
  
  // Calculate fee (satoshis per byte * size)
  const feeRate = blockStats.avgfeerate || 1; // fallback to 1 sat/byte
  return Math.ceil(feeRate * estimatedSize);
}

5. Working with Different Address Types

import { getAddressInfo } from '@bigmi/core';

function createInputForUTXO(utxo: UTXO, addressInfo: AddressInfo) {
  const input: any = {
    hash: utxo.txId,
    index: utxo.vout,
  };
  
  switch (addressInfo.type) {
    case 'p2wpkh':
    case 'p2wsh':
      // Witness UTXOs for SegWit
      input.witnessUtxo = {
        script: Buffer.from(utxo.scriptHex, 'hex'),
        value: utxo.value,
      };
      break;
    case 'p2pkh':
    case 'p2sh':
      // Need full transaction for legacy
      // You'd need to fetch the full transaction here
      input.nonWitnessUtxo = Buffer.from(fullTransactionHex, 'hex');
      break;
  }
  
  return input;
}

6. Advanced: Multi-signature Transaction

import * as bitcoin from 'bitcoinjs-lib';

function createMultisigTransaction(
  utxos: UTXO[],
  toAddress: string,
  amount: number,
  redeemScript: Buffer,
  signatures: Buffer[]
) {
  const psbt = new bitcoin.Psbt();
  
  // Add inputs with redeem script
  for (const utxo of utxos) {
    psbt.addInput({
      hash: utxo.txId,
      index: utxo.vout,
      witnessUtxo: {
        script: Buffer.from(utxo.scriptHex, 'hex'),
        value: utxo.value,
      },
      witnessScript: redeemScript,
    });
  }
  
  // Add output
  psbt.addOutput({
    address: toAddress,
    value: amount,
  });
  
  // Add signatures
  signatures.forEach((sig, index) => {
    psbt.updateInput(index, {
      partialSig: [{
        pubkey: extractPubkeyFromSig(sig),
        signature: sig,
      }],
    });
  });
  
  psbt.finalizeAllInputs();
  return psbt.extractTransaction().toHex();
}

7. RBF (Replace-By-Fee) Transaction

function createRBFTransaction(
  originalPsbt: bitcoin.Psbt,
  newFeeRate: number
): bitcoin.Psbt {
  const newPsbt = new bitcoin.Psbt();
  
  // Copy inputs with RBF sequence
  originalPsbt.data.inputs.forEach((input, index) => {
    newPsbt.addInput({
      hash: originalPsbt.txInputs[index].hash,
      index: originalPsbt.txInputs[index].index,
      sequence: 0xfffffffd, // RBF enabled
      witnessUtxo: input.witnessUtxo,
    });
  });
  
  // Adjust outputs for new fee
  const totalInput = calculateTotalInput(originalPsbt);
  const totalOutput = calculateTotalOutput(originalPsbt);
  const oldFee = totalInput - totalOutput;
  const newFee = calculateNewFee(newFeeRate);
  
  originalPsbt.txOutputs.forEach((output, index) => {
    if (isChangeOutput(output)) {
      // Reduce change by fee difference
      newPsbt.addOutput({
        script: output.script,
        value: output.value - (newFee - oldFee),
      });
    } else {
      // Keep other outputs unchanged
      newPsbt.addOutput({
        script: output.script,
        value: output.value,
      });
    }
  });
  
  return newPsbt;
}

8. UTXO Selection Strategies

// Coin selection algorithms
function selectUTXOs(
  utxos: UTXO[],
  targetAmount: number,
  strategy: 'fifo' | 'largest' | 'optimal'
): UTXO[] {
  const sortedUTXOs = [...utxos];
  
  switch (strategy) {
    case 'fifo':
      // First In First Out - use oldest UTXOs first
      sortedUTXOs.sort((a, b) => a.blockHeight - b.blockHeight);
      break;
    case 'largest':
      // Use largest UTXOs first to minimize inputs
      sortedUTXOs.sort((a, b) => b.value - a.value);
      break;
    case 'optimal':
      // Branch and bound algorithm for optimal selection
      return branchAndBoundSelection(utxos, targetAmount);
  }
  
  const selected: UTXO[] = [];
  let total = 0;
  
  for (const utxo of sortedUTXOs) {
    selected.push(utxo);
    total += utxo.value;
    if (total >= targetAmount) break;
  }
  
  return selected;
}

Alternative Libraries for Transaction Creation

  1. @scure/btc-signer - Modern, audited Bitcoin transaction library

    import { Transaction } from '@scure/btc-signer';
  2. bitcore-lib - Alternative to bitcoinjs-lib

    import bitcore from 'bitcore-lib';
  3. bcoin - Full Bitcoin implementation with transaction creation

    import { MTX } from 'bcoin';

Integration Pattern

The typical pattern for using bigmi with transaction creation:

  1. Fetch blockchain data with bigmi

    • Get UTXOs
    • Check balances
    • Estimate fees from block stats
  2. Create and sign transactions with bitcoinjs-lib

    • Build PSBT
    • Add inputs and outputs
    • Sign with private keys
  3. Broadcast and monitor with bigmi

    • Send raw transaction
    • Wait for confirmations
    • Handle replacements

Security Considerations

  1. Never expose private keys in client-side code
  2. Use hardware wallets for production applications
  3. Validate all inputs before creating transactions
  4. Test on testnet before mainnet deployment
  5. Implement proper error handling for all edge cases

Example: Complete Send Function with Error Handling

async function safeSendBitcoin(
  client: Client,
  fromAddress: string,
  toAddress: string,
  amount: number,
  privateKey: Buffer,
  options?: {
    feeRate?: number;
    rbfEnabled?: boolean;
  }
) {
  try {
    // Validate addresses
    const fromInfo = getAddressInfo(fromAddress);
    const toInfo = getAddressInfo(toAddress);
    
    // Check balance
    const balance = await getBalance(client, { address: fromAddress });
    if (balance < amount + 1000) {
      throw new Error('Insufficient balance');
    }
    
    // Get UTXOs
    const utxos = await getUTXOs(client, {
      address: fromAddress,
      minValue: amount + 1000,
    });
    
    if (utxos.length === 0) {
      throw new Error('No UTXOs available');
    }
    
    // Create transaction
    const psbt = new bitcoin.Psbt();
    
    let totalInput = 0;
    for (const utxo of utxos) {
      psbt.addInput({
        hash: utxo.txId,
        index: utxo.vout,
        sequence: options?.rbfEnabled ? 0xfffffffd : 0xffffffff,
        witnessUtxo: {
          script: Buffer.from(utxo.scriptHex, 'hex'),
          value: utxo.value,
        },
      });
      totalInput += utxo.value;
    }
    
    // Add recipient output
    psbt.addOutput({
      address: toAddress,
      value: amount,
    });
    
    // Calculate and add change
    const fee = await estimateFee(client, utxos.length, 2);
    const change = totalInput - amount - fee;
    
    if (change < 0) {
      throw new Error('Insufficient funds for fee');
    }
    
    if (change > 546) { // Dust threshold
      psbt.addOutput({
        address: fromAddress,
        value: change,
      });
    }
    
    // Sign transaction
    const keyPair = bitcoin.ECPair.fromPrivateKey(privateKey);
    psbt.signAllInputs(keyPair);
    psbt.finalizeAllInputs();
    
    // Broadcast
    const txHex = psbt.extractTransaction().toHex();
    const txId = await sendUTXOTransaction(client, { hex: txHex });
    
    // Wait for confirmation
    const confirmed = await waitForTransaction(client, {
      txId,
      txHex,
      senderAddress: fromAddress,
      confirmations: 1,
      onReplaced: (replacement) => {
        console.log('Transaction replaced:', replacement);
      },
    });
    
    return {
      txId,
      fee,
      confirmed,
    };
    
  } catch (error) {
    console.error('Transaction failed:', error);
    throw error;
  }
}

This separation of concerns makes sense as transaction creation involves key management and signing, which are intentionally kept separate from blockchain data operations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment