UNPKG

bitgo

Version:
1,052 lines • 840 kB
"use strict"; // // Tests for Wallet // Object.defineProperty(exports, "__esModule", { value: true }); const should = require("should"); const sinon = require("sinon"); require("../lib/asserts"); const nock = require("nock"); const _ = require("lodash"); const sdk_core_1 = require("@bitgo/sdk-core"); const sdk_test_1 = require("@bitgo/sdk-test"); const src_1 = require("../../../src"); const utxoLib = require("@bitgo/utxo-lib"); const crypto_1 = require("crypto"); const sdk_coin_sol_1 = require("@bitgo/sdk-coin-sol"); const sdk_coin_eth_1 = require("@bitgo/sdk-coin-eth"); const nftResponses_1 = require("../fixtures/nfts/nftResponses"); require('should-sinon'); nock.disableNetConnect(); describe('V2 Wallet:', function () { const reqId = new sdk_core_1.RequestTracer(); const bitgo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'test' }); bitgo.initializeTestVars(); const basecoin = bitgo.coin('tbtc'); const walletData = { id: '5b34252f1bf349930e34020a00000000', coin: 'tbtc', keys: ['5b3424f91bf349930e34017500000000', '5b3424f91bf349930e34017600000000', '5b3424f91bf349930e34017700000000'], coinSpecific: {}, multisigType: 'onchain', type: 'hot', }; const coldWalletData = { id: '65774419fb4d9690847fbe4b00000000', coin: 'tbtc', keys: ['65774412e54b7516393c9df800000000', '6577442428664ffe791af7ea00000000', '6577442b7317a945756c2fd900000000'], coinSpecific: {}, multisigType: 'onchain', type: 'cold', }; const wallet = new sdk_core_1.Wallet(bitgo, basecoin, walletData); const coldWallet = new sdk_core_1.Wallet(bitgo, basecoin, coldWalletData); const bgUrl = sdk_core_1.common.Environments[bitgo.getEnv()].uri; const address1 = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be'; const address2 = '0x7e85bdc27c050e3905ebf4b8e634d9ad6edd0de6'; const tbtcHotWalletDefaultParams = { txFormat: 'psbt', changeAddressType: ['p2trMusig2', 'p2wsh', 'p2shP2wsh', 'p2sh', 'p2tr'], }; afterEach(function () { sinon.restore(); sinon.reset(); }); describe('Wallet transfers', function () { it('should search in wallet for a transfer', async function () { const params = { limit: 1, searchLabel: 'test' }; const scope = nock(bgUrl) .get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/transfer`) .query(params) .reply(200, { coin: 'tbch', transfers: [ { wallet: wallet.id(), comment: 'tests', }, ], }); try { await wallet.transfers(params); } catch (e) { // test is successful if nock is consumed, HMAC errors expected } scope.isDone().should.be.True(); }); it('should forward all valid parameters', async function () { const params = { limit: 1, address: ['address1', 'address2'], dateGte: 'dateString0', dateLt: 'dateString1', valueGte: 0, valueLt: 300000000, allTokens: true, searchLabel: 'abc', includeHex: true, type: 'transfer_type', state: 'transfer_state', }; // The actual api request will only send strings, but the SDK function expects numbers for some values const apiParams = _.mapValues(params, (param) => (Array.isArray(param) ? param : String(param))); const scope = nock(bgUrl) .get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/transfer`) .query(_.matches(apiParams)) .reply(200); await wallet.transfers(params); scope.isDone().should.be.True(); }); it('should accept a string argument for address', async function () { const params = { limit: 1, address: 'stringAddress', }; const apiParams = { limit: '1', address: 'stringAddress', }; const scope = nock(bgUrl) .get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/transfer`) .query(_.matches(apiParams)) .reply(200); try { await wallet.transfers(params); } catch (e) { // test is successful if nock is consumed, HMAC errors expected } scope.isDone().should.be.True(); }); it('should throw errors for invalid expected parameters', async function () { await wallet // @ts-expect-error checking type mismatch .transfers({ address: 13375 }) .should.be.rejectedWith('invalid address argument, expecting string or array'); await wallet // @ts-expect-error checking type mismatch .transfers({ address: [null] }) .should.be.rejectedWith('invalid address argument, expecting array of address strings'); await wallet // @ts-expect-error checking type mismatch .transfers({ dateGte: 20101904 }) .should.be.rejectedWith('invalid dateGte argument, expecting string'); // @ts-expect-error checking type mismatch await wallet.transfers({ dateLt: 20101904 }).should.be.rejectedWith('invalid dateLt argument, expecting string'); await wallet // @ts-expect-error checking type mismatch .transfers({ valueGte: '10230005' }) .should.be.rejectedWith('invalid valueGte argument, expecting number'); // @ts-expect-error checking type mismatch await wallet.transfers({ valueLt: '-5e8' }).should.be.rejectedWith('invalid valueLt argument, expecting number'); await wallet // @ts-expect-error checking type mismatch .transfers({ includeHex: '123' }) .should.be.rejectedWith('invalid includeHex argument, expecting boolean'); await wallet // @ts-expect-error checking type mismatch .transfers({ state: 123 }) .should.be.rejectedWith('invalid state argument, expecting string or array'); await wallet // @ts-expect-error checking type mismatch .transfers({ state: [123, 456] }) .should.be.rejectedWith('invalid state argument, expecting array of state strings'); // @ts-expect-error checking type mismatch await wallet.transfers({ type: 123 }).should.be.rejectedWith('invalid type argument, expecting string'); }); }); describe('Wallet addresses', function () { it('should search in wallet addresses', async function () { const params = { limit: 1, labelContains: 'test' }; const scope = nock(bgUrl) .get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/addresses`) .query(params) .reply(200, { coin: 'tbch', transfers: [ { wallet: wallet.id(), comment: 'tests', }, ], }); try { await wallet.addresses(params); } catch (e) { // test is successful if nock is consumed, HMAC errors expected } scope.isDone().should.be.True(); }); }); it('should verify bch cashaddr format as valid', async function () { const coin = bitgo.coin('tbch'); const valid = coin.isValidAddress('bchtest:pzfkxv532t0q5zchck2mhmmf2y02cdejyssq5qrz7a'); valid.should.be.True(); }); it('should verify bch legacy format as valid', async function () { const coin = bitgo.coin('tbch'); const valid = coin.isValidAddress('2N6gY9r9iuXQQzZiSyngWJeoUuL5mC1x4Ac'); valid.should.be.True(); }); describe('TETH Wallet Addresses', function () { let ethWallet; before(async function () { const walletData = { id: '598f606cd8fc24710d2ebadb1d9459bb', coin: 'teth', keys: [ '598f606cd8fc24710d2ebad89dce86c2', '598f606cc8e43aef09fcb785221d9dd2', '5935d59cf660764331bafcade1855fd7', ], }; ethWallet = new sdk_core_1.Wallet(bitgo, bitgo.coin('teth'), walletData); }); it('search list addresses should return success', async function () { const params = { includeBalances: true, includeTokens: true, returnBalancesForToken: 'gterc6dp', pendingDeployment: false, includeTotalAddressCount: true, }; const scope = nock(bgUrl) .get(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/addresses`) .query(params) .reply(200); try { await wallet.addresses(params); throw ''; } catch (error) { // test is successful if nock is consumed, HMAC errors expected } scope.isDone().should.be.True(); }); it('should throw errors for invalid expected parameters', async function () { await ethWallet .addresses({ includeBalances: true, returnBalancesForToken: 1 }) .should.be.rejectedWith('invalid returnBalancesForToken argument, expecting string'); await ethWallet .addresses({ pendingDeployment: 1 }) .should.be.rejectedWith('invalid pendingDeployment argument, expecting boolean'); await ethWallet .addresses({ includeBalances: 1 }) .should.be.rejectedWith('invalid includeBalances argument, expecting boolean'); await ethWallet .addresses({ includeTokens: 1 }) .should.be.rejectedWith('invalid includeTokens argument, expecting boolean'); await ethWallet .addresses({ includeTotalAddressCount: 1 }) .should.be.rejectedWith('invalid includeTotalAddressCount argument, expecting boolean'); }); it('get forwarder balance', async function () { const forwarders = [ { address: '0xbfbcc0fe2b865de877134246af09378e9bc3c91d', balance: '200000', }, { address: '0xe59524ed8b47165f4cb0850c9428069a6002e5eb', balance: '10000000000000000', }, ]; nock(bgUrl).get(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/forwarders/balances`).reply(200, { forwarders, }); const forwarderBalance = await ethWallet.getForwarderBalance(); forwarderBalance.forwarders[0].address.should.eql(forwarders[0].address); forwarderBalance.forwarders[0].balance.should.eql(forwarders[0].balance); forwarderBalance.forwarders[1].address.should.eql(forwarders[1].address); forwarderBalance.forwarders[1].balance.should.eql(forwarders[1].balance); }); }); describe('Get User Prv', () => { const prv = 'xprv9s21ZrQH143K3hekyNj7TciR4XNYe1kMj68W2ipjJGNHETWP7o42AjDnSPgKhdZ4x8NBAvaL72RrXjuXNdmkMqLERZza73oYugGtbLFXG8g'; const derivedPrv = 'xprv9yoG67Td11uwjXwbV8zEmrySVXERu5FZAsLD9suBeEJbgJqANs8Yng5dEJoii7hag5JermK6PbfxgDmSzW7ewWeLmeJEkmPfmZUSLdETtHx'; it('should use the cold derivation seed to derive the proper user private key', async () => { const userPrvOptions = { prv, coldDerivationSeed: '123', }; wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv); }); it('should use the user keychain derivedFromParentWithSeed as the cold derivation seed if none is provided', async () => { const userPrvOptions = { prv, keychain: { derivedFromParentWithSeed: '123', id: '456', pub: '789', type: 'independent', }, }; wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv); }); it('should prefer the explicit cold derivation seed to the user keychain derivedFromParentWithSeed', async () => { const userPrvOptions = { prv, coldDerivationSeed: '123', keychain: { derivedFromParentWithSeed: '456', id: '789', pub: '012', type: 'independent', }, }; wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv); }); it('should return the prv provided for TSS SMC', async () => { const tssWalletData = { id: '5b34252f1bf349930e34020a00000000', coin: 'tsol', keys: [ '5b3424f91bf349930e34017500000000', '5b3424f91bf349930e34017600000000', '5b3424f91bf349930e34017700000000', ], coinSpecific: {}, multisigType: 'tss', }; const tsolcoin = bitgo.coin('tsol'); const wallet = new sdk_core_1.Wallet(bitgo, tsolcoin, tssWalletData); const prv = 'longstringifiedjson'; const keychain = { derivedFromParentWithSeed: 'random seed', id: '123', commonKeychain: 'longstring', type: 'tss', }; const userPrvOptions = { prv, keychain, }; wallet.getUserPrv(userPrvOptions).should.eql(prv); }); }); describe('TETH Wallet Transactions', function () { let ethWallet; before(async function () { const walletData = { id: '598f606cd8fc24710d2ebadb1d9459bb', coin: 'teth', keys: [ '598f606cd8fc24710d2ebad89dce86c2', '598f606cc8e43aef09fcb785221d9dd2', '5935d59cf660764331bafcade1855fd7', ], multisigType: 'onchain', }; ethWallet = new sdk_core_1.Wallet(bitgo, bitgo.coin('teth'), walletData); }); afterEach(async function () { nock.cleanAll(); }); it('should error eip1559 and gasPrice are passed', async function () { const params = { gasPrice: 100, eip1559: { maxPriorityFeePerGas: 10, maxFeePerGas: 10, }, amount: 10, address: sdk_test_1.TestBitGo.V2.TEST_WALLET1_ADDRESS, walletPassphrase: sdk_test_1.TestBitGo.V2.TEST_WALLET1_PASSCODE, }; await ethWallet.send(params).should.be.rejected(); }); it('should search for pending transaction correctly', async function () { const params = { walletId: wallet.id() }; const scope = nock(bgUrl).get(`/api/v2/${wallet.coin()}/tx/pending/first`).query(params).reply(200); try { await wallet.getFirstPendingTransaction(); throw ''; } catch (error) { // test is successful if nock is consumed, HMAC errors expected } scope.isDone().should.be.True(); }); it('should try to change the fee correctly', async function () { const params = { txid: '0xffffffff', fee: '10000000' }; const scope = nock(bgUrl).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/changeFee`, params).reply(200); try { await wallet.changeFee({ txid: '0xffffffff', fee: '10000000' }); throw ''; } catch (error) { // test is successful if nock is consumed, HMAC errors expected } scope.isDone().should.be.True(); }); it('should try to change the fee correctly using eip1559', async function () { const params = { txid: '0xffffffff', eip1559: { maxPriorityFeePerGas: '1000000000', maxFeePerGas: '25000000000', }, }; const scope = nock(bgUrl).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/changeFee`, params).reply(200); try { await wallet.changeFee(params); throw ''; } catch (error) { // test is successful if nock is consumed, HMAC errors expected } scope.isDone().should.be.True(); }); it('should pass data parameter and amount: 0 when using sendTransaction', async function () { const path = `/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/tx/build`; const recipientAddress = '0x7db562c4dd465cc895761c56f83b6af0e32689ba'; const recipients = [ { address: recipientAddress, amount: 0, data: '0x00110011', }, ]; const response = nock(bgUrl) .post(path, _.matches({ recipients })) // use _.matches to do a partial match on request body object instead of strict matching .reply(200); const nockKeyChain = nock(bgUrl).get(`/api/v2/${ethWallet.coin()}/key/${ethWallet.keyIds()[0]}`).reply(200, {}); try { await ethWallet.send({ address: recipients[0].address, data: recipients[0].data, amount: recipients[0].amount, }); } catch (e) { // test is successful if nock is consumed, HMAC errors expected } response.isDone().should.be.true(); nockKeyChain.isDone().should.be.true(); }); it('should pass data parameter and amount: 0 when using sendMany', async function () { const path = `/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/tx/build`; const recipientAddress = '0x7db562c4dd465cc895761c56f83b6af0e32689ba'; const recipients = [ { address: recipientAddress, amount: 0, data: '0x00110011', }, ]; const response = nock(bgUrl) .post(path, _.matches({ recipients })) // use _.matches to do a partial match on request body object instead of strict matching .reply(200); const nockKeyChain = nock(bgUrl).get(`/api/v2/${ethWallet.coin()}/key/${ethWallet.keyIds()[0]}`).reply(200, {}); try { await ethWallet.sendMany({ recipients }); } catch (e) { // test is successful if nock is consumed, HMAC errors expected } response.isDone().should.be.true(); nockKeyChain.isDone().should.be.true(); }); it('should not pass recipients in sendMany when transaction type is fillNonce', async function () { const recipientAddress = '0x7db562c4dd465cc895761c56f83b6af0e32689ba'; const recipients = [ { address: recipientAddress, amount: 0, }, ]; const sendManyParams = { recipients, type: 'fillNonce', isTss: true, nonce: '13' }; try { await ethWallet.sendMany(sendManyParams); } catch (e) { e.message.should.equal('cannot provide recipients for transaction type fillNonce'); // test is successful if nock is consumed, HMAC errors expected } }); it('should not pass receiveAddress in sendMany when TSS transaction type is transfer or transferToken', async function () { const recipientAddress = '0x7db562c4dd465cc895761c56f83b6af0e32689ba'; const recipients = [ { address: recipientAddress, amount: 0, }, ]; const errorMessage = 'cannot use receive address for TSS transactions of type transfer'; const sendManyParamsReceiveAddressError = { receiveAddress: 'throw', recipients, type: 'transfer', isTss: true, nonce: '13', }; const sendManyParams = { recipients, type: 'transfer', isTss: true, nonce: '13' }; try { await ethWallet.sendMany(sendManyParamsReceiveAddressError); } catch (e) { e.message.should.equal(errorMessage); } try { await ethWallet.sendMany(sendManyParams); } catch (e) { e.message.should.not.equal(errorMessage); } }); it('should throw error early if password is wrong', async function () { const recipientAddress = '0x7db562c4dd465cc895761c56f83b6af0e32689ba'; const recipients = [ { address: recipientAddress, amount: 0, }, ]; const errorMessage = `unable to decrypt keychain with the given wallet passphrase`; const sendManyParamsCorrectPassPhrase = { recipients, type: 'transfer', isTss: true, nonce: '13', walletPassphrase: sdk_test_1.TestBitGo.V2.TEST_ETH_WALLET_PASSPHRASE, }; const nockKeychain = nock(bgUrl) .get(`/api/v2/${ethWallet.coin()}/key/${ethWallet.keyIds()[0]}`) .times(2) .reply(200, { id: '598f606cd8fc24710d2ebad89dce86c2', pub: 'xpub661MyMwAqRbcFXDcWD2vxuebcT1ZpTF4Vke6qmMW8yzddwNYpAPjvYEEL5jLfyYXW2fuxtAxY8TgjPUJLcf1C8qz9N6VgZxArKX4EwB8rH5', ethAddress: '0x26a163ba9739529720c0914c583865dec0d37278', source: 'user', encryptedPrv: '{"iv":"15FsbDVI1zG9OggD8YX+Hg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"hHbNH3Sz/aU=","ct":"WoNVKz7afiRxXI2w/YkzMdMyoQg/B15u1Q8aQgi96jJZ9wk6TIaSEc6bXFH3AHzD9MdJCWJQUpRhoQc/rgytcn69scPTjKeeyVMElGCxZdFVS/psQcNE+lue3//2Zlxj+6t1NkvYO+8yAezSMRBK5OdftXEjNQI="}', coinSpecific: {}, }); await ethWallet .sendMany({ ...sendManyParamsCorrectPassPhrase, walletPassphrase: 'wrongPassphrase' }) .should.be.rejectedWith(errorMessage); try { const customSigningFunction = () => { return 'mock'; }; // Should not validate passphrase if custom signing function is provided await ethWallet.sendMany({ ...sendManyParamsCorrectPassPhrase, walletPassphrase: 'wrongPassphrase', customSigningFunction, }); } catch (e) { e.message.should.not.equal(errorMessage); } try { await ethWallet.sendMany({ ...sendManyParamsCorrectPassPhrase }); } catch (e) { e.message.should.not.equal(errorMessage); } nockKeychain.isDone().should.be.true(); }); }); describe('OFC Create Address', () => { let ofcWallet; let nocks; before(async function () { const walletDataOfc = { id: '5b34252f1bf349930e3400b00000000', coin: 'ofc', keys: [ '5b3424f91bf349930e34017800000000', '5b3424f91bf349930e34017900000000', '5b3424f91bf349930e34018000000000', ], coinSpecific: {}, multisigType: 'onchain', }; ofcWallet = new sdk_core_1.Wallet(bitgo, bitgo.coin('ofc'), walletDataOfc); }); beforeEach(async function () { nocks = [ nock(bgUrl).get(`/api/v2/ofc/key/${ofcWallet.keyIds()[0]}`).reply(200, { id: ofcWallet.keyIds()[0], pub: 'xpub661MyMwAqRbcFXDcWD2vxuebcT1ZpTF4Vke6qmMW8yzddwNYpAPjvYEEL5jLfyYXW2fuxtAxY8TgjPUJLcf1C8qz9N6VgZxArKX4EwB8rH5', source: 'user', encryptedPrv: '{"iv":"15FsbDVI1zG9OggD8YX+Hg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"hHbNH3Sz/aU=","ct":"WoNVKz7afiRxXI2w/YkzMdMyoQg/B15u1Q8aQgi96jJZ9wk6TIaSEc6bXFH3AHzD9MdJCWJQUpRhoQc/rgytcn69scPTjKeeyVMElGCxZdFVS/psQcNE+lue3//2Zlxj+6t1NkvYO+8yAezSMRBK5OdftXEjNQI="}', coinSpecific: {}, }), nock(bgUrl).get(`/api/v2/ofc/key/${ofcWallet.keyIds()[1]}`).reply(200, { id: ofcWallet.keyIds()[1], pub: 'xpub661MyMwAqRbcGhSaXikpuTC9KU88Xx9LrjKSw1JKsvXNgabpTdgjy7LSovh9ZHhcqhAHQu7uthu7FguNGdcC4aXTKK5gqTcPe4WvLYRbCSG', source: 'backup', coinSpecific: {}, }), nock(bgUrl).get(`/api/v2/ofc/key/${ofcWallet.keyIds()[2]}`).reply(200, { id: ofcWallet.keyIds()[2], pub: 'xpub661MyMwAqRbcFsXShW8R3hJsHNTYTUwzcejnLkY7KCtaJbDqcGkcBF99BrEJSjNZHeHveiYUrsAdwnjUMGwpgmEbiKcZWRuVA9HxnRaA3r3', source: 'bitgo', coinSpecific: {}, }), ]; }); afterEach(async function () { nock.cleanAll(); nocks.forEach((scope) => scope.isDone().should.be.true()); }); it('should correctly validate arguments to create address on OFC wallet', async function () { await ofcWallet.createAddress().should.be.rejectedWith('onToken is a mandatory parameter for OFC wallets'); // @ts-expect-error test passing invalid number argument await ofcWallet.createAddress({ onToken: 42 }).should.be.rejectedWith('onToken has to be a string'); }); it('address creation with valid onToken argument succeeds', async function () { const scope = nock(bgUrl) .post(`/api/v2/ofc/wallet/${ofcWallet.id()}/address`, { onToken: 'ofctbtc' }) .reply(200, { id: '638a48c6c3dba40007a3497fa49a080c', address: 'generated address', chain: 0, index: 1, coin: 'tbtc', wallet: ofcWallet.id, }); const address = await ofcWallet.createAddress({ onToken: 'ofctbtc' }); address.address.should.equal('generated address'); scope.isDone().should.be.true(); }); }); describe('TETH Create Address', () => { let ethWallet, nocks; const walletData = { id: '598f606cd8fc24710d2ebadb1d9459bb', coinSpecific: { baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', }, coin: 'teth', keys: [ '598f606cd8fc24710d2ebad89dce86c2', '598f606cc8e43aef09fcb785221d9dd2', '5935d59cf660764331bafcade1855fd7', ], }; beforeEach(async function () { ethWallet = new sdk_core_1.Wallet(bitgo, bitgo.coin('teth'), walletData); nocks = [ nock(bgUrl).get(`/api/v2/${ethWallet.coin()}/key/${ethWallet.keyIds()[0]}`).reply(200, { id: '598f606cd8fc24710d2ebad89dce86c2', pub: 'xpub661MyMwAqRbcFXDcWD2vxuebcT1ZpTF4Vke6qmMW8yzddwNYpAPjvYEEL5jLfyYXW2fuxtAxY8TgjPUJLcf1C8qz9N6VgZxArKX4EwB8rH5', ethAddress: '0x26a163ba9739529720c0914c583865dec0d37278', source: 'user', encryptedPrv: '{"iv":"15FsbDVI1zG9OggD8YX+Hg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"hHbNH3Sz/aU=","ct":"WoNVKz7afiRxXI2w/YkzMdMyoQg/B15u1Q8aQgi96jJZ9wk6TIaSEc6bXFH3AHzD9MdJCWJQUpRhoQc/rgytcn69scPTjKeeyVMElGCxZdFVS/psQcNE+lue3//2Zlxj+6t1NkvYO+8yAezSMRBK5OdftXEjNQI="}', coinSpecific: {}, }), nock(bgUrl).get(`/api/v2/${ethWallet.coin()}/key/${ethWallet.keyIds()[1]}`).reply(200, { id: '598f606cc8e43aef09fcb785221d9dd2', pub: 'xpub661MyMwAqRbcGhSaXikpuTC9KU88Xx9LrjKSw1JKsvXNgabpTdgjy7LSovh9ZHhcqhAHQu7uthu7FguNGdcC4aXTKK5gqTcPe4WvLYRbCSG', ethAddress: '0xa1a88a502274073b1bc4fe06ea0f5fe77e151b91', source: 'backup', coinSpecific: {}, }), nock(bgUrl).get(`/api/v2/${ethWallet.coin()}/key/${ethWallet.keyIds()[2]}`).reply(200, { id: '5935d59cf660764331bafcade1855fd7', pub: 'xpub661MyMwAqRbcFsXShW8R3hJsHNTYTUwzcejnLkY7KCtaJbDqcGkcBF99BrEJSjNZHeHveiYUrsAdwnjUMGwpgmEbiKcZWRuVA9HxnRaA3r3', ethAddress: '0x032821b7ea40ea5d446f47c29a0f777ee035aa10', source: 'bitgo', coinSpecific: {}, }), ]; }); afterEach(async function () { nock.cleanAll(); nocks.forEach((scope) => scope.isDone().should.be.true()); }); it('should correctly validate arguments to create address', async function () { let message = 'gasPrice has to be an integer or numeric string'; // @ts-expect-error checking type mismatch await wallet.createAddress({ gasPrice: {} }).should.be.rejectedWith(message); await wallet.createAddress({ gasPrice: 'abc' }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ gasPrice: null }).should.be.rejectedWith(message); message = 'chain has to be an integer'; // @ts-expect-error checking type mismatch await wallet.createAddress({ chain: {} }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ chain: 'abc' }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ chain: null }).should.be.rejectedWith(message); message = 'count has to be a number between 1 and 250'; // @ts-expect-error checking type mismatch await wallet.createAddress({ count: {} }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ count: 'abc' }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ count: null }).should.be.rejectedWith(message); await wallet.createAddress({ count: -1 }).should.be.rejectedWith(message); await wallet.createAddress({ count: 0 }).should.be.rejectedWith(message); await wallet.createAddress({ count: 251 }).should.be.rejectedWith(message); message = 'baseAddress has to be a string'; // @ts-expect-error checking type mismatch await wallet.createAddress({ baseAddress: {} }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ baseAddress: 123 }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ baseAddress: null }).should.be.rejectedWith(message); message = 'allowSkipVerifyAddress has to be a boolean'; // @ts-expect-error checking type mismatch await wallet.createAddress({ allowSkipVerifyAddress: {} }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ allowSkipVerifyAddress: 123 }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ allowSkipVerifyAddress: 'abc' }).should.be.rejectedWith(message); // @ts-expect-error checking type mismatch await wallet.createAddress({ allowSkipVerifyAddress: null }).should.be.rejectedWith(message); message = 'forwarderVersion has to be an integer 0, 1, 2, 3, 4 or 5'; await wallet.createAddress({ forwarderVersion: 6 }).should.be.rejectedWith(message); await wallet.createAddress({ forwarderVersion: -1 }).should.be.rejectedWith(message); }); it('address creation with forwarder version 3 succeeds', async function () { const scope = nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 3 }) .reply(200, { id: '638a48c6c3dba40007a3497fa49a080c', address: '0x5e61b64f38f1b5f85078fb84b27394830b4c8e80', chain: 0, index: 1, coin: 'tpolygon', lastNonce: 0, wallet: '63785f95af7c760007cfae068c2f31ae', coinSpecific: { nonce: -1, updateTime: '2022-12-02T18:49:42.348Z', txCount: 0, pendingChainInitialization: false, creationFailure: [], salt: '0x1', pendingDeployment: true, forwarderVersion: 3, isTss: true, }, }); const address = await ethWallet.createAddress({ chain: 0, forwarderVersion: 3 }); address.coinSpecific.forwarderVersion.should.equal(3); scope.isDone().should.be.true(); }); it('address creation with forwarder version 3 fails due invalid address', async function () { const address = '0x5e61b6'; // invalid address nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 3 }) .reply(200, { id: '638a48c6c3dba40007a3497fa49a080c', address: address, chain: 0, index: 1, coin: 'tpolygon', lastNonce: 0, wallet: '63785f95af7c760007cfae068c2f31ae', coinSpecific: { nonce: -1, updateTime: '2022-12-02T18:49:42.348Z', txCount: 0, pendingChainInitialization: false, creationFailure: [], salt: '0x1', pendingDeployment: true, forwarderVersion: 3, isTss: true, }, }); await ethWallet .createAddress({ chain: 0, forwarderVersion: 3 }) .should.be.rejectedWith(`invalid address: ${address}`); }); it('address creation with forwarder version 2 succeeds', async function () { const scope = nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 2 }) .reply(200, { id: '638a48c6c3dba40007a3497fa49a080c', address: '0x5e61b64f38f1b5f85078fb84b27394830b4c8e80', chain: 0, index: 1, coin: 'tpolygon', lastNonce: 0, wallet: '63785f95af7c760007cfae068c2f31ae', coinSpecific: { nonce: -1, updateTime: '2022-12-02T18:49:42.348Z', txCount: 0, pendingChainInitialization: true, creationFailure: [], salt: '0x1', pendingDeployment: true, forwarderVersion: 2, isTss: true, }, }); const address = await ethWallet.createAddress({ chain: 0, forwarderVersion: 2 }); address.coinSpecific.forwarderVersion.should.equal(2); scope.isDone().should.be.true(); }); it('verify address when pendingChainInitialization is true in case of eth v1 forwarder', async function () { const scope = nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 1 }) .reply(200, { id: '615c643a98a2a100068e023c639c0f74', address: '0x8c13cd0bb198858f628d5631ba4b2293fc08df49', baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', chain: 0, index: 3179, coin: 'teth', lastNonce: 0, wallet: '598f606cd8fc24710d2ebadb1d9459bb', coinSpecific: { nonce: -1, updateTime: '2021-10-05T14:42:02.399Z', txCount: 0, pendingChainInitialization: true, creationFailure: [], salt: '0xc6b', pendingDeployment: true, forwarderVersion: 1, }, }); await ethWallet .createAddress({ chain: 0, forwarderVersion: 1 }) .should.be.rejectedWith('address validation failure: expected 0x32a226cda14e352a47bf4b1658648d8037736f80 but got 0x8c13cd0bb198858f628d5631ba4b2293fc08df49'); scope.isDone().should.be.true(); }); it('verify address when invalid baseAddress is passed', async function () { const scope = nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 1 }) .reply(200, { id: '615c643a98a2a100068e023c639c0f74', address: '0x32a226cda14e352a47bf4b1658648d8037736f80', baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', chain: 0, index: 3179, coin: 'teth', lastNonce: 0, wallet: '598f606cd8fc24710d2ebadb1d9459bb', coinSpecific: { nonce: -1, updateTime: '2021-10-05T14:42:02.399Z', txCount: 0, pendingChainInitialization: true, creationFailure: [], salt: '0xc6b', pendingDeployment: true, forwarderVersion: 1, }, }); await ethWallet .createAddress({ chain: 0, forwarderVersion: 1, baseAddress: 'asgf' }) .should.be.rejectedWith('invalid base address'); scope.isDone().should.be.true(); }); it('verify address when incorrect baseAddress is passed', async function () { const scope = nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 1 }) .reply(200, { id: '615c643a98a2a100068e023c639c0f74', address: '0x32a226cda14e352a47bf4b1658648d8037736f80', baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', chain: 0, index: 3179, coin: 'teth', lastNonce: 0, wallet: '598f606cd8fc24710d2ebadb1d9459bb', coinSpecific: { nonce: -1, updateTime: '2021-10-05T14:42:02.399Z', txCount: 0, pendingChainInitialization: true, creationFailure: [], salt: '0xc6b', pendingDeployment: true, forwarderVersion: 1, }, }); // incorrect address is generated while validating due to incorrect baseAddress await ethWallet .createAddress({ chain: 0, forwarderVersion: 1, baseAddress: '0x8c13cd0bb198858f628d5631ba4b2293fc08df49' }) .should.be.rejectedWith('address validation failure: expected 0x36748926007790e7ee416c6485b32e00cfb177a3 but got 0x32a226cda14e352a47bf4b1658648d8037736f80'); scope.isDone().should.be.true(); }); it('verify address when pendingChainInitialization is true and allowSkipVerifyAddress is false in case of eth v0 forwarder', async function () { const scope = nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 0 }) .reply(200, { id: '615c643a98a2a100068e023c639c0f74', address: '0x32a26cda14e352a47bf4b1658648d8037736f80', baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', chain: 0, index: 3179, coin: 'teth', lastNonce: 0, wallet: '598f606cd8fc24710d2ebadb1d9459bb', coinSpecific: { nonce: -1, updateTime: '2021-10-05T14:42:02.399Z', txCount: 0, pendingChainInitialization: true, creationFailure: [], salt: '0xc6b', pendingDeployment: true, forwarderVersion: 1, }, }); await ethWallet .createAddress({ chain: 0, forwarderVersion: 0, allowSkipVerifyAddress: false }) .should.be.rejectedWith('address verification skipped for count = 1'); scope.isDone().should.be.true(); }); it('verify address with allowSkipVerifyAddress set to false and eth v1 forwarder', async function () { const scope = nock(bgUrl) .post(`/api/v2/${ethWallet.coin()}/wallet/${ethWallet.id()}/address`, { chain: 0, forwarderVersion: 1 }) .reply(200, { id: '615c643a98a2a100068e023c639c0f74', address: '0x32a226cda14e352a47bf4b1658648d8037736f80', baseAddress: '0xdf07117705a9f8dc4c2a78de66b7f1797dba9d4e', chain: 0, index: 3179, coin: 'teth', lastNonce: 0, wallet: '598f606cd8fc24710d2ebadb1d9459bb', coinSpecific: { nonce: -1, updateTime: '2021-10-05T14:42:02.399Z', txCount: 0, pendingChainInitialization: true, creationFailure: [], salt: '0xc6b', pendingDeployment: true, forwarderVersion: 0, }, }); const newAddress = await ethWallet.createAddress({ chain: 0, forwarderVersion: 1, allowSkipVerifyAddress: false, }); newAddress.index.should.equal(3179); scope.isDone().should.be.true(); }); }); describe('Algorand tests', () => { let algoWallet; before(async () => { // This is not a real TALGO wallet const walletData = { id: '650204cf43d8b40007cd9e11a872ce65', coin: 'talgo', keys: [ '650204b78a75c90007790bce979ae34d', '650204b766c56a00072956c08fb9cdf1', '650204b8ccf1370007b32bb8155dfbec', ], coinSpecific: { rootAddress: '2ULRGE64U7LTMT5M6REB7ORHX5GLJYWHTIV5EAXVLWQTTATVJDGM5KJMII', }, }; algoWallet = new sdk_core_1.Wallet(bitgo, bitgo.coin('talgo'), walletData); }); it('Should build token enablement transactions', async () => { const params = { enableTokens: [ { name: 'talgo:USDt-180447', }, ], }; const txRequestNock = nock(bgUrl) .post(`/api/v2/${algoWallet.coin()}/wallet/${algoWallet.id()}/tx/build`) .reply((uri, body) => { const params = body; params.recipients.length.should.equal(1); params.recipients[0].tokenName.should.equal('talgo:USDt-180447'); params.type.should.equal('enabletoken'); should.not.exist(params.enableTokens); return [200, params]; }); await algoWallet.buildTokenEnablements(params); txRequestNock.isDone().should.equal(true); }); afterEach(() => { nock.cleanAll(); }); }); describe('Hedera tests', () => { let hbarWallet; before(async () => { // This is not a real THBAR wallet const walletData = { id: '598f606cd8fc24710d2ebadb1d9459bb', coin: 'thbar', keys: [ '598f606cd8fc24710d2ebad89dce86c2', '598f606cc8e43aef09fcb785221d9dd2', '5935d59cf660764331bafcade1855fd7', ], coinSpecific: { baseAddress: '0.0.47841511', }, }; hbarWallet = new sdk_core_1.Wallet(bitgo, bitgo.coin('thbar'), walletData); }); it('Should build token enablement transactions', async () => { const params = { enableTokens: [ { name: 'thbar:usdc', }, ], }; const txRequestNock = nock(bgUrl) .post(`/api/v2/${hbarWallet.coin()}/wallet/${hbarWallet.id()}/tx/build`) .reply((uri, body) => { const params = body; params.recipients.length.should.equal(1); params.recipients[0].tokenName.should.equal('thbar:usdc'); params.type.should.equal('enabletoken'); should.not.exist(params.enableTokens); return [200, params]; }); await hbarWallet.buildTokenEnablements(params); txRequestNock.isDone().should.equal(true); }); afterEach(() => { nock.cleanAll(); }); }); describe('Canton tests: ', () => { let cantonWallet; const cantonBitgo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' }); cantonBitgo.initializeTestVars(); const walletData = { id: '598f606cd8fc24710d2ebadb1d9459bb', coinSpecific: { baseAddress: '12205::12205b4e3537a95126d90604592344d8ad3c3ddccda4f79901954280ee19c576714d', pendingChainInitialization: true, lastChainIndex: { 0: 0 }, }, coin: 'tcanton', keys: [ '598f606cd8fc24710d2ebad89dce86c2', '598f606cc8e43aef09fcb785221d9dd2', '5935d59cf660764331bafcade1855fd7', ], multisigType: 'tss', }; before(async function () { cantonWallet = new sdk_core_1.Wallet(bitgo, bitgo.coin('tcanton'), walletData); nock(bgUrl).get(`/api/v2/${cantonWallet.coin()}/key/${cantonWallet.keyIds()[0]}`).times(3).reply(200, { id: '598f606cd8fc24710d2ebad89dce86c2', pub: '5f8WmC2uW9SAk7LMX2r4G1Bx8MMwx8sdgpotyHGodiZo', source: 'user', encryptedPrv: '{"iv":"hNK3rg82P1T94MaueXFAbA==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"cV4wU4EzPjs=","ct":"9VZX99Ztsb6p75Cxl2lrcXBplmssIAQ9k7ZA81vdDYG4N5dZ36BQNWVfDoelj9O31XyJ+Xri0XKIWUzl0KKLfUERplmtNoOCn5ifJcZwCrOxpHZQe3AJ700o8Wmsrk5H"}', coinSpecific: {}, }); nock(bgUrl).get(`/api/v2/${cantonWallet.coin()}/key/${cantonWallet.keyIds()[1]}`).times(2).reply(200, { id: '598f606cc8e43aef09fcb785221d9dd2', pub: 'G1s43JTzNZzqhUn4aNpwgcc6wb9FUsZQD5JjffG6isyd', encryptedPrv: '{"iv":"UFrt/QlIUR1XeQafPBaAlw==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"7VPBYaJXPm8=","ct":"ajFKv2y8yaIBXQ39sAbBWcnbiEEzbjS4AoQtp5cXYqjeDRxt3aCxemPm22pnkJaCijFjJrMHbkmsNhNYzHg5aHFukN+nEAVssyNwHbzlhSnm8/BVN50yAdAAtWreh8cp"}', source: 'backup', coinSpec