PHP WebShell

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

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

//
// Tests for Wallet
//
// Copyright 2014, BitGo, Inc.  All Rights Reserved.
//

import { getFixture } from './fixtures';

const Wallet = require('../../../src/v1/wallet');
import { BitGoAPI } from '../../../src/bitgoAPI';
import * as _ from 'lodash';
import { common } from '@bitgo/sdk-core';
import * as utxolib from '@bitgo/utxo-lib';
import * as should from 'should';
import nock from 'nock';
import * as sinon from 'sinon';

import { getFixtures } from './fixtures/accelerate-tx';

nock.disableNetConnect();

const TestBitGo = {
  TEST_WALLET1_PASSCODE: 'iVWeATjqLS1jJShrPpETti0b',
};
const originalFetchConstants = BitGoAPI.prototype.fetchConstants;
BitGoAPI.prototype.fetchConstants = function (this: any) {
  nock(this._baseUrl).get('/api/v1/client/constants').reply(200, { ttl: 3600, constants: {} });

  // force client constants reload
  BitGoAPI['_constants'] = undefined;

  return originalFetchConstants.apply(this, arguments as any);
};
describe('Wallet Prototype Methods', function () {
  const fixtures = getFixtures();

  let bitgo = new BitGoAPI({ env: 'test' });
  // bitgo.initializeTestVars();

  const userKeypair = {
    xprv: 'xprv9s21ZrQH143K2fJ91S4BRsupcYrE6mmY96fcX5HkhoTrrwmwjd16Cn87cWinJjByrfpojjx7ezsJLx7TAKLT8m8hM5Kax9YcoxnBeJZ3t2k',
    xpub: 'xpub661MyMwAqRbcF9Nc7TbBo1rZAagiWEVPWKbDKThNG8zqjk76HAKLkaSbTn6dK2dQPfuD7xjicxCZVWvj67fP5nQ9W7QURmoMVAX8m6jZsGp',
    rawPub: '02c103ac74481874b5ef0f385d12725e4f14aedc9e00bc814ce96f47f62ce7adf2',
    rawPrv: '936c5af3f8af81f75cdad1b08f29e7d9c01e598e2db2d7be18b9e5a8646e87c6',
    path: 'm',
    walletSubPath: '/0/0',
  };
  const backupKeypair = {
    xprv: 'xprv9s21ZrQH143K47sEkLkykgYmq1xF5ZWrPYhUZcmBpPFMQojvGUmEcr5jFXYGfr8CpFdpTvhQ7L9NN2rLtsBFjSix3BAjwJcBj6U3D5hxTPc',
    xpub: 'xpub661MyMwAqRbcGbwhrNHz7pVWP3njV2Ehkmd5N1AoNinLHc54p25VAeQD6q2oTS3uuDMDnfnXnthbS9ufC8JVYpNnWU5Rn3pYaNuLCNywkw1',
    rawPub: '03bbcb73997977068d9e36666bbd5cd37579acae8e2bd5ce9d0a6e5c150a423bc3',
    rawPrv: '77a15f14796f4001d1092ae84f766bd869e9bee6bffae6547def5045b96fa943',
    path: 'm',
    walletSubPath: '/0/0',
  };
  const bitgoKey = {
    xpub: 'xpub661MyMwAqRbcGQcVFiwcrtc7c3vopsX96jsJUYPcFMREcRTqAqsqbv2ZRyCJAPLm5NMHCy85E3ZwpT4EAUw9WGU7vMhG6z83hDeKXBWn6Lf',
    path: 'm',
    walletSubPath: '/0/0',
  };

  const fakeWallet = new Wallet(bitgo, {
    id: '2NCoSfHH6Ls4CdTS5QahgC9k7x9RfXeSwY4',
    private: { keychains: [userKeypair, backupKeypair, bitgoKey] },
  });

  describe('Generate Address', function () {
    before(() => nock.pendingMocks().should.be.empty());

    it('generate first address', function () {
      const idAddress = fakeWallet.generateAddress({ path: '/0/0', segwit: false });
      idAddress.address.should.equal(fakeWallet.id());
      idAddress.chain.should.equal(0);
      idAddress.index.should.equal(0);
      idAddress.chainPath.should.equal('/0/0');
      idAddress.path.should.equal('/0/0');
      idAddress.outputScript.should.equal('a914d682476e9bd54454a885f9dff1e604e99cef43dc87');
      idAddress.redeemScript.should.equal(
        '522102cd3c8e6006a4627705021d1d016d097c2944d98100a47bf2da67a5fe15aeeb342102ee1fa9e812e779356aa3c31ebf317d0cffebab92864cfe38bab223e0820f98bc21026ba05752baa6eafd5c5659da62b7f0ac51fd2886b65c241d0afef1c4fdfa1cbc53ae'
      );
      idAddress.wallet.should.equal(fakeWallet.id());
    });

    it('generate second address', function () {
      const p2shAddress = fakeWallet.generateAddress({ path: '/0/1', segwit: false });
      p2shAddress.address.should.equal('2N5y5RLVqdZi7qp5PmzMdPR6YvQzUqBQFWK');
      p2shAddress.chain.should.equal(0);
      p2shAddress.index.should.equal(1);
      p2shAddress.chainPath.should.equal('/0/1');
      p2shAddress.path.should.equal('/0/1');
      p2shAddress.outputScript.should.equal('a9148b8bd3da68ef0f2465523146bd2de33c86b9c87187');
      p2shAddress.redeemScript.should.equal(
        '522102709edb6a2198d364c485a76b981d12065eabde8aa2d85bd7e7a035f7ecb3579b2102a724efed499c05fdb4da1e139700951fae00c006b3283888bdfd1b46979292242102b32abe44d61986ff57b835e3bd16293d93f303d0d8fb0454e2c9cceda5c4929853ae'
      );
      p2shAddress.wallet.should.equal(fakeWallet.id());
    });

    it('generate change address', function () {
      const p2shAddress = fakeWallet.generateAddress({ path: '/1/0', segwit: false });
      p2shAddress.address.should.equal('2NFj9JrpZc5MyYnCouyREtzNY4eoyKWDfgP');
      p2shAddress.chain.should.equal(1);
      p2shAddress.index.should.equal(0);
      p2shAddress.chainPath.should.equal('/1/0');
      p2shAddress.path.should.equal('/1/0');
      p2shAddress.outputScript.should.equal('a914f69a81fad75ea65ad166da76515291679a4f1ad887');
      p2shAddress.redeemScript.should.equal(
        '5221020b4c4f891a5520f5a0b6818d8d53919552a0d4d806b5fa05c97708079d83737e2102c5cc49bf0331eb0b0890a7e7d87f7e9e0dea515438280dc76834c21d198efe08210370e52cf741ebf4513749d028839d696891eb789ba7a58592cfbc857cdc0a9de753ae'
      );
      p2shAddress.wallet.should.equal(fakeWallet.id());
    });

    it('generate segwit address', function () {
      const segwitAddress = fakeWallet.generateAddress({ path: '/10/0', segwit: true });
      segwitAddress.address.should.equal('2N5EVegRPWnmed2PpqDggZPw7DcNDguRYv8');
      segwitAddress.chain.should.equal(10);
      segwitAddress.index.should.equal(0);
      segwitAddress.chainPath.should.equal('/10/0');
      segwitAddress.path.should.equal('/10/0');
      segwitAddress.outputScript.should.equal('a914837e2adcb6f6386fea3c5d40316b282ccf39121d87');
      segwitAddress.redeemScript.should.equal('0020a62afee1d211c5adb9739f81ed4e36330e6cda651c7bdd314e32ccc465ec2203');
      segwitAddress.witnessScript.should.equal(
        '5221027b30505777a4ed8947b069fcb0116e287995d97278d84da4db6c613270649d3d21034c30e51f1e614cad667815c91d041404c18225d0b2f79e2c0bcb63fd2604316b2103b65ddfc06159b691693390761e75a0b8cc7a65b6ff305d094f3ad972f17953fe53ae'
      );
      segwitAddress.wallet.should.equal(fakeWallet.id());
    });
  });

  describe('Create Transaction', function () {
    let bgUrl, bgUrlTest;
    let fakeProdWallet;

    before(function () {
      nock.pendingMocks().should.be.empty();
      const prodBitgo = new BitGoAPI({ env: 'prod' });
      // prodBitgo.initializeTestVars();
      bgUrl = common.Environments[prodBitgo.getEnv()].uri;
      fakeProdWallet = new Wallet(prodBitgo, {
        id: '2NCoSfHH6Ls4CdTS5QahgC9k7x9RfXeSwY4',
        private: { keychains: [userKeypair, backupKeypair, bitgoKey] },
      });
      bgUrlTest = common.Environments[bitgo.getEnv()].uri;
    });

    it('extra unspent fetch params', async function () {
      const billingAddress = '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy';
      const customUnspentsFetchParams = { test: 123 };
      const sendAmount = 1e5;

      nock(bgUrl).post('/api/v1/billing/address').reply(200, { address: billingAddress });

      const scope = nock(bgUrl)
        .get(`/api/v1/wallet/${fakeProdWallet.id()}/unspents`)
        .query(
          _.merge(customUnspentsFetchParams, {
            segwit: true,
            target: sendAmount,
            minSize: 0,
          })
        )
        .reply(200, { unspents: [] });

      await fakeProdWallet
        .createTransaction({
          unspentsFetchParams: customUnspentsFetchParams,
          recipients: { [billingAddress]: sendAmount },
          feeRate: 10000,
          bitgoFee: {
            amount: 0,
            address: '',
          },
        })
        .should.be.rejectedWith('0 unspents available for transaction creation');

      scope.isDone().should.be.true();
    });

    it('default p2sh', async function () {
      const p2shAddress = fakeProdWallet.generateAddress({ path: '/0/13', segwit: false });
      const unspent: any = {
        addresses: ['2NCEDmmKNNnqKvnWw7pE3RLzuFe5aHHVy1X'],
        value: '0.00504422',
        value_int: 504422,
        txid: 'b816ded89c3d8d5021b01097f4a3129a6a68a5cb7c886e97945f4205cba5de44',
        n: 1,
        script_pub_key: {
          asm: 'OP_HASH160 d039cb3344294a5a384a5508a006444c420cbc11 OP_EQUAL',
          hex: 'a914d039cb3344294a5a384a5508a006444c420cbc1187',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 9,
        id: 61330229,
      };
      _.extend(unspent, p2shAddress);
      unspent.value = unspent.value_int;
      unspent.tx_hash = unspent.txid;
      unspent.tx_output_n = unspent.n;
      unspent.script = unspent.outputScript;

      nock(bgUrl).post('/api/v1/billing/address').reply(200, { address: '2MswQjkvN6oWYdE7L2brJ5cAAMjPmG59oco' });

      const transaction = (await fakeProdWallet.createTransaction({
        changeAddress: p2shAddress.address,
        unspents: [unspent],
        recipients: {},
        noSplitChange: true,
        forceChangeAtEnd: true,
        feeRate: 10000,
        bitgoFee: {
          amount: 0,
          address: '',
        },
        opReturns: { 'BitGo p2sh test': 1000 },
      })) as any;
      transaction.transactionHex.should.equal(
        '010000000144dea5cb05425f94976e887ccba5686a9a12a3f49710b021508d3d9cd8de16b80100000000ffffffff02e803000000000000116a0f426974476f2070327368207465737422a107000000000017a914d039cb3344294a5a384a5508a006444c420cbc118700000000'
      );

      // add first signature
      transaction.keychain = userKeypair;
      const signature1 = (await fakeProdWallet.signTransaction(transaction)) as any;
      signature1.tx.should.equal(
        '010000000144dea5cb05425f94976e887ccba5686a9a12a3f49710b021508d3d9cd8de16b801000000b600473044022021fa73d5fe61ac8942cd70ff4507c574677ce747de5bc46c3dd2e38ec2448fce022047906d2c0154337ab96041e8fb58c243b9bce5f8818fa991643c1260a1859ad80100004c695221031cd227e40ad61b4e137109cb2845eb6f5a584ed5c67d9d3135cdaa5045a842ea2103a2e7b54c7b2da0992555353b8e26c6acff4248f4351f08787bf3e2efc94b658321025c2a6cde33c2d73ccf12eecf64c54f08f722c2f073824498950695e9883b141253aeffffffff02e803000000000000116a0f426974476f2070327368207465737422a107000000000017a914d039cb3344294a5a384a5508a006444c420cbc118700000000'
      );

      // add second signature
      transaction.transactionHex = signature1.tx;
      transaction.keychain = backupKeypair;
      transaction.fullLocalSigning = true;
      const signature2 = (await fakeProdWallet.signTransaction(transaction)) as any;
      // This transaction has actually worked: https://testnet.smartbit.com.au/tx/a8ccb928169032d6e1f37bf81dfd9ab6d90362a4f84e577397fa690aa711550c
      // Note that the tx hex below no longer corresponds to the above transaction because our fee estimation has
      // changed, changing the output amounts and thus the tx hex.
      signature2.tx.should.equal(
        '010000000144dea5cb05425f94976e887ccba5686a9a12a3f49710b021508d3d9cd8de16b801000000fdfd0000473044022021fa73d5fe61ac8942cd70ff4507c574677ce747de5bc46c3dd2e38ec2448fce022047906d2c0154337ab96041e8fb58c243b9bce5f8818fa991643c1260a1859ad80147304402202ae01f01b5ae0c3fa7d67ac73db81932cb5aca10db16a99063fef45e3f1398cd022055001ba7e163cb350910fc7321ecd7eb6359b321d4c04887484d9c7284b78c4701004c695221031cd227e40ad61b4e137109cb2845eb6f5a584ed5c67d9d3135cdaa5045a842ea2103a2e7b54c7b2da0992555353b8e26c6acff4248f4351f08787bf3e2efc94b658321025c2a6cde33c2d73ccf12eecf64c54f08f722c2f073824498950695e9883b141253aeffffffff02e803000000000000116a0f426974476f2070327368207465737422a107000000000017a914d039cb3344294a5a384a5508a006444c420cbc118700000000'
      );
    });

    it('BCH p2sh', async function () {
      const p2shAddress = fakeProdWallet.generateAddress({ path: '/0/13', segwit: false });
      const unspent: any = {
        addresses: ['2NCEDmmKNNnqKvnWw7pE3RLzuFe5aHHVy1X'],
        value: '0.00504422',
        value_int: 504422,
        txid: 'b816ded89c3d8d5021b01097f4a3129a6a68a5cb7c886e97945f4205cba5de44',
        n: 1,
        script_pub_key: {
          asm: 'OP_HASH160 d039cb3344294a5a384a5508a006444c420cbc11 OP_EQUAL',
          hex: 'a914d039cb3344294a5a384a5508a006444c420cbc1187',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 9,
        id: 61330229,
      };
      _.extend(unspent, p2shAddress);
      unspent.value = unspent.value_int;
      unspent.tx_hash = unspent.txid;
      unspent.tx_output_n = unspent.n;
      unspent.script = unspent.outputScript;

      nock(bgUrl).post('/api/v1/billing/address').reply(200, { address: '2MswQjkvN6oWYdE7L2brJ5cAAMjPmG59oco' });

      const transaction = (await fakeProdWallet.createTransaction({
        changeAddress: p2shAddress.address,
        unspents: [unspent],
        recipients: {},
        noSplitChange: true,
        forceChangeAtEnd: true,
        feeRate: 10000,
        bitgoFee: {
          amount: 0,
          address: '',
        },
        opReturns: { 'BitGo p2sh test': 1000 },
      })) as any;
      transaction.transactionHex.should.equal(
        '010000000144dea5cb05425f94976e887ccba5686a9a12a3f49710b021508d3d9cd8de16b80100000000ffffffff02e803000000000000116a0f426974476f2070327368207465737422a107000000000017a914d039cb3344294a5a384a5508a006444c420cbc118700000000'
      );

      // add first signature
      transaction.keychain = userKeypair;
      transaction.forceBCH = true;
      const signature1 = (await fakeProdWallet.signTransaction(transaction)) as any;
      signature1.tx.should.equal(
        '010000000144dea5cb05425f94976e887ccba5686a9a12a3f49710b021508d3d9cd8de16b801000000b60047304402206221a97f081d87e02e3b14988a64861811a6a8de4f11f74f5aaea45981cf612e022077a08a5bd7d781e79838afbb126af2e48802fefad660afdbd8805f5e598ed5884100004c695221031cd227e40ad61b4e137109cb2845eb6f5a584ed5c67d9d3135cdaa5045a842ea2103a2e7b54c7b2da0992555353b8e26c6acff4248f4351f08787bf3e2efc94b658321025c2a6cde33c2d73ccf12eecf64c54f08f722c2f073824498950695e9883b141253aeffffffff02e803000000000000116a0f426974476f2070327368207465737422a107000000000017a914d039cb3344294a5a384a5508a006444c420cbc118700000000'
      );
      // add second signature
      transaction.transactionHex = signature1.tx;
      transaction.keychain = backupKeypair;
      transaction.fullLocalSigning = true;
      const signature2 = (await fakeProdWallet.signTransaction(transaction)) as any;
      // this transaction has actually worked: https://testnet.smartbit.com.au/tx/a8ccb928169032d6e1f37bf81dfd9ab6d90362a4f84e577397fa690aa711550c
      // Note that the tx hex below no longer corresponds to the above transaction because our fee estimation has
      // changed, changing the output amounts and thus the tx hex.
      signature2.tx.should.equal(
        '010000000144dea5cb05425f94976e887ccba5686a9a12a3f49710b021508d3d9cd8de16b801000000fdfe000047304402206221a97f081d87e02e3b14988a64861811a6a8de4f11f74f5aaea45981cf612e022077a08a5bd7d781e79838afbb126af2e48802fefad660afdbd8805f5e598ed5884148304502210082bc546293858459f3895db24c85ccf37505c56f8faf4bb8f78cf40135bc2f2b02203dc1c78d7c7ceaf6b924eca3c39b95e8a227b069a07047581273136b47ca7ac441004c695221031cd227e40ad61b4e137109cb2845eb6f5a584ed5c67d9d3135cdaa5045a842ea2103a2e7b54c7b2da0992555353b8e26c6acff4248f4351f08787bf3e2efc94b658321025c2a6cde33c2d73ccf12eecf64c54f08f722c2f073824498950695e9883b141253aeffffffff02e803000000000000116a0f426974476f2070327368207465737422a107000000000017a914d039cb3344294a5a384a5508a006444c420cbc118700000000'
      );
    });

    it('default segwit', async function () {
      const segwitAddress = fakeProdWallet.generateAddress({ path: '/10/13', segwit: true });
      const unspent: any = {
        addresses: ['2MxKkH8yB3S9YWmTQRbvmborYQyQnH5petP'],
        value: '0.18750000',
        value_int: 18750000,
        txid: '7d282878a85daee5d46e043827daed57596d75d1aa6e04fd0c09a36f9130881f',
        n: 0,
        script_pub_key: {
          asm: 'OP_HASH160 37b393fce627a0ec634eb543dda1e608e2d1c78a OP_EQUAL',
          hex: 'a91437b393fce627a0ec634eb543dda1e608e2d1c78a87',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 0,
        id: 61331617,
      };
      _.extend(unspent, segwitAddress);
      unspent.value = unspent.value_int;
      unspent.tx_hash = unspent.txid;
      unspent.tx_output_n = unspent.n;
      unspent.script = unspent.outputScript;

      nock(bgUrl).post('/api/v1/billing/address').reply(200, { address: '2MswQjkvN6oWYdE7L2brJ5cAAMjPmG59oco' });

      const transaction = (await fakeProdWallet.createTransaction({
        changeAddress: segwitAddress.address,
        unspents: [unspent],
        recipients: {},
        noSplitChange: true,
        forceChangeAtEnd: true,
        feeRate: 10000,
        bitgoFee: {
          amount: 0,
          address: '',
        },
        opReturns: { 'BitGo segwit test': 1000 },
      })) as any;
      transaction.transactionHex.should.equal(
        '01000000011f8830916fa3090cfd046eaad1756d5957edda2738046ed4e5ae5da87828287d0000000000ffffffff02e803000000000000136a11426974476f2073656777697420746573740e0f1e010000000017a91437b393fce627a0ec634eb543dda1e608e2d1c78a8700000000'
      );

      // add first signature
      transaction.keychain = userKeypair;
      const signature1 = (await fakeProdWallet.signTransaction(transaction)) as any;
      signature1.tx.should.equal(
        '010000000001011f8830916fa3090cfd046eaad1756d5957edda2738046ed4e5ae5da87828287d0000000023220020440e858228b753544b4c57e300296b55717f811053883f9be9b6a712eacd931cffffffff02e803000000000000136a11426974476f2073656777697420746573740e0f1e010000000017a91437b393fce627a0ec634eb543dda1e608e2d1c78a870500483045022100bf3a8914a1bfe92661f27ca37c0d6b5c0b3c7353614c955646929f2e7eb89ffe02202d556b0ffab37c104bae67406ca16f8859cfa37c6a40f2013d89afcecd5594f3010000695221032c505fc8a1e4b56811b27366a371e61c9faf565dd2fabaff7a70eac19c32157c210251160b583bd5dc0f0d48096505131c4347ab65b4f21ed57d76c38157499c003d2102679712d62a2560917cc43fd2cc3a1b9b61f528c88bc64905bae6ee079e60609f53ae00000000'
      );

      // add second signature
      transaction.transactionHex = signature1.tx;
      transaction.keychain = backupKeypair;
      transaction.fullLocalSigning = true;
      const signature2 = (await fakeProdWallet.signTransaction(transaction)) as any;
      // this transaction has actually worked: https://testnet.smartbit.com.au/tx/d67266f1de905baaee750011fa4b3d88a8e3a1758d5173a659c67709488dde07
      // Note that the tx hex below no longer corresponds to the above transaction because our fee estimation has
      // changed, changing the output amounts and thus the tx hex.
      signature2.tx.should.equal(
        '010000000001011f8830916fa3090cfd046eaad1756d5957edda2738046ed4e5ae5da87828287d0000000023220020440e858228b753544b4c57e300296b55717f811053883f9be9b6a712eacd931cffffffff02e803000000000000136a11426974476f2073656777697420746573740e0f1e010000000017a91437b393fce627a0ec634eb543dda1e608e2d1c78a870500483045022100bf3a8914a1bfe92661f27ca37c0d6b5c0b3c7353614c955646929f2e7eb89ffe02202d556b0ffab37c104bae67406ca16f8859cfa37c6a40f2013d89afcecd5594f30147304402205cf8d2f2be6ce083d35654bdc3fa85d7e71b227d457e9245bb603b21e7b5165102203ea686226db8320e08c26bfb304048b3a9473d0e05797d3658dacb2f09a2b51c0100695221032c505fc8a1e4b56811b27366a371e61c9faf565dd2fabaff7a70eac19c32157c210251160b583bd5dc0f0d48096505131c4347ab65b4f21ed57d76c38157499c003d2102679712d62a2560917cc43fd2cc3a1b9b61f528c88bc64905bae6ee079e60609f53ae00000000'
      );
    });

    it('creates an unsigned tx made of uncompressed public keys of v1 safe wallet', async function () {
      const { address, redeemScript, scriptPubKey } = await getFixture<Record<string, unknown>>(
        `${__dirname}/fixtures/sign-transaction.json`
      );
      const testBitgo = new BitGoAPI({ env: 'test' });
      const fakeTestV1SafeWallet = new Wallet(testBitgo, {
        id: address,
        private: { safe: { redeemScript } },
      });
      const unspentsToSpend = [
        {
          value: 100000,
          redeemScript,
          script: scriptPubKey,
          tx_hash: 'a55d11dc8b701bd19601fbfe711a1e465fc8f128ec4474e78e1fd087e808e5fe',
          tx_output_n: 0,
          confirmations: 1,
        },
        {
          value: 100000,
          redeemScript,
          script: scriptPubKey,
          tx_hash: '48fb879cec879356045a331937023aed859f5dc5db955a1dc8a5ccf29f49d108',
          tx_output_n: 0,
          confirmations: 1,
        },
      ];
      const recipients = {
        '2MyGxrhLC4kRfuVjLqCVYFtC7DchhgMCiNz': 191340, // purposely set to simulate a sweep transaction
      };

      const scope = nock(bgUrlTest)
        .post('/api/v1/billing/address')
        .reply(200, { address: '2N3L9cu9WN2Df7Xvb1Y8owokuDVj5Hdyv4i' });

      const result = await fakeTestV1SafeWallet.createTransaction({
        recipients,
        unspents: unspentsToSpend,
        feeRate: 10000, // 10 sat/byte
        bitgoFee: {
          amount: 0,
          address: '',
        },
      });

      scope.isDone().should.be.true();

      result.estimatedSize.should.equal(866);
      result.fee.should.equal(8660);
      // This should equal to 1 because this is a sweep transaction but due to hardcoded addition of
      // 1 change output in transactionBuilder, it is 2.
      // Because of this the estimated size of the transactions is more than what it actually is in the hex.
      result.txInfo.nOutputs.should.equal(2);
    });

    it('signs an unsigned tx made of uncompressed public keys of v1 safe wallet & verifies signatures', async function () {
      const {
        address,
        redeemScript,
        scriptPubKey,
        userKeyWIF: userSigningKey,
        bitgoKeyWIF: bitgoSigningKey,
        unsignedTxHex,
        halfSignedTxHex,
        fullSignedTxHex,
      } = await getFixture<Record<string, unknown>>(`${__dirname}/fixtures/sign-transaction.json`);
      const testBitgo = new BitGoAPI({ env: 'test' });
      const fakeTestV1SafeWallet = new Wallet(testBitgo, {
        id: address,
        private: { safe: { redeemScript } },
      });
      const unspentsToSpend = [
        { value: 100000, redeemScript, script: scriptPubKey },
        { value: 100000, redeemScript, script: scriptPubKey },
      ];
      const halfSignedTx = await fakeTestV1SafeWallet.signTransaction({
        transactionHex: unsignedTxHex,
        signingKey: userSigningKey,
        unspents: unspentsToSpend,
        validate: true,
      });
      halfSignedTx.tx.should.equal(halfSignedTxHex);

      const fullSignedTx = await fakeTestV1SafeWallet.signTransaction({
        transactionHex: halfSignedTxHex,
        signingKey: bitgoSigningKey,
        unspents: unspentsToSpend,
        validate: true,
        fullLocalSigning: true,
      });
      // Upon calling txb.build() instead after getting 2 valid signatures, we get a valid full signed tx that was broadcast
      // and confirmed on testnet here: https://mempool.space/testnet/tx/bde09f1bd5e6661c28d90e4c96291853e21ba15ab42f3e4a30719decb73e791b
      // It's present in the fullSignedTxHexBuildComplete property of the fixture.
      fullSignedTx.tx.should.equal(fullSignedTxHex);
    });

    it('BCH segwit should fail', async function () {
      const segwitAddress = fakeProdWallet.generateAddress({ path: '/10/13', segwit: true });
      const unspent: any = {
        addresses: ['2MxKkH8yB3S9YWmTQRbvmborYQyQnH5petP'],
        value: '0.18750000',
        value_int: 18750000,
        txid: '7d282878a85daee5d46e043827daed57596d75d1aa6e04fd0c09a36f9130881f',
        n: 0,
        script_pub_key: {
          asm: 'OP_HASH160 37b393fce627a0ec634eb543dda1e608e2d1c78a OP_EQUAL',
          hex: 'a91437b393fce627a0ec634eb543dda1e608e2d1c78a87',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 0,
        id: 61331617,
      };
      _.extend(unspent, segwitAddress);
      unspent.value = unspent.value_int;
      unspent.tx_hash = unspent.txid;
      unspent.tx_output_n = unspent.n;
      unspent.script = unspent.outputScript;

      nock(bgUrl).post('/api/v1/billing/address').reply(200, { address: '2MswQjkvN6oWYdE7L2brJ5cAAMjPmG59oco' });

      const transaction = (await fakeProdWallet.createTransaction({
        changeAddress: segwitAddress.address,
        unspents: [unspent],
        recipients: {},
        noSplitChange: true,
        forceChangeAtEnd: true,
        feeRate: 10000,
        bitgoFee: {
          amount: 0,
          address: '',
        },
        opReturns: { 'BitGo segwit test': 1000 },
      })) as any;
      transaction.transactionHex.should.equal(
        '01000000011f8830916fa3090cfd046eaad1756d5957edda2738046ed4e5ae5da87828287d0000000000ffffffff02e803000000000000136a11426974476f2073656777697420746573740e0f1e010000000017a91437b393fce627a0ec634eb543dda1e608e2d1c78a8700000000'
      );

      // add first signature
      transaction.keychain = userKeypair;
      transaction.forceBCH = true;
      (() => fakeProdWallet.signTransaction(transaction)).should.throw('BCH does not support segwit inputs');
    });

    it('mixed p2sh & segwit', async function () {
      const p2shAddress = fakeWallet.generateAddress({ path: '/0/14', segwit: false });
      const segwitAddress = fakeWallet.generateAddress({ path: '/10/14', segwit: true });
      const p2shUnspent = {
        addresses: ['2N533fqgyPYKVD892nBRaYmFHbbTykhYSEw'],
        value: '2.99996610',
        value_int: 299996610,
        txid: 'f654ce0a5be3f12df7fecf4ee777b6d86b5aa8c710ef6946ec121206b4f8757c',
        n: 1,
        script_pub_key: {
          asm: 'OP_HASH160 8153e7a35508088b6cf599226792c7de2dbff252 OP_EQUAL',
          hex: 'a9148153e7a35508088b6cf599226792c7de2dbff25287',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 0,
        id: 61331263,
      };
      const segwitUnspent = {
        addresses: ['2NBtpXcDruf3zRutmF4AbCMFNQHXsGNP6kT'],
        value: '1.50000000',
        value_int: 150000000,
        txid: 'a4409c3f042fae67b890ac3df40ef0db03539c67331fd7e9260511893b4f9f24',
        n: 0,
        script_pub_key: {
          asm: 'OP_HASH160 cc8e7cbf481389d3183a590acfa6aa66eb97c8e1 OP_EQUAL',
          hex: 'a914cc8e7cbf481389d3183a590acfa6aa66eb97c8e187',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 0,
        id: 61330882,
      };
      const addresses = [p2shAddress, segwitAddress];
      const unspents = [p2shUnspent, segwitUnspent].map((unspent: any, index) => {
        const address = addresses[index];
        _.extend(unspent, address);
        unspent.value = unspent.value_int;
        unspent.tx_hash = unspent.txid;
        unspent.tx_output_n = unspent.n;
        unspent.script = unspent.outputScript;
        return unspent;
      });

      const transaction = (await fakeWallet.createTransaction({
        changeAddress: p2shAddress.address,
        unspents: unspents,
        recipients: {},
        noSplitChange: true,
        forceChangeAtEnd: true,
        feeRate: 10000,
        opReturns: { 'BitGo mixed p2sh & segwit test': 400000000 },
        bitgoFee: {
          amount: 81760,
          address: '2ND7jQR5itjGTbh3DKgbpZWSY9ungDrwcwb',
        },
      })) as any;
      transaction.transactionHex.should.equal(
        '01000000027c75f8b4061212ec4669ef10c7a85a6bd8b677e74ecffef72df1e35b0ace54f60100000000ffffffff249f4f3b89110526e9d71f33679c5303dbf00ef43dac90b867ae2f043f9c40a40000000000ffffffff030084d71700000000206a1e426974476f206d6978656420703273682026207365677769742074657374b08ff9020000000017a9148153e7a35508088b6cf599226792c7de2dbff25287603f01000000000017a914d9f7be47975c036f94228b0bfd70701912758ba98700000000'
      );

      // add first signature
      transaction.keychain = userKeypair;
      const signature1 = (await fakeProdWallet.signTransaction(transaction)) as any;
      signature1.tx.should.equal(
        '010000000001027c75f8b4061212ec4669ef10c7a85a6bd8b677e74ecffef72df1e35b0ace54f601000000b700483045022100ffc45d93cbaf4c1c850e21f277c5b311d3e3957f1338955cb165d72a768a054c022052020593b36781eea00a9f8dcbeb76608f920c7a933a9088318ab2f70c11e1d90100004c69522103da95b28a13aa2d4bb490d70628e2e5d912461d375fef381aadd89dc1256220752103121287a510c5f32e8ba72d2479e90eb52ba44a467173df339feb0ff215f100e32102977cdfbee76066ae739db72d55371ad49dc6712fb8f2f3f69bb1a4c2422b0b1a53aeffffffff249f4f3b89110526e9d71f33679c5303dbf00ef43dac90b867ae2f043f9c40a400000000232200208b91aa03eb0f7f31e3917088084168ba5282a915e7cde0a5a934b7ea02eb057bffffffff030084d71700000000206a1e426974476f206d6978656420703273682026207365677769742074657374b08ff9020000000017a9148153e7a35508088b6cf599226792c7de2dbff25287603f01000000000017a914d9f7be47975c036f94228b0bfd70701912758ba98700050047304402205898bee711467c09a5e22e1dcb1a11fce1a0d6ea129d911f813f87c7d45e067b02202f69fb118bbf0b072ed26d72cf8073e7acd66c205419a4a00f86a7ba0f6e3dd6010000695221030780186c0be5df0d2d62cf54cc2f3d2c09911e377aa95b5fe875fa352aed0a592103f3237edd2d87010e8fe9f43f34e8c63de6384283de909795d62af4ddb4d579542102ad03de5504ef947e4e6ee2fa6b15d150d553c21275f49f2ce2359d9fdedb9ade53ae00000000'
      );

      // add second signature
      transaction.transactionHex = signature1.tx;
      transaction.keychain = backupKeypair;
      transaction.fullLocalSigning = true;
      const signature2 = (await fakeProdWallet.signTransaction(transaction)) as any;
      // this transaction has actually worked: https://testnet.smartbit.com.au/tx/e2f696bcba91a376c36bb525df8c367938f6e2fd6344c90587bf12802091124c
      // Note that the tx hex below no longer corresponds to the above transaction because our fee estimation has
      // changed, changing the output amounts and thus the tx hex.
      signature2.tx.should.equal(
        '010000000001027c75f8b4061212ec4669ef10c7a85a6bd8b677e74ecffef72df1e35b0ace54f601000000fdff0000483045022100ffc45d93cbaf4c1c850e21f277c5b311d3e3957f1338955cb165d72a768a054c022052020593b36781eea00a9f8dcbeb76608f920c7a933a9088318ab2f70c11e1d9014830450221008254d100401a3a831ed019e1662dbd90b96c6c4072b81ce640d152bc29295c10022013f86c5af5716234999a7bd6e94fc8f428f7697cc3138b3649d0ec4dd8681bc701004c69522103da95b28a13aa2d4bb490d70628e2e5d912461d375fef381aadd89dc1256220752103121287a510c5f32e8ba72d2479e90eb52ba44a467173df339feb0ff215f100e32102977cdfbee76066ae739db72d55371ad49dc6712fb8f2f3f69bb1a4c2422b0b1a53aeffffffff249f4f3b89110526e9d71f33679c5303dbf00ef43dac90b867ae2f043f9c40a400000000232200208b91aa03eb0f7f31e3917088084168ba5282a915e7cde0a5a934b7ea02eb057bffffffff030084d71700000000206a1e426974476f206d6978656420703273682026207365677769742074657374b08ff9020000000017a9148153e7a35508088b6cf599226792c7de2dbff25287603f01000000000017a914d9f7be47975c036f94228b0bfd70701912758ba98700050047304402205898bee711467c09a5e22e1dcb1a11fce1a0d6ea129d911f813f87c7d45e067b02202f69fb118bbf0b072ed26d72cf8073e7acd66c205419a4a00f86a7ba0f6e3dd60147304402207713d671b45989688e2665c2b11ab7e5ea8d57eb14f9da233c095dabe441308d022069521b5aeb071b07a70a7197a0c2bbc40a23ae63a04160cf3627250c4ba4c40f0100695221030780186c0be5df0d2d62cf54cc2f3d2c09911e377aa95b5fe875fa352aed0a592103f3237edd2d87010e8fe9f43f34e8c63de6384283de909795d62af4ddb4d579542102ad03de5504ef947e4e6ee2fa6b15d150d553c21275f49f2ce2359d9fdedb9ade53ae00000000'
      );
    });

    it('should send to bech32 recipient', async function () {
      const p2shAddress = fakeWallet.generateAddress({ path: '/0/14', segwit: false });
      const segwitAddress = fakeWallet.generateAddress({ path: '/10/14', segwit: true });
      const p2shUnspent = {
        addresses: ['2N533fqgyPYKVD892nBRaYmFHbbTykhYSEw'],
        value: '2.99996610',
        value_int: 299996610,
        txid: 'f654ce0a5be3f12df7fecf4ee777b6d86b5aa8c710ef6946ec121206b4f8757c',
        n: 1,
        script_pub_key: {
          asm: 'OP_HASH160 8153e7a35508088b6cf599226792c7de2dbff252 OP_EQUAL',
          hex: 'a9148153e7a35508088b6cf599226792c7de2dbff25287',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 0,
        id: 61331263,
      };
      const segwitUnspent = {
        addresses: ['2NBtpXcDruf3zRutmF4AbCMFNQHXsGNP6kT'],
        value: '1.50000000',
        value_int: 150000000,
        txid: 'a4409c3f042fae67b890ac3df40ef0db03539c67331fd7e9260511893b4f9f24',
        n: 0,
        script_pub_key: {
          asm: 'OP_HASH160 cc8e7cbf481389d3183a590acfa6aa66eb97c8e1 OP_EQUAL',
          hex: 'a914cc8e7cbf481389d3183a590acfa6aa66eb97c8e187',
        },
        req_sigs: 1,
        type: 'scripthash',
        confirmations: 0,
        id: 61330882,
      };
      const addresses = [p2shAddress, segwitAddress];
      const unspents = [p2shUnspent, segwitUnspent].map((unspent: any, index) => {
        const address = addresses[index];
        _.extend(unspent, address);
        unspent.value = unspent.value_int;
        unspent.tx_hash = unspent.txid;
        unspent.tx_output_n = unspent.n;
        unspent.script = unspent.outputScript;
        return unspent;
      });

      const transaction = (await fakeWallet.createTransaction({
        changeAddress: p2shAddress.address,
        unspents: unspents,
        recipients: { tb1qguzyk4w6kaqtpsczs5aj0w8r7598jq36egm8e98wqph3rwmex68seslgsg: 300000 },
        noSplitChange: true,
        forceChangeAtEnd: true,
        feeRate: 10000,
        opReturns: { 'BitGo mixed p2sh & segwit test': 400000000 },
        bitgoFee: {
          amount: 81760,
          address: '2ND7jQR5itjGTbh3DKgbpZWSY9ungDrwcwb',
        },
      })) as any;
      transaction.transactionHex.should.equal(
        '01000000027c75f8b4061212ec4669ef10c7a85a6bd8b677e74ecffef72df1e35b0ace54f60100000000ffffffff249f4f3b89110526e9d71f33679c5303dbf00ef43dac90b867ae2f043f9c40a40000000000ffffffff04e09304000000000022002047044b55dab740b0c302853b27b8e3f50a79023aca367c94ee006f11bb79368f0084d71700000000206a1e426974476f206d69786564207032736820262073656777697420746573747cfaf4020000000017a9148153e7a35508088b6cf599226792c7de2dbff25287603f01000000000017a914d9f7be47975c036f94228b0bfd70701912758ba98700000000'
      );

      // add first signature
      transaction.keychain = userKeypair;
      const signature1 = (await fakeProdWallet.signTransaction(transaction)) as any;
      signature1.tx.should.equal(
        '010000000001027c75f8b4061212ec4669ef10c7a85a6bd8b677e74ecffef72df1e35b0ace54f601000000b7004830450221008809377634e667d6e19f38a138a55b2b6370312af76a5ca3b776df61fc719617022021d90347b9085ab71a76c8400f984e322c15451ecc673dd37de30887436d37b40100004c69522103da95b28a13aa2d4bb490d70628e2e5d912461d375fef381aadd89dc1256220752103121287a510c5f32e8ba72d2479e90eb52ba44a467173df339feb0ff215f100e32102977cdfbee76066ae739db72d55371ad49dc6712fb8f2f3f69bb1a4c2422b0b1a53aeffffffff249f4f3b89110526e9d71f33679c5303dbf00ef43dac90b867ae2f043f9c40a400000000232200208b91aa03eb0f7f31e3917088084168ba5282a915e7cde0a5a934b7ea02eb057bffffffff04e09304000000000022002047044b55dab740b0c302853b27b8e3f50a79023aca367c94ee006f11bb79368f0084d71700000000206a1e426974476f206d69786564207032736820262073656777697420746573747cfaf4020000000017a9148153e7a35508088b6cf599226792c7de2dbff25287603f01000000000017a914d9f7be47975c036f94228b0bfd70701912758ba9870005004830450221008b95ac83e44c727b79ffbf4571171925d06f883a05c122b3b33c055f0bffa70102207b5ee3412ea8a5cec4a5c386f1b464ab68d531c6c697077bc462c05eb44a2832010000695221030780186c0be5df0d2d62cf54cc2f3d2c09911e377aa95b5fe875fa352aed0a592103f3237edd2d87010e8fe9f43f34e8c63de6384283de909795d62af4ddb4d579542102ad03de5504ef947e4e6ee2fa6b15d150d553c21275f49f2ce2359d9fdedb9ade53ae00000000'
      );

      // add second signature
      transaction.transactionHex = signature1.tx;
      transaction.keychain = backupKeypair;
      transaction.fullLocalSigning = true;
      const signature2 = (await fakeProdWallet.signTransaction(transaction)) as any;
      console.log('signature1 ' + JSON.stringify(signature2));
      // this transaction has actually worked: https://testnet.smartbit.com.au/tx/e2f696bcba91a376c36bb525df8c367938f6e2fd6344c90587bf12802091124c
      // Note that the tx hex below no longer corresponds to the above transaction because our fee estimation has
      // changed, changing the output amounts and thus the tx hex.
      signature2.tx.should.equal(
        '010000000001027c75f8b4061212ec4669ef10c7a85a6bd8b677e74ecffef72df1e35b0ace54f601000000fdfe00004830450221008809377634e667d6e19f38a138a55b2b6370312af76a5ca3b776df61fc719617022021d90347b9085ab71a76c8400f984e322c15451ecc673dd37de30887436d37b40147304402205a58e602042b8e8a5da509d19ce31050147dd0ffcfbe2bb337c23d4c88f4cc41022075bfa455d1f74e30fbd9c786cd4811a1defe54310f235b4193ce4ffa0e8309a101004c69522103da95b28a13aa2d4bb490d70628e2e5d912461d375fef381aadd89dc1256220752103121287a510c5f32e8ba72d2479e90eb52ba44a467173df339feb0ff215f100e32102977cdfbee76066ae739db72d55371ad49dc6712fb8f2f3f69bb1a4c2422b0b1a53aeffffffff249f4f3b89110526e9d71f33679c5303dbf00ef43dac90b867ae2f043f9c40a400000000232200208b91aa03eb0f7f31e3917088084168ba5282a915e7cde0a5a934b7ea02eb057bffffffff04e09304000000000022002047044b55dab740b0c302853b27b8e3f50a79023aca367c94ee006f11bb79368f0084d71700000000206a1e426974476f206d69786564207032736820262073656777697420746573747cfaf4020000000017a9148153e7a35508088b6cf599226792c7de2dbff25287603f01000000000017a914d9f7be47975c036f94228b0bfd70701912758ba9870005004830450221008b95ac83e44c727b79ffbf4571171925d06f883a05c122b3b33c055f0bffa70102207b5ee3412ea8a5cec4a5c386f1b464ab68d531c6c697077bc462c05eb44a283201473044022053690234582a6911a28cae9f534c980b7d7918749a7413c1c59327debf16ff0b022056175f5c27a363416b2ce4791aefc3d55545cacbd0527202e869e1127fc2f24d0100695221030780186c0be5df0d2d62cf54cc2f3d2c09911e377aa95b5fe875fa352aed0a592103f3237edd2d87010e8fe9f43f34e8c63de6384283de909795d62af4ddb4d579542102ad03de5504ef947e4e6ee2fa6b15d150d553c21275f49f2ce2359d9fdedb9ade53ae00000000'
      );
    });
  });

  describe('Send Many', function () {
    it('responds with proper fee and fee rate', async function () {
      const params = {
        recipients: [
          {
            address: '2MutpXVYs8Lyk74pVDn3eAG7xnK4Wc2kjTQ',
            amount: 300000,
          },
        ],
      };
      const unspents = [
        {
          value: 8170,
        },
        {
          value: 800000,
        },
      ];
      const createAndSignResponse = {
        bitgoFee: 0,
        travelInfos: [],
        unspents,
        tx: 'halfsignedhex',
      };
      sinon.stub(fakeWallet, 'createAndSignTransaction').returns(Promise.resolve(createAndSignResponse));
      const getSendTxResponse = () => ({
        status: 'accepted',
        tx: '0100000000010228b5c3e2789d4770fc397ec79fa7255f86235297c5a04def678b481b8b09e81b0100000023220020b3bbe067960be39501f365b8999d53f2a8285d8d9836f61fad020e6a4a9e26fdffffffff1510e90411a86c49f2a52546a32a03febde2bc604741f0e85dc47adec33f515900000000fdfd0000483045022100a44bbf97b155c57703862be69d2b20c4b2ab9e94f402595880bf74402ccc87e202200a4aaf98f939b65c98ca08eb96074c222ecb1fc37e359b1d67a05f1c56dedfc001473044022003f3989a14284f132bbb550118c20256d4ea737704123a29955acc1d03ea6eb7022017223da7edcf73076d89875aa33360aa6a12141807f683c6c1b9a5a0d3ff6019014c695221025789857cc8be110ff4cbf354b52dd0e7e9326c6bfe0aee6c30c1ee69660c3dc02102f58f1b1516d05814ae688ca701856695e27050e3e16d3a2351284d7af84498882102385c7bcec3f38c13e87b558aebf2f20a8928e7ecbe11e7c3a47792bc8e33fe8853aeffffffff02e09304000000000017a9141d0c791cec3af1f37808d42f04593095d6fdea268705bc07000000000017a914d8c720f646c7c56c5467248e47c72dc0b2d30bbc87040047304402201eaa1359fffd3bdec5b48268bd2f15193a299c22b1970356f390883473324651022074186232f02245af9c0977031448c2c99e7b7e2b05b2ba4b32c3227d8ca1494e01483045022100bd61b37051c28533ea0b00dda75b1c4f1dee1b683bb7351b2d8dd720f6dfbe1102203acc4cf9d2dd44b294aa25812e99e7d8eb3730e4ff6f889d3cdcd525195750b8016952210219d093c18c27cb547737b4a49dddac9c3412b10e9f880eb30053c3eba81928542103747118892cac1b4da11526fc4ebeebe168dae0907cefb1a1812541cd46b07602210339f73b6750f8f91efd484b5aa2974321a6cc2776d5bd78b9cfb5fe18e3b2d66253ae005a9e1600',
        hash: 'f8df43c2c650b3bb11277aee4531db99a715fa3b9dfd3d45a8d171342c1bf780',
        instant: false,
      });
      const expectedResult = Object.assign(
        {
          fee: 1285,
          feeRate: 2519.607843137255,
        },
        getSendTxResponse()
      );
      sinon.stub(fakeWallet, 'sendTransaction').resolves(getSendTxResponse());
      const result = (await fakeWallet.sendMany(params)) as any;
      result.tx.should.equal(expectedResult.tx);
      result.fee.should.equal(expectedResult.fee);
      result.feeRate.should.equal(expectedResult.feeRate);
    });
  });

  describe('Accelerate Transaction (server mocked)', function accelerateTxMockedDescribe() {
    let wallet;
    let bgUrl;
    let explorerUrl;
    let minChangeSize;

    let parentTxId = '6a74b74df4991d93c32d751336c85b5f2d1ee544a2dfbae2e5f4beb4f914e5e0';
    const outputIdx = 0;
    const outputAddress = '2NCoSfHH6Ls4CdTS5QahgC9k7x9RfXeSwY4';
    const unrelatedTxId = '08f5e0b4acb5ab8245229dfe161ce4ca0da1ec983e7a34b09e72f56979a467df';
    const walletId = '2NCoSfHH6Ls4CdTS5QahgC9k7x9RfXeSwY4';

    /**
     * Helper function to get the parent transaction ID from a transaction input
     *
     * This function converts `hash` (which is a Buffer object) to a usable txid string.
     * The issue is that the bytes are stored in reverse order in the buffer, so
     * the simple approach of just comparing the hex strings doesn't work.
     * Instead, the Buffer is copied to a new Buffer object, the new Buffer
     * object is reversed in-place, and then the new Buffer is converted to a
     * hex string. After this, the result is a hex string which is the parent txid
     *
     * @param hash a bitcoinjs-lib transaction object's input hash
     */
    function inputParentTxId({ hash }): string {
      return (Buffer.from(hash).reverse() as Buffer).toString('hex');
    }

    before(function accelerateTxMockedBefore() {
      nock.pendingMocks().should.be.empty();

      bitgo = new BitGoAPI({ env: 'mock' });
      // bitgo.initializeTestVars();
      bitgo.setValidate(false);
      wallet = new Wallet(bitgo, { id: walletId, private: { keychains: [userKeypair, backupKeypair, bitgoKey] } });
      (wallet as any).bitgo = bitgo;
      bgUrl = common.Environments[bitgo.getEnv()].uri;
      explorerUrl = common.Environments[bitgo.getEnv()].btcExplorerBaseUrl;

      // try to get the min change size from the server, otherwise default to 0.1 BTC
      // TODO: minChangeSize is not currently a constant defined on the client and should be added
      minChangeSize = 1e7;
    });

    after(function accelerateTxMockedAfter() {
      // make sure all nocks are cleared or consumed after the tests are complete
      nock.pendingMocks().should.be.empty();
    });

    it('arguments', async () => {
      await wallet.accelerateTransaction({ feeRate: 123 }).should.be.rejectedWith(/^Missing parameter: transactionID$/);

      await wallet
        .accelerateTransaction({ transactionID: 123, feeRate: 123 })
        .should.be.rejectedWith(/^Expecting parameter string: transactionID but found number$/);

      await wallet
        .accelerateTransaction({ transactionID: '123' })
        .should.be.rejectedWith(/^Missing parameter: feeRate$/);

      const feeRatesParams = ['123', 0, -10, -Infinity, Infinity, NaN];
      for (const feeRate of feeRatesParams) {
        await wallet
          .accelerateTransaction({ transactionID: '123', feeRate })
          .should.be.rejectedWith(/^Expecting positive finite number for parameter: feeRate$/);
      }
    });

    describe('bad input', function badInputDescribe() {
      after(() => {
        // make sure all nocks are cleared or consumed after the tests are complete
        nock.pendingMocks().should.be.empty();
      });

      it('non existent transaction ID', async () => {
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(404, 'transaction not found on this wallet');

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 123 })
          .should.be.rejectedWith(/^404\ntransaction not found on this wallet$/);
      });

      it('confirmed transaction', async () => {
        nock(bgUrl).get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`).reply(200, {
          confirmations: 6,
        });

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 2000 })
          .should.be.rejectedWith(/^Transaction [0-9a-f]+ is already confirmed and cannot be accelerated$/);
      });

      it('no outputs to wallet', async () => {
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            outputs: [
              {
                account: outputAddress,
                value: 1890000,
                vout: 0,
                chain: 0,
              },
            ],
            confirmations: 0,
          });

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 2000 })
          .should.be.rejectedWith(
            /^Transaction [0-9a-f]+ contains no outputs to this wallet, and thus cannot be accelerated$/
          );
      });

      /*
       * This test covers the case where a failure occurs during the process of
       * converting an output from the parent transaction into an unspent which
       * can be used to chain the child tx to the parent.
       *
       * This should never happen, but it is possible (for example, in the case
       * of an attempted double spend of the output from the parent, or a race
       * between finding the parent output, and retrieving the corresponding unspent).
       */
      it('cannot find correct unspent to use', async () => {
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            outputs: [
              {
                account: outputAddress,
                value: 50 * 1e4,
                vout: outputIdx,
                isMine: true,
                chain: 0,
              },
            ],
            confirmations: 0,
            hex: parentTxId,
            fee: 10,
          });

        nock(bgUrl).get(`/api/v1/wallet/${wallet.id()}/unspents`).query(true).reply(200, {
          count: 0,
          unspents: [],
        });

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 2000 })
          .should.be.rejectedWith(/^Could not find unspent output from parent tx to use as child input$/);
      });

      it('Detects when an incorrect tx hex is returned by the external service', async () => {
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            outputs: [
              {
                account: outputAddress,
                value: 10,
                vout: outputIdx,
                isMine: true,
                chain: 0,
              },
            ],
            confirmations: 0,
            hex: fixtures[parentTxId],
            fee: 10,
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            count: 1,
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: outputIdx,
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[unrelatedTxId]);

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 2000 })
          .should.be.rejectedWith(/^Decoded transaction id is [0-9a-f]+, which does not match given txid [0-9a-f]+$/);
      });

      it('cannot cover child fee with one parent output and one wallet unspent', async () => {
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            outputs: [
              {
                account: outputAddress,
                value: 10,
                vout: outputIdx,
                isMine: true,
                chain: 0,
              },
            ],
            confirmations: 0,
            hex: fixtures[parentTxId],
            fee: 10,
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            count: 1,
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: outputIdx,
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        nock(bgUrl).get(`/api/v1/wallet/${wallet.id()}/unspents`).query(true).reply(200, {
          count: 0,
          unspents: [],
        });

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 2000 })
          .should.be.rejectedWith(/^Insufficient confirmed unspents available to cover the child fee$/);
      });

      it('cannot lower fee rate', async () => {
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            outputs: [
              {
                account: outputAddress,
                value: 10,
                vout: outputIdx,
                isMine: true,
                chain: 11,
              },
            ],
            confirmations: 0,
            hex: fixtures[parentTxId],
            fee: 10000, // large fee, and thus fee rate, for parent
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            count: 1,
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: outputIdx,
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 2000 })
          .should.be.rejectedWith(
            /^Cannot lower fee rate! \(Parent tx fee rate is \d+\.?\d* sat\/kB, and requested fee rate was \d+\.?\d* sat\/kB\)$/
          );
      });

      it('cannot break maximum fee limit for combined transaction', async () => {
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            outputs: [
              {
                account: outputAddress,
                value: 3e7,
                vout: outputIdx,
                isMine: true,
                chain: 11,
              },
            ],
            confirmations: 0,
            hex: fixtures[parentTxId],
            fee: 1000,
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            count: 1,
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: outputIdx,
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        await wallet
          .accelerateTransaction({ transactionID: parentTxId, feeRate: 2e6 })
          .should.be.rejectedWith(
            /^Transaction cannot be accelerated\. Combined fee rate of \d+\.?\d* sat\/kB exceeds maximum fee rate of \d+\.?\d* sat\/kB$/
          );
      });
    });

    describe('successful tx acceleration', function successfulTxDescribe() {
      const feeRate = 20000;

      beforeEach(() => {
        nock(bgUrl).post(`/api/v1/wallet/${wallet.id()}/address/1`).reply(200, {
          address: '2NCYjG8Q56yr8tx9jazNoYnGKxjgB2MQSfY',
        });

        nock(bgUrl).post('/api/v1/billing/address').reply(200, {
          address: '2NFbvo2HK4eXZm1aqDcSDGGqD64FPt7T6d8',
        });

        nock(bgUrl).get('/api/v1/tx/fee').query(true).reply(200, {
          feePerKb: 0,
        });

        nock(bgUrl)
          .post(`/api/v1/keychain/${userKeypair.xpub}`, {})
          .reply(200, {
            encryptedXprv: bitgo.encrypt({ input: userKeypair.xprv, password: TestBitGo.TEST_WALLET1_PASSCODE }),
            path: userKeypair.path + userKeypair.walletSubPath,
          });
      });

      it('accelerates a stuck tx without additional unspents', async () => {
        parentTxId = '75cfc5a7b214c4b73c92c7b02608cde70b226767a9576f84c04407e43fd385bd';
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            fee: 434,
            outputs: [
              {
                vout: 0,
                value: 10348500,
                isMine: true,
                chain: 1,
              },
              {
                vout: 1,
                value: 10000,
                isMine: true,
                chain: 11,
              },
            ],
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: 0,
                value: 10348500,
                redeemScript: '0020f7b58d455351b7b8ddd7c8986d98244f6a95f0746720091537323b967800f744',
                chainPath: '/11/160',
                witnessScript:
                  '5221027f0b45bb4155ea532e3b4312fe0be80166f297d1e0753d2d4a9118c073ad6514210310aa9d68c98831625f329b7826b6c3e3b53e16736b1994b8902442bdcd6653d121026e0ca414f2488b0ab572b99e0ae5442911ab4e0821b2709d885175a527fd552b53ae',
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        nock(bgUrl)
          .post('/api/v1/tx/send', (body) => {
            return !body.ignoreMaxFeeRate;
          })
          .reply(200, function (_, body) {
            return {
              transaction: (body as any).tx,
            };
          });

        const childTx = await wallet.accelerateTransaction({
          transactionID: parentTxId,
          feeRate,
          walletPassphrase: TestBitGo.TEST_WALLET1_PASSCODE,
        });

        should.exist(childTx);
        childTx.should.have.property('status', 'accepted');
        childTx.should.have.property('tx');

        // assert the following:
        // 0) The child tx has exactly one input
        // 1) The parent tx output is an input
        // 2) The child tx has exactly one output
        // 3) The child tx output meets the minimum change threshold
        const decodedChild = utxolib.bitgo.createTransactionFromHex(childTx.tx, utxolib.networks.bitcoin);
        decodedChild.ins.length.should.equal(1);
        decodedChild.outs.length.should.equal(1);

        const childInput = decodedChild.ins[0];
        childInput.should.have.property('index', 0);
        childInput.should.have.property('hash');

        const inputTxId = inputParentTxId(childInput);
        inputTxId.should.equal(parentTxId);

        const childOutput = decodedChild.outs[0];
        childOutput.should.have.property('value');
        childOutput.value.should.be.above(minChangeSize);
      });

      it('accelerates a stuck tx with one additional segwit unspent', async () => {
        parentTxId = '8815f202c8654b6c8b295749545c711878cd845a14cb1ea982394d0c14945c33';
        const additionalUnspentTxId = '07d6ee57b024ce2b6108f67847454a0a79a4fcfb98ab255553a2993a1a170b87';
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            fee: 1336,
            outputs: [
              {
                vout: 0,
                value: 10000,
                isMine: true,
                chain: 11,
              },
              {
                vout: 1,
                value: 8664,
                isMine: true,
                chain: 1,
              },
            ],
            confirmations: 0,
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: 0,
                value: 10000,
                redeemScript:
                  '522102cd3c8e6006a4627705021d1d016d097c2944d98100a47bf2da67a5fe15aeeb342102ee1fa9e812e779356aa3c31ebf317d0cffebab92864cfe38bab223e0820f98bc21026ba05752baa6eafd5c5659da62b7f0ac51fd2886b65c241d0afef1c4fdfa1cbc53ae',
                chainPath: '/0/0',
              },
            ],
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: additionalUnspentTxId,
                tx_output_n: 0,
                value: 19935526,
                redeemScript: '0020d34ef6dd34ef2a4fbea67c541c1c796749a60afe4a97fee8ec7ded188bd749da',
                chainPath: '/11/155',
                witnessScript:
                  '522102219d2aa8417633f0bce3911374a1604c1b64161f83a3c2ee409c27c42355f08e2102c9734920dc4da06c289fe69171dfcd75e3b9b4f190d0cbc3d5d0ff3f5fdeeaae2103ccd68d7fa8dc0d02dd45dad165557a48582eda4435fae7377b3c31e08ad065c953ae',
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        nock(bgUrl)
          .post('/api/v1/tx/send', (body) => {
            return !body.ignoreMaxFeeRate;
          })
          .reply(200, function (_, body) {
            return {
              transaction: (body as any).tx,
            };
          });

        const childTx = await wallet.accelerateTransaction({
          transactionID: parentTxId,
          feeRate,
          walletPassphrase: TestBitGo.TEST_WALLET1_PASSCODE,
        });

        should.exist(childTx);
        childTx.should.have.property('status', 'accepted');
        childTx.should.have.property('tx');

        // assert the following:
        // 0) The child tx has exactly two inputs
        // 1) The parent tx output is an input
        // 2) The additional unspent output is an input
        // 3) The child tx has exactly one output
        // 4) The child tx output meets the minimum change threshold
        const decodedChild = utxolib.bitgo.createTransactionFromHex(childTx.tx, utxolib.networks.bitcoin);
        decodedChild.ins.length.should.equal(2);
        decodedChild.outs.length.should.equal(1);

        let inputFromParent: any = undefined;
        let additionalInput: any = undefined;

        _.forEach(decodedChild.ins, (input) => {
          input.should.have.property('hash');
          input.should.have.property('index');

          const inputTxId = inputParentTxId(input);

          if (inputTxId === parentTxId) {
            inputFromParent = input;
          } else {
            additionalInput = input;
          }
        });

        should.exist(inputFromParent);
        const inputFromParentHash = inputParentTxId(inputFromParent);
        inputFromParentHash.should.equal(parentTxId);
        inputFromParent.index.should.equal(0);

        should.exist(additionalInput);
        const additionalInputHash = inputParentTxId(additionalInput);
        additionalInputHash.should.equal(additionalUnspentTxId);
        additionalInput.index.should.equal(0);

        const childOutput = decodedChild.outs[0];
        childOutput.should.have.property('value');
        childOutput.value.should.be.above(minChangeSize);
      });

      it('accelerates a stuck tx with one additional P2SH unspent', async () => {
        parentTxId = '8815f202c8654b6c8b295749545c711878cd845a14cb1ea982394d0c14945c33';
        const additionalUnspentTxId = 'e190310f2f3f71aa8846f1161cbce1533c24a857dd24e4501b131feb400aad58';
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            fee: 1336,
            outputs: [
              {
                vout: 0,
                value: 10000,
                isMine: true,
                chain: 11,
              },
              {
                vout: 1,
                value: 8664,
                isMine: true,
                chain: 1,
              },
            ],
            confirmations: 0,
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: 0,
                value: 10000,
                redeemScript:
                  '522102cd3c8e6006a4627705021d1d016d097c2944d98100a47bf2da67a5fe15aeeb342102ee1fa9e812e779356aa3c31ebf317d0cffebab92864cfe38bab223e0820f98bc21026ba05752baa6eafd5c5659da62b7f0ac51fd2886b65c241d0afef1c4fdfa1cbc53ae',
                chainPath: '/0/0',
              },
            ],
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: additionalUnspentTxId,
                tx_output_n: 1,
                value: 20000000,
                redeemScript:
                  '522102cd3c8e6006a4627705021d1d016d097c2944d98100a47bf2da67a5fe15aeeb342102ee1fa9e812e779356aa3c31ebf317d0cffebab92864cfe38bab223e0820f98bc21026ba05752baa6eafd5c5659da62b7f0ac51fd2886b65c241d0afef1c4fdfa1cbc53ae',
                chainPath: '/0/0',
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        nock(bgUrl)
          .post('/api/v1/tx/send', (body) => {
            return !body.ignoreMaxFeeRate;
          })
          .reply(200, function (_, body) {
            return {
              transaction: (body as any).tx,
            };
          });

        const childTx = await wallet.accelerateTransaction({
          transactionID: parentTxId,
          feeRate,
          walletPassphrase: TestBitGo.TEST_WALLET1_PASSCODE,
        });

        should.exist(childTx);
        childTx.should.have.property('status', 'accepted');
        childTx.should.have.property('tx');

        // assert the following:
        // 0) The child tx has exactly two inputs
        // 1) The parent tx output is an input
        // 2) The additional unspent output is an input
        // 3) The child tx has exactly one output
        // 4) The child tx output meets the minimum change threshold
        const decodedChild = utxolib.bitgo.createTransactionFromHex(childTx.tx, utxolib.networks.bitcoin);
        decodedChild.ins.length.should.equal(2);
        decodedChild.outs.length.should.equal(1);

        let inputFromParent: any = undefined;
        let additionalInput: any = undefined;

        _.forEach(decodedChild.ins, (input) => {
          input.should.have.property('hash');
          input.should.have.property('index');

          const inputHash = inputParentTxId(input);

          if (inputHash === parentTxId) {
            inputFromParent = input;
          } else {
            additionalInput = input;
          }
        });

        should.exist(inputFromParent);
        const inputFromParentHash = inputParentTxId(inputFromParent);
        inputFromParentHash.should.equal(parentTxId);
        inputFromParent.index.should.equal(0);

        should.exist(additionalInput);
        const additionalInputHash = inputParentTxId(additionalInput);
        additionalInputHash.should.equal(additionalUnspentTxId);
        additionalInput.index.should.equal(1);

        const childOutput = decodedChild.outs[0];
        childOutput.should.have.property('value');
        childOutput.value.should.be.above(minChangeSize);
      });

      it('accelerates a stuck tx with two additional unspents (segwit and P2SH)', async () => {
        parentTxId = '8815f202c8654b6c8b295749545c711878cd845a14cb1ea982394d0c14945c33';
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            fee: 1336,
            outputs: [
              {
                vout: 0,
                value: 10000,
                isMine: true,
                chain: 11,
              },
              {
                vout: 1,
                value: 8664,
                isMine: true,
                chain: 1,
              },
            ],
            confirmations: 0,
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: 0,
                value: 10000,
                redeemScript:
                  '522102cd3c8e6006a4627705021d1d016d097c2944d98100a47bf2da67a5fe15aeeb342102ee1fa9e812e779356aa3c31ebf317d0cffebab92864cfe38bab223e0820f98bc21026ba05752baa6eafd5c5659da62b7f0ac51fd2886b65c241d0afef1c4fdfa1cbc53ae',
                chainPath: '/0/0',
              },
            ],
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: 'e190310f2f3f71aa8846f1161cbce1533c24a857dd24e4501b131feb400aad58',
                tx_output_n: 1,
                value: 800000,
                redeemScript:
                  '522102cd3c8e6006a4627705021d1d016d097c2944d98100a47bf2da67a5fe15aeeb342102ee1fa9e812e779356aa3c31ebf317d0cffebab92864cfe38bab223e0820f98bc21026ba05752baa6eafd5c5659da62b7f0ac51fd2886b65c241d0afef1c4fdfa1cbc53ae',
                chainPath: '/0/0',
              },
              {
                tx_hash: '07d6ee57b024ce2b6108f67847454a0a79a4fcfb98ab255553a2993a1a170b87',
                tx_output_n: 0,
                value: 20006284,
                redeemScript: '0020d34ef6dd34ef2a4fbea67c541c1c796749a60afe4a97fee8ec7ded188bd749da',
                chainPath: '/11/155',
                witnessScript:
                  '522102219d2aa8417633f0bce3911374a1604c1b64161f83a3c2ee409c27c42355f08e2102c9734920dc4da06c289fe69171dfcd75e3b9b4f190d0cbc3d5d0ff3f5fdeeaae2103ccd68d7fa8dc0d02dd45dad165557a48582eda4435fae7377b3c31e08ad065c953ae',
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        nock(bgUrl)
          .post('/api/v1/tx/send', (body) => {
            return !body.ignoreMaxFeeRate;
          })
          .reply(200, function (_, body) {
            return {
              transaction: (body as any).tx,
            };
          });

        const childTx = await wallet.accelerateTransaction({
          transactionID: parentTxId,
          walletPassphrase: TestBitGo.TEST_WALLET1_PASSCODE,
          feeRate,
        });

        should.exist(childTx);
        childTx.should.have.property('status', 'accepted');
        childTx.should.have.property('tx');

        // assert the following:
        // 0) The child tx has exactly three inputs
        // 1) The parent tx output is an input
        // 2) The child tx has exactly one output
        // 3) The child tx output meets the minimum change threshold
        const decodedChild = utxolib.bitgo.createTransactionFromHex(childTx.tx, utxolib.networks.bitcoin);
        decodedChild.ins.length.should.equal(3);
        decodedChild.outs.length.should.equal(1);

        let inputFromParent: any = undefined;
        const additionalInputs: any[] = [];

        _.forEach(decodedChild.ins, (input) => {
          input.should.have.property('hash');
          input.should.have.property('index');

          const inputHash = inputParentTxId(input);

          if (inputHash === parentTxId) {
            inputFromParent = input;
          } else {
            additionalInputs.push(input);
          }
        });

        should.exist(inputFromParent);
        const inputFromParentHash = inputParentTxId(inputFromParent);
        inputFromParentHash.should.equal(parentTxId);
        inputFromParent.index.should.equal(0);

        additionalInputs.length.should.equal(2);

        const childOutput = decodedChild.outs[0];
        childOutput.should.have.property('value');
        childOutput.value.should.be.above(minChangeSize);
      });

      it('correctly uses the ignoreMaxFeeRate parameter only when necessary', async () => {
        parentTxId = '75cfc5a7b214c4b73c92c7b02608cde70b226767a9576f84c04407e43fd385bd';
        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/tx/${parentTxId}`)
          .reply(200, {
            fee: 434,
            outputs: [
              {
                vout: 0,
                value: 10348500,
                isMine: true,
                chain: 0,
              },
              {
                vout: 1,
                value: 10000,
                isMine: true,
                chain: 11,
              },
            ],
          });

        nock(bgUrl)
          .get(`/api/v1/wallet/${wallet.id()}/unspents`)
          .query(true)
          .reply(200, {
            unspents: [
              {
                tx_hash: parentTxId,
                tx_output_n: 0,
                value: 10348500,
                redeemScript: '0020f7b58d455351b7b8ddd7c8986d98244f6a95f0746720091537323b967800f744',
                chainPath: '/11/160',
                witnessScript:
                  '5221027f0b45bb4155ea532e3b4312fe0be80166f297d1e0753d2d4a9118c073ad6514210310aa9d68c98831625f329b7826b6c3e3b53e16736b1994b8902442bdcd6653d121026e0ca414f2488b0ab572b99e0ae5442911ab4e0821b2709d885175a527fd552b53ae',
              },
            ],
          });

        nock(explorerUrl).get(`/tx/${parentTxId}/hex`).reply(200, fixtures[parentTxId]);

        nock(bgUrl)
          .post('/api/v1/tx/send', (body) => {
            // ignore max fee rate must be set for this test
            return body.ignoreMaxFeeRate;
          })
          .reply(200);

        // monkey patch the bitgo getConstants() function
        const oldGetConstants = (bitgo as any).__proto__.getConstants;
        (bitgo as any).__proto__.getConstants = () => ({
          // child fee rate in this test is 31378 sat/kb
          // so set the max fee rate just below that limit,
          // but above the combined fee rate of 20000
          maxFeeRate: 30000,
        });

        await wallet.accelerateTransaction({
          transactionID: parentTxId,
          feeRate,
          walletPassphrase: TestBitGo.TEST_WALLET1_PASSCODE,
        });
        nock.pendingMocks().should.be.empty();

        (bitgo as any).__proto__.getConstants = oldGetConstants;
      });
    });
  });
});

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


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