PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-sui/src/lib/mystenlab/builder

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

import { fromB64 } from '@mysten/bcs';
import { is, mask } from 'superstruct';
import { ObjectId, SuiObjectRef } from '../types';
import { Transactions, TransactionArgument, TransactionType, TransactionBlockInput } from './Transactions';
import { BuilderCallArg, getIdFromCallArg, Inputs, ObjectCallArg } from './Inputs';
import { TransactionBlockDataBuilder, TransactionExpiration } from './TransactionDataBlock';
import { create } from './utils';

type TransactionResult = TransactionArgument & TransactionArgument[];

function createTransactionResult(index: number): TransactionResult {
  const baseResult: TransactionArgument = { kind: 'Result', index };

  const nestedResults: TransactionArgument[] = [];
  const nestedResultFor = (resultIndex: number): TransactionArgument =>
    (nestedResults[resultIndex] ??= {
      kind: 'NestedResult',
      index,
      resultIndex,
    });

  return new Proxy(baseResult, {
    set() {
      throw new Error('The transaction result is a proxy, and does not support setting properties directly');
    },
    // TODO: Instead of making this return a concrete argument, we should ideally
    // make it reference-based (so that this gets resolved at build-time), which
    // allows re-ordering transactions.
    get(target, property) {
      // This allows this transaction argument to be used in the singular form:
      if (property in target) {
        return Reflect.get(target, property);
      }

      // Support destructuring:
      if (property === Symbol.iterator) {
        return function* () {
          let i = 0;
          while (true) {
            yield nestedResultFor(i);
            i++;
          }
        };
      }

      if (typeof property === 'symbol') return;

      const resultIndex = parseInt(property, 10);
      if (Number.isNaN(resultIndex) || resultIndex < 0) return;
      return nestedResultFor(resultIndex);
    },
  }) as TransactionResult;
}

const TRANSACTION_BRAND = Symbol.for('@mysten/transaction');

// The maximum number of gas objects that can be selected for one transaction.
const MAX_GAS_OBJECTS = 256;

// The maximum gas that is allowed.
const MAX_GAS = 1000000000;

// A guess about how much overhead each coin provides for gas calculations.
// @ts-ignore
const GAS_OVERHEAD_PER_COIN = 10n;

interface BuildOptions {
  onlyTransactionKind?: boolean;
}

/**
 * Transaction Builder
 */
export class TransactionBlock {
  /** Returns `true` if the object is an instance of the Transaction builder class. */
  static is(obj: unknown): obj is TransactionBlock {
    return !!obj && typeof obj === 'object' && (obj as any)[TRANSACTION_BRAND] === true;
  }

  /**
   * Converts from a serialize transaction kind (built with `build({ onlyTransactionKind: true })`) to a `Transaction` class.
   * Supports either a byte array, or base64-encoded bytes.
   */
  static fromKind(serialized: string | Uint8Array) {
    const tx = new TransactionBlock();

    tx.#blockData = TransactionBlockDataBuilder.fromKindBytes(
      typeof serialized === 'string' ? fromB64(serialized) : serialized
    );

    return tx;
  }

  /**
   * Converts from a serialized transaction format to a `Transaction` class.
   * There are two supported serialized formats:
   * - A string returned from `Transaction#serialize`. The serialized format must be compatible, or it will throw an error.
   * - A byte array (or base64-encoded bytes) containing BCS transaction data.
   */
  static from(serialized: string | Uint8Array) {
    const tx = new TransactionBlock();

    // Check for bytes:
    if (typeof serialized !== 'string' || !serialized.startsWith('{')) {
      tx.#blockData = TransactionBlockDataBuilder.fromBytes(
        typeof serialized === 'string' ? fromB64(serialized) : serialized
      );
    } else {
      tx.#blockData = TransactionBlockDataBuilder.restore(JSON.parse(serialized));
    }

    return tx;
  }

  /** A helper to retrieve the Transaction builder `Transactions` */
  static get Transactions() {
    return Transactions;
  }

  /** A helper to retrieve the Transaction builder `Inputs` */
  static get Inputs() {
    return Inputs;
  }

  setSender(sender: string) {
    this.#blockData.sender = sender;
  }
  /**
   * Sets the sender only if it has not already been set.
   * This is useful for sponsored transaction flows where the sender may not be the same as the signer address.
   */
  setSenderIfNotSet(sender: string) {
    if (!this.#blockData.sender) {
      this.#blockData.sender = sender;
    }
  }
  setExpiration(expiration?: TransactionExpiration) {
    this.#blockData.expiration = expiration;
  }
  setGasPrice(price: number | bigint) {
    this.#blockData.gasConfig.price = String(price);
  }
  setGasBudget(budget: number | bigint) {
    this.#blockData.gasConfig.budget = String(budget);
  }
  setGasOwner(owner: string) {
    this.#blockData.gasConfig.owner = owner;
  }
  setGasPayment(payments: SuiObjectRef[]) {
    if (payments.length >= MAX_GAS_OBJECTS) {
      throw new Error(`Payment objects exceed maximum amount ${MAX_GAS_OBJECTS}`);
    }
    this.#blockData.gasConfig.payment = payments.map((payment) => mask(payment, SuiObjectRef));
  }

  #blockData: TransactionBlockDataBuilder;
  /** Get a snapshot of the transaction data, in JSON form: */
  get blockData() {
    return this.#blockData.snapshot();
  }

  // Used to brand transaction classes so that they can be identified, even between multiple copies
  // of the builder.
  get [TRANSACTION_BRAND]() {
    return true;
  }

  constructor(transaction?: TransactionBlock) {
    this.#blockData = new TransactionBlockDataBuilder(transaction ? transaction.#blockData : undefined);
  }

  /** Returns an argument for the gas coin, to be used in a transaction. */
  get gas(): TransactionArgument {
    return { kind: 'GasCoin' };
  }

  /**
   * Dynamically create a new input, which is separate from the `input`. This is important
   * for generated clients to be able to define unique inputs that are non-overlapping with the
   * defined inputs.
   *
   * For `Uint8Array` type automatically convert the input into a `Pure` CallArg, since this
   * is the format required for custom serialization.
   *
   */
  input(type: 'object' | 'pure', value?: unknown) {
    const index = this.#blockData.inputs.length;
    const input = create(
      {
        kind: 'Input',
        // bigints can't be serialized to JSON, so just string-convert them here:
        value: typeof value === 'bigint' ? String(value) : value,
        index,
        type,
      },
      TransactionBlockInput
    );
    this.#blockData.inputs.push(input);
    return input;
  }

  /**
   * Add a new object input to the transaction.
   */
  object(value: ObjectId | ObjectCallArg) {
    const id = getIdFromCallArg(value);
    // deduplicate
    const inserted = this.#blockData.inputs.find((i) => i.type === 'object' && id === getIdFromCallArg(i.value));
    return inserted ?? this.input('object', value);
  }

  /**
   * Add a new non-object input to the transaction.
   */
  pure(
    /**
     * The pure value that will be used as the input value. If this is a Uint8Array, then the value
     * is assumed to be raw bytes, and will be used directly.
     */
    value: unknown,
    /**
     * The BCS type to serialize the value into. If not provided, the type will automatically be determined
     * based on how the input is used.
     */
    type?: string
  ) {
    // TODO: we can also do some deduplication here
    return this.input(
      'pure',
      value instanceof Uint8Array ? Inputs.Pure(value) : type ? Inputs.Pure(value, type) : value
    );
  }

  /** Add a transaction to the transaction block. */
  add(transaction: TransactionType) {
    const index = this.#blockData.transactions.push(transaction);
    return createTransactionResult(index - 1);
  }

  // Method shorthands:

  splitCoins(...args: Parameters<(typeof Transactions)['SplitCoins']>) {
    return this.add(Transactions.SplitCoins(...args));
  }
  mergeCoins(...args: Parameters<(typeof Transactions)['MergeCoins']>) {
    return this.add(Transactions.MergeCoins(...args));
  }
  publish(...args: Parameters<(typeof Transactions)['Publish']>) {
    return this.add(Transactions.Publish(...args));
  }
  moveCall(...args: Parameters<(typeof Transactions)['MoveCall']>) {
    return this.add(Transactions.MoveCall(...args));
  }
  transferObjects(...args: Parameters<(typeof Transactions)['TransferObjects']>) {
    return this.add(Transactions.TransferObjects(...args));
  }
  makeMoveVec(...args: Parameters<(typeof Transactions)['MakeMoveVec']>) {
    return this.add(Transactions.MakeMoveVec(...args));
  }

  /**
   * Serialize the transaction to a string so that it can be sent to a separate context.
   * This is different from `build` in that it does not serialize to BCS bytes, and instead
   * uses a separate format that is unique to the transaction builder. This allows
   * us to serialize partially-complete transactions, that can then be completed and
   * built in a separate context.
   *
   * For example, a dapp can construct a transaction, but not provide gas objects
   * or a gas budget. The transaction then can be sent to the wallet, where this
   * information is automatically filled in (e.g. by querying for coin objects
   * and performing a dry run).
   */
  serialize() {
    return JSON.stringify(this.#blockData.snapshot());
  }

  /** Build the transaction to BCS bytes. */
  async build({ onlyTransactionKind }: BuildOptions = {}): Promise<Uint8Array> {
    return this.#blockData.build({ onlyTransactionKind });
  }

  /** Derive transaction digest */
  async getDigest(): Promise<string> {
    return this.#blockData.getDigest();
  }
}

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


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