PHP WebShell

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

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

import BigNumber from 'bignumber.js';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import {
  BaseAddress,
  BaseKey,
  BaseTransactionBuilder,
  BuildTransactionError,
  FeeOptions,
  PublicKey as BasePublicKey,
  Signature,
  SigningError,
  TransactionType,
} from '@bitgo/sdk-core';
import { Transaction } from './transaction';
import { Blockhash, PublicKey, Transaction as SolTransaction } from '@solana/web3.js';
import {
  isValidAddress,
  isValidAmount,
  isValidBlockId,
  isValidMemo,
  validateAddress,
  validateRawTransaction,
} from './utils';
import { KeyPair } from '.';
import { InstructionBuilderTypes } from './constants';
import { solInstructionFactory } from './solInstructionFactory';
import assert from 'assert';
import { DurableNonceParams, InstructionParams, Memo, Nonce, SetPriorityFee, Transfer } from './iface';
import { instructionParamsFactory } from './instructionParamsFactory';

export abstract class TransactionBuilder extends BaseTransactionBuilder {
  protected _transaction: Transaction;
  private _signatures: Signature[] = [];
  private _lamportsPerSignature: number;
  private _tokenAccountRentExemptAmount: string;

  protected _sender: string;
  protected _recentBlockhash: Blockhash;
  protected _nonceInfo: Nonce;
  protected _instructionsData: InstructionParams[] = [];
  protected _signers: KeyPair[] = [];
  protected _memo?: string;
  protected _feePayer?: string;
  protected _priorityFee: number;

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

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

  /**
   * 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();

    const filteredTransferInstructionsData = txData.instructionsData.filter(
      (data) => data.type === InstructionBuilderTypes.Transfer
    );
    let sender;
    if (filteredTransferInstructionsData.length > 0) {
      const transferInstructionsData = filteredTransferInstructionsData[0] as Transfer;
      sender = transferInstructionsData.params.fromAddress;
    } else {
      sender = txData.feePayer;
    }
    this.sender(sender);
    this.feePayer(txData.feePayer as string);
    this.nonce(txData.nonce, txData.durableNonce);
    this._instructionsData = instructionParamsFactory(tx.type, tx.solTransaction.instructions, this._coinConfig.name);
    // Parse priority fee instruction data
    const filteredPriorityFeeInstructionsData = txData.instructionsData.filter(
      (data) => data.type === InstructionBuilderTypes.SetPriorityFee
    );

    for (const instruction of this._instructionsData) {
      if (instruction.type === InstructionBuilderTypes.Memo) {
        const memoInstruction: Memo = instruction;
        this.memo(memoInstruction.params.memo);
      }

      if (instruction.type === InstructionBuilderTypes.NonceAdvance) {
        const advanceNonceInstruction: Nonce = instruction;
        this.nonce(txData.nonce, advanceNonceInstruction.params);
      }

      // If prio fee instruction exists, set the priority fee variable
      if (instruction.type === InstructionBuilderTypes.SetPriorityFee) {
        const priorityFeeInstructionsData = filteredPriorityFeeInstructionsData[0] as SetPriorityFee;
        this.setPriorityFee({ amount: Number(priorityFeeInstructionsData.params.fee) });
      }
    }
  }

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

  /** @inheritdoc */
  protected async buildImplementation(): Promise<Transaction> {
    this.transaction.solTransaction = this.buildSolTransaction();
    this.transaction.setTransactionType(this.transactionType);
    this.transaction.loadInputsAndOutputs();
    this._transaction.tokenAccountRentExemptAmount = this._tokenAccountRentExemptAmount;
    return this.transaction;
  }

  /**
   * Builds the solana transaction.
   */
  protected buildSolTransaction(): SolTransaction {
    assert(this._sender, new BuildTransactionError('sender is required before building'));
    assert(this._recentBlockhash, new BuildTransactionError('recent blockhash is required before building'));

    const tx = new SolTransaction();
    if (this._transaction?.solTransaction?.signatures) {
      tx.signatures = this._transaction?.solTransaction?.signatures;
    }

    tx.feePayer = this._feePayer ? new PublicKey(this._feePayer) : new PublicKey(this._sender);

    if (this._nonceInfo) {
      tx.nonceInfo = {
        nonce: this._recentBlockhash,
        nonceInstruction: solInstructionFactory(this._nonceInfo)[0],
      };
    } else {
      tx.recentBlockhash = this._recentBlockhash;
    }
    for (const instruction of this._instructionsData) {
      tx.add(...solInstructionFactory(instruction));
    }

    if (this._memo) {
      const memoData: Memo = {
        type: InstructionBuilderTypes.Memo,
        params: {
          memo: this._memo,
        },
      };
      this._instructionsData.push(memoData);
      tx.add(...solInstructionFactory(memoData));
    }

    this._transaction.lamportsPerSignature = this._lamportsPerSignature;

    for (const signer of this._signers) {
      const publicKey = new PublicKey(signer.getKeys().pub);
      const secretKey = signer.getKeys(true).prv;
      assert(secretKey instanceof Uint8Array);
      tx.partialSign({ publicKey, secretKey });
    }

    for (const signature of this._signatures) {
      const solPublicKey = new PublicKey(signature.publicKey.pub);
      tx.addSignature(solPublicKey, signature.signature);
    }

    return tx;
  }

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

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

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

    return this._transaction;
  }

  /** @inheritDoc */
  addSignature(publicKey: BasePublicKey, signature: Buffer): void {
    this._signatures.push({ publicKey, signature });
  }

  /**
   * Sets the sender of this transaction.
   * This account will be responsible for paying transaction fees.
   *
   * @param {string} senderAddress the account that is sending this transaction
   * @returns {TransactionBuilder} This transaction builder
   */
  sender(senderAddress: string): this {
    validateAddress(senderAddress, 'sender');
    this._sender = senderAddress;
    return this;
  }

  /**
   * Set the transaction nonce
   * Requires both optional params in order to use the durable nonce
   *
   * @param {Blockhash} blockHash The latest blockHash
   * @param {DurableNonceParams} [durableNonceParams] An object containing the walletNonceAddress and the authWalletAddress (required for durable nonce)
   * @returns {TransactionBuilder} This transaction builder
   */
  nonce(blockHash: Blockhash, durableNonceParams?: DurableNonceParams): this {
    if (!blockHash || !isValidBlockId(blockHash)) {
      throw new BuildTransactionError('Invalid or missing blockHash, got: ' + blockHash);
    }
    if (durableNonceParams) {
      validateAddress(durableNonceParams.walletNonceAddress, 'walletNonceAddress');
      validateAddress(durableNonceParams.authWalletAddress, 'authWalletAddress');
      if (durableNonceParams.walletNonceAddress === durableNonceParams.authWalletAddress) {
        throw new BuildTransactionError('Invalid params: walletNonceAddress cannot be equal to authWalletAddress');
      }
      this._nonceInfo = {
        type: InstructionBuilderTypes.NonceAdvance,
        params: durableNonceParams,
      };
    }
    this._recentBlockhash = blockHash;
    return this;
  }

  /**
   *  Set the memo
   *
   * @param {string} memo
   * @returns {TransactionBuilder} This transaction builder
   */
  memo(memo: string): this {
    this.validateMemo(memo);
    this._memo = memo;
    return this;
  }

  fee(feeOptions: FeeOptions): this {
    this._lamportsPerSignature = Number(feeOptions.amount);
    return this;
  }

  public setPriorityFee(feeOptions: FeeOptions): this {
    this._priorityFee = Number(feeOptions.amount);
    return this;
  }

  feePayer(feePayer: string): this {
    this._feePayer = feePayer;
    return this;
  }

  /**
   * Used to set the minimum rent exempt amount for an ATA
   *
   * @param tokenAccountRentExemptAmount minimum rent exempt amount in lamports
   */
  associatedTokenAccountRent(tokenAccountRentExemptAmount: string): this {
    this.validateRentExemptAmount(tokenAccountRentExemptAmount);
    this._tokenAccountRentExemptAmount = tokenAccountRentExemptAmount;
    return this;
  }

  private validateRentExemptAmount(tokenAccountRentExemptAmount: string) {
    // _tokenAccountRentExemptAmount is allowed to be undefined or a valid amount if it's defined
    if (tokenAccountRentExemptAmount && !isValidAmount(tokenAccountRentExemptAmount)) {
      throw new BuildTransactionError('Invalid tokenAccountRentExemptAmount, got: ' + tokenAccountRentExemptAmount);
    }
  }

  // endregion

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

  /** @inheritdoc */
  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');
    }
  }

  /** @inheritdoc */
  validateRawTransaction(rawTransaction: string): void {
    validateRawTransaction(rawTransaction);
  }

  /** @inheritdoc */
  validateTransaction(transaction?: Transaction): void {
    this.validateSender();
    this.validateNonce();
    this.validateRentExemptAmount(this._tokenAccountRentExemptAmount);
  }

  /** @inheritdoc */
  validateValue(value: BigNumber): void {
    if (value.isLessThan(0)) {
      throw new BuildTransactionError('Value cannot be less than zero');
    }
  }
  /** Validates the memo
   *
   * @param {string} memo - the memo as string
   */
  validateMemo(memo: string): void {
    if (!memo) {
      throw new BuildTransactionError('Invalid memo, got: ' + memo);
    }
    if (!isValidMemo(memo)) {
      throw new BuildTransactionError('Memo is too long');
    }
  }

  /**
   * Validates that the given key is not already in this._signers
   *
   * @param {BaseKey} key - The key to check
   */
  private checkDuplicatedSigner(key: BaseKey) {
    this._signers.forEach((kp) => {
      if (kp.getKeys().prv === key.key) {
        throw new SigningError('Duplicated signer: ' + key.key);
      }
    });
  }

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

  /**
   * Validates that the nonce field is defined
   */
  private validateNonce(): void {
    if (this._recentBlockhash === undefined) {
      throw new BuildTransactionError('Invalid transaction: missing nonce blockhash');
    }
  }
  // endregion
}

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


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