PHP WebShell

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

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

/*

Classes used for tracking sats across transactions.

https://github.com/casey/ord/blob/master/bip.mediawiki#design

> The ordinal numbers of sats in transaction inputs are transferred to output sats in
> first-in-first-out order, according to the size and order of the transactions inputs and outputs.


Sample scenario:
   inputs         i0, i1, i2
   outputs        u0, u1
   inscriptions   r0, r1, r2, r3, r4


createOutputs(
  [i0, i1],
  [
    [u0, [r0, r1]],
    [u1, [r2, r3]],
  ]
);

  r4 is donated to the miner

  ┌────────┬────────┐
  │ i0     │ u0     │
  │        │        │
  │     r0 ┼        │
  │        │        │
  ├────────┤        │
  │ i1     │        │
  │     r1 ┼        │
  │        │        │
  │        ├────────┤
  │        │ u1     │
  │     r2 ┼        │
  │        │        │
  ├────────┤        │
  │ i2     │        │
  │     r3 ┼        │
  │        │        │
  │        │        │
  │        ├────────┘
  │        │
  │     r4 ┼
  │        │
  └────────┘

 */

import { SatRange } from './SatRange';

export class InvalidOrdOutput extends Error {
  constructor(message: string, public value: bigint, public ordinals: SatRange[]) {
    super(message);
  }
}

/**
 * The ordinal metadata for an output
 */
export class OrdOutput {
  /**
   * @param value - the input value
   * @param ordinals - The ordinal ranges of an output, relative to the first satoshi.
   *                   Required to be ordered and non-overlapping.
   *                   Not required to be exhaustive.
   */
  constructor(public value: bigint, public ordinals: SatRange[] = []) {
    const maxRange = this.asSatRange();
    ordinals.forEach((r, i) => {
      if (!maxRange.isSupersetOf(r)) {
        throw new InvalidOrdOutput(`range ${r} outside output maxRange ${maxRange}`, value, ordinals);
      }
      if (0 < i) {
        const prevRange = ordinals[i - 1];
        if (r.start <= prevRange.end) {
          throw new InvalidOrdOutput(`SatRange #${i - 1} ${prevRange} overlaps SatRange #${i} ${r}`, value, ordinals);
        }
      }
    });
  }

  /**
   * @param other
   * @return OrdOutput extended by other.value and SatRanges shifted by this.value
   */
  joinedWith(other: OrdOutput): OrdOutput {
    return new OrdOutput(this.value + other.value, [
      ...this.ordinals,
      ...other.ordinals.map((r) => r.shiftedBy(this.value)),
    ]);
  }

  /**
   * @param ords
   * @return single OrdOutput containing all SatRanges, shifted by preceding output values
   */
  static joinAll(ords: OrdOutput[]): OrdOutput {
    if (ords.length === 0) {
      throw new TypeError(`empty input`);
    }
    return ords.reduce((a, b) => a.joinedWith(b));
  }

  asSatRange(): SatRange {
    return new SatRange(BigInt(0), this.value - BigInt(1));
  }

  /**
   * @param r
   * @return new OrdOutput with all ranges fully contained in _r_. SatRanges are aligned to new start.
   */
  fromSatRange(r: SatRange): OrdOutput {
    return new OrdOutput(
      r.size(),
      this.ordinals.flatMap((s) => {
        if (r.intersectsWith(s)) {
          if (!r.isSupersetOf(s)) {
            throw new Error(`partial overlap in ${r} and ${s}`);
          }
          return s.shiftedBy(-r.start);
        }
        return [];
      })
    );
  }

  /**
   * @param value
   * @return first OrdOutput with value `value`, second OrdOutput with remaining value.
   *         With respective SatRanges
   */
  splitAt(value: bigint): [OrdOutput, OrdOutput] {
    if (this.value < value) {
      throw new Error(`must split at value inside range`);
    }
    return [
      this.fromSatRange(new SatRange(BigInt(0), value - BigInt(1))),
      this.fromSatRange(new SatRange(value, this.value - BigInt(1))),
    ];
  }

  /**
   * Like splitAt but returns _null_ where a zero-sized OrdOutput would be
   * @param value
   */
  splitAtAllowZero(value: bigint): [OrdOutput | null, OrdOutput | null] {
    if (value === BigInt(0)) {
      return [null, this.fromSatRange(this.asSatRange())];
    }
    if (value === this.value) {
      return [this.fromSatRange(this.asSatRange()), null];
    }
    return this.splitAt(value);
  }

  /**
   * Split output successively at values.
   * @param values
   * @param exact - when set, ensure that value sum matches _this.value_
   * @param allowZero - when set, return _null_ for zero-sized values
   * @return (OrdOutput | null)[]. Zero-sized outputs are substituted with _null_.
   */
  splitAllWithParams(
    values: bigint[],
    { exact = false, allowZero = false }: { allowZero?: boolean; exact?: boolean }
  ): (OrdOutput | null)[] {
    if (values.length === 0) {
      throw new Error(`invalid argument`);
    }
    if (exact) {
      const valueSum = values.reduce((a, b) => a + b, BigInt(0));
      if (this.value !== valueSum) {
        throw new Error(`value sum ${valueSum} does not match this.value ${this.value}`);
      }
      return this.splitAllWithParams(values.slice(0, -1), { allowZero, exact: false });
    }
    const [v, ...rest] = values;
    const [a, b] = allowZero ? this.splitAtAllowZero(v) : this.splitAt(v);
    if (rest.length) {
      if (b === null) {
        throw new Error(`invalid remainder`);
      } else {
        return [a, ...b.splitAllWithParams(rest, { exact, allowZero })];
      }
    } else {
      return [a, b];
    }
  }

  /**
   * Split output successively at values.
   * @param values
   * @return OrdOutput[] with length _values.length + 1_
   */
  splitAll(values: bigint[]): OrdOutput[] {
    return this.splitAllWithParams(values, { exact: false, allowZero: false }) as OrdOutput[];
  }
}

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


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