PHP WebShell

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

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

import {
  BaseKey,
  BaseTransaction,
  Entry,
  InvalidTransactionError,
  ParseTransactionError,
  TransactionRecipient,
  TransactionType,
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { fromHex, toBase64 } from '@cosmjs/encoding';
import { makeSignBytes } from '@cosmjs/proto-signing';
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { UNAVAILABLE_TEXT } from './constants';
import {
  CosmosLikeTransaction,
  DelegateOrUndelegeteMessage,
  ExecuteContractMessage,
  RedelegateMessage,
  SendMessage,
  TransactionExplanation,
  TxData,
  WithdrawDelegatorRewardsMessage,
} from './iface';
import { CosmosUtils } from './utils';

export class CosmosTransaction<CustomMessage = never> extends BaseTransaction {
  protected _cosmosLikeTransaction: CosmosLikeTransaction<CustomMessage>;
  protected _accountNumber: number;
  protected _chainId: string;

  protected _utils: CosmosUtils<CustomMessage>;

  constructor(_coinConfig: Readonly<CoinConfig>, utils: CosmosUtils<CustomMessage>) {
    super(_coinConfig);
    this._utils = utils;
  }

  get cosmosLikeTransaction(): CosmosLikeTransaction<CustomMessage> {
    return this._cosmosLikeTransaction;
  }

  set cosmosLikeTransaction(cosmosLikeTransaction: Readonly<CosmosLikeTransaction<CustomMessage>>) {
    this._cosmosLikeTransaction = cosmosLikeTransaction;
  }

  get chainId(): string {
    return this._chainId;
  }

  set chainId(chainId: string) {
    this._chainId = chainId;
  }

  get accountNumber(): number {
    return this._accountNumber;
  }

  set accountNumber(accountNumber: number) {
    this._accountNumber = accountNumber;
  }

  /** @inheritDoc **/
  get id(): string {
    if (this._id) {
      return this._id;
    } else if (this._cosmosLikeTransaction?.hash !== undefined) {
      return this._cosmosLikeTransaction.hash;
    }
    return UNAVAILABLE_TEXT;
  }

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

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

  /** @inheritdoc */
  toJson(): TxData<CustomMessage> {
    if (!this._cosmosLikeTransaction) {
      throw new ParseTransactionError('Empty transaction');
    }
    const tx = this._cosmosLikeTransaction;
    return {
      id: this.id,
      type: this._type,
      sequence: tx.sequence,
      sendMessages: tx.sendMessages,
      gasBudget: tx.gasBudget,
      publicKey: tx.publicKey,
      signature: tx.signature,
      accountNumber: this._accountNumber,
      chainId: this._chainId,
      hash: tx.hash,
      memo: tx.memo,
    };
  }

  /**
   * Add a signature to the transaction
   * @param {string} signature in hex format
   */
  addSignature(signature: string) {
    this._signatures = [];
    this._signatures.push(signature);
  }

  /** @inheritDoc */
  explainTransaction(): TransactionExplanation {
    const result = this.toJson();
    const displayOrder = ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'];
    const outputs: TransactionRecipient[] = [];

    const explanationResult: TransactionExplanation = {
      displayOrder,
      id: this.id,
      outputs,
      outputAmount: '0',
      changeOutputs: [],
      changeAmount: '0',
      fee: { fee: this.cosmosLikeTransaction.gasBudget.amount[0].amount },
      type: this.type,
    };
    return this.explainTransactionInternal(result, explanationResult);
  }

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

  /**
   * Serialize the transaction to a JSON string
   * @returns {string} serialized base64 encoded transaction
   */
  serialize(): string {
    const txRaw = this._utils.createTxRawFromCosmosLikeTransaction(this.cosmosLikeTransaction);
    if (this.cosmosLikeTransaction?.publicKey !== undefined && this._signatures.length > 0) {
      const signedRawTx = this._utils.createSignedTxRaw(
        this.cosmosLikeTransaction.publicKey,
        this._signatures[0],
        txRaw
      );
      return toBase64(TxRaw.encode(signedRawTx).finish());
    }
    return toBase64(TxRaw.encode(txRaw).finish());
  }

  /** @inheritdoc **/
  get signablePayload(): Buffer {
    return Buffer.from(
      makeSignBytes(this._utils.createSignDoc(this.cosmosLikeTransaction, this._accountNumber, this._chainId))
    );
  }

  /**
   * Returns a complete explanation for a transfer transaction
   * Currently only supports one message per transfer.
   * @param {TxData} json The transaction data in json format
   * @param {TransactionExplanation} explanationResult The transaction explanation to be completed
   * @returns {TransactionExplanation}
   */
  explainTransactionInternal(
    json: TxData<CustomMessage>,
    explanationResult: TransactionExplanation
  ): TransactionExplanation {
    let outputs: TransactionRecipient[];
    let outputAmount;
    switch (json.type) {
      case TransactionType.Send:
        explanationResult.type = TransactionType.Send;
        outputAmount = BigInt(0);
        outputs = json.sendMessages.map((message) => {
          const sendMessage = message.value as SendMessage;
          outputAmount = outputAmount + BigInt(sendMessage.amount[0].amount);
          return {
            address: sendMessage.toAddress,
            amount: sendMessage.amount[0].amount,
          };
        });
        break;
      case TransactionType.StakingActivate:
        explanationResult.type = TransactionType.StakingActivate;
        outputAmount = BigInt(0);
        outputs = json.sendMessages.map((message) => {
          const delegateMessage = message.value as DelegateOrUndelegeteMessage;
          outputAmount = outputAmount + BigInt(delegateMessage.amount.amount);
          return {
            address: delegateMessage.validatorAddress,
            amount: delegateMessage.amount.amount,
          };
        });
        break;
      case TransactionType.StakingDeactivate:
        explanationResult.type = TransactionType.StakingDeactivate;
        outputAmount = BigInt(0);
        outputs = json.sendMessages.map((message) => {
          const delegateMessage = message.value as DelegateOrUndelegeteMessage;
          outputAmount = outputAmount + BigInt(delegateMessage.amount.amount);
          return {
            address: delegateMessage.validatorAddress,
            amount: delegateMessage.amount.amount,
          };
        });
        break;
      case TransactionType.StakingWithdraw:
        explanationResult.type = TransactionType.StakingWithdraw;
        outputs = json.sendMessages.map((message) => {
          const withdrawMessage = message.value as WithdrawDelegatorRewardsMessage;
          return {
            address: withdrawMessage.validatorAddress,
            amount: UNAVAILABLE_TEXT,
          };
        });
        break;
      case TransactionType.ContractCall:
        explanationResult.type = TransactionType.ContractCall;
        outputAmount = BigInt(0);
        outputs = json.sendMessages.map((message) => {
          const executeContractMessage = message.value as ExecuteContractMessage;
          outputAmount = outputAmount + BigInt(executeContractMessage.funds?.[0]?.amount ?? '0');
          return {
            address: executeContractMessage.contract,
            amount: executeContractMessage.funds?.[0]?.amount ?? '0',
          };
        });
        break;
      case TransactionType.StakingRedelegate:
        explanationResult.type = TransactionType.StakingRedelegate;
        outputAmount = BigInt(0);
        outputs = json.sendMessages.map((message) => {
          const redelegateMessage = message.value as RedelegateMessage;
          outputAmount = outputAmount + BigInt(redelegateMessage.amount.amount);
          return {
            address: redelegateMessage.validatorDstAddress,
            amount: redelegateMessage.amount.amount,
          };
        });
        break;
      default:
        throw new InvalidTransactionError('Transaction type not supported');
    }
    if (json.memo) {
      outputs.forEach((output) => {
        output.memo = json.memo;
      });
    }
    return {
      ...explanationResult,
      outputAmount: outputAmount?.toString(),
      outputs,
    };
  }

  /**
   * Load the input and output data on this transaction using the transaction json
   * if there are outputs. For transactions without outputs (e.g. wallet initializations),
   * this function will not do anything
   */
  loadInputsAndOutputs(): void {
    if (this.type === undefined || !this.cosmosLikeTransaction) {
      throw new InvalidTransactionError('Transaction type or cosmosLikeTransaction is not set');
    }

    const outputs: Entry[] = [];
    const inputs: Entry[] = [];
    switch (this.type) {
      case TransactionType.Send:
        this.cosmosLikeTransaction.sendMessages.forEach((message) => {
          const sendMessage = message.value as SendMessage;
          inputs.push({
            address: sendMessage.fromAddress,
            value: sendMessage.amount[0].amount,
            coin: this._coinConfig.name,
          });
          outputs.push({
            address: sendMessage.toAddress,
            value: sendMessage.amount[0].amount,
            coin: this._coinConfig.name,
          });
        });
        break;
      case TransactionType.StakingActivate:
      case TransactionType.StakingDeactivate:
        this.cosmosLikeTransaction.sendMessages.forEach((message) => {
          const delegateMessage = message.value as DelegateOrUndelegeteMessage;
          inputs.push({
            address: delegateMessage.delegatorAddress,
            value: delegateMessage.amount.amount,
            coin: this._coinConfig.name,
          });
          outputs.push({
            address: delegateMessage.validatorAddress,
            value: delegateMessage.amount.amount,
            coin: this._coinConfig.name,
          });
        });
        break;
      case TransactionType.StakingWithdraw:
        this.cosmosLikeTransaction.sendMessages.forEach((message) => {
          const withdrawMessage = message.value as WithdrawDelegatorRewardsMessage;
          inputs.push({
            address: withdrawMessage.delegatorAddress,
            value: UNAVAILABLE_TEXT,
            coin: this._coinConfig.name,
          });
          outputs.push({
            address: withdrawMessage.validatorAddress,
            value: UNAVAILABLE_TEXT,
            coin: this._coinConfig.name,
          });
        });
        break;
      case TransactionType.ContractCall:
        this.cosmosLikeTransaction.sendMessages.forEach((message) => {
          const executeContractMessage = message.value as ExecuteContractMessage;
          inputs.push({
            address: executeContractMessage.sender,
            value: executeContractMessage.funds?.[0]?.amount ?? '0',
            coin: this._coinConfig.name,
          });
          outputs.push({
            address: executeContractMessage.contract,
            value: executeContractMessage.funds?.[0]?.amount ?? '0',
            coin: this._coinConfig.name,
          });
        });
        break;
      case TransactionType.StakingRedelegate:
        this.cosmosLikeTransaction.sendMessages.forEach((message) => {
          const redelegateMessage = message.value as RedelegateMessage;
          inputs.push({
            address: redelegateMessage.delegatorAddress,
            value: redelegateMessage.amount.amount,
            coin: this._coinConfig.name,
          });
          outputs.push({
            address: redelegateMessage.validatorDstAddress,
            value: redelegateMessage.amount.amount,
            coin: this._coinConfig.name,
          });
        });
        break;
      default:
        throw new InvalidTransactionError('Transaction type not supported');
    }
    this._inputs = inputs;
    this._outputs = outputs;
  }

  /**
   * Sets this transaction payload
   * @param rawTransaction raw transaction in base64 encoded string
   */
  enrichTransactionDetailsFromRawTransaction(rawTransaction: string): void {
    if (this._utils.isValidHexString(rawTransaction)) {
      this.cosmosLikeTransaction = this._utils.deserializeTransaction(toBase64(fromHex(rawTransaction)));
    } else {
      this.cosmosLikeTransaction = this._utils.deserializeTransaction(rawTransaction);
    }
    if (this.cosmosLikeTransaction.signature) {
      this.addSignature(Buffer.from(this.cosmosLikeTransaction.signature).toString('hex'));
    }
    const typeUrl = this.cosmosLikeTransaction.sendMessages[0].typeUrl;
    const transactionType = this._utils.getTransactionTypeFromTypeUrl(typeUrl);

    if (transactionType === undefined) {
      throw new Error('Transaction type is not supported ' + typeUrl);
    }
    this.transactionType = transactionType;
  }
}

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


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