PHP WebShell

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

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

// src/staking/stakingScript.ts
import { opcodes, script } from "bitcoinjs-lib";

// src/constants/keys.ts
var NO_COORD_PK_BYTE_LENGTH = 32;

// src/staking/stakingScript.ts
var MAGIC_BYTES_LEN = 4;
var StakingScriptData = class {
  constructor(stakerKey, finalityProviderKeys, covenantKeys, covenantThreshold, stakingTimelock, unbondingTimelock) {
    if (!stakerKey || !finalityProviderKeys || !covenantKeys || !covenantThreshold || !stakingTimelock || !unbondingTimelock) {
      throw new Error("Missing required input values");
    }
    this.stakerKey = stakerKey;
    this.finalityProviderKeys = finalityProviderKeys;
    this.covenantKeys = covenantKeys;
    this.covenantThreshold = covenantThreshold;
    this.stakingTimeLock = stakingTimelock;
    this.unbondingTimeLock = unbondingTimelock;
    if (!this.validate()) {
      throw new Error("Invalid script data provided");
    }
  }
  /**
   * Validates the staking script.
   * @returns {boolean} Returns true if the staking script is valid, otherwise false.
   */
  validate() {
    if (this.stakerKey.length != NO_COORD_PK_BYTE_LENGTH) {
      return false;
    }
    if (this.finalityProviderKeys.some(
      (finalityProviderKey) => finalityProviderKey.length != NO_COORD_PK_BYTE_LENGTH
    )) {
      return false;
    }
    if (this.covenantKeys.some((covenantKey) => covenantKey.length != NO_COORD_PK_BYTE_LENGTH)) {
      return false;
    }
    const allPks = [
      this.stakerKey,
      ...this.finalityProviderKeys,
      ...this.covenantKeys
    ];
    const allPksSet = new Set(allPks);
    if (allPks.length !== allPksSet.size) {
      return false;
    }
    if (this.covenantThreshold <= 0 || this.covenantThreshold > this.covenantKeys.length) {
      return false;
    }
    if (this.stakingTimeLock <= 0 || this.stakingTimeLock > 65535) {
      return false;
    }
    if (this.unbondingTimeLock <= 0 || this.unbondingTimeLock > 65535) {
      return false;
    }
    return true;
  }
  // The staking script allows for multiple finality provider public keys
  // to support (re)stake to multiple finality providers
  // Covenant members are going to have multiple keys
  /**
   * Builds a timelock script.
   * @param timelock - The timelock value to encode in the script.
   * @returns {Buffer} containing the compiled timelock script.
   */
  buildTimelockScript(timelock) {
    return script.compile([
      this.stakerKey,
      opcodes.OP_CHECKSIGVERIFY,
      script.number.encode(timelock),
      opcodes.OP_CHECKSEQUENCEVERIFY
    ]);
  }
  /**
   * Builds the staking timelock script.
   * Only holder of private key for given pubKey can spend after relative lock time
   * Creates the timelock script in the form:
   *    <stakerPubKey>
   *    OP_CHECKSIGVERIFY
   *    <stakingTimeBlocks>
   *    OP_CHECKSEQUENCEVERIFY
   * @returns {Buffer} The staking timelock script.
   */
  buildStakingTimelockScript() {
    return this.buildTimelockScript(this.stakingTimeLock);
  }
  /**
   * Builds the unbonding timelock script.
   * Creates the unbonding timelock script in the form:
   *    <stakerPubKey>
   *    OP_CHECKSIGVERIFY
   *    <unbondingTimeBlocks>
   *    OP_CHECKSEQUENCEVERIFY
   * @returns {Buffer} The unbonding timelock script.
   */
  buildUnbondingTimelockScript() {
    return this.buildTimelockScript(this.unbondingTimeLock);
  }
  /**
   * Builds the unbonding script in the form:
   *    buildSingleKeyScript(stakerPk, true) ||
   *    buildMultiKeyScript(covenantPks, covenantThreshold, false)
   *    || means combining the scripts
   * @returns {Buffer} The unbonding script.
   */
  buildUnbondingScript() {
    return Buffer.concat([
      this.buildSingleKeyScript(this.stakerKey, true),
      this.buildMultiKeyScript(
        this.covenantKeys,
        this.covenantThreshold,
        false
      )
    ]);
  }
  /**
   * Builds the slashing script for staking in the form:
   *    buildSingleKeyScript(stakerPk, true) ||
   *    buildMultiKeyScript(finalityProviderPKs, 1, true) ||
   *    buildMultiKeyScript(covenantPks, covenantThreshold, false)
   *    || means combining the scripts
   * The slashing script is a combination of single-key and multi-key scripts.
   * The single-key script is used for staker key verification.
   * The multi-key script is used for finality provider key verification and covenant key verification.
   * @returns {Buffer} The slashing script as a Buffer.
   */
  buildSlashingScript() {
    return Buffer.concat([
      this.buildSingleKeyScript(this.stakerKey, true),
      this.buildMultiKeyScript(
        this.finalityProviderKeys,
        // The threshold is always 1 as we only need one
        // finalityProvider signature to perform slashing
        // (only one finalityProvider performs an offence)
        1,
        // OP_VERIFY/OP_CHECKSIGVERIFY is added at the end
        true
      ),
      this.buildMultiKeyScript(
        this.covenantKeys,
        this.covenantThreshold,
        // No need to add verify since covenants are at the end of the script
        false
      )
    ]);
  }
  /**
   * Builds the staking scripts.
   * @returns {StakingScripts} The staking scripts.
   */
  buildScripts() {
    return {
      timelockScript: this.buildStakingTimelockScript(),
      unbondingScript: this.buildUnbondingScript(),
      slashingScript: this.buildSlashingScript(),
      unbondingTimelockScript: this.buildUnbondingTimelockScript()
    };
  }
  // buildSingleKeyScript and buildMultiKeyScript allow us to reuse functionality
  // for creating Bitcoin scripts for the unbonding script and the slashing script
  /**
   * Builds a single key script in the form:
   * buildSingleKeyScript creates a single key script
   *    <pk> OP_CHECKSIGVERIFY (if withVerify is true)
   *    <pk> OP_CHECKSIG (if withVerify is false)
   * @param pk - The public key buffer.
   * @param withVerify - A boolean indicating whether to include the OP_CHECKSIGVERIFY opcode.
   * @returns The compiled script buffer.
   */
  buildSingleKeyScript(pk, withVerify) {
    if (pk.length != NO_COORD_PK_BYTE_LENGTH) {
      throw new Error("Invalid key length");
    }
    return script.compile([
      pk,
      withVerify ? opcodes.OP_CHECKSIGVERIFY : opcodes.OP_CHECKSIG
    ]);
  }
  /**
   * Builds a multi-key script in the form:
   *    <pk1> OP_CHEKCSIG <pk2> OP_CHECKSIGADD <pk3> OP_CHECKSIGADD ... <pkN> OP_CHECKSIGADD <threshold> OP_NUMEQUAL
   *    <withVerify -> OP_NUMEQUALVERIFY>
   * It validates whether provided keys are unique and the threshold is not greater than number of keys
   * If there is only one key provided it will return single key sig script
   * @param pks - An array of public keys.
   * @param threshold - The required number of valid signers.
   * @param withVerify - A boolean indicating whether to include the OP_VERIFY opcode.
   * @returns The compiled multi-key script as a Buffer.
   * @throws {Error} If no keys are provided, if the required number of valid signers is greater than the number of provided keys, or if duplicate keys are provided.
   */
  buildMultiKeyScript(pks, threshold, withVerify) {
    if (!pks || pks.length === 0) {
      throw new Error("No keys provided");
    }
    if (pks.some((pk) => pk.length != NO_COORD_PK_BYTE_LENGTH)) {
      throw new Error("Invalid key length");
    }
    if (threshold > pks.length) {
      throw new Error(
        "Required number of valid signers is greater than number of provided keys"
      );
    }
    if (pks.length === 1) {
      return this.buildSingleKeyScript(pks[0], withVerify);
    }
    const sortedPks = [...pks].sort(Buffer.compare);
    for (let i = 0; i < sortedPks.length - 1; ++i) {
      if (sortedPks[i].equals(sortedPks[i + 1])) {
        throw new Error("Duplicate keys provided");
      }
    }
    const scriptElements = [sortedPks[0], opcodes.OP_CHECKSIG];
    for (let i = 1; i < sortedPks.length; i++) {
      scriptElements.push(sortedPks[i]);
      scriptElements.push(opcodes.OP_CHECKSIGADD);
    }
    scriptElements.push(script.number.encode(threshold));
    if (withVerify) {
      scriptElements.push(opcodes.OP_NUMEQUALVERIFY);
    } else {
      scriptElements.push(opcodes.OP_NUMEQUAL);
    }
    return script.compile(scriptElements);
  }
};

// src/error/index.ts
var StakingError = class _StakingError extends Error {
  constructor(code, message) {
    super(message);
    this.code = code;
  }
  // Static method to safely handle unknown errors
  static fromUnknown(error, code, fallbackMsg) {
    if (error instanceof _StakingError) {
      return error;
    }
    if (error instanceof Error) {
      return new _StakingError(code, error.message);
    }
    return new _StakingError(code, fallbackMsg);
  }
};

// src/staking/transactions.ts
import { Psbt, Transaction as Transaction2, payments as payments3, script as script2, address as address3, opcodes as opcodes3 } from "bitcoinjs-lib";

// src/constants/dustSat.ts
var BTC_DUST_SAT = 546;

// src/constants/internalPubkey.ts
var key = "0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0";
var internalPubkey = Buffer.from(key, "hex").subarray(1, 33);

// src/utils/btc.ts
import * as ecc from "@bitcoin-js/tiny-secp256k1-asmjs";
import { initEccLib, address, networks } from "bitcoinjs-lib";
var initBTCCurve = () => {
  initEccLib(ecc);
};
var isValidBitcoinAddress = (btcAddress, network) => {
  try {
    return !!address.toOutputScript(btcAddress, network);
  } catch (error) {
    return false;
  }
};
var isTaproot = (taprootAddress, network) => {
  try {
    const decoded = address.fromBech32(taprootAddress);
    if (decoded.version !== 1) {
      return false;
    }
    if (network.bech32 === networks.bitcoin.bech32) {
      return taprootAddress.startsWith("bc1p");
    } else if (network.bech32 === networks.testnet.bech32) {
      return taprootAddress.startsWith("tb1p") || taprootAddress.startsWith("sb1p");
    }
    return false;
  } catch (error) {
    return false;
  }
};
var isNativeSegwit = (segwitAddress, network) => {
  try {
    const decoded = address.fromBech32(segwitAddress);
    if (decoded.version !== 0) {
      return false;
    }
    if (network.bech32 === networks.bitcoin.bech32) {
      return segwitAddress.startsWith("bc1q");
    } else if (network.bech32 === networks.testnet.bech32) {
      return segwitAddress.startsWith("tb1q");
    }
    return false;
  } catch (error) {
    return false;
  }
};
var isValidNoCoordPublicKey = (pkWithNoCoord) => {
  try {
    const keyBuffer = Buffer.from(pkWithNoCoord, "hex");
    return validateNoCoordPublicKeyBuffer(keyBuffer);
  } catch (error) {
    return false;
  }
};
var getPublicKeyNoCoord = (pkHex) => {
  const publicKey = Buffer.from(pkHex, "hex");
  const publicKeyNoCoordBuffer = publicKey.length === NO_COORD_PK_BYTE_LENGTH ? publicKey : publicKey.subarray(1, 33);
  if (!validateNoCoordPublicKeyBuffer(publicKeyNoCoordBuffer)) {
    throw new Error("Invalid public key without coordinate");
  }
  return publicKeyNoCoordBuffer.toString("hex");
};
var validateNoCoordPublicKeyBuffer = (pkBuffer) => {
  if (pkBuffer.length !== NO_COORD_PK_BYTE_LENGTH) {
    return false;
  }
  const compressedKeyEven = Buffer.concat([Buffer.from([2]), pkBuffer]);
  const compressedKeyOdd = Buffer.concat([Buffer.from([3]), pkBuffer]);
  return ecc.isPoint(compressedKeyEven) || ecc.isPoint(compressedKeyOdd);
};
var transactionIdToHash = (txId) => {
  if (txId === "") {
    throw new Error("Transaction id cannot be empty");
  }
  return Buffer.from(txId, "hex").reverse();
};

// src/utils/fee/index.ts
import { script as bitcoinScript2 } from "bitcoinjs-lib";

// src/constants/fee.ts
var DEFAULT_INPUT_SIZE = 180;
var P2WPKH_INPUT_SIZE = 68;
var P2TR_INPUT_SIZE = 58;
var TX_BUFFER_SIZE_OVERHEAD = 11;
var LOW_RATE_ESTIMATION_ACCURACY_BUFFER = 30;
var MAX_NON_LEGACY_OUTPUT_SIZE = 43;
var WITHDRAW_TX_BUFFER_SIZE = 17;
var WALLET_RELAY_FEE_RATE_THRESHOLD = 2;
var OP_RETURN_OUTPUT_VALUE_SIZE = 8;
var OP_RETURN_VALUE_SERIALIZE_SIZE = 1;

// src/utils/fee/utils.ts
import { script as bitcoinScript, opcodes as opcodes2, payments } from "bitcoinjs-lib";
var isOP_RETURN = (script4) => {
  const decompiled = bitcoinScript.decompile(script4);
  return !!decompiled && decompiled[0] === opcodes2.OP_RETURN;
};
var getInputSizeByScript = (script4) => {
  try {
    const { address: p2wpkhAddress } = payments.p2wpkh({
      output: script4
    });
    if (p2wpkhAddress) {
      return P2WPKH_INPUT_SIZE;
    }
  } catch (error) {
  }
  try {
    const { address: p2trAddress } = payments.p2tr({
      output: script4
    });
    if (p2trAddress) {
      return P2TR_INPUT_SIZE;
    }
  } catch (error) {
  }
  return DEFAULT_INPUT_SIZE;
};
var getEstimatedChangeOutputSize = () => {
  return MAX_NON_LEGACY_OUTPUT_SIZE;
};
var inputValueSum = (inputUTXOs) => {
  return inputUTXOs.reduce((acc, utxo) => acc + utxo.value, 0);
};

// src/utils/fee/index.ts
var getStakingTxInputUTXOsAndFees = (availableUTXOs, stakingAmount, feeRate, outputs) => {
  if (availableUTXOs.length === 0) {
    throw new Error("Insufficient funds");
  }
  const validUTXOs = availableUTXOs.filter((utxo) => {
    const script4 = Buffer.from(utxo.scriptPubKey, "hex");
    return !!bitcoinScript2.decompile(script4);
  });
  if (validUTXOs.length === 0) {
    throw new Error("Insufficient funds: no valid UTXOs available for staking");
  }
  const sortedUTXOs = validUTXOs.sort((a, b) => b.value - a.value);
  const selectedUTXOs = [];
  let accumulatedValue = 0;
  let estimatedFee = 0;
  for (const utxo of sortedUTXOs) {
    selectedUTXOs.push(utxo);
    accumulatedValue += utxo.value;
    const estimatedSize = getEstimatedSize(selectedUTXOs, outputs);
    estimatedFee = estimatedSize * feeRate + rateBasedTxBufferFee(feeRate);
    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
  };
};
var getWithdrawTxFee = (feeRate) => {
  const inputSize = P2TR_INPUT_SIZE;
  const outputSize = getEstimatedChangeOutputSize();
  return feeRate * (inputSize + outputSize + TX_BUFFER_SIZE_OVERHEAD + WITHDRAW_TX_BUFFER_SIZE) + rateBasedTxBufferFee(feeRate);
};
var getEstimatedSize = (inputUtxos, outputs) => {
  const inputSize = inputUtxos.reduce((acc, u) => {
    const script4 = Buffer.from(u.scriptPubKey, "hex");
    const decompiledScript = bitcoinScript2.decompile(script4);
    if (!decompiledScript) {
      return acc;
    }
    return acc + getInputSizeByScript(script4);
  }, 0);
  const outputSize = outputs.reduce((acc, output) => {
    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;
};
var rateBasedTxBufferFee = (feeRate) => {
  return feeRate <= WALLET_RELAY_FEE_RATE_THRESHOLD ? LOW_RATE_ESTIMATION_ACCURACY_BUFFER : 0;
};

// src/utils/staking/index.ts
import { address as address2, payments as payments2 } from "bitcoinjs-lib";

// src/constants/unbonding.ts
var MIN_UNBONDING_OUTPUT_VALUE = 1e3;

// src/utils/staking/index.ts
var buildStakingTransactionOutputs = (scripts, network, amount) => {
  const stakingOutputInfo = deriveStakingOutputInfo(scripts, network);
  const transactionOutputs = [
    {
      scriptPubKey: stakingOutputInfo.scriptPubKey,
      value: amount
    }
  ];
  if (scripts.dataEmbedScript) {
    transactionOutputs.push({
      scriptPubKey: scripts.dataEmbedScript,
      value: 0
    });
  }
  return transactionOutputs;
};
var deriveStakingOutputInfo = (scripts, network) => {
  const scriptTree = [
    {
      output: scripts.slashingScript
    },
    [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
  ];
  const stakingOutput = payments2.p2tr({
    internalPubkey,
    scriptTree,
    network
  });
  if (!stakingOutput.address) {
    throw new StakingError(
      "INVALID_OUTPUT" /* INVALID_OUTPUT */,
      "Failed to build staking output"
    );
  }
  return {
    outputAddress: stakingOutput.address,
    scriptPubKey: address2.toOutputScript(stakingOutput.address, network)
  };
};
var deriveUnbondingOutputInfo = (scripts, network) => {
  const outputScriptTree = [
    {
      output: scripts.slashingScript
    },
    { output: scripts.unbondingTimelockScript }
  ];
  const unbondingOutput = payments2.p2tr({
    internalPubkey,
    scriptTree: outputScriptTree,
    network
  });
  if (!unbondingOutput.address) {
    throw new StakingError(
      "INVALID_OUTPUT" /* INVALID_OUTPUT */,
      "Failed to build unbonding output"
    );
  }
  return {
    outputAddress: unbondingOutput.address,
    scriptPubKey: address2.toOutputScript(unbondingOutput.address, network)
  };
};
var deriveSlashingOutput = (scripts, network) => {
  const slashingOutput = payments2.p2tr({
    internalPubkey,
    scriptTree: { output: scripts.unbondingTimelockScript },
    network
  });
  const slashingOutputAddress = slashingOutput.address;
  if (!slashingOutputAddress) {
    throw new StakingError(
      "INVALID_OUTPUT" /* INVALID_OUTPUT */,
      "Failed to build slashing output address"
    );
  }
  return {
    outputAddress: slashingOutputAddress,
    scriptPubKey: address2.toOutputScript(slashingOutputAddress, network)
  };
};
var findMatchingTxOutputIndex = (tx, outputAddress, network) => {
  const index = tx.outs.findIndex((output) => {
    return address2.fromOutputScript(output.script, network) === outputAddress;
  });
  if (index === -1) {
    throw new StakingError(
      "INVALID_OUTPUT" /* INVALID_OUTPUT */,
      `Matching output not found for address: ${outputAddress}`
    );
  }
  return index;
};
var validateStakingTxInputData = (stakingAmountSat, timelock, params, inputUTXOs, feeRate) => {
  if (stakingAmountSat < params.minStakingAmountSat || stakingAmountSat > params.maxStakingAmountSat) {
    throw new StakingError(
      "INVALID_INPUT" /* INVALID_INPUT */,
      "Invalid staking amount"
    );
  }
  if (timelock < params.minStakingTimeBlocks || timelock > params.maxStakingTimeBlocks) {
    throw new StakingError(
      "INVALID_INPUT" /* INVALID_INPUT */,
      "Invalid timelock"
    );
  }
  if (inputUTXOs.length == 0) {
    throw new StakingError(
      "INVALID_INPUT" /* INVALID_INPUT */,
      "No input UTXOs provided"
    );
  }
  if (feeRate <= 0) {
    throw new StakingError(
      "INVALID_INPUT" /* INVALID_INPUT */,
      "Invalid fee rate"
    );
  }
};
var validateParams = (params) => {
  if (params.covenantNoCoordPks.length == 0) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Could not find any covenant public keys"
    );
  }
  if (params.covenantNoCoordPks.length < params.covenantQuorum) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Covenant public keys must be greater than or equal to the quorum"
    );
  }
  params.covenantNoCoordPks.forEach((pk) => {
    if (!isValidNoCoordPublicKey(pk)) {
      throw new StakingError(
        "INVALID_PARAMS" /* INVALID_PARAMS */,
        "Covenant public key should contains no coordinate"
      );
    }
  });
  if (params.unbondingTime <= 0) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Unbonding time must be greater than 0"
    );
  }
  if (params.unbondingFeeSat <= 0) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Unbonding fee must be greater than 0"
    );
  }
  if (params.maxStakingAmountSat < params.minStakingAmountSat) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Max staking amount must be greater or equal to min staking amount"
    );
  }
  if (params.minStakingAmountSat < params.unbondingFeeSat + MIN_UNBONDING_OUTPUT_VALUE) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      `Min staking amount must be greater than unbonding fee plus ${MIN_UNBONDING_OUTPUT_VALUE}`
    );
  }
  if (params.maxStakingTimeBlocks < params.minStakingTimeBlocks) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Max staking time must be greater or equal to min staking time"
    );
  }
  if (params.minStakingTimeBlocks <= 0) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Min staking time must be greater than 0"
    );
  }
  if (params.covenantQuorum <= 0) {
    throw new StakingError(
      "INVALID_PARAMS" /* INVALID_PARAMS */,
      "Covenant quorum must be greater than 0"
    );
  }
  if (params.slashing) {
    if (params.slashing.slashingRate <= 0) {
      throw new StakingError(
        "INVALID_PARAMS" /* INVALID_PARAMS */,
        "Slashing rate must be greater than 0"
      );
    }
    if (params.slashing.slashingRate > 1) {
      throw new StakingError(
        "INVALID_PARAMS" /* INVALID_PARAMS */,
        "Slashing rate must be less or equal to 1"
      );
    }
    if (params.slashing.slashingPkScriptHex.length == 0) {
      throw new StakingError(
        "INVALID_PARAMS" /* INVALID_PARAMS */,
        "Slashing public key script is missing"
      );
    }
    if (params.slashing.minSlashingTxFeeSat <= 0) {
      throw new StakingError(
        "INVALID_PARAMS" /* INVALID_PARAMS */,
        "Minimum slashing transaction fee must be greater than 0"
      );
    }
  }
};
var validateStakingTimelock = (stakingTimelock, params) => {
  if (stakingTimelock < params.minStakingTimeBlocks || stakingTimelock > params.maxStakingTimeBlocks) {
    throw new StakingError(
      "INVALID_INPUT" /* INVALID_INPUT */,
      "Staking transaction timelock is out of range"
    );
  }
};
var toBuffers = (inputs) => {
  try {
    return inputs.map(
      (i) => Buffer.from(i, "hex")
    );
  } catch (error) {
    throw StakingError.fromUnknown(
      error,
      "INVALID_INPUT" /* INVALID_INPUT */,
      "Cannot convert values to buffers"
    );
  }
};

// src/constants/psbt.ts
var NON_RBF_SEQUENCE = 4294967295;
var TRANSACTION_VERSION = 2;

// src/constants/transaction.ts
var REDEEM_VERSION = 192;

// src/staking/transactions.ts
var BTC_LOCKTIME_HEIGHT_TIME_CUTOFF = 5e8;
var BTC_SLASHING_FRACTION_DIGITS = 4;
function stakingTransaction(scripts, amount, changeAddress, inputUTXOs, network, feeRate, lockHeight) {
  if (amount <= 0 || feeRate <= 0) {
    throw new Error("Amount and fee rate must be bigger than 0");
  }
  if (!isValidBitcoinAddress(changeAddress, network)) {
    throw new Error("Invalid change address");
  }
  const stakingOutputs = buildStakingTransactionOutputs(scripts, network, amount);
  const { selectedUTXOs, fee } = getStakingTxInputUTXOsAndFees(
    inputUTXOs,
    amount,
    feeRate,
    stakingOutputs
  );
  const tx = new Transaction2();
  tx.version = TRANSACTION_VERSION;
  for (let i = 0; i < selectedUTXOs.length; ++i) {
    const input = selectedUTXOs[i];
    tx.addInput(
      transactionIdToHash(input.txid),
      input.vout,
      NON_RBF_SEQUENCE
    );
  }
  stakingOutputs.forEach((o) => {
    tx.addOutput(o.scriptPubKey, o.value);
  });
  const inputsSum = inputValueSum(selectedUTXOs);
  if (inputsSum - (amount + fee) > BTC_DUST_SAT) {
    tx.addOutput(
      address3.toOutputScript(changeAddress, network),
      inputsSum - (amount + fee)
    );
  }
  if (lockHeight) {
    if (lockHeight >= BTC_LOCKTIME_HEIGHT_TIME_CUTOFF) {
      throw new Error("Invalid lock height");
    }
    tx.locktime = lockHeight;
  }
  return {
    transaction: tx,
    fee
  };
}
function withdrawEarlyUnbondedTransaction(scripts, unbondingTx, withdrawalAddress, network, feeRate) {
  const scriptTree = [
    {
      output: scripts.slashingScript
    },
    { output: scripts.unbondingTimelockScript }
  ];
  return withdrawalTransaction(
    {
      timelockScript: scripts.unbondingTimelockScript
    },
    scriptTree,
    unbondingTx,
    withdrawalAddress,
    network,
    feeRate,
    0
    // unbonding always has a single output
  );
}
function withdrawTimelockUnbondedTransaction(scripts, tx, withdrawalAddress, network, feeRate, outputIndex = 0) {
  const scriptTree = [
    {
      output: scripts.slashingScript
    },
    [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
  ];
  return withdrawalTransaction(
    scripts,
    scriptTree,
    tx,
    withdrawalAddress,
    network,
    feeRate,
    outputIndex
  );
}
function withdrawSlashingTransaction(scripts, slashingTx, withdrawalAddress, network, feeRate, outputIndex) {
  const scriptTree = { output: scripts.unbondingTimelockScript };
  return withdrawalTransaction(
    {
      timelockScript: scripts.unbondingTimelockScript
    },
    scriptTree,
    slashingTx,
    withdrawalAddress,
    network,
    feeRate,
    outputIndex
  );
}
function withdrawalTransaction(scripts, scriptTree, tx, withdrawalAddress, network, feeRate, outputIndex = 0) {
  if (feeRate <= 0) {
    throw new Error("Withdrawal feeRate must be bigger than 0");
  }
  if (outputIndex < 0) {
    throw new Error("Output index must be bigger or equal to 0");
  }
  const timePosition = 2;
  const decompiled = script2.decompile(scripts.timelockScript);
  if (!decompiled) {
    throw new Error("Timelock script is not valid");
  }
  let timelock = 0;
  if (typeof decompiled[timePosition] !== "number") {
    const timeBuffer = decompiled[timePosition];
    timelock = script2.number.decode(timeBuffer);
  } else {
    const wrap = decompiled[timePosition] % 16;
    timelock = wrap === 0 ? 16 : wrap;
  }
  const redeem = {
    output: scripts.timelockScript,
    redeemVersion: REDEEM_VERSION
  };
  const p2tr = payments3.p2tr({
    internalPubkey,
    scriptTree,
    redeem,
    network
  });
  const tapLeafScript = {
    leafVersion: redeem.redeemVersion,
    script: redeem.output,
    controlBlock: p2tr.witness[p2tr.witness.length - 1]
  };
  const psbt = new Psbt({ network });
  psbt.setVersion(TRANSACTION_VERSION);
  psbt.addInput({
    hash: tx.getHash(),
    index: outputIndex,
    tapInternalKey: internalPubkey,
    witnessUtxo: {
      value: tx.outs[outputIndex].value,
      script: tx.outs[outputIndex].script
    },
    tapLeafScript: [tapLeafScript],
    sequence: timelock
  });
  const estimatedFee = getWithdrawTxFee(feeRate);
  const outputValue = tx.outs[outputIndex].value - estimatedFee;
  if (outputValue < 0) {
    throw new Error(
      "Not enough funds to cover the fee for withdrawal transaction"
    );
  }
  if (outputValue < BTC_DUST_SAT) {
    throw new Error("Output value is less than dust limit");
  }
  psbt.addOutput({
    address: withdrawalAddress,
    value: outputValue
  });
  psbt.setLocktime(0);
  return {
    psbt,
    fee: estimatedFee
  };
}
function slashTimelockUnbondedTransaction(scripts, stakingTransaction2, slashingPkScriptHex, slashingRate, minimumFee, network, outputIndex = 0) {
  const slashingScriptTree = [
    {
      output: scripts.slashingScript
    },
    [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
  ];
  return slashingTransaction(
    {
      unbondingTimelockScript: scripts.unbondingTimelockScript,
      slashingScript: scripts.slashingScript
    },
    slashingScriptTree,
    stakingTransaction2,
    slashingPkScriptHex,
    slashingRate,
    minimumFee,
    network,
    outputIndex
  );
}
function slashEarlyUnbondedTransaction(scripts, unbondingTx, slashingPkScriptHex, slashingRate, minimumSlashingFee, network) {
  const unbondingScriptTree = [
    {
      output: scripts.slashingScript
    },
    {
      output: scripts.unbondingTimelockScript
    }
  ];
  return slashingTransaction(
    {
      unbondingTimelockScript: scripts.unbondingTimelockScript,
      slashingScript: scripts.slashingScript
    },
    unbondingScriptTree,
    unbondingTx,
    slashingPkScriptHex,
    slashingRate,
    minimumSlashingFee,
    network,
    0
    // unbonding always has a single output
  );
}
function slashingTransaction(scripts, scriptTree, transaction, slashingPkScriptHex, slashingRate, minimumFee, network, outputIndex = 0) {
  if (slashingRate <= 0 || slashingRate >= 1) {
    throw new Error("Slashing rate must be between 0 and 1");
  }
  slashingRate = parseFloat(slashingRate.toFixed(BTC_SLASHING_FRACTION_DIGITS));
  if (minimumFee <= 0 || !Number.isInteger(minimumFee)) {
    throw new Error("Minimum fee must be a positve integer");
  }
  if (outputIndex < 0 || !Number.isInteger(outputIndex)) {
    throw new Error("Output index must be an integer bigger or equal to 0");
  }
  if (!transaction.outs[outputIndex]) {
    throw new Error("Output index is out of range");
  }
  const redeem = {
    output: scripts.slashingScript,
    redeemVersion: REDEEM_VERSION
  };
  const p2tr = payments3.p2tr({
    internalPubkey,
    scriptTree,
    redeem,
    network
  });
  const tapLeafScript = {
    leafVersion: redeem.redeemVersion,
    script: redeem.output,
    controlBlock: p2tr.witness[p2tr.witness.length - 1]
  };
  const stakingAmount = transaction.outs[outputIndex].value;
  const slashingAmount = Math.round(stakingAmount * slashingRate);
  const slashingOutput = Buffer.from(slashingPkScriptHex, "hex");
  if (opcodes3.OP_RETURN != slashingOutput[0]) {
    if (slashingAmount <= BTC_DUST_SAT) {
      throw new Error("Slashing amount is less than dust limit");
    }
  }
  const userFunds = stakingAmount - slashingAmount - minimumFee;
  if (userFunds <= BTC_DUST_SAT) {
    throw new Error("User funds are less than dust limit");
  }
  const psbt = new Psbt({ network });
  psbt.setVersion(TRANSACTION_VERSION);
  psbt.addInput({
    hash: transaction.getHash(),
    index: outputIndex,
    tapInternalKey: internalPubkey,
    witnessUtxo: {
      value: stakingAmount,
      script: transaction.outs[outputIndex].script
    },
    tapLeafScript: [tapLeafScript],
    // not RBF-able
    sequence: NON_RBF_SEQUENCE
  });
  psbt.addOutput({
    script: slashingOutput,
    value: slashingAmount
  });
  const changeOutput = payments3.p2tr({
    internalPubkey,
    scriptTree: { output: scripts.unbondingTimelockScript },
    network
  });
  psbt.addOutput({
    address: changeOutput.address,
    value: userFunds
  });
  psbt.setLocktime(0);
  return { psbt };
}
function unbondingTransaction(scripts, stakingTx, unbondingFee, network, outputIndex = 0) {
  if (unbondingFee <= 0) {
    throw new Error("Unbonding fee must be bigger than 0");
  }
  if (outputIndex < 0) {
    throw new Error("Output index must be bigger or equal to 0");
  }
  const tx = new Transaction2();
  tx.version = TRANSACTION_VERSION;
  tx.addInput(
    stakingTx.getHash(),
    outputIndex,
    NON_RBF_SEQUENCE
    // not RBF-able
  );
  const unbondingOutputInfo = deriveUnbondingOutputInfo(scripts, network);
  const outputValue = stakingTx.outs[outputIndex].value - unbondingFee;
  if (outputValue < BTC_DUST_SAT) {
    throw new Error("Output value is less than dust limit for unbonding transaction");
  }
  if (!unbondingOutputInfo.outputAddress) {
    throw new Error("Unbonding output address is not defined");
  }
  tx.addOutput(
    unbondingOutputInfo.scriptPubKey,
    outputValue
  );
  tx.locktime = 0;
  return {
    transaction: tx,
    fee: unbondingFee
  };
}
var createCovenantWitness = (originalWitness, paramsCovenants, covenantSigs, covenantQuorum) => {
  if (covenantSigs.length < covenantQuorum) {
    throw new Error(
      `Not enough covenant signatures. Required: ${covenantQuorum}, got: ${covenantSigs.length}`
    );
  }
  for (const sig of covenantSigs) {
    const btcPkHexBuf = Buffer.from(sig.btcPkHex, "hex");
    if (!paramsCovenants.some((covenant) => covenant.equals(btcPkHexBuf))) {
      throw new Error(
        `Covenant signature public key ${sig.btcPkHex} not found in params covenants`
      );
    }
  }
  const covenantSigsBuffers = covenantSigs.slice(0, covenantQuorum).map((sig) => ({
    btcPkHex: Buffer.from(sig.btcPkHex, "hex"),
    sigHex: Buffer.from(sig.sigHex, "hex")
  }));
  const paramsCovenantsSorted = [...paramsCovenants].sort(Buffer.compare).reverse();
  const composedCovenantSigs = paramsCovenantsSorted.map((covenant) => {
    const covenantSig = covenantSigsBuffers.find(
      (sig) => sig.btcPkHex.compare(covenant) === 0
    );
    return covenantSig?.sigHex || Buffer.alloc(0);
  });
  return [...composedCovenantSigs, ...originalWitness];
};

// src/staking/psbt.ts
import { Psbt as Psbt2, payments as payments5 } from "bitcoinjs-lib";

// src/utils/utxo/findInputUTXO.ts
var findInputUTXO = (inputUTXOs, input) => {
  const inputUTXO = inputUTXOs.find(
    (u) => transactionIdToHash(u.txid).toString("hex") === input.hash.toString("hex") && u.vout === input.index
  );
  if (!inputUTXO) {
    throw new Error(
      `Input UTXO not found for txid: ${Buffer.from(input.hash).reverse().toString("hex")} and vout: ${input.index}`
    );
  }
  return inputUTXO;
};

// src/utils/utxo/getScriptType.ts
import { payments as payments4 } from "bitcoinjs-lib";
var BitcoinScriptType = /* @__PURE__ */ ((BitcoinScriptType2) => {
  BitcoinScriptType2["P2PKH"] = "pubkeyhash";
  BitcoinScriptType2["P2SH"] = "scripthash";
  BitcoinScriptType2["P2WPKH"] = "witnesspubkeyhash";
  BitcoinScriptType2["P2WSH"] = "witnessscripthash";
  BitcoinScriptType2["P2TR"] = "taproot";
  return BitcoinScriptType2;
})(BitcoinScriptType || {});
var getScriptType = (script4) => {
  try {
    payments4.p2pkh({ output: script4 });
    return "pubkeyhash" /* P2PKH */;
  } catch {
  }
  try {
    payments4.p2sh({ output: script4 });
    return "scripthash" /* P2SH */;
  } catch {
  }
  try {
    payments4.p2wpkh({ output: script4 });
    return "witnesspubkeyhash" /* P2WPKH */;
  } catch {
  }
  try {
    payments4.p2wsh({ output: script4 });
    return "witnessscripthash" /* P2WSH */;
  } catch {
  }
  try {
    payments4.p2tr({ output: script4 });
    return "taproot" /* P2TR */;
  } catch {
  }
  throw new Error("Unknown script type");
};

// src/utils/utxo/getPsbtInputFields.ts
var getPsbtInputFields = (utxo, publicKeyNoCoord) => {
  const scriptPubKey = Buffer.from(utxo.scriptPubKey, "hex");
  const type = getScriptType(scriptPubKey);
  switch (type) {
    case "pubkeyhash" /* P2PKH */: {
      if (!utxo.rawTxHex) {
        throw new Error("Missing rawTxHex for legacy P2PKH input");
      }
      return { nonWitnessUtxo: Buffer.from(utxo.rawTxHex, "hex") };
    }
    case "scripthash" /* P2SH */: {
      if (!utxo.rawTxHex) {
        throw new Error("Missing rawTxHex for P2SH input");
      }
      if (!utxo.redeemScript) {
        throw new Error("Missing redeemScript for P2SH input");
      }
      return {
        nonWitnessUtxo: Buffer.from(utxo.rawTxHex, "hex"),
        redeemScript: Buffer.from(utxo.redeemScript, "hex")
      };
    }
    case "witnesspubkeyhash" /* P2WPKH */: {
      return {
        witnessUtxo: {
          script: scriptPubKey,
          value: utxo.value
        }
      };
    }
    case "witnessscripthash" /* P2WSH */: {
      if (!utxo.witnessScript) {
        throw new Error("Missing witnessScript for P2WSH input");
      }
      return {
        witnessUtxo: {
          script: scriptPubKey,
          value: utxo.value
        },
        witnessScript: Buffer.from(utxo.witnessScript, "hex")
      };
    }
    case "taproot" /* P2TR */: {
      return {
        witnessUtxo: {
          script: scriptPubKey,
          value: utxo.value
        },
        // this is needed only if the wallet is in taproot mode
        ...publicKeyNoCoord && { tapInternalKey: publicKeyNoCoord }
      };
    }
    default:
      throw new Error(`Unsupported script type: ${type}`);
  }
};

// src/staking/psbt.ts
var stakingPsbt = (stakingTx, network, inputUTXOs, publicKeyNoCoord) => {
  if (publicKeyNoCoord && publicKeyNoCoord.length !== NO_COORD_PK_BYTE_LENGTH) {
    throw new Error("Invalid public key");
  }
  const psbt = new Psbt2({ network });
  if (stakingTx.version !== void 0)
    psbt.setVersion(stakingTx.version);
  if (stakingTx.locktime !== void 0)
    psbt.setLocktime(stakingTx.locktime);
  stakingTx.ins.forEach((input) => {
    const inputUTXO = findInputUTXO(inputUTXOs, input);
    const psbtInputData = getPsbtInputFields(inputUTXO, publicKeyNoCoord);
    psbt.addInput({
      hash: input.hash,
      index: input.index,
      sequence: input.sequence,
      ...psbtInputData
    });
  });
  stakingTx.outs.forEach((o) => {
    psbt.addOutput({ script: o.script, value: o.value });
  });
  return psbt;
};
var unbondingPsbt = (scripts, unbondingTx, stakingTx, network) => {
  if (unbondingTx.outs.length !== 1) {
    throw new Error("Unbonding transaction must have exactly one output");
  }
  if (unbondingTx.ins.length !== 1) {
    throw new Error("Unbonding transaction must have exactly one input");
  }
  validateUnbondingOutput(scripts, unbondingTx, network);
  const psbt = new Psbt2({ network });
  if (unbondingTx.version !== void 0) {
    psbt.setVersion(unbondingTx.version);
  }
  if (unbondingTx.locktime !== void 0) {
    psbt.setLocktime(unbondingTx.locktime);
  }
  const input = unbondingTx.ins[0];
  const outputIndex = input.index;
  const inputScriptTree = [
    { output: scripts.slashingScript },
    [{ output: scripts.unbondingScript }, { output: scripts.timelockScript }]
  ];
  const inputRedeem = {
    output: scripts.unbondingScript,
    redeemVersion: REDEEM_VERSION
  };
  const p2tr = payments5.p2tr({
    internalPubkey,
    scriptTree: inputScriptTree,
    redeem: inputRedeem,
    network
  });
  const inputTapLeafScript = {
    leafVersion: inputRedeem.redeemVersion,
    script: inputRedeem.output,
    controlBlock: p2tr.witness[p2tr.witness.length - 1]
  };
  psbt.addInput({
    hash: input.hash,
    index: input.index,
    sequence: input.sequence,
    tapInternalKey: internalPubkey,
    witnessUtxo: {
      value: stakingTx.outs[outputIndex].value,
      script: stakingTx.outs[outputIndex].script
    },
    tapLeafScript: [inputTapLeafScript]
  });
  psbt.addOutput({
    script: unbondingTx.outs[0].script,
    value: unbondingTx.outs[0].value
  });
  return psbt;
};
var validateUnbondingOutput = (scripts, unbondingTx, network) => {
  const unbondingOutputInfo = deriveUnbondingOutputInfo(scripts, network);
  if (unbondingOutputInfo.scriptPubKey.toString("hex") !== unbondingTx.outs[0].script.toString("hex")) {
    throw new Error(
      "Unbonding output script does not match the expected script while building psbt"
    );
  }
};

// src/staking/index.ts
var Staking = class {
  constructor(network, stakerInfo, params, finalityProviderPkNoCoordHex, stakingTimelock) {
    if (!isValidBitcoinAddress(stakerInfo.address, network)) {
      throw new StakingError(
        "INVALID_INPUT" /* INVALID_INPUT */,
        "Invalid staker bitcoin address"
      );
    }
    if (!isValidNoCoordPublicKey(stakerInfo.publicKeyNoCoordHex)) {
      throw new StakingError(
        "INVALID_INPUT" /* INVALID_INPUT */,
        "Invalid staker public key"
      );
    }
    if (!isValidNoCoordPublicKey(finalityProviderPkNoCoordHex)) {
      throw new StakingError(
        "INVALID_INPUT" /* INVALID_INPUT */,
        "Invalid finality provider public key"
      );
    }
    validateParams(params);
    validateStakingTimelock(stakingTimelock, params);
    this.network = network;
    this.stakerInfo = stakerInfo;
    this.params = params;
    this.finalityProviderPkNoCoordHex = finalityProviderPkNoCoordHex;
    this.stakingTimelock = stakingTimelock;
  }
  /**
   * buildScripts builds the staking scripts for the staking transaction.
   * Note: different staking types may have different scripts.
   * e.g the observable staking script has a data embed script.
   * 
   * @returns {StakingScripts} - The staking scripts.
   */
  buildScripts() {
    const { covenantQuorum, covenantNoCoordPks, unbondingTime } = this.params;
    let stakingScriptData;
    try {
      stakingScriptData = new StakingScriptData(
        Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex"),
        [Buffer.from(this.finalityProviderPkNoCoordHex, "hex")],
        toBuffers(covenantNoCoordPks),
        covenantQuorum,
        this.stakingTimelock,
        unbondingTime
      );
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
        "Cannot build staking script data"
      );
    }
    let scripts;
    try {
      scripts = stakingScriptData.buildScripts();
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
        "Cannot build staking scripts"
      );
    }
    return scripts;
  }
  /**
   * Create a staking transaction for staking.
   * 
   * @param {number} stakingAmountSat - The amount to stake in satoshis.
   * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking 
   * transaction.
   * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
   * @returns {TransactionResult} - An object containing the unsigned
   * transaction, and fee
   * @throws {StakingError} - If the transaction cannot be built
   */
  createStakingTransaction(stakingAmountSat, inputUTXOs, feeRate) {
    validateStakingTxInputData(
      stakingAmountSat,
      this.stakingTimelock,
      this.params,
      inputUTXOs,
      feeRate
    );
    const scripts = this.buildScripts();
    try {
      const { transaction, fee } = stakingTransaction(
        scripts,
        stakingAmountSat,
        this.stakerInfo.address,
        inputUTXOs,
        this.network,
        feeRate
      );
      return {
        transaction,
        fee
      };
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build unsigned staking transaction"
      );
    }
  }
  /**
   * Create a staking psbt based on the existing staking transaction.
   * 
   * @param {Transaction} stakingTx - The staking transaction.
   * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking 
   * transaction. The UTXOs that were used to create the staking transaction should
   * be included in this array.
   * @returns {Psbt} - The psbt.
   */
  toStakingPsbt(stakingTx, inputUTXOs) {
    const scripts = this.buildScripts();
    const stakingOutputInfo = deriveStakingOutputInfo(scripts, this.network);
    findMatchingTxOutputIndex(
      stakingTx,
      stakingOutputInfo.outputAddress,
      this.network
    );
    return stakingPsbt(
      stakingTx,
      this.network,
      inputUTXOs,
      isTaproot(
        this.stakerInfo.address,
        this.network
      ) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
    );
  }
  /**
   * Create an unbonding transaction for staking.
   * 
   * @param {Transaction} stakingTx - The staking transaction to unbond.
   * @returns {TransactionResult} - An object containing the unsigned
   * transaction, and fee
   * @throws {StakingError} - If the transaction cannot be built
   */
  createUnbondingTransaction(stakingTx) {
    const scripts = this.buildScripts();
    const { outputAddress } = deriveStakingOutputInfo(scripts, this.network);
    const stakingOutputIndex = findMatchingTxOutputIndex(
      stakingTx,
      outputAddress,
      this.network
    );
    try {
      const { transaction } = unbondingTransaction(
        scripts,
        stakingTx,
        this.params.unbondingFeeSat,
        this.network,
        stakingOutputIndex
      );
      return {
        transaction,
        fee: this.params.unbondingFeeSat
      };
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build the unbonding transaction"
      );
    }
  }
  /**
   * Create an unbonding psbt based on the existing unbonding transaction and
   * staking transaction.
   * 
   * @param {Transaction} unbondingTx - The unbonding transaction.
   * @param {Transaction} stakingTx - The staking transaction.
   * 
   * @returns {Psbt} - The psbt.
   */
  toUnbondingPsbt(unbondingTx, stakingTx) {
    return unbondingPsbt(
      this.buildScripts(),
      unbondingTx,
      stakingTx,
      this.network
    );
  }
  /**
   * Creates a withdrawal transaction that spends from an unbonding or slashing
   * transaction. The timelock on the input transaction must have expired before
   * this withdrawal can be valid.
   * 
   * @param {Transaction} earlyUnbondedTx - The unbonding or slashing
   * transaction to withdraw from
   * @param {number} feeRate - Fee rate in satoshis per byte for the withdrawal
   * transaction
   * @returns {PsbtResult} - Contains the unsigned PSBT and fee amount
   * @throws {StakingError} - If the input transaction is invalid or withdrawal
   * transaction cannot be built
   */
  createWithdrawEarlyUnbondedTransaction(earlyUnbondedTx, feeRate) {
    const scripts = this.buildScripts();
    try {
      return withdrawEarlyUnbondedTransaction(
        scripts,
        earlyUnbondedTx,
        this.stakerInfo.address,
        this.network,
        feeRate
      );
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build unsigned withdraw early unbonded transaction"
      );
    }
  }
  /**
   * Create a withdrawal psbt that spends a naturally expired staking 
   * transaction.
   * 
   * @param {Transaction} stakingTx - The staking transaction to withdraw from.
   * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
   * @returns {PsbtResult} - An object containing the unsigned psbt and fee
   * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
   */
  createWithdrawStakingExpiredPsbt(stakingTx, feeRate) {
    const scripts = this.buildScripts();
    const { outputAddress } = deriveStakingOutputInfo(scripts, this.network);
    const stakingOutputIndex = findMatchingTxOutputIndex(
      stakingTx,
      outputAddress,
      this.network
    );
    try {
      return withdrawTimelockUnbondedTransaction(
        scripts,
        stakingTx,
        this.stakerInfo.address,
        this.network,
        feeRate,
        stakingOutputIndex
      );
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build unsigned timelock unbonded transaction"
      );
    }
  }
  /**
   * Create a slashing psbt spending from the staking output.
   * 
   * @param {Transaction} stakingTx - The staking transaction to slash.
   * @returns {PsbtResult} - An object containing the unsigned psbt and fee
   * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
   */
  createStakingOutputSlashingPsbt(stakingTx) {
    if (!this.params.slashing) {
      throw new StakingError(
        "INVALID_PARAMS" /* INVALID_PARAMS */,
        "Slashing parameters are missing"
      );
    }
    const scripts = this.buildScripts();
    try {
      const { psbt } = slashTimelockUnbondedTransaction(
        scripts,
        stakingTx,
        this.params.slashing.slashingPkScriptHex,
        this.params.slashing.slashingRate,
        this.params.slashing.minSlashingTxFeeSat,
        this.network
      );
      return {
        psbt,
        fee: this.params.slashing.minSlashingTxFeeSat
      };
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build the slash timelock unbonded transaction"
      );
    }
  }
  /**
   * Create a slashing psbt for an unbonding output.
   * 
   * @param {Transaction} unbondingTx - The unbonding transaction to slash.
   * @returns {PsbtResult} - An object containing the unsigned psbt and fee
   * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
   */
  createUnbondingOutputSlashingPsbt(unbondingTx) {
    if (!this.params.slashing) {
      throw new StakingError(
        "INVALID_PARAMS" /* INVALID_PARAMS */,
        "Slashing parameters are missing"
      );
    }
    const scripts = this.buildScripts();
    try {
      const { psbt } = slashEarlyUnbondedTransaction(
        scripts,
        unbondingTx,
        this.params.slashing.slashingPkScriptHex,
        this.params.slashing.slashingRate,
        this.params.slashing.minSlashingTxFeeSat,
        this.network
      );
      return {
        psbt,
        fee: this.params.slashing.minSlashingTxFeeSat
      };
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build the slash early unbonded transaction"
      );
    }
  }
  /**
   * Create a withdraw slashing psbt that spends a slashing transaction from the
   * staking output.
   * 
   * @param {Transaction} slashingTx - The slashing transaction.
   * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
   * @returns {PsbtResult} - An object containing the unsigned psbt and fee
   * @throws {StakingError} - If the delegation is invalid or the transaction cannot be built
   */
  createWithdrawSlashingPsbt(slashingTx, feeRate) {
    const scripts = this.buildScripts();
    const slashingOutputInfo = deriveSlashingOutput(scripts, this.network);
    const slashingOutputIndex = findMatchingTxOutputIndex(
      slashingTx,
      slashingOutputInfo.outputAddress,
      this.network
    );
    try {
      return withdrawSlashingTransaction(
        scripts,
        slashingTx,
        this.stakerInfo.address,
        this.network,
        feeRate,
        slashingOutputIndex
      );
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build withdraw slashing transaction"
      );
    }
  }
};

// src/staking/observable/observableStakingScript.ts
import { opcodes as opcodes4, script as script3 } from "bitcoinjs-lib";
var ObservableStakingScriptData = class extends StakingScriptData {
  constructor(stakerKey, finalityProviderKeys, covenantKeys, covenantThreshold, stakingTimelock, unbondingTimelock, magicBytes) {
    super(
      stakerKey,
      finalityProviderKeys,
      covenantKeys,
      covenantThreshold,
      stakingTimelock,
      unbondingTimelock
    );
    if (!magicBytes) {
      throw new Error("Missing required input values");
    }
    if (magicBytes.length != MAGIC_BYTES_LEN) {
      throw new Error("Invalid script data provided");
    }
    this.magicBytes = magicBytes;
  }
  /**
   * Builds a data embed script for staking in the form:
   *    OP_RETURN || <serializedStakingData>
   * where serializedStakingData is the concatenation of:
   *    MagicBytes || Version || StakerPublicKey || FinalityProviderPublicKey || StakingTimeLock
   * Note: Only a single finality provider key is supported for now in phase 1
   * @throws {Error} If the number of finality provider keys is not equal to 1.
   * @returns {Buffer} The compiled data embed script.
   */
  buildDataEmbedScript() {
    if (this.finalityProviderKeys.length != 1) {
      throw new Error("Only a single finality provider key is supported");
    }
    const version = Buffer.alloc(1);
    version.writeUInt8(0);
    const stakingTimeLock = Buffer.alloc(2);
    stakingTimeLock.writeUInt16BE(this.stakingTimeLock);
    const serializedStakingData = Buffer.concat([
      this.magicBytes,
      version,
      this.stakerKey,
      this.finalityProviderKeys[0],
      stakingTimeLock
    ]);
    return script3.compile([opcodes4.OP_RETURN, serializedStakingData]);
  }
  /**
   * Builds the staking scripts.
   * @returns {ObservableStakingScripts} The staking scripts that can be used to stake.
   * contains the timelockScript, unbondingScript, slashingScript,
   * unbondingTimelockScript, and dataEmbedScript.
   * @throws {Error} If script data is invalid.
   */
  buildScripts() {
    const scripts = super.buildScripts();
    return {
      ...scripts,
      dataEmbedScript: this.buildDataEmbedScript()
    };
  }
};

// src/staking/observable/index.ts
var ObservableStaking = class extends Staking {
  constructor(network, stakerInfo, params, finalityProviderPkNoCoordHex, stakingTimelock) {
    super(
      network,
      stakerInfo,
      params,
      finalityProviderPkNoCoordHex,
      stakingTimelock
    );
    if (!params.tag) {
      throw new StakingError(
        "INVALID_INPUT" /* INVALID_INPUT */,
        "Observable staking parameters must include tag"
      );
    }
    if (!params.btcActivationHeight) {
      throw new StakingError(
        "INVALID_INPUT" /* INVALID_INPUT */,
        "Observable staking parameters must include a positive activation height"
      );
    }
    this.params = params;
  }
  /**
   * Build the staking scripts for observable staking.
   * This method overwrites the base method to include the OP_RETURN tag based 
   * on the tag provided in the parameters.
   * 
   * @returns {ObservableStakingScripts} - The staking scripts for observable staking.
   * @throws {StakingError} - If the scripts cannot be built.
   */
  buildScripts() {
    const { covenantQuorum, covenantNoCoordPks, unbondingTime, tag } = this.params;
    let stakingScriptData;
    try {
      stakingScriptData = new ObservableStakingScriptData(
        Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex"),
        [Buffer.from(this.finalityProviderPkNoCoordHex, "hex")],
        toBuffers(covenantNoCoordPks),
        covenantQuorum,
        this.stakingTimelock,
        unbondingTime,
        Buffer.from(tag, "hex")
      );
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
        "Cannot build staking script data"
      );
    }
    let scripts;
    try {
      scripts = stakingScriptData.buildScripts();
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "SCRIPT_FAILURE" /* SCRIPT_FAILURE */,
        "Cannot build staking scripts"
      );
    }
    return scripts;
  }
  /**
   * Create a staking transaction for observable staking.
   * This overwrites the method from the Staking class with the addtion
   * of the 
   * 1. OP_RETURN tag in the staking scripts
   * 2. lockHeight parameter
   * 
   * @param {number} stakingAmountSat - The amount to stake in satoshis.
   * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking 
   * transaction.
   * @param {number} feeRate - The fee rate for the transaction in satoshis per byte.
   * @returns {TransactionResult} - An object containing the unsigned transaction,
   * and fee
   */
  createStakingTransaction(stakingAmountSat, inputUTXOs, feeRate) {
    validateStakingTxInputData(
      stakingAmountSat,
      this.stakingTimelock,
      this.params,
      inputUTXOs,
      feeRate
    );
    const scripts = this.buildScripts();
    try {
      const { transaction, fee } = stakingTransaction(
        scripts,
        stakingAmountSat,
        this.stakerInfo.address,
        inputUTXOs,
        this.network,
        feeRate,
        // `lockHeight` is exclusive of the provided value.
        // For example, if a Bitcoin height of X is provided,
        // the transaction will be included starting from height X+1.
        // https://learnmeabitcoin.com/technical/transaction/locktime/
        this.params.btcActivationHeight - 1
      );
      return {
        transaction,
        fee
      };
    } catch (error) {
      throw StakingError.fromUnknown(
        error,
        "BUILD_TRANSACTION_FAILURE" /* BUILD_TRANSACTION_FAILURE */,
        "Cannot build unsigned staking transaction"
      );
    }
  }
  /**
   * Create a staking psbt for observable staking.
   * 
   * @param {Transaction} stakingTx - The staking transaction.
   * @param {UTXO[]} inputUTXOs - The UTXOs to use as inputs for the staking 
   * transaction.
   * @returns {Psbt} - The psbt.
   */
  toStakingPsbt(stakingTx, inputUTXOs) {
    return stakingPsbt(
      stakingTx,
      this.network,
      inputUTXOs,
      isTaproot(
        this.stakerInfo.address,
        this.network
      ) ? Buffer.from(this.stakerInfo.publicKeyNoCoordHex, "hex") : void 0
    );
  }
};

// src/utils/babylon.ts
import { fromBech32 } from "@cosmjs/encoding";
var isValidBabylonAddress = (address4) => {
  try {
    const { prefix } = fromBech32(address4);
    return prefix === "bbn";
  } catch (error) {
    return false;
  }
};

// src/utils/staking/param.ts
var getBabylonParamByBtcHeight = (height, babylonParamsVersions) => {
  const sortedParams = [...babylonParamsVersions].sort(
    (a, b) => b.btcActivationHeight - a.btcActivationHeight
  );
  const params = sortedParams.find(
    (p) => height >= p.btcActivationHeight
  );
  if (!params)
    throw new Error(`Babylon params not found for height ${height}`);
  return params;
};
var getBabylonParamByVersion = (version, babylonParams) => {
  const params = babylonParams.find((p) => p.version === version);
  if (!params)
    throw new Error(`Babylon params not found for version ${version}`);
  return params;
};

// src/staking/manager.ts
import { Psbt as Psbt3 } from "bitcoinjs-lib";
import {
  btccheckpoint,
  btcstaking,
  btcstakingtx
} from "@babylonlabs-io/babylon-proto-ts";
import {
  BIP322Sig,
  BTCSigType
} from "@babylonlabs-io/babylon-proto-ts/dist/generated/babylon/btcstaking/v1/pop";

// src/constants/registry.ts
var BABYLON_REGISTRY_TYPE_URLS = {
  MsgCreateBTCDelegation: "/babylon.btcstaking.v1.MsgCreateBTCDelegation"
};

// src/utils/index.ts
var reverseBuffer = (buffer) => {
  const clonedBuffer = new Uint8Array(buffer);
  if (clonedBuffer.length < 1)
    return clonedBuffer;
  for (let i = 0, j = clonedBuffer.length - 1; i < clonedBuffer.length / 2; i++, j--) {
    let tmp = clonedBuffer[i];
    clonedBuffer[i] = clonedBuffer[j];
    clonedBuffer[j] = tmp;
  }
  return clonedBuffer;
};

// src/staking/manager.ts
var SigningStep = /* @__PURE__ */ ((SigningStep2) => {
  SigningStep2["STAKING_SLASHING"] = "staking-slashing";
  SigningStep2["UNBONDING_SLASHING"] = "unbonding-slashing";
  SigningStep2["PROOF_OF_POSSESSION"] = "proof-of-possession";
  SigningStep2["CREATE_BTC_DELEGATION_MSG"] = "create-btc-delegation-msg";
  SigningStep2["STAKING"] = "staking";
  SigningStep2["UNBONDING"] = "unbonding";
  SigningStep2["WITHDRAW_STAKING_EXPIRED"] = "withdraw-staking-expired";
  SigningStep2["WITHDRAW_EARLY_UNBONDED"] = "withdraw-early-unbonded";
  SigningStep2["WITHDRAW_SLASHING"] = "withdraw-slashing";
  return SigningStep2;
})(SigningStep || {});
var BabylonBtcStakingManager = class {
  constructor(network, stakingParams, btcProvider, babylonProvider) {
    this.network = network;
    this.btcProvider = btcProvider;
    this.babylonProvider = babylonProvider;
    if (stakingParams.length === 0) {
      throw new Error("No staking parameters provided");
    }
    this.stakingParams = stakingParams;
  }
  /**
   * Creates a signed Pre-Staking Registration transaction that is ready to be
   * sent to the Babylon chain.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param stakingInput - The staking inputs.
   * @param babylonBtcTipHeight - The Babylon BTC tip height.
   * @param inputUTXOs - The UTXOs that will be used to pay for the staking
   * transaction.
   * @param feeRate - The fee rate in satoshis per byte. Typical value for the
   * fee rate is above 1. If the fee rate is too low, the transaction will not
   * be included in a block.
   * @param babylonAddress - The Babylon bech32 encoded address of the staker.
   * @returns The signed babylon pre-staking registration transaction in base64 
   * format.
   */
  async preStakeRegistrationBabylonTransaction(stakerBtcInfo, stakingInput, babylonBtcTipHeight, inputUTXOs, feeRate, babylonAddress) {
    if (babylonBtcTipHeight === 0) {
      throw new Error("Babylon BTC tip height cannot be 0");
    }
    if (inputUTXOs.length === 0) {
      throw new Error("No input UTXOs provided");
    }
    if (!isValidBabylonAddress(babylonAddress)) {
      throw new Error("Invalid Babylon address");
    }
    const params = getBabylonParamByBtcHeight(
      babylonBtcTipHeight,
      this.stakingParams
    );
    const staking = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const { transaction } = staking.createStakingTransaction(
      stakingInput.stakingAmountSat,
      inputUTXOs,
      feeRate
    );
    const msg = await this.createBtcDelegationMsg(
      staking,
      stakingInput,
      transaction,
      babylonAddress,
      stakerBtcInfo,
      params
    );
    return {
      signedBabylonTx: await this.babylonProvider.signTransaction(
        "create-btc-delegation-msg" /* CREATE_BTC_DELEGATION_MSG */,
        msg
      ),
      stakingTx: transaction
    };
  }
  /**
   * Creates a signed post-staking registration transaction that is ready to be 
   * sent to the Babylon chain. This is used when a staking transaction is 
   * already created and included in a BTC block and we want to register it on 
   * the Babylon chain.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param stakingTx - The staking transaction.
   * @param stakingTxHeight - The BTC height in which the staking transaction 
   * is included.
   * @param stakingInput - The staking inputs.
   * @param inclusionProof - Merkle Proof of Inclusion: Verifies transaction
   * inclusion in a Bitcoin block that is k-deep.
   * @param babylonAddress - The Babylon bech32 encoded address of the staker.
   * @returns The signed babylon transaction in base64 format.
   */
  async postStakeRegistrationBabylonTransaction(stakerBtcInfo, stakingTx, stakingTxHeight, stakingInput, inclusionProof, babylonAddress) {
    const params = getBabylonParamByBtcHeight(stakingTxHeight, this.stakingParams);
    if (!isValidBabylonAddress(babylonAddress)) {
      throw new Error("Invalid Babylon address");
    }
    const stakingInstance = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const scripts = stakingInstance.buildScripts();
    const stakingOutputInfo = deriveStakingOutputInfo(scripts, this.network);
    findMatchingTxOutputIndex(
      stakingTx,
      stakingOutputInfo.outputAddress,
      this.network
    );
    const delegationMsg = await this.createBtcDelegationMsg(
      stakingInstance,
      stakingInput,
      stakingTx,
      babylonAddress,
      stakerBtcInfo,
      params,
      this.getInclusionProof(inclusionProof)
    );
    return {
      signedBabylonTx: await this.babylonProvider.signTransaction(
        "create-btc-delegation-msg" /* CREATE_BTC_DELEGATION_MSG */,
        delegationMsg
      )
    };
  }
  /**
   * Estimates the BTC fee required for staking.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param babylonBtcTipHeight - The BTC tip height recorded on the Babylon
   * chain.
   * @param stakingInput - The staking inputs.
   * @param inputUTXOs - The UTXOs that will be used to pay for the staking
   * transaction.
   * @param feeRate - The fee rate in satoshis per byte. Typical value for the
   * fee rate is above 1. If the fee rate is too low, the transaction will not
   * be included in a block.
   * @returns The estimated BTC fee in satoshis.
   */
  estimateBtcStakingFee(stakerBtcInfo, babylonBtcTipHeight, stakingInput, inputUTXOs, feeRate) {
    if (babylonBtcTipHeight === 0) {
      throw new Error("Babylon BTC tip height cannot be 0");
    }
    const params = getBabylonParamByBtcHeight(
      babylonBtcTipHeight,
      this.stakingParams
    );
    const staking = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const { fee: stakingFee } = staking.createStakingTransaction(
      stakingInput.stakingAmountSat,
      inputUTXOs,
      feeRate
    );
    return stakingFee;
  }
  /**
   * Creates a signed staking transaction that is ready to be sent to the BTC 
   * network.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param stakingInput - The staking inputs.
   * @param unsignedStakingTx - The unsigned staking transaction.
   * @param inputUTXOs - The UTXOs that will be used to pay for the staking
   * transaction.
   * @param stakingParamsVersion - The params version that was used to create the
   * delegation in Babylon chain
   * @returns The signed staking transaction.
   */
  async createSignedBtcStakingTransaction(stakerBtcInfo, stakingInput, unsignedStakingTx, inputUTXOs, stakingParamsVersion) {
    const params = getBabylonParamByVersion(stakingParamsVersion, this.stakingParams);
    if (inputUTXOs.length === 0) {
      throw new Error("No input UTXOs provided");
    }
    const staking = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const stakingPsbt2 = staking.toStakingPsbt(
      unsignedStakingTx,
      inputUTXOs
    );
    const signedStakingPsbtHex = await this.btcProvider.signPsbt(
      "staking" /* STAKING */,
      stakingPsbt2.toHex()
    );
    return Psbt3.fromHex(signedStakingPsbtHex).extractTransaction();
  }
  /**
   * Creates a partial signed unbonding transaction that is only signed by the
   * staker. In order to complete the unbonding transaction, the covenant
   * unbonding signatures need to be added to the transaction before sending it
   * to the BTC network.
   * NOTE: This method should only be used for Babylon phase-1 unbonding.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param stakingInput - The staking inputs.
   * @param stakingParamsVersion - The params version that was used to create the
   * delegation in Babylon chain
   * @param stakingTx - The staking transaction.
   * @returns The partial signed unbonding transaction and its fee.
   */
  async createPartialSignedBtcUnbondingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx) {
    const params = getBabylonParamByVersion(
      stakingParamsVersion,
      this.stakingParams
    );
    const staking = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const {
      transaction: unbondingTx,
      fee
    } = staking.createUnbondingTransaction(stakingTx);
    const psbt = staking.toUnbondingPsbt(unbondingTx, stakingTx);
    const signedUnbondingPsbtHex = await this.btcProvider.signPsbt(
      "unbonding" /* UNBONDING */,
      psbt.toHex()
    );
    const signedUnbondingTx = Psbt3.fromHex(
      signedUnbondingPsbtHex
    ).extractTransaction();
    return {
      transaction: signedUnbondingTx,
      fee
    };
  }
  /**
   * Creates a signed unbonding transaction that is ready to be sent to the BTC 
   * network.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param stakingInput - The staking inputs.
   * @param stakingParamsVersion - The params version that was used to create the
   * delegation in Babylon chain
   * @param stakingTx - The staking transaction.
   * @param unsignedUnbondingTx - The unsigned unbonding transaction.
   * @param covenantUnbondingSignatures - The covenant unbonding signatures.
   * It can be retrieved from the Babylon chain or API.
   * @returns The signed unbonding transaction and its fee.
   */
  async createSignedBtcUnbondingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx, unsignedUnbondingTx, covenantUnbondingSignatures) {
    const params = getBabylonParamByVersion(
      stakingParamsVersion,
      this.stakingParams
    );
    const {
      transaction: signedUnbondingTx,
      fee
    } = await this.createPartialSignedBtcUnbondingTransaction(
      stakerBtcInfo,
      stakingInput,
      stakingParamsVersion,
      stakingTx
    );
    if (signedUnbondingTx.getId() !== unsignedUnbondingTx.getId()) {
      throw new Error(
        "Unbonding transaction hash does not match the computed hash"
      );
    }
    const covenantBuffers = params.covenantNoCoordPks.map(
      (covenant) => Buffer.from(covenant, "hex")
    );
    const witness = createCovenantWitness(
      // Since unbonding transactions always have a single input and output,
      // we expect exactly one signature in TaprootScriptSpendSig when the
      // signing is successful
      signedUnbondingTx.ins[0].witness,
      covenantBuffers,
      covenantUnbondingSignatures,
      params.covenantQuorum
    );
    signedUnbondingTx.ins[0].witness = witness;
    return {
      transaction: signedUnbondingTx,
      fee
    };
  }
  /**
   * Creates a signed withdrawal transaction on the unbodning output expiry path 
   * that is ready to be sent to the BTC network.
   * @param stakingInput - The staking inputs.
   * @param stakingParamsVersion - The params version that was used to create the
   * delegation in Babylon chain
   * @param earlyUnbondingTx - The early unbonding transaction.
   * @param feeRate - The fee rate in satoshis per byte. Typical value for the
   * fee rate is above 1. If the fee rate is too low, the transaction will not
   * be included in a block.
   * @returns The signed withdrawal transaction and its fee.
   */
  async createSignedBtcWithdrawEarlyUnbondedTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, earlyUnbondingTx, feeRate) {
    const params = getBabylonParamByVersion(
      stakingParamsVersion,
      this.stakingParams
    );
    const staking = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const { psbt: unbondingPsbt2, fee } = staking.createWithdrawEarlyUnbondedTransaction(
      earlyUnbondingTx,
      feeRate
    );
    const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
      "withdraw-early-unbonded" /* WITHDRAW_EARLY_UNBONDED */,
      unbondingPsbt2.toHex()
    );
    return {
      transaction: Psbt3.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
      fee
    };
  }
  /**
   * Creates a signed withdrawal transaction on the staking output expiry path 
   * that is ready to be sent to the BTC network.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param stakingInput - The staking inputs.
   * @param stakingParamsVersion - The params version that was used to create the
   * delegation in Babylon chain
   * @param stakingTx - The staking transaction.
   * @param feeRate - The fee rate in satoshis per byte. Typical value for the
   * fee rate is above 1. If the fee rate is too low, the transaction will not
   * be included in a block.
   * @returns The signed withdrawal transaction and its fee.
   */
  async createSignedBtcWithdrawStakingExpiredTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, stakingTx, feeRate) {
    const params = getBabylonParamByVersion(
      stakingParamsVersion,
      this.stakingParams
    );
    const staking = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const { psbt, fee } = staking.createWithdrawStakingExpiredPsbt(
      stakingTx,
      feeRate
    );
    const signedWithdrawalPsbtHex = await this.btcProvider.signPsbt(
      "withdraw-staking-expired" /* WITHDRAW_STAKING_EXPIRED */,
      psbt.toHex()
    );
    return {
      transaction: Psbt3.fromHex(signedWithdrawalPsbtHex).extractTransaction(),
      fee
    };
  }
  /**
   * Creates a signed withdrawal transaction for the expired slashing output that 
   * is ready to be sent to the BTC network.
   * @param stakerBtcInfo - The staker BTC info which includes the BTC address
   * and the no-coord public key in hex format.
   * @param stakingInput - The staking inputs.
   * @param stakingParamsVersion - The params version that was used to create the
   * delegation in Babylon chain
   * @param slashingTx - The slashing transaction.
   * @param feeRate - The fee rate in satoshis per byte. Typical value for the
   * fee rate is above 1. If the fee rate is too low, the transaction will not
   * be included in a block.
   * @returns The signed withdrawal transaction and its fee.
   */
  async createSignedBtcWithdrawSlashingTransaction(stakerBtcInfo, stakingInput, stakingParamsVersion, slashingTx, feeRate) {
    const params = getBabylonParamByVersion(
      stakingParamsVersion,
      this.stakingParams
    );
    const staking = new Staking(
      this.network,
      stakerBtcInfo,
      params,
      stakingInput.finalityProviderPkNoCoordHex,
      stakingInput.stakingTimelock
    );
    const { psbt, fee } = staking.createWithdrawSlashingPsbt(
      slashingTx,
      feeRate
    );
    const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
      "withdraw-slashing" /* WITHDRAW_SLASHING */,
      psbt.toHex()
    );
    return {
      transaction: Psbt3.fromHex(signedSlashingPsbtHex).extractTransaction(),
      fee
    };
  }
  /**
   * Creates a proof of possession for the staker based on ECDSA signature.
   * @param bech32Address - The staker's bech32 address on the babylon chain
   * @param stakerBtcAddress - The staker's BTC address.
   * @returns The proof of possession.
   */
  async createProofOfPossession(bech32Address, stakerBtcAddress) {
    let sigType = BTCSigType.ECDSA;
    if (isTaproot(stakerBtcAddress, this.network) || isNativeSegwit(stakerBtcAddress, this.network)) {
      sigType = BTCSigType.BIP322;
    }
    const signedBabylonAddress = await this.btcProvider.signMessage(
      "proof-of-possession" /* PROOF_OF_POSSESSION */,
      bech32Address,
      sigType === BTCSigType.BIP322 ? "bip322-simple" : "ecdsa"
    );
    let btcSig;
    if (sigType === BTCSigType.BIP322) {
      const bip322Sig = BIP322Sig.fromPartial({
        address: stakerBtcAddress,
        sig: Buffer.from(signedBabylonAddress, "base64")
      });
      btcSig = BIP322Sig.encode(bip322Sig).finish();
    } else {
      btcSig = Buffer.from(signedBabylonAddress, "base64");
    }
    return {
      btcSigType: sigType,
      btcSig
    };
  }
  /**
   * Creates the unbonding, slashing, and unbonding slashing transactions and 
   * PSBTs.
   * @param stakingInstance - The staking instance.
   * @param stakingTx - The staking transaction.
   * @returns The unbonding, slashing, and unbonding slashing transactions and 
   * PSBTs.
   */
  async createDelegationTransactionsAndPsbts(stakingInstance, stakingTx) {
    const { transaction: unbondingTx } = stakingInstance.createUnbondingTransaction(stakingTx);
    const { psbt: slashingPsbt } = stakingInstance.createStakingOutputSlashingPsbt(stakingTx);
    const { psbt: unbondingSlashingPsbt } = stakingInstance.createUnbondingOutputSlashingPsbt(unbondingTx);
    return {
      unbondingTx,
      slashingPsbt,
      unbondingSlashingPsbt
    };
  }
  /**
   * Creates a protobuf message for the BTC delegation.
   * @param stakingInstance - The staking instance.
   * @param stakingInput - The staking inputs.
   * @param stakingTx - The staking transaction.
   * @param bech32Address - The staker's babylon chain bech32 address
   * @param stakerBtcInfo - The staker's BTC information such as address and 
   * public key
   * @param params - The staking parameters.
   * @param inclusionProof - The inclusion proof of the staking transaction.
   * @returns The protobuf message.
   */
  async createBtcDelegationMsg(stakingInstance, stakingInput, stakingTx, bech32Address, stakerBtcInfo, params, inclusionProof) {
    const {
      unbondingTx,
      slashingPsbt,
      unbondingSlashingPsbt
    } = await this.createDelegationTransactionsAndPsbts(
      stakingInstance,
      stakingTx
    );
    const signedSlashingPsbtHex = await this.btcProvider.signPsbt(
      "staking-slashing" /* STAKING_SLASHING */,
      slashingPsbt.toHex()
    );
    const signedSlashingTx = Psbt3.fromHex(
      signedSlashingPsbtHex
    ).extractTransaction();
    const slashingSig = extractFirstSchnorrSignatureFromTransaction(
      signedSlashingTx
    );
    if (!slashingSig) {
      throw new Error("No signature found in the staking output slashing PSBT");
    }
    const signedUnbondingSlashingPsbtHex = await this.btcProvider.signPsbt(
      "unbonding-slashing" /* UNBONDING_SLASHING */,
      unbondingSlashingPsbt.toHex()
    );
    const signedUnbondingSlashingTx = Psbt3.fromHex(
      signedUnbondingSlashingPsbtHex
    ).extractTransaction();
    const unbondingSignatures = extractFirstSchnorrSignatureFromTransaction(
      signedUnbondingSlashingTx
    );
    if (!unbondingSignatures) {
      throw new Error("No signature found in the unbonding output slashing PSBT");
    }
    const proofOfPossession = await this.createProofOfPossession(
      bech32Address,
      stakerBtcInfo.address
    );
    const msg = btcstakingtx.MsgCreateBTCDelegation.fromPartial({
      stakerAddr: bech32Address,
      pop: proofOfPossession,
      btcPk: Uint8Array.from(
        Buffer.from(stakerBtcInfo.publicKeyNoCoordHex, "hex")
      ),
      fpBtcPkList: [
        Uint8Array.from(
          Buffer.from(stakingInput.finalityProviderPkNoCoordHex, "hex")
        )
      ],
      stakingTime: stakingInput.stakingTimelock,
      stakingValue: stakingInput.stakingAmountSat,
      stakingTx: Uint8Array.from(stakingTx.toBuffer()),
      slashingTx: Uint8Array.from(
        Buffer.from(clearTxSignatures(signedSlashingTx).toHex(), "hex")
      ),
      delegatorSlashingSig: Uint8Array.from(slashingSig),
      unbondingTime: params.unbondingTime,
      unbondingTx: Uint8Array.from(unbondingTx.toBuffer()),
      unbondingValue: stakingInput.stakingAmountSat - params.unbondingFeeSat,
      unbondingSlashingTx: Uint8Array.from(
        Buffer.from(
          clearTxSignatures(signedUnbondingSlashingTx).toHex(),
          "hex"
        )
      ),
      delegatorUnbondingSlashingSig: Uint8Array.from(unbondingSignatures),
      stakingTxInclusionProof: inclusionProof
    });
    return {
      typeUrl: BABYLON_REGISTRY_TYPE_URLS.MsgCreateBTCDelegation,
      value: msg
    };
  }
  /**
   * Gets the inclusion proof for the staking transaction.
   * See the type `InclusionProof` for more information
   * @param inclusionProof - The inclusion proof.
   * @returns The inclusion proof.
   */
  getInclusionProof(inclusionProof) {
    const {
      pos,
      merkle,
      blockHashHex
    } = inclusionProof;
    const proofHex = deriveMerkleProof(merkle);
    const hash = reverseBuffer(Uint8Array.from(Buffer.from(blockHashHex, "hex")));
    const inclusionProofKey = btccheckpoint.TransactionKey.fromPartial({
      index: pos,
      hash
    });
    return btcstaking.InclusionProof.fromPartial({
      key: inclusionProofKey,
      proof: Uint8Array.from(Buffer.from(proofHex, "hex"))
    });
  }
};
var extractFirstSchnorrSignatureFromTransaction = (singedTransaction) => {
  for (const input of singedTransaction.ins) {
    if (input.witness && input.witness.length > 0) {
      const schnorrSignature = input.witness[0];
      if (schnorrSignature.length === 64) {
        return schnorrSignature;
      }
    }
  }
  return void 0;
};
var clearTxSignatures = (tx) => {
  tx.ins.forEach((input) => {
    input.script = Buffer.alloc(0);
    input.witness = [];
  });
  return tx;
};
var deriveMerkleProof = (merkle) => {
  const proofHex = merkle.reduce((acc, m) => {
    return acc + Buffer.from(m, "hex").reverse().toString("hex");
  }, "");
  return proofHex;
};
var getUnbondingTxStakerSignature = (unbondingTx) => {
  try {
    return unbondingTx.ins[0].witness[0].toString("hex");
  } catch (error) {
    throw StakingError.fromUnknown(
      error,
      "INVALID_INPUT" /* INVALID_INPUT */,
      "Failed to get staker signature"
    );
  }
};
export {
  BabylonBtcStakingManager,
  BitcoinScriptType,
  ObservableStaking,
  ObservableStakingScriptData,
  SigningStep,
  Staking,
  StakingScriptData,
  buildStakingTransactionOutputs,
  createCovenantWitness,
  deriveSlashingOutput,
  deriveStakingOutputInfo,
  deriveUnbondingOutputInfo,
  findInputUTXO,
  findMatchingTxOutputIndex,
  getBabylonParamByBtcHeight,
  getBabylonParamByVersion,
  getPsbtInputFields,
  getPublicKeyNoCoord,
  getScriptType,
  getUnbondingTxStakerSignature,
  initBTCCurve,
  isNativeSegwit,
  isTaproot,
  isValidBabylonAddress,
  isValidBitcoinAddress,
  isValidNoCoordPublicKey,
  slashEarlyUnbondedTransaction,
  slashTimelockUnbondedTransaction,
  stakingTransaction,
  toBuffers,
  transactionIdToHash,
  unbondingTransaction,
  validateParams,
  validateStakingTimelock,
  validateStakingTxInputData,
  withdrawEarlyUnbondedTransaction,
  withdrawSlashingTransaction,
  withdrawTimelockUnbondedTransaction
};

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


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