Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions modules/bitgo/test/v2/unit/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,87 @@ describe('V2 Wallet:', function () {
});
});

describe('Canton tests: ', () => {
let cantonWallet: Wallet;
const cantonBitgo = TestBitGo.decorate(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 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',
coinSpecific: {},
});

nock(bgUrl).get(`/api/v2/${cantonWallet.coin()}/key/${cantonWallet.keyIds()[2]}`).times(2).reply(200, {
id: '5935d59cf660764331bafcade1855fd7',
pub: 'GH1LV1e9FdqGe8U2c8PMEcma3fDeh1ktcGVBrD3AuFqx',
encryptedPrv:
'{"iv":"iIuWOHIOErEDdiJn6g46mg==","v":1,"iter":10000,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"Rzh7RRJksj0=","ct":"rcNICUfp9FakT53l+adB6XKzS1vNTc0Qq9jAtqnxA+ScssiS4Q0l3sgG/0gDy5DaZKtXryKBDUvGsi7b/fYaFCUpAoZn/VZTOhOUN/mo7ZHb4OhOXL29YPPkiryAq9Cr"}',
source: 'bitgo',
coinSpecific: {},
});
});

after(async function () {
nock.cleanAll();
});

it('Should build wallet initialization transactions correctly', async function () {
const txRequestNock = nock(bgUrl)
.post(`/api/v2/wallet/${cantonWallet.id()}/txrequests`)
.reply((url, body) => {
const bodyParams = body as any;
bodyParams.intent.intentType.should.equal('createAccount');
bodyParams.intent.recipients.length.should.equal(0);
return [
200,
{
apiVersion: 'full',
transactions: [
{
unsignedTx: {
serializedTxHex: 'fake transaction',
feeInfo: 'fake fee info',
},
},
],
},
];
});
await cantonWallet.sendWalletInitialization();
txRequestNock.isDone().should.equal(true);
});
});

describe('Solana tests: ', () => {
let solWallet: Wallet;
const passphrase = '#Bondiola1234';
Expand Down
5 changes: 5 additions & 0 deletions modules/sdk-coin-canton/src/canton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export class Canton extends BaseCoin {
return multisigTypes.tss;
}

/** inherited doc */
requiresWalletInitializationTransaction(): boolean {
return true;
}

getMPCAlgorithm(): MPCAlgorithm {
return 'eddsa';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class WalletInitTransaction extends BaseTransaction {
if (!this._preparedParty) {
throw new InvalidTransactionError('Empty transaction data');
}
return Buffer.from(this._preparedParty.multiHash);
return Buffer.from(this._preparedParty.multiHash, 'base64');
}

fromRawTransaction(rawTx: string): void {
Expand Down
8 changes: 8 additions & 0 deletions modules/sdk-core/src/bitgo/baseCoin/baseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,14 @@ export abstract class BaseCoin implements IBaseCoin {
return false;
}

/**
* Check whether a coin requires wallet initialization
* @returns {boolean}
*/
requiresWalletInitializationTransaction(): boolean {
return false;
}

/**
* Check whether a coin supports signing of Typed data
* @returns {boolean}
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,4 +604,5 @@ export interface IBaseCoin {
* @param {string} params.multiSigType - The type of multisig (e.g. 'onchain' or 'tss')
*/
assertIsValidKey({ publicKey, encryptedPrv, walletPassphrase, multiSigType }: AuditKeyParams): void;
requiresWalletInitializationTransaction(): boolean;
}
6 changes: 6 additions & 0 deletions modules/sdk-core/src/bitgo/wallet/iWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,11 @@ export type SendNFTResult = {
pendingApproval: PendingApprovalData;
};

export type WalletInitResult = {
success: PrebuildTransactionResult[];
failure: Error[];
};

export interface IWallet {
bitgo: BitGoBase;
baseCoin: IBaseCoin;
Expand Down Expand Up @@ -985,6 +990,7 @@ export interface IWallet {
buildTokenEnablements(params?: BuildTokenEnablementOptions): Promise<PrebuildTransactionResult[]>;
sendTokenEnablement(params?: PrebuildAndSignTransactionOptions): Promise<any>;
sendTokenEnablements(params?: BuildTokenEnablementOptions): Promise<any>;
sendWalletInitialization(params?: PrebuildTransactionOptions): Promise<WalletInitResult>;
signMessage(params: WalletSignMessageOptions): Promise<SignedMessage>;
buildSignMessageRequest(params: WalletSignMessageOptions): Promise<TxRequest>;
signTypedData(params: WalletSignTypedDataOptions): Promise<SignedMessage>;
Expand Down
53 changes: 53 additions & 0 deletions modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import {
WalletSignTypedDataOptions,
WalletType,
BuildTokenApprovalResponse,
WalletInitResult,
} from './iWallet';

const debug = require('debug')('bitgo:v2:wallet');
Expand Down Expand Up @@ -3319,6 +3320,48 @@ export class Wallet implements IWallet {
};
}

/**
* The chain canton, requires the wallet to be initialized by sending out a transaction
* to be onboarded onto the validator.
* Builds, Signs and sends a transaction that initializes the canton wallet
* @param params
*/
public async sendWalletInitialization(params: PrebuildAndSignTransactionOptions = {}): Promise<WalletInitResult> {
if (!this.baseCoin.requiresWalletInitializationTransaction()) {
throw new Error(`Wallet initialization is not required for ${this.baseCoin.getFullName()}`);
}
if (this._wallet.multisigType !== 'tss') {
throw new Error('Wallet initialization transaction is only supported for TSS wallets');
}
if (params.reqId) {
this.bitgo.setRequestTracer(params.reqId);
}
const buildParams: PrebuildTransactionOptions = _.pick(params, this.prebuildWhitelistedParams());
if (!buildParams.type) {
buildParams.type = 'createAccount';
}
const prebuildTx = await this.prebuildTransaction(buildParams);
const unsignedBuildWithOptions: PrebuildAndSignTransactionOptions = {
...params,
prebuildTx,
};
if (typeof params.prebuildTx === 'string' || params.prebuildTx?.buildParams?.type !== 'createAccount') {
throw new Error('Invalid build of wallet init');
}
const successfulTxs: PrebuildTransactionResult[] = [];
const failedTxs = new Array<Error>();
try {
const sendTx = await this.sendManyTxRequests(unsignedBuildWithOptions);
successfulTxs.push(sendTx);
} catch (e) {
failedTxs.push(e);
}
return {
success: successfulTxs,
failure: failedTxs,
};
}

/* MARK: TSS Helpers */

/**
Expand Down Expand Up @@ -3439,6 +3482,16 @@ export class Wallet implements IWallet {
params.preview
);
break;
case 'createAccount':
txRequest = await this.tssUtils!.prebuildTxWithIntent(
{
reqId,
intentType: 'createAccount',
},
apiVersion,
params.preview
);
break;
case 'customTx':
txRequest = await this.tssUtils!.prebuildTxWithIntent(
{
Expand Down