PHP WebShell

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

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

import { TransactionBuilder } from './transactionBuilder';
import { BuildTransactionError, TransactionType } from '@bitgo/sdk-core';
import { Transaction } from './transaction';
import {
  getAssociatedTokenAccountAddress,
  getSolTokenFromTokenName,
  isValidAmount,
  validateAddress,
  validateMintAddress,
  validateOwnerAddress,
} from './utils';
import { BaseCoin as CoinConfig, SolCoin } from '@bitgo/statics';
import assert from 'assert';
import { AtaInit, TokenAssociateRecipient, TokenTransfer, Transfer, SetPriorityFee } from './iface';
import { InstructionBuilderTypes } from './constants';
import _ from 'lodash';

export interface SendParams {
  address: string;
  amount: string;
  tokenName?: string;
}

const UNSIGNED_BIGINT_MAX = BigInt('18446744073709551615');

export class TransferBuilderV2 extends TransactionBuilder {
  private _sendParams: SendParams[] = [];
  private _createAtaParams: TokenAssociateRecipient[];
  constructor(_coinConfig: Readonly<CoinConfig>) {
    super(_coinConfig);
    this._createAtaParams = [];
  }

  protected get transactionType(): TransactionType {
    return TransactionType.Send;
  }

  initBuilder(tx: Transaction): void {
    super.initBuilder(tx);

    for (const instruction of this._instructionsData) {
      if (instruction.type === InstructionBuilderTypes.Transfer) {
        const transferInstruction: Transfer = instruction;
        this.sender(transferInstruction.params.fromAddress);
        this.send({
          address: transferInstruction.params.toAddress,
          amount: transferInstruction.params.amount,
        });
      } else if (instruction.type === InstructionBuilderTypes.TokenTransfer) {
        const transferInstruction: TokenTransfer = instruction;
        this.sender(transferInstruction.params.fromAddress);
        this.send({
          address: transferInstruction.params.toAddress,
          amount: transferInstruction.params.amount,
          tokenName: transferInstruction.params.tokenName,
        });
      } else if (instruction.type === InstructionBuilderTypes.CreateAssociatedTokenAccount) {
        const ataInitInstruction: AtaInit = instruction;
        this._createAtaParams.push({
          ownerAddress: ataInitInstruction.params.ownerAddress,
          tokenName: ataInitInstruction.params.tokenName,
          ataAddress: ataInitInstruction.params.ataAddress,
        });
      }
    }
  }

  /**
   * Set a feePayer
   * @param payerAddress
   */
  feePayer(payerAddress: string): this {
    validateAddress(payerAddress, 'address');
    this._feePayer = payerAddress;
    return this;
  }

  /**
   *  Set a transfer
   *
   * @param {SendParams} sendParams - sendParams
   * @returns {TransactionBuilder} This transaction builder
   */
  send(sendParams: SendParams): this {
    validateAddress(sendParams.address, 'address');
    if (!sendParams.amount || !isValidAmount(sendParams.amount)) {
      throw new BuildTransactionError('Invalid or missing amount, got: ' + sendParams.amount);
    }
    if (sendParams.tokenName && BigInt(sendParams.amount) > UNSIGNED_BIGINT_MAX) {
      throw new BuildTransactionError(`input amount ${sendParams.amount} exceeds big int limit ${UNSIGNED_BIGINT_MAX}`);
    } else if (!sendParams.tokenName && BigInt(sendParams.amount) > BigInt(Number.MAX_SAFE_INTEGER)) {
      throw new BuildTransactionError(
        `input amount ${sendParams.amount} exceeds max safe int ${Number.MAX_SAFE_INTEGER}`
      );
    }

    this._sendParams.push(sendParams);
    return this;
  }

  /**
   *
   * @param {TokenAssociateRecipient} recipient - recipient of the associated token account creation
   * @param {string} recipient.ownerAddress - owner of the associated token account
   * @param {string} recipient.tokenName - name of the token that is intended to associate
   * @returns {TransactionBuilder} This transaction builder
   */
  createAssociatedTokenAccount(recipient: TokenAssociateRecipient): this {
    validateOwnerAddress(recipient.ownerAddress);
    const token = getSolTokenFromTokenName(recipient.tokenName);
    if (!token) {
      throw new BuildTransactionError('Invalid token name, got: ' + recipient.tokenName);
    }
    validateMintAddress(token.tokenAddress);

    this._createAtaParams.push(recipient);
    return this;
  }

  /** @inheritdoc */
  protected async buildImplementation(): Promise<Transaction> {
    assert(this._sender, 'Sender must be set before building the transaction');
    const sendInstructions = await Promise.all(
      this._sendParams.map(async (sendParams: SendParams): Promise<Transfer | TokenTransfer> => {
        if (sendParams.tokenName) {
          const coin = getSolTokenFromTokenName(sendParams.tokenName);
          assert(coin instanceof SolCoin);
          const sourceAddress = await getAssociatedTokenAccountAddress(coin.tokenAddress, this._sender);
          return {
            type: InstructionBuilderTypes.TokenTransfer,
            params: {
              fromAddress: this._sender,
              toAddress: sendParams.address,
              amount: sendParams.amount,
              tokenName: coin.name,
              sourceAddress: sourceAddress,
            },
          };
        } else {
          return {
            type: InstructionBuilderTypes.Transfer,
            params: {
              fromAddress: this._sender,
              toAddress: sendParams.address,
              amount: sendParams.amount,
            },
          };
        }
      })
    );
    const uniqueCreateAtaParams = _.uniqBy(this._createAtaParams, (recipient: TokenAssociateRecipient) => {
      return recipient.ownerAddress + recipient.tokenName;
    });
    const createAtaInstructions = await Promise.all(
      uniqueCreateAtaParams.map(async (recipient: TokenAssociateRecipient): Promise<AtaInit> => {
        const coin = getSolTokenFromTokenName(recipient.tokenName);
        assert(coin instanceof SolCoin);
        const recipientTokenAddress = await getAssociatedTokenAccountAddress(coin.tokenAddress, recipient.ownerAddress);
        return {
          type: InstructionBuilderTypes.CreateAssociatedTokenAccount,
          params: {
            ownerAddress: recipient.ownerAddress,
            tokenName: coin.name,
            mintAddress: coin.tokenAddress,
            ataAddress: recipientTokenAddress,
            payerAddress: this._sender,
          },
        };
      })
    );

    let addPriorityFeeInstruction: SetPriorityFee;
    // If there are createAtaInstructions, then token is involved and we need to add a priority fee instruction
    if (!this._priorityFee || this._priorityFee === Number(0)) {
      this._instructionsData = [...createAtaInstructions, ...sendInstructions];
    } else if (
      createAtaInstructions.length !== 0 ||
      sendInstructions.some((instruction) => instruction.type === InstructionBuilderTypes.TokenTransfer)
    ) {
      addPriorityFeeInstruction = {
        type: InstructionBuilderTypes.SetPriorityFee,
        params: {
          fee: this._priorityFee,
        },
      };
      this._instructionsData = [addPriorityFeeInstruction, ...createAtaInstructions, ...sendInstructions];
    }

    return await super.buildImplementation();
  }
}

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


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