bitgo
Version:
BitGo JavaScript SDK
1,109 lines • 496 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//
// Tests for Wallets
//
const assert = require("assert");
const nock = require("nock");
const sinon = require("sinon");
const should = require("should");
const _ = require("lodash");
const utxoLib = require("@bitgo/utxo-lib");
const sdk_test_1 = require("@bitgo/sdk-test");
const sdk_core_1 = require("@bitgo/sdk-core");
const src_1 = require("../../../src");
const mocha_1 = require("mocha");
const moduleBitgo = require("@bitgo/sdk-core");
describe('V2 Wallets:', function () {
const bitgo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' });
let wallets;
let ofcWallets;
let bgUrl;
before(function () {
nock('https://bitgo.fakeurl').persist().get('/api/v1/client/constants').reply(200, { ttl: 3600, constants: {} });
bitgo.initializeTestVars();
const basecoin = bitgo.coin('tbtc');
wallets = basecoin.wallets();
ofcWallets = bitgo.coin('ofc').wallets();
bgUrl = sdk_core_1.common.Environments[bitgo.getEnv()].uri;
});
after(function () {
nock.cleanAll();
nock.pendingMocks().length.should.equal(0);
});
describe('Add Wallet:', function () {
it('throws on invalid arguments', async function () {
// isCustodial flag is not a boolean
await wallets
.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, isCustodial: 1 })
.should.be.rejectedWith('invalid argument for isCustodial - boolean expected');
// type is not a string
await wallets
.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, type: 1 })
.should.be.rejectedWith('Expecting parameter string: type but found number');
// Address is an invalid address
await wallets
.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, address: '$' })
.should.be.rejectedWith('invalid argument for address - valid address string expected');
// gasPrice is a number
await wallets
.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, gasPrice: '17' })
.should.be.rejectedWith('invalid argument for gasPrice - number expected');
// walletVersion is a number
await wallets
.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, walletVersion: '1' })
.should.be.rejectedWith('invalid argument for walletVersion - number expected');
});
it('creates a paired custodial wallet', async function () {
nock(bgUrl)
.post('/api/v2/tbtc/wallet/add', function (body) {
body.isCustodial.should.be.true();
body.should.have.property('keys');
body.m.should.equal(2);
body.n.should.equal(3);
return true;
})
.reply(200, {});
await wallets.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, isCustodial: true });
});
it('creates an eos wallet with custom address', async function () {
const eosBitGo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' });
eosBitGo.initializeTestVars();
const eosWallets = eosBitGo.coin('teos').wallets();
const address = 'testeosaddre';
nock(bgUrl)
.post('/api/v2/teos/wallet/add', function (body) {
body.should.have.property('keys');
body.m.should.equal(2);
body.n.should.equal(3);
body.address.should.equal(address);
return true;
})
.reply(200, {});
await eosWallets.add({ label: 'label', enterprise: 'enterprise', keys: [], m: 2, n: 3, address });
});
it('creates a single custodial wallet', async function () {
nock(bgUrl)
.post('/api/v2/tbtc/wallet/add', function (body) {
body.type.should.equal('custodial');
body.should.not.have.property('keys');
body.should.not.have.property('m');
body.should.not.have.property('n');
return true;
})
.reply(200, {});
await wallets.add({ label: 'label', enterprise: 'enterprise', type: 'custodial' });
});
it('creates a wallet with custom gasPrice', async function () {
const ethBitGo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' });
ethBitGo.initializeTestVars();
const ethWallets = ethBitGo.coin('teth').wallets();
nock(bgUrl)
.post('/api/v2/teth/wallet/add', function (body) {
body.type.should.equal('custodial');
body.gasPrice.should.equal(20000000000);
body.should.not.have.property('keys');
body.should.not.have.property('m');
body.should.not.have.property('n');
return true;
})
.reply(200, {});
await ethWallets.add({
label: 'label',
enterprise: 'enterprise',
type: 'custodial',
gasPrice: 20000000000,
});
});
it('creates a new wallet with walletVersion', async function () {
const ethBitGo = sdk_test_1.TestBitGo.decorate(src_1.BitGo, { env: 'mock' });
ethBitGo.initializeTestVars();
const ethWallets = ethBitGo.coin('teth').wallets();
nock(bgUrl)
.post('/api/v2/teth/wallet/add', function (body) {
body.type.should.equal('custodial');
body.walletVersion.should.equal(1);
body.should.not.have.property('keys');
body.should.not.have.property('m');
body.should.not.have.property('n');
return true;
})
.reply(200, {});
await ethWallets.add({ label: 'label', enterprise: 'enterprise', type: 'custodial', walletVersion: 1 });
});
it('creates a new hot wallet with userKey', async function () {
nock(bgUrl)
.post('/api/v2/tbtc/wallet/add', function (body) {
body.type.should.equal('hot');
body.should.have.property('keys');
body.should.have.property('m');
body.should.have.property('n');
return true;
})
.reply(200, {});
await wallets.add({
label: 'label',
enterprise: 'enterprise',
type: 'hot',
keys: [],
m: 2,
n: 3,
userKey: 'test123',
});
});
});
describe('Generate wallet:', function () {
const sandbox = sinon.createSandbox();
it('should validate parameters', async function () {
let params = {};
await wallets.generateWallet(params).should.be.rejectedWith('Missing parameter: label');
params = {
label: 'abc',
backupXpub: 'backup',
backupXpubProvider: 'provider',
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('Cannot provide more than one backupXpub or backupXpubProvider flag');
params = {
label: 'abc',
passcodeEncryptionCode: 123,
};
await wallets.generateWallet(params).should.be.rejectedWith('passcodeEncryptionCode must be a string');
params = {
label: 'abc',
enterprise: 1234,
};
await wallets.generateWallet(params).should.be.rejectedWith('invalid enterprise argument, expecting string');
params = {
label: 'abc',
disableTransactionNotifications: 'string',
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('invalid disableTransactionNotifications argument, expecting boolean');
params = {
label: 'abc',
gasPrice: 'string',
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('invalid gas price argument, expecting number or number as string');
params = {
label: 'abc',
gasPrice: true,
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('invalid gas price argument, expecting number or number as string');
params = {
label: 'abc',
gasPrice: 123,
eip1559: {
maxFeePerGas: 1234,
maxPriorityFeePerGas: 123,
},
};
await wallets.generateWallet(params).should.be.rejectedWith('can not use both eip1559 and gasPrice values');
params = {
label: 'abc',
eip1559: {
maxFeePerGas: 'q1234',
maxPriorityFeePerGas: '123',
},
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('invalid max fee argument, expecting number or number as string');
params = {
label: 'abc',
eip1559: {
maxFeePerGas: 1234,
maxPriorityFeePerGas: '123a',
},
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('invalid priority fee argument, expecting number or number as string');
params = {
label: 'abc',
disableKRSEmail: 'string',
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('invalid disableKRSEmail argument, expecting boolean');
params = {
label: 'abc',
krsSpecific: {
malicious: {
javascript: {
code: 'bad.js',
},
},
},
};
await wallets
.generateWallet(params)
.should.be.rejectedWith('krsSpecific object contains illegal values. values must be strings, booleans, or numbers');
});
it('should correctly disable krs emails when creating backup keychains', async function () {
const params = {
label: 'my_wallet',
disableKRSEmail: true,
backupXpubProvider: 'test',
passphrase: 'test123',
userKey: 'xpub123',
};
// bitgo key
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' }))
.reply(200);
// user key
nock(bgUrl)
.post('/api/v2/tbtc/key', _.conforms({ pub: (p) => p.startsWith('xpub') }))
.reply(200);
// backup key
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({
source: 'backup',
provider: params.backupXpubProvider,
disableKRSEmail: true,
}))
.reply(200);
// wallet
nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200);
await wallets.generateWallet(params);
});
it('should correctly pass through the krsSpecific param when creating backup keychains', async function () {
const params = {
label: 'my_wallet',
backupXpubProvider: 'test',
passphrase: 'test123',
userKey: 'xpub123',
krsSpecific: { coverage: 'insurance', expensive: true, howExpensive: 25 },
};
// bitgo key
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' }))
.reply(200);
// user key
nock(bgUrl)
.post('/api/v2/tbtc/key', _.conforms({ pub: (p) => p.startsWith('xpub') }))
.reply(200);
// backup key
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({
source: 'backup',
provider: params.backupXpubProvider,
krsSpecific: { coverage: 'insurance', expensive: true, howExpensive: 25 },
}))
.reply(200);
// wallet
nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200);
await wallets.generateWallet(params);
});
it('should send the cold derivation seed for a user key', async () => {
const params = {
label: 'my-cold-wallet',
passphrase: 'test123',
userKey: 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
coldDerivationSeed: '123',
};
// bitgo key
const bitgoKeyNock = nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' }))
.reply(200);
// user key
const userKeyNock = nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({
derivedFromParentWithSeed: params.coldDerivationSeed,
}))
.reply(200);
// backup key
const backupKeyNock = nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'backup' }))
.reply(200);
// wallet
const walletNock = nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200);
await wallets.generateWallet(params);
for (const scope of [bitgoKeyNock, userKeyNock, backupKeyNock, walletNock]) {
scope.done();
}
});
it('should generate custodial onchain wallet without passing m, n, keys, keySignatures', async () => {
const params = {
label: 'test wallet',
enterprise: 'myenterprise',
type: 'custodial',
};
const walletNock = nock(bgUrl)
.post('/api/v2/tbtc/wallet/add', function (body) {
body.type.should.equal('custodial');
should.not.exist(body.m);
should.not.exist(body.n);
should.not.exist(body.keys);
should.not.exist(body.keySignatures);
return true;
})
.reply(200, { id: '123', baseCoin: bitgo.coin('tbtc'), keys: ['123', '456', '789'] });
nock(bgUrl).get('/api/v2/tbtc/key/123').reply(200, { pub: 'bitgoPub', id: '789' });
nock(bgUrl).get('/api/v2/tbtc/key/456', _.matches({})).reply(200);
nock(bgUrl).get('/api/v2/tbtc/key/789').reply(200, { pub: 'backupPub', id: '789' });
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
assert.ok(response.encryptedWalletPassphrase === undefined);
assert.ok(response.wallet);
});
it('should generate hot onchain wallet', async () => {
const params = {
label: 'test wallet',
passphrase: 'multisig password',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
};
const walletNock = nock(bgUrl)
.post('/api/v2/tbtc/wallet/add', function (body) {
body.type.should.equal('hot');
return true;
})
.reply(200);
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' }))
.reply(200, { pub: 'bitgoPub' });
nock(bgUrl).post('/api/v2/tbtc/key', _.matches({})).reply(200);
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'backup' }))
.reply(200, { pub: 'backupPub' });
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase);
});
it('should generate hot onchain wallet without passing multisig type', async () => {
const params = {
label: 'test wallet',
passphrase: 'multisig password',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
};
const walletNock = nock(bgUrl)
.post('/api/v2/tbtc/wallet/add', function (body) {
body.type.should.equal('hot');
return true;
})
.reply(200);
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' }))
.reply(200, { pub: 'bitgoPub' });
nock(bgUrl).post('/api/v2/tbtc/key', _.matches({})).reply(200);
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'backup' }))
.reply(200, { pub: 'backupPub' });
const generateWalletSpy = sandbox.spy(wallets, 'generateWallet');
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
sinon.assert.calledOnce(generateWalletSpy);
assert.equal(generateWalletSpy.firstCall?.args[0]?.multisigType, sdk_core_1.multisigTypes.onchain);
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase);
});
it('should generate Go Account wallet', async () => {
const params = {
label: 'Go Account Wallet',
passphrase: 'go_account_password',
enterprise: 'enterprise-id',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
type: 'trading',
};
const keychainId = 'user_keychain_id';
// Mock keychain creation and upload
nock(bgUrl)
.post('/api/v2/ofc/key', function (body) {
body.should.have.property('encryptedPrv');
body.should.have.property('originalPasscodeEncryptionCode');
body.keyType.should.equal('independent');
body.source.should.equal('user');
return true;
})
.reply(200, { id: keychainId, pub: 'userPub', encryptedPrv: 'encryptedPrivateKey' });
// Mock wallet creation
const walletNock = nock(bgUrl)
.post('/api/v2/ofc/wallet/add', function (body) {
body.type.should.equal('trading');
body.m.should.equal(1);
body.n.should.equal(1);
body.keys.should.have.length(1);
body.keys[0].should.equal(keychainId);
return true;
})
.reply(200, { id: 'wallet123', keys: [keychainId] });
const response = await ofcWallets.generateWallet(params);
walletNock.isDone().should.be.true();
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.ok('userKeychain' in response);
});
});
describe('Generate TSS wallet:', function () {
const tsol = bitgo.coin('tsol');
const sandbox = sinon.createSandbox();
beforeEach(function () {
nock('https://bitgo.fakeurl')
.get(`/api/v2/tss/settings`)
.times(2)
.reply(200, {
coinSettings: {
eth: {
walletCreationSettings: {},
},
bsc: {
walletCreationSettings: {},
},
polygon: {
walletCreationSettings: {},
},
},
});
});
(0, mocha_1.afterEach)(function () {
nock.cleanAll();
sandbox.verifyAndRestore();
});
it('should create a new TSS wallet', async function () {
const stubbedKeychainsTriplet = {
userKeychain: {
id: '1',
pub: 'userPub',
type: 'independent',
source: 'user',
},
backupKeychain: {
id: '2',
pub: 'userPub',
type: 'independent',
source: 'backup',
},
bitgoKeychain: {
id: '3',
pub: 'userPub',
type: 'independent',
source: 'bitgo',
},
};
sandbox.stub(sdk_core_1.TssUtils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet);
const walletNock = nock('https://bitgo.fakeurl').post('/api/v2/tsol/wallet/add').reply(200);
const wallets = new sdk_core_1.Wallets(bitgo, tsol);
const params = {
label: 'tss wallet',
passphrase: 'tss password',
multisigType: 'tss',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
};
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase);
});
it('should create a new TSS wallet without passing multisig type', async function () {
const stubbedKeychainsTriplet = {
userKeychain: {
id: '1',
pub: 'userPub',
type: 'independent',
source: 'user',
},
backupKeychain: {
id: '2',
pub: 'userPub',
type: 'independent',
source: 'backup',
},
bitgoKeychain: {
id: '3',
pub: 'userPub',
type: 'independent',
source: 'bitgo',
},
};
sandbox.stub(sdk_core_1.TssUtils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet);
const walletNock = nock('https://bitgo.fakeurl')
.post('/api/v2/tsol/wallet/add', function (body) {
body.multisigType.should.equal(sdk_core_1.multisigTypes.tss);
return true;
})
.reply(200);
const wallets = new sdk_core_1.Wallets(bitgo, tsol);
const params = {
label: 'tss wallet',
passphrase: 'tss password',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
};
const generateWalletSpy = sandbox.spy(wallets, 'generateWallet');
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
sinon.assert.calledOnce(generateWalletSpy);
assert.equal(generateWalletSpy.firstCall?.args[0]?.multisigType, sdk_core_1.multisigTypes.tss);
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase);
});
it('should create a new TSS wallet without providing passcodeEncryptionCode', async function () {
const stubbedKeychainsTriplet = {
userKeychain: {
id: '1',
pub: 'userPub',
type: 'independent',
source: 'user',
},
backupKeychain: {
id: '2',
pub: 'userPub',
type: 'independent',
source: 'backup',
},
bitgoKeychain: {
id: '3',
pub: 'userPub',
type: 'independent',
source: 'bitgo',
},
};
sandbox.stub(sdk_core_1.TssUtils.prototype, 'createKeychains').resolves(stubbedKeychainsTriplet);
const walletNock = nock('https://bitgo.fakeurl').post('/api/v2/tsol/wallet/add').reply(200);
const wallets = new sdk_core_1.Wallets(bitgo, tsol);
const response = await wallets.generateWallet({
label: 'tss wallet',
passphrase: 'tss password',
multisigType: 'tss',
enterprise: 'enterprise',
});
walletNock.isDone().should.be.true();
assert.ok(response.wallet);
assert.ok(response.encryptedWalletPassphrase === undefined);
});
it('should fail to create TSS wallet with invalid inputs', async function () {
const tbtc = bitgo.coin('tbtc');
const params = {
label: 'my-cold-wallet',
passphrase: 'test123',
userKey: 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
coldDerivationSeed: '123',
};
const wallets = new sdk_core_1.Wallets(bitgo, tbtc);
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'bitgo' }))
.reply(200);
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ derivedFromParentWithSeed: params.coldDerivationSeed }))
.reply(200);
nock(bgUrl)
.post('/api/v2/tbtc/key', _.matches({ source: 'backup' }))
.reply(200);
nock(bgUrl).post('/api/v2/tbtc/wallet/add').reply(200);
// create a non tss wallet for coin that doesn't support tss even though multisigType is set to tss
await wallets.generateWallet({ ...params, multisigType: 'tss' });
const tsolWallets = new sdk_core_1.Wallets(bitgo, tsol);
await tsolWallets
.generateWallet({
label: 'tss cold wallet',
passphrase: 'passphrase',
userKey: 'user key',
multisigType: 'tss',
})
.should.be.rejectedWith('enterprise is required for TSS wallet');
await tsolWallets
.generateWallet({
label: 'tss cold wallet',
userKey: 'user key',
multisigType: 'tss',
enterprise: 'enterpriseId',
})
.should.be.rejectedWith('cannot generate TSS keys without passphrase');
});
it('should create a new TSS custodial wallet', async function () {
const keys = ['1', '2', '3'];
const walletParams = {
label: 'tss wallet',
multisigType: 'tss',
enterprise: 'enterprise',
type: 'custodial',
};
const walletNock = nock('https://bitgo.fakeurl')
.post('/api/v2/tsol/wallet/add')
.times(1)
.reply(200, { ...walletParams, keys });
const wallets = new sdk_core_1.Wallets(bitgo, tsol);
const res = await wallets.generateWallet(walletParams);
if (!(0, sdk_core_1.isWalletWithKeychains)(res)) {
throw new Error('wallet missing required keychains');
}
res.wallet.label().should.equal(walletParams.label);
should.equal(res.wallet.type(), walletParams.type);
res.wallet.toJSON().enterprise.should.equal(walletParams.enterprise);
res.wallet.multisigType().should.equal(walletParams.multisigType);
res.userKeychain.id.should.equal(keys[0]);
res.backupKeychain.id.should.equal(keys[1]);
res.bitgoKeychain.id.should.equal(keys[2]);
walletNock.isDone().should.be.true();
});
it('should create a new TSS SMC wallet', async function () {
const commonKeychain = 'longstring';
const seed = 'seed';
const keys = {
userKeychain: {
id: '1',
commonKeychain,
type: 'tss',
derivedFromParentWithSeed: seed,
source: 'user',
},
backupKeychain: {
id: '2',
commonKeychain,
type: 'tss',
derivedFromParentWithSeed: seed,
source: 'backup',
},
bitgoKeychain: {
id: '3',
commonKeychain,
type: 'tss',
source: 'bitgo',
},
};
const bitgoKeyNock = nock('https://bitgo.fakeurl').get('/api/v2/tsol/key/3').reply(200, keys.bitgoKeychain);
const userKeyExpectedBody = {
source: 'user',
keyType: 'tss',
commonKeychain,
derivedFromParentWithSeed: seed,
};
const userKeyNock = nock('https://bitgo.fakeurl')
.post('/api/v2/tsol/key', userKeyExpectedBody)
.reply(200, keys.userKeychain);
const backupKeyExpectedBody = {
source: 'backup',
keyType: 'tss',
commonKeychain,
derivedFromParentWithSeed: seed,
};
const backupKeyNock = nock('https://bitgo.fakeurl')
.post('/api/v2/tsol/key', backupKeyExpectedBody)
.reply(200, keys.backupKeychain);
const walletParams = {
label: 'tss wallet',
multisigType: 'tss',
enterprise: 'enterprise',
type: 'cold',
bitgoKeyId: keys.bitgoKeychain.id,
commonKeychain,
coldDerivationSeed: seed,
};
const walletNockExpected = {
label: walletParams.label,
m: 2,
n: 3,
keys: [keys.userKeychain.id, keys.backupKeychain.id, keys.bitgoKeychain.id],
type: walletParams.type,
multisigType: walletParams.multisigType,
enterprise: walletParams.enterprise,
walletVersion: undefined,
};
const walletNock = nock('https://bitgo.fakeurl')
.post('/api/v2/tsol/wallet/add', walletNockExpected)
.reply(200, { ...walletNockExpected, responseType: 'WalletWithKeychains' });
const wallets = new sdk_core_1.Wallets(bitgo, tsol);
const res = await wallets.generateWallet(walletParams);
if (!(0, sdk_core_1.isWalletWithKeychains)(res)) {
throw new Error('wallet missing required keychains');
}
res.responseType.should.equal('WalletWithKeychains');
res.wallet.label().should.equal(walletParams.label);
should.equal(res.wallet.type(), walletParams.type);
res.wallet.toJSON().enterprise.should.equal(walletParams.enterprise);
res.wallet.multisigType().should.equal(walletParams.multisigType);
res.userKeychain.should.deepEqual(keys.userKeychain);
res.backupKeychain.should.deepEqual(keys.backupKeychain);
res.bitgoKeychain.should.deepEqual(keys.bitgoKeychain);
bitgoKeyNock.isDone().should.be.true();
userKeyNock.isDone().should.be.true();
backupKeyNock.isDone().should.be.true();
walletNock.isDone().should.be.true();
});
it('should throw an error for TSS SMC wallet if the bitgoKeyId is not a bitgo key ', async function () {
const commonKeychain = 'longstring';
const seed = 'seed';
const keys = {
userKeychain: {
id: '1',
commonKeychain,
type: 'tss',
derivedFromParentWithSeed: seed,
source: 'user',
},
backupKeychain: {
id: '2',
commonKeychain,
type: 'tss',
derivedFromParentWithSeed: seed,
source: 'backup',
},
bitgoKeychain: {
id: '3',
commonKeychain,
type: 'tss',
source: 'bitgo',
},
};
const bitgoKeyNock = nock('https://bitgo.fakeurl').get('/api/v2/tsol/key/1').reply(200, keys.userKeychain);
const walletParams = {
label: 'tss wallet',
multisigType: 'tss',
enterprise: 'enterprise',
type: 'cold',
bitgoKeyId: keys.userKeychain.id,
commonKeychain,
coldDerivationSeed: seed,
};
const wallets = new sdk_core_1.Wallets(bitgo, tsol);
await wallets
.generateWallet(walletParams)
.should.be.rejectedWith('The provided bitgoKeyId is not a BitGo keychain');
bitgoKeyNock.isDone().should.be.true();
});
});
describe('Generate TSS MPCv2 wallet:', async function () {
const sandbox = sinon.createSandbox();
beforeEach(function () {
const tssSettings = {
coinSettings: {
eth: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
bsc: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
polygon: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
atom: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
tia: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
bera: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
arbeth: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
opeth: {
walletCreationSettings: {
multiSigTypeVersion: 'MPCv2',
coldMultiSigTypeVersion: 'MPCv2',
custodialMultiSigTypeVersion: 'MPCv2',
},
},
},
};
nock('https://bitgo.fakeurl').get(`/api/v2/tss/settings`).times(2).reply(200, tssSettings);
});
(0, mocha_1.afterEach)(function () {
nock.cleanAll();
sandbox.verifyAndRestore();
});
['hteth', 'tbsc', 'tpolygon', 'ttia', 'tatom', 'tbera', 'tarbeth', 'topeth'].forEach((coin) => {
it(`should create a new ${coin} TSS MPCv2 hot wallet`, async function () {
const testCoin = bitgo.coin(coin);
const stubbedKeychainsTriplet = {
userKeychain: {
id: '1',
commonKeychain: 'userPub',
type: 'tss',
source: 'user',
},
backupKeychain: {
id: '2',
commonKeychain: 'userPub',
type: 'tss',
source: 'backup',
},
bitgoKeychain: {
id: '3',
commonKeychain: 'userPub',
type: 'tss',
source: 'bitgo',
},
};
const stubCreateKeychains = sandbox
.stub(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.prototype, 'createKeychains')
.resolves(stubbedKeychainsTriplet);
const walletNock = nock('https://bitgo.fakeurl').post(`/api/v2/${coin}/wallet/add`).reply(200);
const wallets = new sdk_core_1.Wallets(bitgo, testCoin);
const params = {
label: 'tss wallet',
passphrase: 'tss password',
multisigType: 'tss',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
walletVersion: 3,
};
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
stubCreateKeychains.calledOnce.should.be.true();
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase);
});
it(`should create a new ${coin} TSS MPCv2 hot wallet without passing multisig type`, async function () {
const testCoin = bitgo.coin(coin);
const stubbedKeychainsTriplet = {
userKeychain: {
id: '1',
commonKeychain: 'userPub',
type: 'tss',
source: 'user',
},
backupKeychain: {
id: '2',
commonKeychain: 'userPub',
type: 'tss',
source: 'backup',
},
bitgoKeychain: {
id: '3',
commonKeychain: 'userPub',
type: 'tss',
source: 'bitgo',
},
};
const stubCreateKeychains = sandbox
.stub(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.prototype, 'createKeychains')
.resolves(stubbedKeychainsTriplet);
const walletNock = nock('https://bitgo.fakeurl')
.post(`/api/v2/${coin}/wallet/add`, function (body) {
body.multisigType.should.equal(sdk_core_1.multisigTypes.tss);
return true;
})
.reply(200);
const wallets = new sdk_core_1.Wallets(bitgo, testCoin);
const params = {
label: 'tss wallet',
passphrase: 'tss password',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
walletVersion: 3,
};
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
stubCreateKeychains.calledOnce.should.be.true();
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase);
});
it(`should create a new ${coin} TSS MPCv2 cold wallet`, async function () {
const testCoin = bitgo.coin(coin);
const bitgoKeyId = 'key123';
const commonKeychain = '0xabc';
const bitgoKeyNock = nock('https://bitgo.fakeurl')
.get(`/api/v2/${coin}/key/${bitgoKeyId}`)
.times(1)
.reply(200, {
id: 'key123',
pub: 'bitgoPub',
type: 'tss',
source: 'bitgo',
commonKeychain,
});
const userKeyNock = nock('https://bitgo.fakeurl')
.post(`/api/v2/${coin}/key`, {
source: 'user',
keyType: 'tss',
commonKeychain,
derivedFromParentWithSeed: '37',
isMPCv2: true,
})
.times(1)
.reply(200, {
id: 'userKey123',
pub: 'userPub',
type: 'tss',
source: 'user',
});
const backupKeyNock = nock('https://bitgo.fakeurl')
.post(`/api/v2/${coin}/key`, {
source: 'backup',
keyType: 'tss',
commonKeychain,
derivedFromParentWithSeed: '37',
isMPCv2: true,
})
.times(1)
.reply(200, {
id: 'backupKey123',
pub: 'backupPub',
type: 'tss',
source: 'backup',
});
const walletNock = nock('https://bitgo.fakeurl')
.post(`/api/v2/${coin}/wallet/add`, {
label: 'tss wallet',
m: 2,
n: 3,
keys: ['userKey123', 'backupKey123', 'key123'],
type: 'cold',
multisigType: 'tss',
enterprise: 'enterprise',
walletVersion: 5,
})
.reply(200);
const wallets = new sdk_core_1.Wallets(bitgo, testCoin);
const params = {
label: 'tss wallet',
multisigType: 'tss',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
walletVersion: 5,
type: 'cold',
bitgoKeyId: 'key123',
commonKeychain: '0xabc',
coldDerivationSeed: '37',
};
const response = (await wallets.generateWallet(params));
bitgoKeyNock.isDone().should.be.true();
userKeyNock.isDone().should.be.true();
backupKeyNock.isDone().should.be.true();
walletNock.isDone().should.be.true();
should.exist(response.wallet);
should.exist(response.userKeychain);
should.exist(response.backupKeychain);
should.exist(response.bitgoKeychain);
response.responseType.should.equal('WalletWithKeychains');
response.userKeychain.id.should.equal('userKey123');
response.backupKeychain.id.should.equal('backupKey123');
response.bitgoKeychain.id.should.equal('key123');
});
it(`should create a new ${coin} TSS MPCv2 custody wallet`, async function () {
const testCoin = bitgo.coin(coin);
const keys = ['userKey', 'backupKey', 'bitgoKey'];
const params = {
label: 'tss wallet',
passphrase: 'tss password',
multisigType: 'tss',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
walletVersion: 5,
type: 'custodial',
};
const walletNock = nock('https://bitgo.fakeurl')
.post(`/api/v2/${coin}/wallet/add`)
.times(1)
.reply(200, { ...params, keys });
const wallets = new sdk_core_1.Wallets(bitgo, testCoin);
const response = (await wallets.generateWallet(params));
walletNock.isDone().should.be.true();
should.exist(response.wallet);
should.exist(response.userKeychain);
should.exist(response.backupKeychain);
should.exist(response.bitgoKeychain);
response.responseType.should.equal('WalletWithKeychains');
response.userKeychain.id.should.equal(keys[0]);
response.backupKeychain.id.should.equal(keys[1]);
response.bitgoKeychain.id.should.equal(keys[2]);
});
});
it(`should create a new hteth TSS MPCv2 wallet with walletVersion 6`, async function () {
const testCoin = bitgo.coin('hteth');
const stubbedKeychainsTriplet = {
userKeychain: {
id: '1',
commonKeychain: 'userPub',
type: 'tss',
source: 'user',
},
backupKeychain: {
id: '2',
commonKeychain: 'userPub',
type: 'tss',
source: 'backup',
},
bitgoKeychain: {
id: '3',
commonKeychain: 'userPub',
type: 'tss',
source: 'bitgo',
},
};
const stubCreateKeychains = sandbox
.stub(sdk_core_1.ECDSAUtils.EcdsaMPCv2Utils.prototype, 'createKeychains')
.resolves(stubbedKeychainsTriplet);
const walletNock = nock('https://bitgo.fakeurl')
.post(`/api/v2/hteth/wallet/add`, (body) => {
body.walletVersion.should.equal(6);
return true;
})
.reply(200);
const wallets = new sdk_core_1.Wallets(bitgo, testCoin);
const params = {
label: 'tss wallet',
passphrase: 'tss password',
multisigType: 'tss',
enterprise: 'enterprise',
passcodeEncryptionCode: 'originalPasscodeEncryptionCode',
walletVersion: 6,
};
const response = await wallets.generateWallet(params);
walletNock.isDone().should.be.true();
stubCreateKeychains.calledOnce.should.be.true();
assert.ok(response.encryptedWalletPassphrase);
assert.ok(response.wallet);
assert.equal(bitgo.decrypt({ input: response.encryptedWalletPassphrase, password: params.passcodeEncryptionCode }), params.passphrase);
});
it(`should create a new MPCv2 wallet with version 5 if walletVersion passed is not 5 or 6`, async function () {
const testCoin = bitgo.coin('hteth');
const stubbedKeychainsTriplet = {
userKeychain: {
id: '1',
commonKeychain: 'userPub',
type: 'tss',
source: 'user',
},
backupKeychain: {
id: '2',
commonKeychain: 'userPub',
type: 'tss',
source: