PHP WebShell

Текущая директория: /opt/BitGoJS/modules/sdk-coin-eth/test/unit

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

import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';

import nock from 'nock';
import sinon from 'sinon';
import { bip32 } from '@bitgo/secp256k1';
import * as secp256k1 from 'secp256k1';
import {
  common,
  InvalidAddressError,
  InvalidAddressVerificationObjectPropertyError,
  TransactionType,
  UnexpectedAddressError,
  Wallet,
} from '@bitgo/sdk-core';
import { BitGoAPI } from '@bitgo/sdk-api';
import {
  AbstractEthLikeNewCoins,
  Erc20Token,
  Hteth,
  Teth,
  TransactionBuilder,
  TransferBuilder,
  UnsignedSweepTxMPCv2,
} from '../../src';
import { EthereumNetwork } from '@bitgo/statics';
import assert from 'assert';
import { getBuilder } from './getBuilder';
import * as testData from '../resources/eth';
import * as mockData from '../fixtures/eth';
import should from 'should';

nock.enableNetConnect();

describe('ETH:', function () {
  let bitgo: TestBitGoAPI;
  let hopTxBitgoSignature;
  let sandbox: sinon.SinonSandbox;

  const address1 = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
  const address2 = '0x7e85bdc27c050e3905ebf4b8e634d9ad6edd0de6';
  const hopContractAddress = '0x47ce7cc86efefef19f8fb516b11735d183da8635';
  const hopDestinationAddress = '0x9c7e8ce6825bD48278B3Ab59228EE26f8BE7925b';
  const hopTx =
    '0xf86b808504a817c8ff8252ff949c7e8ce6825bd48278b3ab59228ee26f8be7925b87038d7ea4c68000801ca011bc22c664570133dfca4f08a0b8d02339cf467046d6a4152f04f368d0eaf99ea01d6dc5cf0c897c8d4c3e1df53d0d042784c424536a4cc5b802552b7d64fee8b5';
  const hopTxid = '0x4af65143bc77da2b50f35b3d13cacb4db18f026bf84bc0743550bc57b9b53351';
  const userReqSig =
    '0x404db307f6147f0d8cd338c34c13906ef46a6faa7e0e119d5194ef05aec16e6f3d710f9b7901460f97e924066b62efd74443bd34402c6d40b49c203a559ff2c8';

  before(function () {
    const bitgoKeyXprv =
      'xprv9s21ZrQH143K3tpWBHWe31sLoXNRQ9AvRYJgitkKxQ4ATFQMwvr7hHNqYRUnS7PsjzB7aK1VxqHLuNQjj1sckJ2Jwo2qxmsvejwECSpFMfC';
    const bitgoKey = bip32.fromBase58(bitgoKeyXprv);
    if (!bitgoKey.privateKey) {
      throw new Error('no privateKey');
    }
    const bitgoXpub = bitgoKey.neutered().toBase58();
    hopTxBitgoSignature =
      '0xaa' +
      Buffer.from(secp256k1.ecdsaSign(Buffer.from(hopTxid.slice(2), 'hex'), bitgoKey.privateKey).signature).toString(
        'hex'
      );

    const env = 'test';
    bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
    Erc20Token.createTokenConstructors().forEach(({ name, coinConstructor }) => {
      bitgo.safeRegister(name, coinConstructor);
    });
    bitgo.safeRegister('teth', Teth.createInstance);
    bitgo.safeRegister('hteth', Hteth.createInstance);
    common.Environments[env].hsmXpub = bitgoXpub;
    bitgo.initializeTestVars();
    sandbox = sinon.createSandbox();
  });

  after(function () {
    nock.cleanAll();
    sandbox.restore();
  });

  describe('EIP1559', function () {
    it('should sign a transaction with EIP1559 fee params', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const userKeychain = {
        prv: 'xprv9s21ZrQH143K3hekyNj7TciR4XNYe1kMj68W2ipjJGNHETWP7o42AjDnSPgKhdZ4x8NBAvaL72RrXjuXNdmkMqLERZza73oYugGtbLFXG8g',
        pub: 'xpub661MyMwAqRbcGBjE5QG7pkf9cZD33UUD6K46q7ELrbuG7FqXfLNGiXYGHeEnGBb5AWREnk1eA28g8ArZvURbhshXWkTtddHRo54fgyVvLdb',
        rawPub: '023636e68b7b204573abda2616aff6b584910dece2543f1cc6d842caac7d74974b',
        rawPrv: '7438a50010ce7b1dfd86e68046cc78ba1ebd242d6d85d9904d3fcc08734bc172',
      };

      const halfSignedTransaction = await coin.signTransaction({
        txPrebuild: {
          eip1559: { maxPriorityFeePerGas: 10, maxFeePerGas: 10 },
          isBatch: false,
          recipients: [
            {
              amount: '42',
              address: '0xc93b13642d93b4218bb85f67317d6b37286e8028',
            },
          ],
          expireTime: 1627949214,
          contractSequenceId: 12,
          gasLimit: undefined,
          gasPrice: undefined,
          hopTransaction: undefined,
          backupKeyNonce: undefined,
          sequenceId: undefined,
          nextContractSequenceId: 0,
        },
        prv: userKeychain.prv,
      } as any);

      (halfSignedTransaction as any).halfSigned.eip1559.maxPriorityFeePerGas.should.equal(10);
      (halfSignedTransaction as any).halfSigned.eip1559.maxFeePerGas.should.equal(10);
    });

    it('should sign a transaction with EIP1559 fee params for CCR', async function () {
      const coin = bitgo.coin('hteth') as Hteth;
      const signTransaction = sinon.spy(AbstractEthLikeNewCoins.prototype, 'signTransaction');

      const userKeychain = {
        prv: 'xprv9s21ZrQH143K3hekyNj7TciR4XNYe1kMj68W2ipjJGNHETWP7o42AjDnSPgKhdZ4x8NBAvaL72RrXjuXNdmkMqLERZza73oYugGtbLFXG8g',
        pub: 'xpub661MyMwAqRbcGBjE5QG7pkf9cZD33UUD6K46q7ELrbuG7FqXfLNGiXYGHeEnGBb5AWREnk1eA28g8ArZvURbhshXWkTtddHRo54fgyVvLdb',
        rawPub: '023636e68b7b204573abda2616aff6b584910dece2543f1cc6d842caac7d74974b',
        rawPrv: '7438a50010ce7b1dfd86e68046cc78ba1ebd242d6d85d9904d3fcc08734bc172',
      };
      const txBuilder = getBuilder('hteth') as TransactionBuilder;
      txBuilder.type(TransactionType.Send);
      txBuilder.fee({
        fee: '10',
        gasLimit: '1000',
      });
      const key = testData.KEYPAIR_PRV.getKeys().prv as string;
      const transferBuilder = txBuilder.transfer() as TransferBuilder;
      transferBuilder
        .amount('0')
        .to('0x19645032c7f1533395d44a629462e751084d3e4c')
        .expirationTime(1590066728)
        .contractSequenceId(5)
        .key(key);
      txBuilder.contract(address1);
      const tx = await txBuilder.build();

      const halfSignedTransaction = await coin.signTransaction({
        txPrebuild: {
          eip1559: { maxPriorityFeePerGas: 10, maxFeePerGas: 10 },
          isBatch: false,
          recipients: [
            {
              amount: '42',
              address: '0xc93b13642d93b4218bb85f67317d6b37286e8028',
            },
          ],
          expireTime: 1627949214,
          contractSequenceId: 12,
          gasLimit: undefined,
          gasPrice: undefined,
          hopTransaction: undefined,
          backupKeyNonce: undefined,
          sequenceId: undefined,
          nextContractSequenceId: 0,
          txHex: tx.toBroadcastFormat(),
        },
        prv: userKeychain.prv,
        isEvmBasedCrossChainRecovery: true,
      } as any);

      assert((halfSignedTransaction as any).halfSigned.txHex);
      assert.strictEqual((halfSignedTransaction as any).halfSigned.eip1559.maxFeePerGas, 10);

      sandbox.assert.calledOnce(signTransaction);
    });
  });

  describe('Transaction Verification', function () {
    it('should verify a normal txPrebuild from the bitgo server that matches the client txParams', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        wallet: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      const isTransactionVerified = await coin.verifyTransaction({
        txParams,
        txPrebuild: txPrebuild as any,
        wallet,
        verification,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should verify a batch txPrebuild from the bitgo server that matches the client txParams', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [
          { amount: '1000000000000', address: address1 },
          { amount: '2500000000000', address: address2 },
        ],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [
          { amount: '3500000000000', address: (coin?.staticsCoin?.network as EthereumNetwork).batcherContractAddress },
        ],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: true,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      const isTransactionVerified = await coin.verifyTransaction({
        txParams,
        txPrebuild: txPrebuild as any,
        wallet,
        verification,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should verify ENS address resolution changing recipient address in client txParams', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: '1000000000000', address: 'bitgotestwallet.eth' }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [{ amount: '1000000000000', address: '0x40a663963810449d6e72533657a74f112c3b901a' }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      const isTransactionVerified = await coin.verifyTransaction({
        txParams,
        txPrebuild: txPrebuild as any,
        wallet,
        verification,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should verify a hop txPrebuild from the bitgo server that matches the client txParams', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: 1000000000000000, address: hopDestinationAddress }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
        hop: true,
      };

      const txPrebuild = {
        recipients: [{ amount: '5000000000000000', address: hopContractAddress }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
        hopTransaction: {
          tx: hopTx,
          id: hopTxid,
          signature: hopTxBitgoSignature,
          paymentId: '2773928196',
          gasPrice: 20000000000,
          gasLimit: 500000,
          amount: '1000000000000000',
          recipient: hopDestinationAddress,
          nonce: 0,
          userReqSig: userReqSig,
          gasPriceMax: 500000000000,
        },
      };

      const verification = {};

      const isTransactionVerified = await coin.verifyTransaction({
        txParams,
        txPrebuild: txPrebuild as any,
        wallet,
        verification,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should reject when client txParams are missing', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = null;

      const txPrebuild = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams: txParams as any, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith('missing params');
    });

    it('should reject txPrebuild that is both batch and hop', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [
          { amount: '1000000000000', address: address1 },
          { amount: '2500000000000', address: address2 },
        ],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
        hop: true,
      };

      const txPrebuild = {
        recipients: [{ amount: '3500000000000', address: address1 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: true,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
        hopTransaction: {
          tx: hopTx,
          id: hopTxid,
          signature: hopTxBitgoSignature,
          paymentId: '2773928196',
          gasPrice: 20000000000,
          gasLimit: 500000,
          amount: '1000000000000000',
          recipient: hopDestinationAddress,
          nonce: 0,
          userReqSig: userReqSig,
          gasPriceMax: 500000000000,
        },
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith('tx cannot be both a batch and hop transaction');
    });

    it('should reject a txPrebuild with more than one recipient', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [
          { amount: '1000000000000', address: address1 },
          { amount: '2500000000000', address: address2 },
        ],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [
          { amount: '1000000000000', address: address1 },
          { amount: '2500000000000', address: address2 },
        ],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: true,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith(
          `teth doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
        );
    });

    it('should reject a hop txPrebuild that does not send to its hop address', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: '1000000000000000', address: hopDestinationAddress }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
        hop: true,
      };

      const txPrebuild = {
        recipients: [{ amount: '5000000000000000', address: address1 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
        hopTransaction: {
          tx: hopTx,
          id: hopTxid,
          signature: hopTxBitgoSignature,
          paymentId: '0',
          gasPrice: 20000000000,
          gasLimit: 500000,
          amount: '1000000000000000',
          recipient: hopDestinationAddress,
          nonce: 0,
          userReqSig: userReqSig,
          gasPriceMax: 500000000000,
        },
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith('recipient address of txPrebuild does not match hop address');
    });

    it('should reject a batch txPrebuild from the bitgo server with the wrong total amount', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [
          { amount: '1000000000000', address: address1 },
          { amount: '2500000000000', address: address2 },
        ],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [
          { amount: '5500000000000', address: (coin?.staticsCoin?.network as EthereumNetwork).batcherContractAddress },
        ],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: true,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith(
          'batch transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
        );
    });

    it('should reject a batch txPrebuild from the bitgo server that does not send to the batcher contract address', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [
          { amount: '1000000000000', address: address1 },
          { amount: '2500000000000', address: address2 },
        ],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [{ amount: '3500000000000', address: hopContractAddress }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: true,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith('recipient address of txPrebuild does not match batcher address');
    });

    it('should reject a normal txPrebuild from the bitgo server with the wrong amount', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [{ amount: '2000000000000', address: address1 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith(
          'normal transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
        );
    });

    it('should reject a normal txPrebuild from the bitgo server with the wrong recipient', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [{ amount: '1000000000000', address: address2 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith(
          'destination address in normal txPrebuild does not match that in txParams supplied by client'
        );
    });

    it('should verify a token txPrebuild from the bitgo server that matches the client txParams', async function () {
      const coin = bitgo.coin('test');
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'teth',
        token: 'test',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      const isTransactionVerified = await coin.verifyTransaction({
        txParams,
        txPrebuild: txPrebuild as any,
        wallet,
        verification,
      });
      isTransactionVerified.should.equal(true);
    });

    it('should reject a txPrebuild from the bitgo server with the wrong coin', async function () {
      const coin = bitgo.coin('teth') as Teth;
      const wallet = new Wallet(bitgo, coin, {});

      const txParams = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        wallet: wallet,
        walletPassphrase: 'fakeWalletPassphrase',
      };

      const txPrebuild = {
        recipients: [{ amount: '1000000000000', address: address1 }],
        nextContractSequenceId: 0,
        gasPrice: 20000000000,
        gasLimit: 500000,
        isBatch: false,
        coin: 'btc',
        walletId: 'fakeWalletId',
        walletContractAddress: 'fakeWalletContractAddress',
      };

      const verification = {};

      await coin
        .verifyTransaction({ txParams, txPrebuild: txPrebuild as any, wallet, verification })
        .should.be.rejectedWith('coin in txPrebuild did not match that in txParams supplied by client');
    });
  });

  describe('Address Verification', function () {
    it('should verify an address generated using forwarder version 0', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const params = {
        id: '6127bff4ecd84c0006cd9a0e5ccdc36f',
        chain: 0,
        index: 3174,
        coin: 'teth',
        lastNonce: 0,
        wallet: '598f606cd8fc24710d2ebadb1d9459bb',
        baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e',
        coinSpecific: {
          nonce: -1,
          updateTime: '2021-08-26T16:23:16.563Z',
          txCount: 0,
          pendingChainInitialization: true,
          creationFailure: [],
          pendingDeployment: false,
          forwarderVersion: 0,
        },
      };

      const isAddressVerified = await coin.verifyAddress(params as any);
      isAddressVerified.should.equal(true);
    });

    it('should verify an address generated using forwarder version 1', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const params = {
        id: '61250217c8c02b000654b15e7af6f618',
        address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d',
        chain: 0,
        index: 3162,
        coin: 'teth',
        lastNonce: 0,
        wallet: '598f606cd8fc24710d2ebadb1d9459bb',
        baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e',
        coinSpecific: {
          nonce: -1,
          updateTime: '2021-08-24T14:28:39.841Z',
          txCount: 0,
          pendingChainInitialization: true,
          creationFailure: [],
          salt: '0xc5a',
          pendingDeployment: true,
          forwarderVersion: 1,
        },
      };

      const isAddressVerified = await coin.verifyAddress(params);
      isAddressVerified.should.equal(true);
    });

    it('should reject address verification if coinSpecific field is not an object', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const params = {
        id: '61250217c8c02b000654b15e7af6f618',
        address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d',
        chain: 0,
        index: 3162,
        coin: 'teth',
        lastNonce: 0,
        wallet: '598f606cd8fc24710d2ebadb1d9459bb',
        baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e',
      };

      assert.rejects(async () => coin.verifyAddress(params), InvalidAddressVerificationObjectPropertyError);
    });

    it('should reject address verification when an actual address is different from expected address', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const params = {
        id: '61250217c8c02b000654b15e7af6f618',
        address: '0x28904591f735994f050804fda3b61b813b16e04c',
        chain: 0,
        index: 3162,
        coin: 'teth',
        lastNonce: 0,
        baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e',
        wallet: '598f606cd8fc24710d2ebadb1d9459bb',
        coinSpecific: {
          nonce: -1,
          updateTime: '2021-08-24T14:28:39.841Z',
          txCount: 0,
          pendingChainInitialization: true,
          creationFailure: [],
          salt: '0xc5a',
          pendingDeployment: true,
          forwarderVersion: 1,
        },
      };

      assert.rejects(async () => coin.verifyAddress(params), UnexpectedAddressError);
    });

    it('should reject address verification if the derived address is in invalid format', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const params = {
        id: '61250217c8c02b000654b15e7af6f618',
        address: '0xe0b56eeae1b283918caca02a14ada2df17a98bvf',
        chain: 0,
        index: 3162,
        coin: 'teth',
        lastNonce: 0,
        wallet: '598f606cd8fc24710d2ebadb1d9459bb',
        baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e',
        coinSpecific: {
          nonce: -1,
          updateTime: '2021-08-24T14:28:39.841Z',
          txCount: 0,
          pendingChainInitialization: true,
          creationFailure: [],
          salt: '0xc5a',
          pendingDeployment: true,
          forwarderVersion: 1,
        },
      };

      assert.rejects(async () => coin.verifyAddress(params), InvalidAddressError);
    });

    it('should reject address verification if base address is undefined', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const params = {
        id: '61250217c8c02b000654b15e7af6f618',
        address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d',
        chain: 0,
        index: 3162,
        coin: 'teth',
        lastNonce: 0,
        wallet: '598f606cd8fc24710d2ebadb1d9459bb',
        coinSpecific: {
          nonce: -1,
          updateTime: '2021-08-24T14:28:39.841Z',
          txCount: 0,
          pendingChainInitialization: true,
          creationFailure: [],
          salt: '0xc5a',
          pendingDeployment: true,
          forwarderVersion: 1,
        },
      };

      assert.rejects(async () => coin.verifyAddress(params), InvalidAddressError);
    });

    it('should reject address verification if base address is in invalid format', async function () {
      const coin = bitgo.coin('teth') as Teth;

      const params = {
        id: '61250217c8c02b000654b15e7af6f618',
        address: '0xb0b56eeae1b283918caca02a14ada2df17a98e6d',
        chain: 0,
        index: 3162,
        coin: 'teth',
        lastNonce: 0,
        wallet: '598f606cd8fc24710d2ebadb1d9459bb',
        baseAddress: '0xe0b56eeae1b283918caca02a14ada2df17a98bvf',
        coinSpecific: {
          nonce: -1,
          updateTime: '2021-08-24T14:28:39.841Z',
          txCount: 0,
          pendingChainInitialization: true,
          creationFailure: [],
          salt: '0xc5a',
          pendingDeployment: true,
          forwarderVersion: 1,
        },
      };

      assert.rejects(async () => coin.verifyAddress(params), InvalidAddressError);
    });
  });

  describe('EVM Cross Chain Recovery', function () {
    const baseUrl = 'https://api-holesky.etherscan.io';
    it('should build a recovery transaction for hot wallet', async function () {
      const userKey =
        '{"iv":"VFZ3jvXhxo1Z+Yaf2MtZnA==","v":1,"iter":10000,"ks":256,"ts":64,"mode"\n' +
        ':"ccm","adata":"","cipher":"aes","salt":"p+fkHuLa/8k=","ct":"hYG7pvljLIgCjZ\n' +
        '53PBlCde5KZRmlUKKHLtDMk+HJfuU46hW+x+C9WsIAO4gFPnTCvFVmQ8x7czCtcNFub5AO2otOG\n' +
        'OsX4GE2gXOEmCl1TpWwwNhm7yMUjGJUpgW6ZZgXSXdDitSKi4V/hk78SGSzjFOBSPYRa6I="}\n';
      const walletContractAddress = TestBitGo.V2.TEST_ETH_WALLET_FIRST_ADDRESS as string;
      const bitgoFeeAddress = '0x33a42faea3c6e87021347e51700b48aaf49aa1e7';
      const destinationAddress = '0xd5ADdE17feD8baed3F32b84AF05B8F2816f7b560';
      const bitgoDestinationAddress = '0xE5986CE4490Deb67d2950562Ceb930Ddf9be7a14';
      const walletPassphrase = TestBitGo.V2.TEST_RECOVERY_PASSCODE as string;

      const basecoin = bitgo.coin('hteth') as Hteth;
      nock(baseUrl)
        .get('/api')
        .query(mockData.getTxListRequest(bitgoFeeAddress))
        .reply(200, mockData.getTxListResponse);
      nock(baseUrl)
        .get('/api')
        .query(mockData.getBalanceRequest(bitgoFeeAddress))
        .reply(200, mockData.getBalanceResponse);
      nock(baseUrl)
        .get('/api')
        .query(mockData.getBalanceRequest(walletContractAddress))
        .reply(200, mockData.getBalanceResponse);
      nock(baseUrl).get('/api').query(mockData.getContractCallRequest).reply(200, mockData.getContractCallResponse);

      const spy = sinon.spy(TransactionBuilder.prototype, 'coinUsesNonPackedEncodingForTxData');
      await basecoin.recover({
        userKey: userKey,
        backupKey: '',
        walletPassphrase: walletPassphrase,
        walletContractAddress: walletContractAddress,
        bitgoFeeAddress: bitgoFeeAddress,
        recoveryDestination: destinationAddress,
        eip1559: { maxFeePerGas: 20000000000, maxPriorityFeePerGas: 10000000000 },
        gasLimit: 500000,
        bitgoDestinationAddress: bitgoDestinationAddress,
        intendedChain: 'tarbeth',
      });
      assert(spy.returned(true));
    });

    describe('Non-BitGo Recovery for Hot Wallets (MPCv2)', function () {
      const baseUrl = 'https://api-holesky.etherscan.io';
      let bitgo: TestBitGoAPI;
      let basecoin: Hteth;

      before(function () {
        bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
        basecoin = bitgo.coin('hteth') as Hteth;
      });

      it('should build a recovery transaction for MPCv2 kind of hot wallets', async function () {
        nock(baseUrl)
          .get('/api')
          .query(mockData.getTxListRequest(mockData.getNonBitGoRecoveryForHotWalletsMPCv2().bitgoFeeAddress))
          .reply(200, mockData.getTxListResponse);

        nock(baseUrl)
          .get('/api')
          .query(mockData.getBalanceRequest(mockData.getNonBitGoRecoveryForHotWalletsMPCv2().bitgoFeeAddress))
          .reply(200, mockData.getBalanceResponse);

        nock(baseUrl)
          .get('/api')
          .query(mockData.getBalanceRequest(mockData.getNonBitGoRecoveryForHotWalletsMPCv2().walletContractAddress))
          .reply(200, mockData.getBalanceResponse);

        nock(baseUrl).get('/api').query(mockData.getContractCallRequest).reply(200, mockData.getContractCallResponse);

        const params = mockData.getNonBitGoRecoveryForHotWalletsMPCv2();

        const transaction = await (basecoin as AbstractEthLikeNewCoins).recover({
          userKey: params.userKey,
          backupKey: params.backupKey,
          walletPassphrase: params.walletPassphrase,
          walletContractAddress: params.walletContractAddress,
          bitgoFeeAddress: params.bitgoFeeAddress,
          recoveryDestination: params.recoveryDestination,
          eip1559: { maxFeePerGas: 20000000000, maxPriorityFeePerGas: 10000000000 },
          gasLimit: 500000,
          bitgoDestinationAddress: params.bitgoDestinationAddress,
          intendedChain: params.intendedChain,
        });

        should.exist(transaction);
        transaction.should.have.property('txHex');
      });

      it('should throw an error for invalid user key', async function () {
        const params = mockData.getInvalidNonBitGoRecoveryParams();

        await assert.rejects(
          async () => {
            await (basecoin as AbstractEthLikeNewCoins).recover({
              userKey: params.userKey,
              backupKey: params.backupKey,
              walletPassphrase: params.walletPassphrase,
              walletContractAddress: params.walletContractAddress,
              bitgoFeeAddress: params.bitgoFeeAddress,
              recoveryDestination: params.recoveryDestination,
              eip1559: { maxFeePerGas: 20000000000, maxPriorityFeePerGas: 10000000000 },
              gasLimit: 500000,
              bitgoDestinationAddress: params.bitgoDestinationAddress,
              intendedChain: params.intendedChain,
            });
          },
          Error,
          'user key is invalid'
        );
      });
    });

    describe('Build Unsigned Sweep for Self-Custody Cold Wallets (MPCv2)', function () {
      const baseUrl = 'https://api-holesky.etherscan.io';
      let bitgo: TestBitGoAPI;
      let basecoin: Hteth;

      before(function () {
        bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
        basecoin = bitgo.coin('hteth') as Hteth;
      });

      it('should generate an unsigned sweep without derivation seed', async function () {
        nock(baseUrl)
          .get('/api')
          .query(mockData.getTxListRequest(mockData.getBuildUnsignedSweepForSelfCustodyColdWalletsMPCv2().address))
          .reply(200, mockData.getTxListResponse);

        nock(baseUrl)
          .get('/api')
          .query(mockData.getBalanceRequest(mockData.getBuildUnsignedSweepForSelfCustodyColdWalletsMPCv2().address))
          .reply(200, mockData.getBalanceResponse);
        nock(baseUrl)
          .get('/api')
          .query(
            mockData.getBalanceRequest(
              mockData.getBuildUnsignedSweepForSelfCustodyColdWalletsMPCv2().walletContractAddress
            )
          )
          .reply(200, mockData.getBalanceResponse);

        nock(baseUrl).get('/api').query(mockData.getContractCallRequest).reply(200, mockData.getContractCallResponse);

        const params = mockData.getBuildUnsignedSweepForSelfCustodyColdWalletsMPCv2();
        const sweepResult = await (basecoin as AbstractEthLikeNewCoins).recover({
          userKey: params.commonKeyChain, // Box A Data
          backupKey: params.commonKeyChain, // Box B Data
          derivationSeed: params.derivationSeed, // Key Derivation Seed (optional)
          recoveryDestination: params.recoveryDestination, // Destination Address
          gasLimit: 200000, // Gas Limit
          eip1559: { maxFeePerGas: 20000000000, maxPriorityFeePerGas: 10000000000 }, // Max Fee Per Gas and Max Priority Fee Per Gas
          walletContractAddress: params.walletContractAddress,
          isTss: true,
          replayProtectionOptions: {
            chain: '42',
            hardfork: 'london',
          },
        });
        should.exist(sweepResult);
        const output = sweepResult as UnsignedSweepTxMPCv2;
        output.should.have.property('txRequests');
        output.txRequests.should.have.length(1);
        output.txRequests[0].should.have.property('transactions');
        output.txRequests[0].transactions.should.have.length(1);
        output.txRequests[0].should.have.property('walletCoin');
        output.txRequests[0].transactions.should.have.length(1);
        output.txRequests[0].transactions[0].should.have.property('unsignedTx');
        output.txRequests[0].transactions[0].unsignedTx.should.have.property('serializedTxHex');
        output.txRequests[0].transactions[0].unsignedTx.should.have.property('signableHex');
        output.txRequests[0].transactions[0].unsignedTx.should.have.property('derivationPath');
        output.txRequests[0].transactions[0].unsignedTx.should.have.property('feeInfo');
        output.txRequests[0].transactions[0].unsignedTx.should.have.property('parsedTx');
        const parsedTx = output.txRequests[0].transactions[0].unsignedTx.parsedTx as { spendAmount: string };
        parsedTx.should.have.property('spendAmount');
        (output.txRequests[0].transactions[0].unsignedTx.parsedTx as { outputs: any[] }).should.have.property(
          'outputs'
        );
      });

      it('should throw an error for invalid address', async function () {
        const params = mockData.getBuildUnsignedSweepForSelfCustodyColdWalletsMPCv2();
        params.recoveryDestination = 'invalidAddress';

        // Ensure userKey and backupKey are the same
        params.userKey =
          '0234eb39b22fed523ece7c78da29ba1f1de5b64a6e48013e0914de793bc1df0570e779de04758732734d97e54b782c8b336283811af6a2c57bd81438798e1c2446';
        params.backupKey =
          '0234eb39b22fed523ece7c78da29ba1f1de5b64a6e48013e0914de793bc1df0570e779de04758732734d97e54b782c8b336283811af6a2c57bd81438798e1c2446';

        await assert.rejects(
          async () => {
            await (basecoin as AbstractEthLikeNewCoins).recover({
              recoveryDestination: params.recoveryDestination, // Destination Address
              gasLimit: 2000, // Gas Limit
              eip1559: { maxFeePerGas: 200, maxPriorityFeePerGas: 10000 }, // Max Fee Per Gas and Max Priority Fee Per Gas
              userKey: params.userKey, // Provide the userKey
              backupKey: params.backupKey, // Provide the backupKey
              walletContractAddress: params.walletContractAddress, // Provide the walletContractAddress
              isTss: true,
              replayProtectionOptions: {
                chain: '42',
                hardfork: 'london',
              },
            });
          },
          Error,
          'Error: invalid address'
        );
      });
    });
  });
});

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


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