PHP WebShell

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

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

import {
  BaseKey,
  BaseTransaction,
  InvalidTransactionError,
  PublicKey as BasePublicKey,
  Signature,
  TransactionType as BitGoTransactionType,
} from '@bitgo/sdk-core';
import { SuiProgrammableTransaction, SuiTransaction, SuiTransactionType, TxData } from './iface';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import utils, { AppId, Intent, IntentScope, IntentVersion, isImmOrOwnedObj } from './utils';
import { GasData, normalizeSuiAddress, normalizeSuiObjectId, SuiObjectRef } from './mystenlab/types';
import { SIGNATURE_SCHEME_BYTES } from './constants';
import { Buffer } from 'buffer';
import { fromB64, toB64 } from '@mysten/bcs';
import bs58 from 'bs58';
import { KeyPair } from './keyPair';
import { TRANSACTION_DATA_MAX_SIZE, TransactionBlockDataBuilder } from './mystenlab/builder/TransactionDataBlock';
import { builder, MergeCoinsTransaction, TransactionType } from './mystenlab/builder';
import blake2b from '@bitgo/blake2b';
import { hashTypedData } from './mystenlab/cryptography/hash';

export abstract class Transaction<T> extends BaseTransaction {
  protected _suiTransaction: SuiTransaction<T>;
  protected _signature: Signature;
  private _serializedSig: Uint8Array;

  protected constructor(_coinConfig: Readonly<CoinConfig>) {
    super(_coinConfig);
  }

  get suiTransaction(): SuiTransaction<T> {
    return this._suiTransaction;
  }

  setSuiTransaction(tx: SuiTransaction<T>): void {
    this._suiTransaction = tx;
  }

  /** @inheritDoc **/
  get id(): string {
    const dataBytes = this.getDataBytes();
    const hash = hashTypedData('TransactionData', dataBytes);
    this._id = bs58.encode(hash);
    return this._id;
  }

  addSignature(publicKey: BasePublicKey, signature: Buffer): void {
    this._signatures.push(signature.toString('hex'));
    this._signature = { publicKey, signature };
    this.serialize();
  }

  get suiSignature(): Signature {
    return this._signature;
  }

  get serializedSig(): Uint8Array {
    return this._serializedSig;
  }

  setSerializedSig(publicKey: BasePublicKey, signature: Buffer): void {
    const pubKey = Buffer.from(publicKey.pub, 'hex');
    const serialized_sig = new Uint8Array(1 + signature.length + pubKey.length);
    serialized_sig.set(SIGNATURE_SCHEME_BYTES);
    serialized_sig.set(signature, 1);
    serialized_sig.set(pubKey, 1 + signature.length);
    this._serializedSig = serialized_sig;
  }

  /** @inheritdoc */
  canSign(key: BaseKey): boolean {
    return true;
  }

  /**
   * Sign this transaction
   *
   * @param {KeyPair} signer key
   */

  sign(signer: KeyPair): void {
    if (!this._suiTransaction) {
      throw new InvalidTransactionError('empty transaction to sign');
    }

    const intentMessage = this.signablePayload;
    const signature = signer.signMessageinUint8Array(intentMessage);

    this.setSerializedSig({ pub: signer.getKeys().pub }, Buffer.from(signature));
    this.addSignature({ pub: signer.getKeys().pub }, Buffer.from(signature));
  }

  /** @inheritdoc */
  toBroadcastFormat(): string {
    if (!this._suiTransaction) {
      throw new InvalidTransactionError('Empty transaction');
    }
    return this.serialize();
  }

  /** @inheritdoc */
  abstract toJson(): TxData;

  /**
   * Set the transaction type.
   *
   * @param {TransactionType} transactionType The transaction type to be set.
   */
  transactionType(transactionType: BitGoTransactionType): void {
    this._type = transactionType;
  }

  /**
   *  get the correct txData with transaction type
   */
  abstract getTxData(): TxData;

  /**
   * Load the input and output data on this transaction.
   */
  abstract loadInputsAndOutputs(): void;

  /**
   * Sets this transaction payload
   *
   * @param rawTransaction
   */
  abstract fromRawTransaction(rawTransaction: string): void;

  getDataBytes(): Uint8Array {
    const txData = this.getTxData();
    const txSer = builder.ser('TransactionData', { V1: txData }, { maxSize: TRANSACTION_DATA_MAX_SIZE });
    return txSer.toBytes();
  }

  /** @inheritDoc */
  get signablePayload(): Buffer {
    const dataBytes = this.getDataBytes();
    const intentMessage = this.messageWithIntent(IntentScope.TransactionData, dataBytes);
    return Buffer.from(blake2b(32).update(intentMessage).digest('binary'));
  }

  private messageWithIntent(scope: IntentScope, message: Uint8Array) {
    const intent = this.intentWithScope(scope);
    const intentMessage = new Uint8Array(intent.length + message.length);
    intentMessage.set(intent);
    intentMessage.set(message, intent.length);
    return intentMessage;
  }

  private intentWithScope(scope: IntentScope): Intent {
    return [scope, IntentVersion.V0, AppId.Sui];
  }

  serialize(): string {
    const dataBytes = this.getDataBytes();
    this._id = bs58.encode(hashTypedData('TransactionData', dataBytes));
    return toB64(dataBytes);
  }

  static deserializeSuiTransaction(serializedTx: string): SuiTransaction<SuiProgrammableTransaction> {
    const data = fromB64(serializedTx);
    const transactionBlock = TransactionBlockDataBuilder.fromBytes(data);
    const inputs = transactionBlock.inputs.map((txInput) => txInput.value);
    const transactions = transactionBlock.transactions;
    const txType = this.getSuiTransactionType(transactions);
    return {
      id: transactionBlock.getDigest(),
      type: txType,
      sender: normalizeSuiAddress(transactionBlock.sender!),
      tx: {
        inputs: inputs,
        transactions: transactions,
      },
      gasData: {
        payment: this.normalizeCoins(transactionBlock.gasConfig.payment!),
        owner: normalizeSuiAddress(transactionBlock.gasConfig.owner!),
        price: Number(transactionBlock.gasConfig.price as string),
        budget: Number(transactionBlock.gasConfig.budget as string),
      },
    };
  }

  private static getSuiTransactionType(transactions: TransactionType[]): SuiTransactionType {
    // tricky to determine custom tx purely from a serialized tx, we can rely on following logic
    if (transactions.length == 1) {
      return utils.getSuiTransactionType(transactions[0]);
    }
    if (transactions.some((tx) => utils.getSuiTransactionType(tx) === SuiTransactionType.WalrusStakeWithPool)) {
      return SuiTransactionType.WalrusStakeWithPool;
    }
    if (transactions.some((tx) => utils.getSuiTransactionType(tx) === SuiTransactionType.WalrusRequestWithdrawStake)) {
      return SuiTransactionType.WalrusRequestWithdrawStake;
    }
    if (transactions.some((tx) => utils.getSuiTransactionType(tx) === SuiTransactionType.WalrusWithdrawStake)) {
      return SuiTransactionType.WalrusWithdrawStake;
    }
    if (transactions.some((tx) => utils.getSuiTransactionType(tx) === SuiTransactionType.AddStake)) {
      return SuiTransactionType.AddStake;
    }
    if (transactions.some((tx) => utils.getSuiTransactionType(tx) === SuiTransactionType.WithdrawStake)) {
      return SuiTransactionType.WithdrawStake;
    }
    if (transactions.some((tx) => utils.getSuiTransactionType(tx) === SuiTransactionType.TokenTransfer)) {
      return SuiTransactionType.TokenTransfer;
    }
    if (transactions.every((tx) => utils.getSuiTransactionType(tx) === SuiTransactionType.Transfer)) {
      return SuiTransactionType.Transfer;
    }

    return SuiTransactionType.CustomTx;
  }

  static getProperGasData(k: any): GasData {
    return {
      payment: [this.normalizeSuiObjectRef(k.gasData.payment)],
      owner: utils.normalizeHexId(k.gasData.owner),
      price: Number(k.gasData.price),
      budget: Number(k.gasData.budget),
    };
  }

  private static normalizeCoins(coins: any[]): SuiObjectRef[] {
    return coins.map((coin) => {
      return this.normalizeSuiObjectRef(coin);
    });
  }

  private static normalizeSuiObjectRef(obj: SuiObjectRef): SuiObjectRef {
    return {
      objectId: normalizeSuiObjectId(obj.objectId),
      version: Number(obj.version),
      digest: obj.digest,
    };
  }

  /**
   * When building transactions with > 255 input gas payment objects, we first use MergeCoins Tranasactions to merge the
   * additional inputs into the gas coin & slice them from the payment in gasData. When initializing the builder using
   * decoded tx data, we need to get these inputs from MergeCoins & add them back to the gas payment to be able to
   * rebuild from a raw transaction.
   */
  protected getInputGasPaymentObjectsFromTx(tx: SuiProgrammableTransaction): SuiObjectRef[] {
    const txInputs = tx.inputs;
    const transactions = tx.transactions;
    const inputGasPaymentObjects: SuiObjectRef[] = [];

    transactions.forEach((transaction) => {
      if (transaction.kind === 'MergeCoins') {
        const { destination, sources } = transaction as MergeCoinsTransaction;
        if (destination.kind === 'GasCoin') {
          sources.forEach((source) => {
            if (source.kind === 'Input') {
              let input = txInputs[source.index];
              if ('value' in input) {
                input = input.value;
              }
              if ('Object' in input && isImmOrOwnedObj(input.Object)) {
                inputGasPaymentObjects.push(input.Object.ImmOrOwned);
              }
            }
          });
        }
      }
    });

    return inputGasPaymentObjects;
  }
}

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


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