PHP WebShell

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

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

import BigNumber from 'bignumber.js';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { DeployUtil, CLPublicKey as PublicKey } from 'casper-js-sdk';
import _ from 'lodash';
import {
  BaseAddress,
  BaseKey,
  BaseTransactionBuilder,
  BuildTransactionError,
  InvalidTransactionError,
  ParseTransactionError,
  SigningError,
  TransactionType,
} from '@bitgo/sdk-core';
import { Transaction } from './transaction';
import { KeyPair } from './keyPair';
import {
  Fee,
  CasperModuleBytesTransaction,
  CasperTransferTransaction,
  CasperDelegateTransaction,
  SignatureData,
} from './ifaces';
import { isValidAddress, removeAlgoPrefixFromHexValue } from './utils';
import { DEFAULT_CHAIN_NAMES, TRANSACTION_EXPIRATION } from './constants';

export const DEFAULT_M = 3;
export const DEFAULT_N = 2;
export abstract class TransactionBuilder extends BaseTransactionBuilder {
  protected _source: BaseAddress;
  protected _fee: Fee;
  private _transaction: Transaction;
  protected _session: CasperTransferTransaction | CasperModuleBytesTransaction | CasperDelegateTransaction;
  protected _expiration: number;
  protected _multiSignerKeyPairs: KeyPair[];
  protected _signatures: SignatureData[];
  protected _chainName: string;

  constructor(_coinConfig: Readonly<CoinConfig>) {
    super(_coinConfig);
    this.transaction = new Transaction(_coinConfig);
    this._multiSignerKeyPairs = [];
    this._signatures = [];
    this._chainName = this.coinName() === 'cspr' ? DEFAULT_CHAIN_NAMES.mainnet : DEFAULT_CHAIN_NAMES.testnet;
  }

  // region Base Builder
  /** @inheritdoc */
  protected async buildImplementation(): Promise<Transaction> {
    const deployParams = this.getDeployParams();
    const session = this.getSession();

    const payment = DeployUtil.standardPayment(_.parseInt(this._fee.gasLimit));

    let cTransaction = this.transaction.casperTx || DeployUtil.makeDeploy(deployParams, session, payment);

    // Cannot add arguments to an already signed deploy.
    if (cTransaction.approvals.length === 0) {
      this._session.extraArguments.forEach((extraArgument, extraArgumentName) => {
        if (!cTransaction.session.getArgByName(extraArgumentName)) {
          cTransaction = DeployUtil.addArgToDeploy(cTransaction, extraArgumentName, extraArgument);
        }
      });
    }

    this.transaction.casperTx = cTransaction;

    this.processSigning();

    return this.transaction;
  }

  /** @inheritdoc */
  protected fromImplementation(rawTransaction: string): Transaction {
    const tx = new Transaction(this._coinConfig);
    const jsonTransaction = JSON.parse(rawTransaction);
    tx.casperTx = DeployUtil.deployFromJson(jsonTransaction).unwrap();
    this.initBuilder(tx);
    return this.transaction;
  }

  /** @inheritdoc */
  protected signImplementation(key: BaseKey): Transaction {
    this.checkDuplicatedKeys(key);
    const signer = new KeyPair({ prv: key.key });

    // Signing the transaction is an operation that relies on all the data being set,
    // so we set the source here and leave the actual signing for the build step
    this._multiSignerKeyPairs.push(signer);
    return this.transaction;
  }

  /**
   * Initialize the transaction builder fields using the decoded transaction data
   *
   * @param {Transaction} tx the transaction data
   */
  initBuilder(tx: Transaction): void {
    this.transaction = tx;
    this.transaction.loadPreviousSignatures();
    const txData = tx.toJson();
    this.fee(txData.fee);
    this.source({ address: txData.from });
    this.expiration(txData.expiration || TRANSACTION_EXPIRATION);
  }

  // endregion

  // region Common builder methods

  /**
   * Set the transaction fees
   *
   * @param {BaseFee} fee The maximum gas to pay
   * @returns {TransactionBuilder} This transaction builder
   */
  fee(fee: Fee): this {
    this.validateValue(new BigNumber(fee.gasLimit));
    this._fee = fee;
    return this;
  }

  /**
   * Set the transaction source
   *
   * @param {BaseAddress} address The source account
   * @returns {TransactionBuilder} This transaction builder
   */
  source(address: BaseAddress): this {
    this.validateAddress(address);
    this._source = address;
    return this;
  }

  /**
   * Set the transaction expirationTime
   *
   * @param {string} expirationTime The transaction expirationTime
   * @returns {TransactionBuilder} This transaction builder
   */
  expiration(expirationTime: number): this {
    const transactionExpiration = new BigNumber(expirationTime);
    if (transactionExpiration.isNaN() || transactionExpiration.isGreaterThan(TRANSACTION_EXPIRATION)) {
      throw new BuildTransactionError('Invalid transaction expiration');
    }
    this.validateValue(transactionExpiration);
    this._expiration = transactionExpiration.toNumber();
    return this;
  }

  /**
   * Set an external transaction signature
   *
   * @param {string} signature Hex encoded signature string
   * @param {KeyPair} keyPair The public key keypair that was used to create the signature
   * @returns {TransactionBuilder} This transaction builder
   */
  signature(signature: string, keyPair: KeyPair): this {
    // if we already have a signature for this key pair, just update it
    for (const oldSignature of this._signatures) {
      if (oldSignature.keyPair.getKeys().pub === keyPair.getKeys().pub) {
        oldSignature.signature = signature;
        return this;
      }
    }

    // otherwise add the new signature
    this._signatures.push({ signature, keyPair });
    return this;
  }

  nodeChainName(chainName: string): this {
    this._chainName = chainName;
    return this;
  }

  // endregion

  // region Validators
  /** @inheritdoc */
  validateAddress(address: BaseAddress): void {
    if (!isValidAddress(address.address)) {
      throw new BuildTransactionError('Invalid address ' + address.address);
    }
  }

  /** @inheritdoc */
  validateKey(key: BaseKey): void {
    if (!new KeyPair({ prv: key.key })) {
      throw new BuildTransactionError('Invalid key');
    }
  }

  /** @inheritdoc */
  validateRawTransaction(rawTransaction: string): void {
    if (!rawTransaction) {
      throw new InvalidTransactionError('Raw transaction is empty');
    }
    try {
      DeployUtil.deployFromJson(JSON.parse(rawTransaction));
    } catch (e) {
      throw new ParseTransactionError('There was an error parsing the JSON string');
    }
  }

  /** @inheritdoc */
  validateTransaction(transaction?: Transaction): void {
    this.validateMandatoryFields();
  }

  /** @inheritdoc */
  validateValue(value: BigNumber): void {
    if (value.isLessThan(0)) {
      throw new BuildTransactionError('Value cannot be less than zero');
    }
  }

  /**
   * Validates that the mandatory fields are defined
   */
  validateMandatoryFields(): void {
    this.validateFee();
    this.validateSource();
  }

  /**
   * Validates that the fee field is defined
   */
  private validateFee(): void {
    if (this._fee === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing fee');
    }
    if (!this._fee.gasLimit) {
      throw new BuildTransactionError('Invalid transaction: missing gas limit');
    }
    try {
      this.validateValue(new BigNumber(this._fee.gasLimit));
    } catch (e) {
      throw new BuildTransactionError('Invalid gas limit');
    }
  }

  /**
   * Validates that the source field is defined
   */
  private validateSource(): void {
    if (this._source === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing source');
    }
    this.validateAddress(this._source);
  }

  /**
   * Validates that the given key is not already in this._multiSignerKeyPairs
   *
   * @param {BaseKey} key - The key to check
   */
  private checkDuplicatedKeys(key: BaseKey) {
    this._multiSignerKeyPairs.forEach((_sourceKeyPair) => {
      if (_sourceKeyPair.getKeys().prv === key.key) {
        throw new SigningError('Repeated sign: ' + key.key);
      }
      // Try to get extended keys in order to validate them
      let xprv;
      try {
        xprv = _sourceKeyPair.getExtendedKeys().xprv;
      } catch (err) {
        return;
      }
      if (xprv && xprv === key.key) {
        throw new SigningError('Repeated sign: ' + key.key);
      }
    });
  }

  // endregion

  // region Getters and Setters
  /** @inheritdoc */
  protected get transaction(): Transaction {
    return this._transaction;
  }

  /** @inheritdoc */
  protected set transaction(transaction: Transaction) {
    this._transaction = transaction;
  }

  /**
   * Get the chain name for the coin environment
   */
  public get chainName(): string {
    return this._chainName;
  }
  // endregion

  // region auxiliaryMethods
  /**
   * Generate a DeployParams instance with the transaction data
   *
   * @returns {DeployUtil.DeployParams}
   */
  private getDeployParams(): DeployUtil.DeployParams {
    const gasPrice = this._fee.gasPrice ? _.parseInt(this._fee.gasPrice) : undefined;
    return new DeployUtil.DeployParams(
      PublicKey.fromHex(this._source.address),
      this._chainName,
      gasPrice,
      this._expiration || TRANSACTION_EXPIRATION
    );
  }

  /**
   * Generate the session for the Deploy according to the transactionType.
   *
   * @returns {DeployUtil.ExecutableDeployItem}
   */
  private getSession(): DeployUtil.ExecutableDeployItem {
    let session;
    switch (this.transaction.type) {
      case TransactionType.Send:
        const transferSession = this._session as CasperTransferTransaction;
        session = DeployUtil.ExecutableDeployItem.newTransferWithOptionalTransferId(
          transferSession.amount,
          transferSession.target,
          undefined,
          transferSession.id
        );
        break;
      case TransactionType.WalletInitialization:
      case TransactionType.StakingLock:
      case TransactionType.StakingUnlock:
        const moduleBytesSession = this._session as CasperModuleBytesTransaction;
        session = DeployUtil.ExecutableDeployItem.newModuleBytes(
          moduleBytesSession.moduleBytes,
          moduleBytesSession.args
        );
        break;
      default:
        throw new BuildTransactionError('Transaction Type error');
    }
    return session;
  }

  /**
   * Checks whether the transaction has the owner signature
   *
   * @param {string} pub - public key of the signer
   * @returns {boolean} true if the pub key already signed th transaction
   * @private
   */
  private isTransactionSignedByPub(pub: string): boolean {
    return (
      _.findIndex(this.transaction.casperTx.approvals, (approval) => {
        const approvalSigner = removeAlgoPrefixFromHexValue(approval.signer);
        return approvalSigner === pub;
      }) !== -1
    );
  }

  /**
   * Add signatures to the transaction
   *
   * @private
   */
  private processSigning(): void {
    for (const keyPair of this._multiSignerKeyPairs) {
      // Add signature if it's not already in the deploy
      if (!this.isTransactionSignedByPub(keyPair.getKeys().pub)) {
        this.transaction.sign(keyPair);
      }
    }
    for (const { signature, keyPair } of this._signatures) {
      // Add signature if it's not already in the deploy
      if (!this.isTransactionSignedByPub(keyPair.getKeys().pub)) {
        this.transaction.addSignature(signature, keyPair);
      }
    }
  }
  // endregion
}

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


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