PHP WebShell

Текущая директория: /opt/BitGoJS/modules/utxo-ord/src

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

import { Network, bitgo, address } from '@bitgo/utxo-lib';
import { Dimensions, VirtualSizes } from '@bitgo/unspents';

import { OrdOutput } from './OrdOutput';
import { parseSatPoint, SatPoint } from './SatPoint';
import { SatRange } from './SatRange';
import { getOrdOutputsForLayout, OutputLayout, toArray, findOutputLayout } from './OutputLayout';
import { powerset } from './combinations';

type WalletUnspent = bitgo.WalletUnspent<bigint>;

export type WalletOutputPath = {
  chain: bitgo.ChainCode;
  index: number;
};

export type WalletInputBuilder = {
  walletKeys: bitgo.RootWalletKeys;
  signer: bitgo.KeyName;
  cosigner: bitgo.KeyName;
};

/**
 * Describes all outputs of an inscription transaction
 */
export type InscriptionTransactionOutputs = {
  inscriptionRecipient: string | Buffer;
  changeOutputs: [WalletOutputPath, WalletOutputPath];
};

/** @deprecated */
export type InscriptionOutputs = InscriptionTransactionOutputs;

export type InscriptionTransactionConstraints = {
  feeRateSatKB: number;
  minChangeOutput?: bigint;
  minInscriptionOutput?: bigint;
  maxInscriptionOutput?: bigint;
};

export const DefaultInscriptionConstraints = {
  minChangeOutput: BigInt(10_000),
  minInscriptionOutput: BigInt(10_000),
  maxInscriptionOutput: BigInt(20_000),
};

export function createPsbtFromOutputLayout(
  network: Network,
  inputBuilder: WalletInputBuilder,
  unspents: WalletUnspent[],
  outputs: InscriptionTransactionOutputs,
  outputLayout: OutputLayout
): bitgo.UtxoPsbt {
  const psbt = bitgo.createPsbtForNetwork({ network: network });
  if (unspents.length === 0) {
    throw new Error(`must provide at least one unspent`);
  }
  unspents.forEach((u) =>
    bitgo.addWalletUnspentToPsbt(psbt, u, inputBuilder.walletKeys, inputBuilder.signer, inputBuilder.cosigner)
  );
  const ordInput = OrdOutput.joinAll(unspents.map((u) => new OrdOutput(u.value)));
  const ordOutputs = getOrdOutputsForLayout(ordInput, outputLayout);
  toArray(ordOutputs).forEach((ordOutput) => {
    if (ordOutput === null) {
      return;
    }
    switch (ordOutput) {
      // skip padding outputs and fee output (virtual)
      case null:
      case ordOutputs.feeOutput:
        return;
      // add padding outputs
      case ordOutputs.firstChangeOutput:
      case ordOutputs.secondChangeOutput:
        const { chain, index } =
          ordOutput === ordOutputs.firstChangeOutput ? outputs.changeOutputs[0] : outputs.changeOutputs[1];
        bitgo.addWalletOutputToPsbt(psbt, inputBuilder.walletKeys, chain, index, ordOutput.value);
        break;
      // add actual inscription output
      case ordOutputs.inscriptionOutput:
        let { inscriptionRecipient } = outputs;
        if (typeof inscriptionRecipient === 'string') {
          inscriptionRecipient = address.toOutputScript(inscriptionRecipient, network);
        }
        psbt.addOutput({
          script: inscriptionRecipient,
          value: ordOutput.value,
        });
        break;
    }
  });
  return psbt;
}

function toSatRange(p: SatPoint) {
  const { offset } = parseSatPoint(p);
  return new SatRange(offset, offset);
}

function getFee(vsize: number, rateSatPerKB: number): bigint {
  return BigInt(Math.ceil((vsize * rateSatPerKB) / 1000));
}

/**
 * @param inputs - inscription input must come first
 * @param satPoint - location of the inscription
 * @param outputs
 * @param constraints
 * @param minimizeInputs
 */
export function findOutputLayoutForWalletUnspents(
  inputs: WalletUnspent[],
  satPoint: SatPoint,
  outputs: InscriptionTransactionOutputs,
  constraints: InscriptionTransactionConstraints,
  { minimizeInputs = false } = {}
): { inputs: WalletUnspent[]; layout: OutputLayout } | undefined {
  if (minimizeInputs) {
    return findSmallestOutputLayoutForWalletUnspents(inputs, satPoint, outputs, constraints);
  }

  if (inputs.length === 0) {
    throw new Error(`must provide at least one input`);
  }

  if (outputs.changeOutputs[0].chain !== outputs.changeOutputs[1].chain) {
    // otherwise our fee calc is too complicated
    throw new Error(`wallet outputs must be on same chain`);
  }

  const {
    minChangeOutput = DefaultInscriptionConstraints.minChangeOutput,
    minInscriptionOutput = DefaultInscriptionConstraints.minInscriptionOutput,
    maxInscriptionOutput = DefaultInscriptionConstraints.maxInscriptionOutput,
  } = constraints;

  // Join all the inputs into a single inscriptionOutput.
  // For the purposes of finding a layout there is no difference.
  const inscriptionOutput = OrdOutput.joinAll(
    inputs.map((i) => new OrdOutput(i.value, i === inputs[0] ? [toSatRange(satPoint)] : []))
  );
  const layout = findOutputLayout(inscriptionOutput, {
    minChangeOutput,
    minInscriptionOutput,
    maxInscriptionOutput,
    feeFixed: getFee(
      VirtualSizes.txSegOverheadVSize +
        Dimensions.fromUnspents(inputs, {
          p2tr: { scriptPathLevel: 1 },
          p2trMusig2: { scriptPathLevel: undefined },
        }).getInputsVSize(),
      constraints.feeRateSatKB
    ),
    feePerOutput: getFee(
      Dimensions.fromOutputOnChain(outputs.changeOutputs[0].chain).getOutputsVSize(),
      constraints.feeRateSatKB
    ),
  });

  return layout ? { inputs, layout } : undefined;
}

export const MAX_UNSPENTS_FOR_OUTPUT_LAYOUT = 5;

/**
 * @param inputs - inscription input must come first
 * @param satPoint - location of the inscription
 * @param outputs
 * @param constraints
 */
function findSmallestOutputLayoutForWalletUnspents(
  inputs: WalletUnspent[],
  satPoint: SatPoint,
  outputs: InscriptionTransactionOutputs,
  constraints: InscriptionTransactionConstraints
): { inputs: WalletUnspent[]; layout: OutputLayout } | undefined {
  if (MAX_UNSPENTS_FOR_OUTPUT_LAYOUT < inputs.length) {
    throw new Error(`input array is too large`);
  }
  // create powerset of all supplementary inputs and find the cheapest result
  const inputsArr = [inputs, ...powerset(inputs.slice(1)).map((s) => [inputs[0], ...s])];
  return inputsArr
    .map((inputs) => findOutputLayoutForWalletUnspents(inputs, satPoint, outputs, constraints))
    .reduce((best, next) => {
      if (best === undefined) {
        return next;
      }
      if (next === undefined) {
        return best;
      }
      return best.layout.feeOutput < next.layout.feeOutput ? best : next;
    });
}

export class ErrorNoLayout extends Error {
  constructor() {
    super('Could not find output layout for inscription passing transaction');
  }
}

/**
 * @param network
 * @param inputBuilder
 * @param unspent
 * @param satPoint
 * @param outputs
 * @param constraints
 * @param supplementaryUnspents - additional inputs to cover fee.
 * @param [minimizeInputs=true] - try to find input combination with minimal fees. Limits supplementaryUnspents to 4.
 */
export function createPsbtForSingleInscriptionPassingTransaction(
  network: Network,
  inputBuilder: WalletInputBuilder,
  unspent: WalletUnspent | WalletUnspent[],
  satPoint: SatPoint,
  outputs: InscriptionTransactionOutputs,
  constraints: InscriptionTransactionConstraints,
  {
    supplementaryUnspents = [],
    minimizeInputs = true,
  }: {
    supplementaryUnspents?: WalletUnspent[];
    minimizeInputs?: boolean;
  } = {}
): bitgo.UtxoPsbt {
  // support for legacy call style
  if (Array.isArray(unspent)) {
    if (unspent.length !== 1) {
      throw new Error(`can only pass single unspent`);
    }
    unspent = unspent[0];
  }

  const result = findOutputLayoutForWalletUnspents(
    [unspent, ...supplementaryUnspents],
    satPoint,
    outputs,
    constraints,
    { minimizeInputs }
  );

  if (!result) {
    throw new ErrorNoLayout();
  }

  return createPsbtFromOutputLayout(network, inputBuilder, result.inputs, outputs, result.layout);
}

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


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