PHP WebShell

Текущая директория: /opt/BitGoJS/modules/babylonlabs-io-btc-staking-ts/src/utils/fee

Просмотр файла: index.ts

import { script as bitcoinScript } from "bitcoinjs-lib";
import { BTC_DUST_SAT } from "../../constants/dustSat";
import {
  LOW_RATE_ESTIMATION_ACCURACY_BUFFER,
  MAX_NON_LEGACY_OUTPUT_SIZE,
  OP_RETURN_OUTPUT_VALUE_SIZE,
  OP_RETURN_VALUE_SERIALIZE_SIZE,
  P2TR_INPUT_SIZE,
  TX_BUFFER_SIZE_OVERHEAD,
  WALLET_RELAY_FEE_RATE_THRESHOLD,
  WITHDRAW_TX_BUFFER_SIZE,
} from "../../constants/fee";
import { UTXO } from "../../types/UTXO";
import {
  TransactionOutput,
} from "../../types/psbtOutputs";
import {
  getEstimatedChangeOutputSize,
  getInputSizeByScript,
  isOP_RETURN,
} from "./utils";

/**
 * Selects UTXOs and calculates the fee for a staking transaction.
 * This method selects the highest value UTXOs from all available UTXOs to
 * cover the staking amount and the transaction fees.
 * The formula used is:
 *
 * totalFee = (inputSize + outputSize) * feeRate + buffer
 * where outputSize may or may not include the change output size depending on the remaining value.
 *
 * @param availableUTXOs - All available UTXOs from the wallet.
 * @param stakingAmount - The amount to stake.
 * @param feeRate - The fee rate in satoshis per byte.
 * @param outputs - The outputs in the transaction.
 * @returns An object containing the selected UTXOs and the fee.
 * @throws Will throw an error if there are insufficient funds or if the fee cannot be calculated.
 */
export const getStakingTxInputUTXOsAndFees = (
  availableUTXOs: UTXO[],
  stakingAmount: number,
  feeRate: number,
  outputs: TransactionOutput[],
): {
  selectedUTXOs: UTXO[];
  fee: number;
} => {
  if (availableUTXOs.length === 0) {
    throw new Error("Insufficient funds");
  }

  const validUTXOs = availableUTXOs.filter((utxo) => {
    const script = Buffer.from(utxo.scriptPubKey, "hex");
    return !!bitcoinScript.decompile(script);
  });

  if (validUTXOs.length === 0) {
    throw new Error("Insufficient funds: no valid UTXOs available for staking");
  }

  // Sort available UTXOs from highest to lowest value
  const sortedUTXOs = validUTXOs.sort((a, b) => b.value - a.value);

  const selectedUTXOs: UTXO[] = [];
  let accumulatedValue = 0;
  let estimatedFee = 0;

  for (const utxo of sortedUTXOs) {
    selectedUTXOs.push(utxo);
    accumulatedValue += utxo.value;

    // Calculate the fee for the current set of UTXOs and outputs
    const estimatedSize = getEstimatedSize(selectedUTXOs, outputs);
    estimatedFee = estimatedSize * feeRate + rateBasedTxBufferFee(feeRate);
    // Check if there will be any change left after the staking amount and fee.
    // If there is, a change output needs to be added, which also comes with an additional fee.
    if (accumulatedValue - (stakingAmount + estimatedFee) > BTC_DUST_SAT) {
      estimatedFee += getEstimatedChangeOutputSize() * feeRate;
    }
    if (accumulatedValue >= stakingAmount + estimatedFee) {
      break;
    }
  }

  if (accumulatedValue < stakingAmount + estimatedFee) {
    throw new Error(
      "Insufficient funds: unable to gather enough UTXOs to cover the staking amount and fees",
    );
  }

  return {
    selectedUTXOs,
    fee: estimatedFee,
  };
};


/**
 * Calculates the estimated fee for a withdrawal transaction.
 * The fee calculation is based on estimated constants for input size,
 * output size, and additional overhead specific to withdrawal transactions.
 * Due to the slightly larger size of withdrawal transactions, an additional
 * buffer is included to account for this difference.
 *
 * @param feeRate - The fee rate in satoshis per vbyte.
 * @returns The estimated fee for a withdrawal transaction in satoshis.
 */
export const getWithdrawTxFee = (feeRate: number): number => {
  const inputSize = P2TR_INPUT_SIZE;
  const outputSize = getEstimatedChangeOutputSize();
  return (
    feeRate *
      (inputSize +
        outputSize +
        TX_BUFFER_SIZE_OVERHEAD +
        WITHDRAW_TX_BUFFER_SIZE) +
    rateBasedTxBufferFee(feeRate)
  );
};


/**
 * Calculates the estimated transaction size using a heuristic formula which
 * includes the input size, output size, and a fixexd buffer for the transaction size.
 * The formula used is:
 *
 * totalSize = inputSize + outputSize + TX_BUFFER_SIZE_OVERHEAD
 *
 * @param inputUtxos - The UTXOs used as inputs in the transaction.
 * @param outputs - The outputs in the transaction.
 * @returns The estimated transaction size in bytes.
 */
const getEstimatedSize = (
  inputUtxos: UTXO[],
  outputs: TransactionOutput[],
): number => {
  // Estimate the input size
  const inputSize = inputUtxos.reduce((acc: number, u: UTXO): number => {
    const script = Buffer.from(u.scriptPubKey, "hex");
    const decompiledScript = bitcoinScript.decompile(script);
    if (!decompiledScript) {
      // Skip UTXOs with scripts that cannot be decompiled
      return acc;
    }
    return acc + getInputSizeByScript(script);
  }, 0);

  // Estimate the output size
  const outputSize = outputs.reduce((acc, output): number => {
    if (isOP_RETURN(output.scriptPubKey)) {
      return (
        acc +
        output.scriptPubKey.length +
        OP_RETURN_OUTPUT_VALUE_SIZE +
        OP_RETURN_VALUE_SERIALIZE_SIZE
      );
    }
    return acc + MAX_NON_LEGACY_OUTPUT_SIZE;
  }, 0);

  return inputSize + outputSize + TX_BUFFER_SIZE_OVERHEAD;
};

/**
 * Adds a buffer to the transaction size-based fee calculation if the fee rate is low.
 * Some wallets have a relayer fee requirement, which means if the fee rate is
 * less than or equal to WALLET_RELAY_FEE_RATE_THRESHOLD (2 satoshis per byte),
 * there is a risk that the fee might not be sufficient to get the transaction relayed.
 * To mitigate this risk, we add a buffer to the fee calculation to ensure that
 * the transaction can be relayed.
 *
 * If the fee rate is less than or equal to WALLET_RELAY_FEE_RATE_THRESHOLD, a fixed buffer is added
 * (LOW_RATE_ESTIMATION_ACCURACY_BUFFER). If the fee rate is higher, no buffer is added.
 *
 * @param feeRate - The fee rate in satoshis per byte.
 * @returns The buffer amount in satoshis to be added to the transaction fee.
 */
const rateBasedTxBufferFee = (feeRate: number): number => {
  return feeRate <= WALLET_RELAY_FEE_RATE_THRESHOLD
    ? LOW_RATE_ESTIMATION_ACCURACY_BUFFER
    : 0;
};

Выполнить команду


Для локальной разработки. Не используйте в интернете!