bitgo
Version:
BitGo JavaScript SDK
1,052 lines • 840 kB
JavaScript
"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