PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-core/src/bitgo/staking

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

/**
 * @prettier
 */
import {
  DelegationOptions,
  DelegationResults,
  IStakingWallet,
  StakeOptions,
  StakingPrebuildTransactionResult,
  StakingRequest,
  StakingSignedTransaction,
  StakingSignOptions,
  StakingTransaction,
  SwitchValidatorOptions,
  TransactionsReadyToSign,
  UnstakeOptions,
  EthUnstakeOptions,
  ClaimRewardsOptions,
} from './iStakingWallet';
import { BitGoBase } from '../bitgoBase';
import { IWallet, PrebuildTransactionResult } from '../wallet';
import { ITssUtils, RequestTracer, TssUtils } from '../utils';
import assert from 'assert';

export class StakingWallet implements IStakingWallet {
  private readonly bitgo: BitGoBase;
  private tokenParentWallet?: IWallet;
  private readonly isEthTss: boolean;

  public wallet: IWallet;
  public tssUtil: ITssUtils;

  constructor(wallet: IWallet, isEthTss: boolean) {
    this.wallet = wallet;
    this.bitgo = wallet.bitgo;
    this.tssUtil = new TssUtils(this.bitgo, this.wallet.baseCoin, this.wallet);
    this.isEthTss = isEthTss;
  }

  get walletId(): string {
    return this.wallet.id();
  }

  get coin(): string {
    return this.wallet.baseCoin.tokenConfig ? this.wallet.baseCoin.tokenConfig.type : this.wallet.coin();
  }

  /**
   * Stake coins
   * @param options - stake options
   * @return StakingRequest
   */
  async stake(options: StakeOptions): Promise<StakingRequest> {
    return await this.createStakingRequest(options, 'STAKE');
  }

  /**
   * Unstake coins
   * @param options - unstake options
   * @return StakingRequest
   */
  async unstake(options: UnstakeOptions | EthUnstakeOptions): Promise<StakingRequest> {
    return await this.createStakingRequest(options, 'UNSTAKE');
  }

  /**
   * Submit a request to switch the validator used for a specific delegation
   * This will create a new delegation with the new validator address and mark the old delegation as inactive
   * @param options - switch validator options
   * @return StakingRequest
   */
  async switchValidator(options: SwitchValidatorOptions): Promise<StakingRequest> {
    return await this.createStakingRequest(options, 'SWITCH_VALIDATOR');
  }

  /**
   * Submit a request to claim rewards for a specific delegation
   * @param options - claim rewards options
   * @return StakingRequest
   */
  async claimRewards(options: ClaimRewardsOptions): Promise<StakingRequest> {
    return await this.createStakingRequest(options, 'CLAIM_REWARDS');
  }

  /**
   * Cancel staking request
   * @param stakingRequestId - id of the staking request to cancel
   * @return StakingRequest
   */
  async cancelStakingRequest(stakingRequestId: string): Promise<StakingRequest> {
    return await this.bitgo.del(this.bitgo.microservicesUrl(this.stakingRequestUrl(stakingRequestId))).result();
  }

  /**
   * Fetch delegations for a specific wallet
   * @param options - unstake options
   * @return StakingRequest
   */
  async delegations(options: DelegationOptions): Promise<DelegationResults> {
    return await this.getDelegations(options);
  }

  /**
   * Get a staking request by ID
   * @param stakingRequestId - id of the staking request to retrieve
   * @return StakingRequest
   */
  async getStakingRequest(stakingRequestId: string): Promise<StakingRequest> {
    return await this.bitgo.get(this.bitgo.microservicesUrl(this.stakingRequestUrl(stakingRequestId))).result();
  }

  /**
   * Get transactions ready to sign
   * @param stakingRequestId
   * @return TransactionsReadyToSign
   */
  async getTransactionsReadyToSign(stakingRequestId: string): Promise<TransactionsReadyToSign> {
    const stakingRequest: StakingRequest = await this.getStakingRequest(stakingRequestId);
    const readyToSign: StakingTransaction[] = stakingRequest.transactions.filter(
      (transaction: StakingTransaction) => transaction.status === `READY`
    );
    const newTransactions: StakingTransaction[] = stakingRequest.transactions.filter(
      (transaction: StakingTransaction) => transaction.status === `NEW`
    );

    return Promise.resolve({
      allSigningComplete:
        stakingRequest.transactions.length > 0 && newTransactions.length === 0 && readyToSign.length === 0,
      transactions: readyToSign,
    });
  }

  /**
   * Build the staking transaction
   * If TSS delete signature shares, else expand build params and then build
   * @param transaction - staking transaction to build
   */
  async build(transaction: StakingTransaction): Promise<StakingPrebuildTransactionResult> {
    if ((this.wallet.baseCoin.supportsTss() && this.wallet.baseCoin.getFamily() !== 'eth') || this.isEthTss) {
      if (!transaction.txRequestId) {
        throw new Error('txRequestId is required to sign and send');
      }
      // delete signature shares before signing for transaction request API
      await this.tssUtil.deleteSignatureShares(transaction.txRequestId);
      return {
        transaction: transaction,
        result: {
          walletId: this.walletId,
          txRequestId: transaction.txRequestId,
        },
      };
    } else {
      transaction = await this.expandBuildParams(transaction);
      if (!transaction.buildParams) {
        throw Error(`Staking transaction ${transaction.id} build params not expanded`);
      }
      const isBtcUndelegate =
        this.wallet.baseCoin.getFamily() === 'btc' &&
        transaction.transactionType.toLowerCase() === 'undelegate_withdraw';
      const wallet = isBtcUndelegate
        ? await this.getDescriptorWallet(transaction)
        : await this.getWalletForBuildingAndSigning();

      return {
        transaction: transaction,
        result: await wallet.prebuildTransaction(transaction.buildParams),
      };
    }
  }

  /**
   * Sign the staking transaction
   * @param signOptions
   * @param stakingPrebuildTransaction
   */
  async sign(
    signOptions: StakingSignOptions,
    stakingPrebuildTransaction: StakingPrebuildTransactionResult
  ): Promise<StakingSignedTransaction> {
    const reqId = new RequestTracer();
    const isBtcUndelegate =
      this.wallet.baseCoin.getFamily() === 'btc' &&
      stakingPrebuildTransaction.transaction.transactionType.toLowerCase() === 'undelegate_withdraw';
    const wallet = isBtcUndelegate
      ? await this.getDescriptorWallet(stakingPrebuildTransaction.transaction)
      : await this.getWalletForBuildingAndSigning();

    const keychain = await wallet.baseCoin.keychains().getKeysForSigning({
      wallet: this.wallet,
      reqId: reqId,
    });
    return {
      transaction: stakingPrebuildTransaction.transaction,
      signed: await wallet.signTransaction({
        txPrebuild: stakingPrebuildTransaction.result,
        walletPassphrase: signOptions.walletPassphrase,
        keychain: keychain[0],
      }),
    };
  }

  /**
   * Send the signed staking transaction if required. Send call is not required if api version is full
   * and this method will return the staking transaction from the incoming object.
   * @param signedTransaction
   */
  async send(signedTransaction: StakingSignedTransaction): Promise<StakingTransaction> {
    if (this.isSendCallRequired()) {
      return await this.bitgo
        .post(this.bitgo.microservicesUrl(this.stakingTransactionURL(signedTransaction.transaction)))
        .send(signedTransaction.signed)
        .result();
    }
    return signedTransaction.transaction;
  }

  /**
   * @Deprecated use buildAndSign
   * Build, sign and send the transaction.
   * @param signOptions
   * @param transaction
   */
  async buildSignAndSend(
    signOptions: StakingSignOptions,
    transaction: StakingTransaction
  ): Promise<StakingTransaction> {
    return await this.buildAndSign(signOptions, transaction).then((result: StakingSignedTransaction) => {
      return this.send(result);
    });
  }

  /**
   * Create prebuilt staking transaction.
   *
   * for transactions with tx request id (TSS transactions), we need to delete signature shares before creating prebuild transaction
   * we only need to get transaction build params if they exist to pre build
   *
   * @param transaction
   */
  async prebuildSelfManagedStakingTransaction(transaction: StakingTransaction): Promise<PrebuildTransactionResult> {
    if (transaction.txRequestId) {
      await this.tssUtil.deleteSignatureShares(transaction.txRequestId);
    }
    const buildParams = (await this.expandBuildParams(transaction)).buildParams;
    const formattedParams = {
      ...buildParams,
      coin: this.coin,
      walletId: this.walletId,
      walletType: this.wallet.type(),
      preview: true,
    };
    return await (await this.getWalletForBuildingAndSigning()).prebuildTransaction(formattedParams);
  }

  /**
   * Build and sign the transaction.
   * @param signOptions
   * @param transaction
   */
  async buildAndSign(
    signOptions: StakingSignOptions,
    transaction: StakingTransaction
  ): Promise<StakingSignedTransaction> {
    const builtTx = await this.build(transaction);
    return await this.sign(signOptions, builtTx);
  }

  private async expandBuildParams(stakingTransaction: StakingTransaction): Promise<StakingTransaction> {
    return await this.bitgo
      .get(this.bitgo.microservicesUrl(this.stakingTransactionURL(stakingTransaction)))
      .query({ expandBuildParams: true })
      .result();
  }

  private async createStakingRequest(
    options: StakeOptions | UnstakeOptions | EthUnstakeOptions | SwitchValidatorOptions | ClaimRewardsOptions,
    type: string
  ): Promise<StakingRequest> {
    return await this.bitgo
      .post(this.bitgo.microservicesUrl(this.stakingRequestsURL()))
      .send({
        ...options,
        type: type,
      })
      .result();
  }

  private stakingRequestsURL() {
    return `/api/staking/v1/${this.coin}/wallets/${this.walletId}/requests`;
  }

  private async getDelegations(options: DelegationOptions): Promise<DelegationResults> {
    return await this.bitgo.get(this.bitgo.microservicesUrl(this.stakingDelegationsURL())).query(options).result();
  }

  private stakingDelegationsURL() {
    return `/api/staking/v1/${this.coin}/wallets/${this.walletId}/delegations`;
  }

  private stakingRequestUrl(stakingRequestId: string): string {
    return `${this.stakingRequestsURL()}/${stakingRequestId}`;
  }

  private stakingTransactionURL(stakingTransaction: StakingTransaction): string {
    return `${this.stakingRequestUrl(stakingTransaction.stakingRequestId)}/transactions/${stakingTransaction.id}`;
  }

  private async getWalletForBuildingAndSigning(): Promise<IWallet> {
    if (this.wallet.baseCoin.tokenConfig) {
      if (!this.tokenParentWallet) {
        this.tokenParentWallet = await this.bitgo
          .coin(this.wallet.baseCoin.tokenConfig.coin)
          .wallets()
          .get({ id: this.wallet.id() });
      }
      return this.tokenParentWallet;
    } else {
      return Promise.resolve(this.wallet);
    }
  }

  /**
   * Send API call is only required for TSS TxRequest api version lite or multi-sig transactions.
   * For Full api version, sign transaction moves the transaction to delivered state.
   * @returns true if send API call to staking service is required else false
   */
  private isSendCallRequired(): boolean {
    if (this.wallet.baseCoin.getFamily() === 'eth') {
      return !this.isEthTss;
    } else if (this.wallet.baseCoin.supportsTss()) {
      return this.wallet.baseCoin.getMPCAlgorithm() !== 'ecdsa';
    } else {
      return true;
    }
  }

  private async getDescriptorWallet(transaction: StakingTransaction): Promise<IWallet> {
    assert(transaction.buildParams?.senderWalletId, 'senderWalletId is required for btc undelegate transaction');
    return await this.wallet.baseCoin.wallets().get({ id: transaction.buildParams.senderWalletId });
  }
}

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


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