PHP WebShell

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

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

import {
  BaseAddress,
  BaseKey,
  BaseTransaction,
  BaseTransactionBuilder,
  BuildTransactionError,
  SigningError,
  TransactionType,
  xprvToRawPrv,
} from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import * as _ from 'lodash';
import * as xrpl from 'xrpl';
import { Signer } from 'xrpl/dist/npm/models/common';
import { XrpTransaction } from './iface';
import { KeyPair } from './keyPair';
import { Transaction } from './transaction';
import utils from './utils';

/**
 * XRP transaction builder.
 */
export abstract class TransactionBuilder extends BaseTransactionBuilder {
  protected _transaction: Transaction;
  protected _sender: string;
  protected _fee?: string;
  protected _sequence?: number;
  protected _lastLedgerSequence?: number;
  protected _flags?: number = 0;
  protected _signingPubKey?: string;
  protected _signers: Signer[];
  protected _txnSignature?: string;
  protected _specificFields: XrpTransaction;

  protected _isMultiSig?: boolean;

  protected _keyPairs: KeyPair[];

  constructor(_coinConfig: Readonly<CoinConfig>) {
    super(_coinConfig);
    this.transaction = new Transaction(_coinConfig);
    this._keyPairs = [];
    this._signers = [];
  }

  /**
   * The transaction type.
   */
  protected abstract get transactionType(): TransactionType;

  /**
   * Set the transaction signature type to multi sig.
   **/
  setMultiSig(): TransactionBuilder {
    this._isMultiSig = true;
    return this;
  }

  /**
   * Set the transaction signature type to single sig.
   **/
  setSingleSig(): TransactionBuilder {
    this._isMultiSig = false;
    return this;
  }

  /**
   * Sets the sender of this transaction.
   *
   * @param {string} address the account that is sending this transaction
   * @returns {TransactionBuilder} This transaction builder
   */
  sender(address: string): TransactionBuilder {
    this.validateAddress({ address });
    this._sender = address;
    return this;
  }

  sequence(sequence: number): TransactionBuilder {
    if (typeof sequence !== 'number' || sequence < 0) {
      throw new Error(`sequence ${sequence} is not valid`);
    }
    this._sequence = sequence;
    return this;
  }

  fee(fee: string): TransactionBuilder {
    if (typeof fee !== 'string') {
      throw new Error(`fee type ${typeof fee} must be a string`);
    }
    const feeBigInt = BigInt(fee);
    if (feeBigInt < 0) {
      throw new Error(`fee ${fee} is not valid`);
    }
    this._fee = fee;
    return this;
  }

  flags(flags: number): TransactionBuilder {
    if (typeof flags !== 'number' || flags < 0) {
      throw new Error(`flags ${flags} is not valid`);
    }
    this._flags = flags;
    return this;
  }

  lastLedgerSequence(lastLedgerSequence: number): TransactionBuilder {
    if (typeof lastLedgerSequence !== 'number' || lastLedgerSequence < 0) {
      throw new Error(`lastLedgerSequence ${lastLedgerSequence} is not valid`);
    }
    this._lastLedgerSequence = lastLedgerSequence;
    return this;
  }

  /**
   * Initialize the transaction builder fields using the decoded transaction data
   *
   * @param {Transaction} tx the transaction data
   */
  initBuilder(tx: Transaction): void {
    this._transaction = tx;
    const txData = tx.toJson();

    if (!_.isUndefined(txData.isMultiSig)) {
      txData.isMultiSig ? this.setMultiSig() : this.setSingleSig();
    }

    this.sender(txData.from);
    if (txData.fee) {
      this.fee(txData.fee);
    }
    if (txData.sequence) {
      this.sequence(txData.sequence);
    }
    if (txData.lastLedgerSequence) {
      this.lastLedgerSequence(txData.lastLedgerSequence);
    }
    if (txData.flags) {
      this.flags(txData.flags);
    }
    this._signers = txData.signers || [];
    this._signingPubKey = txData.signingPubKey;
    this._txnSignature = txData.txnSignature;
  }

  /** @inheritdoc */
  protected fromImplementation(rawTransaction: string): BaseTransaction {
    const tx = new Transaction(this._coinConfig);
    this.validateRawTransaction(rawTransaction);
    tx.fromRawTransaction(rawTransaction);
    this.initBuilder(tx);
    return this.transaction;
  }

  /** @inheritdoc */
  protected signImplementation(key: BaseKey): Transaction {
    this.validateKey(key);
    this.checkDuplicatedKeys(key);
    let prv = key.key;
    if (prv.startsWith('xprv')) {
      const rawPrv = xprvToRawPrv(prv);
      prv = new KeyPair({ prv: rawPrv }).getKeys().prv;
    }
    const signer = new KeyPair({ prv: prv });

    this._keyPairs.push(signer);

    return this.transaction;
  }

  private checkDuplicatedKeys(key: BaseKey) {
    const keyPair = new KeyPair({ prv: key.key }).getKeys();
    const keyPairPrv = keyPair.prv as string;

    this._keyPairs.forEach((kp) => {
      const prv = kp.getKeys().prv as string;
      if (prv.toLowerCase() === keyPairPrv.toLowerCase()) {
        throw new SigningError('Repeated sign');
      }
    });
    this._signers.forEach((signer) => {
      if (signer.Signer.SigningPubKey.toLowerCase() === keyPair.pub.toLowerCase()) {
        throw new SigningError('Repeated sign');
      }
    });
  }

  /** @inheritdoc */
  protected async buildImplementation(): Promise<Transaction> {
    this.transaction.xrpTransaction = this.buildXrpTransaction();
    this.transaction.setTransactionType(this.transactionType);
    this.transaction.loadInputsAndOutputs();
    if (this._keyPairs.length > 0) {
      this.validateIsMultiSig();
      this.transaction.setMultiSigValue(this._isMultiSig as boolean);
      this.transaction.sign(this._keyPairs);
    }
    return this.transaction;
  }

  private buildXrpTransaction() {
    const commonFields: Partial<XrpTransaction> = {
      Account: this._sender,
      Fee: this._fee,
      Sequence: this._sequence,
      Flags: this._flags,
    };

    if (this._signingPubKey) {
      commonFields.SigningPubKey = this._signingPubKey;
    }
    if (this._txnSignature) {
      commonFields.TxnSignature = this._txnSignature;
    }
    if (this._signers.length > 0) {
      commonFields.Signers = this._signers;
    }
    if (this._lastLedgerSequence) {
      commonFields.LastLedgerSequence = this._lastLedgerSequence;
    }

    const tx = Object.assign(commonFields, this._specificFields);
    xrpl.validate(tx as unknown as Record<string, unknown>);
    return tx;
  }

  validateKey(key: BaseKey): void {
    let keyPair: KeyPair;
    try {
      keyPair = new KeyPair({ prv: key.key });
    } catch {
      throw new BuildTransactionError('Invalid key');
    }

    if (!keyPair.getKeys().prv) {
      throw new BuildTransactionError('Invalid key');
    }
  }

  validateIsMultiSig(): void {
    if (_.isUndefined(this._isMultiSig)) {
      throw new BuildTransactionError('Signature type is not defined. Please call setMultiSig or setSingleSig.');
    }
  }

  /** @inheritdoc */
  validateTransaction(): void {
    if (this._sender === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing sender');
    }
    if (this._fee === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing fee');
    }
    if (this._sequence === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing sequence');
    }
  }

  validateAddress(address: BaseAddress): void {
    if (!utils.isValidAddress(address.address)) {
      throw new BuildTransactionError('Invalid address ' + address.address);
    }
  }

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

  validateRawTransaction(rawTransaction: string): void {
    utils.validateRawTransaction(rawTransaction);
  }

  protected get transaction(): Transaction {
    return this._transaction;
  }

  protected set transaction(transaction: Transaction) {
    this._transaction = transaction;
  }
}

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


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