Skip to content

Commit 118423c

Browse files
feat(mbe): unit testing
1 parent 29be005 commit 118423c

File tree

4 files changed

+92
-63
lines changed

4 files changed

+92
-63
lines changed

src/__tests__/api/master/recoveryConsolidationsWallet.test.ts

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import nock from 'nock';
55
import { app as expressApp } from '../../../masterExpressApp';
66
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
77
import { BitGo } from 'bitgo';
8+
import { Wallet } from '@bitgo/sdk-core';
9+
import { Trx } from '@bitgo/sdk-coin-trx';
10+
import { Sol } from '@bitgo/sdk-coin-sol';
811
import { EnclavedExpressClient } from '../../../api/master/clients/enclavedExpressClient';
912

1013
describe('POST /api/:coin/wallet/recoveryConsolidations', () => {
@@ -40,14 +43,18 @@ describe('POST /api/:coin/wallet/recoveryConsolidations', () => {
4043
});
4144

4245
it('should handle TRON consolidation recovery', async () => {
43-
const mockTransactions = [{ txHex: 'unsigned-tx-1' }];
44-
const recoverConsolidationsStub = sinon.stub().resolves({ transactions: mockTransactions });
45-
const coinStub = { recoverConsolidations: recoverConsolidationsStub };
46-
sinon.stub(BitGo.prototype, 'coin').returns(coinStub);
46+
const mockTransactions = [{ txHex: 'unsigned-tx-1', serializedTx: 'serialized-unsigned-tx-1' }];
47+
48+
const recoverConsolidationsStub = sinon
49+
.stub(Trx.prototype, 'recoverConsolidations')
50+
.resolves({
51+
transactions: mockTransactions
52+
});
53+
4754
const recoveryMultisigStub = sinon.stub(EnclavedExpressClient.prototype, 'recoveryMultisig').resolves({ txHex: 'signed-tx' });
4855

4956
const response = await agent
50-
.post(`/api/ttrx/wallet/recoveryConsolidations`)
57+
.post(`/api/trx/wallet/recoveryConsolidations`)
5158
.set('Authorization', `Bearer ${accessToken}`)
5259
.send({
5360
userPub: 'user-xpub',
@@ -63,40 +70,42 @@ describe('POST /api/:coin/wallet/recoveryConsolidations', () => {
6370
sinon.assert.calledOnce(recoverConsolidationsStub);
6471
sinon.assert.calledOnce(recoveryMultisigStub);
6572
const callArgs = recoverConsolidationsStub.firstCall.args[0];
66-
callArgs.tokenContractAddress.should.equal('tron-token');
73+
callArgs.tokenContractAddress!.should.equal('tron-token');
6774
callArgs.userKey.should.equal('user-xpub');
6875
callArgs.backupKey.should.equal('backup-xpub');
6976
callArgs.bitgoKey.should.equal('bitgo-xpub');
7077
});
7178

72-
// it('should handle Solana consolidation recovery', async () => {
73-
// const mockTransactions = [{ txHex: 'unsigned-tx-1' }];
74-
// const recoverConsolidationsStub = sinon.stub().resolves({ transactions: mockTransactions });
75-
// const coinStub = { recoverConsolidations: recoverConsolidationsStub };
76-
// sinon.stub(BitGo.prototype, 'coin').returns(coinStub);
77-
// const recoveryMultisigStub = sinon.stub(EnclavedExpressClient.prototype, 'recoveryMultisig').resolves({ txHex: 'signed-tx' });
79+
it('should handle Solana consolidation recovery', async () => {
80+
const mockTransactions = [{ txHex: 'unsigned-tx-1', serializedTx: 'serialized-unsigned-tx-1' }];
81+
82+
const recoverConsolidationsStub = sinon
83+
.stub(Sol.prototype, 'recoverConsolidations')
84+
.resolves({
85+
transactions: mockTransactions
86+
});
7887

79-
// const response = await agent
80-
// .post(`/api/sol/wallet/recoveryConsolidations`)
81-
// .set('Authorization', `Bearer ${accessToken}`)
82-
// .send({
83-
// userPub: 'user-xpub',
84-
// backupPub: 'backup-xpub',
85-
// bitgoKey: 'bitgo-xpub',
86-
// durableNonces: {
87-
// publicKeys: ['sol-pubkey-1', 'sol-pubkey-2'],
88-
// secretKey: 'sol-secret',
89-
// },
90-
// tokenContractAddress: 'sol-token',
91-
// });
88+
const recoveryMultisigStub = sinon.stub(EnclavedExpressClient.prototype, 'recoveryMultisig').resolves({ txHex: 'signed-tx' });
9289

93-
// response.status.should.equal(200);
94-
// response.body.should.have.property('signedTxs');
95-
// sinon.assert.calledOnce(recoverConsolidationsStub);
96-
// sinon.assert.calledOnce(recoveryMultisigStub);
97-
// const callArgs = recoverConsolidationsStub.firstCall.args[0];
98-
// callArgs.durableNonces.should.have.property('publicKeys').which.is.an.Array();
99-
// callArgs.durableNonces.should.have.property('secretKey', 'sol-secret');
100-
// callArgs.tokenContractAddress.should.equal('sol-token');
101-
// });
90+
const response = await agent
91+
.post(`/api/sol/wallet/recoveryConsolidations`)
92+
.set('Authorization', `Bearer ${accessToken}`)
93+
.send({
94+
userPub: 'user-xpub',
95+
backupPub: 'backup-xpub',
96+
bitgoKey: 'bitgo-xpub',
97+
durableNonces: {
98+
publicKeys: ['sol-pubkey-1', 'sol-pubkey-2'],
99+
secretKey: 'sol-secret',
100+
}
101+
});
102+
103+
response.status.should.equal(200);
104+
response.body.should.have.property('signedTxs');
105+
sinon.assert.calledOnce(recoverConsolidationsStub);
106+
sinon.assert.calledOnce(recoveryMultisigStub);
107+
const callArgs = recoverConsolidationsStub.firstCall.args[0];
108+
callArgs.durableNonces.should.have.property('publicKeys').which.is.an.Array();
109+
callArgs.durableNonces.should.have.property('secretKey', 'sol-secret');
110+
});
102111
});
Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec';
2-
import { UnsupportedOperationError } from '../../../errors';
32
import logger from '../../../logger';
3+
import { isSolCoin, isTrxCoin } from '../../../shared/coinUtils';
4+
import { MPCSweepTxs, MPCTx, MPCTxs } from 'bitgo';
5+
import { RecoveryTransaction } from '@bitgo/sdk-coin-trx';
46

57
// Handler for recovery from receive addresses (consolidation sweeps)
68
export async function handleRecoveryConsolidationsOnPrem(
@@ -10,38 +12,48 @@ export async function handleRecoveryConsolidationsOnPrem(
1012
const coin = req.decoded.coin;
1113
const enclavedExpressClient = req.enclavedExpressClient;
1214

13-
const {
14-
userPub,
15-
backupPub,
16-
bitgoKey,
17-
} = req.decoded;
18-
19-
// Spread req.decoded and explicitly set userKey, backupKey, bitgoKey
20-
const consolidationParams = {
21-
...req.decoded,
22-
userKey: userPub,
23-
backupKey: backupPub,
24-
bitgoKey
25-
};
15+
const { userPub, backupPub, bitgoKey } = req.decoded;
2616

2717
const sdkCoin = bitgo.coin(coin);
18+
let txs: MPCTx[] | RecoveryTransaction[] = [];
19+
20+
logger.debug(`${isSolCoin(sdkCoin) ? 'Solana' : isTrxCoin(sdkCoin) ? 'Tron' : 'Unknown'} recovery`);
21+
22+
// 1. Build unsigned consolidations
23+
if (isSolCoin(sdkCoin)) {
24+
const result = await sdkCoin.recoverConsolidations({
25+
...req.decoded,
26+
userKey: userPub,
27+
backupKey: backupPub,
28+
bitgoKey,
29+
durableNonces: req.decoded.durableNonces!,
30+
});
2831

29-
if (typeof sdkCoin.recoverConsolidations !== 'function') {
30-
throw new UnsupportedOperationError('recoverConsolidations is not supported for this coin: ' + coin);
32+
txs = (result as MPCTxs).transactions || (result as MPCSweepTxs).txRequests || [];
33+
} else if (isTrxCoin(sdkCoin)) {
34+
const result = await sdkCoin.recoverConsolidations({
35+
...req.decoded,
36+
userKey: userPub,
37+
backupKey: backupPub,
38+
bitgoKey,
39+
});
40+
41+
txs = result.transactions;
3142
}
3243

33-
try {
34-
// 1. Build unsigned consolidations
35-
const { transactions } = await sdkCoin.recoverConsolidations(consolidationParams);
44+
console.log(txs);
45+
logger.debug(`Found ${txs.length} unsigned consolidation transactions`);
3646

37-
// 2. For each unsigned sweep, get it signed by EBE (using recoveryMultisig)
38-
const signedTxs = [];
39-
for (const unsignedSweep of transactions) {
47+
// 2. For each unsigned sweep, get it signed by EBE (using recoveryMultisig)
48+
const signedTxs = [];
49+
try {
50+
for (const tx of txs) {
4051
const signedTx = await enclavedExpressClient.recoveryMultisig({
4152
userPub,
4253
backupPub,
43-
unsignedSweepPrebuildTx: unsignedSweep,
44-
apiKey: ""
54+
unsignedSweepPrebuildTx: tx,
55+
apiKey: '',
56+
walletContractAddress: '',
4557
});
4658
signedTxs.push(signedTx);
4759
}
@@ -51,4 +63,4 @@ export async function handleRecoveryConsolidationsOnPrem(
5163
logger.error('Error during consolidation recovery:', err);
5264
throw err;
5365
}
54-
}
66+
}

src/api/master/routers/masterApiSpec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,12 @@ export type RecoveryWalletRequest = typeof RecoveryWalletRequest;
167167
// Request type for /recoveryConsolidations endpoint (supports MPC, TRX, and Solana)
168168
const RecoveryConsolidationsRequest = {
169169
userPub: t.string,
170-
backupPub: t.string,
170+
backupPub: t.string,
171171
bitgoKey: t.string,
172-
walletPassphrase: t.union([t.undefined, t.string]), // Optional (MPC/Sol)
172+
walletPassphrase: t.union([t.undefined, t.string]), // Optional (Sol)
173173
startingScanIndex: t.union([t.undefined, t.number]), // Optional
174174
endingScanIndex: t.union([t.undefined, t.number]), // Optional
175-
seed: t.union([t.undefined, t.string]), // Optional (MPC/Sol)
175+
seed: t.union([t.undefined, t.string]), // Optional (Sol)
176176
tokenContractAddress: t.union([t.undefined, t.string]), // Optional (TRX/Sol)
177177
// Solana-specific
178178
durableNonces: t.union([

src/shared/coinUtils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { FormattedOfflineVaultTxInfo, BackupKeyRecoveryTransansaction } from '@b
22
import { AbstractEthLikeNewCoins } from '@bitgo/abstract-eth';
33
import { CoinFamily } from '@bitgo/statics';
44
import { BaseCoin } from 'bitgo';
5-
import { AbstractUtxoCoin, Eos, Stx, Xtz } from 'bitgo/dist/types/src/v2/coins';
5+
import { AbstractUtxoCoin, Eos, Stx, Xtz, Sol, Trx } from 'bitgo/dist/types/src/v2/coins';
66

77
export function isEthLikeCoin(coin: BaseCoin): coin is AbstractEthLikeNewCoins {
88
const isEthPure = isFamily(coin, CoinFamily.ETH);
@@ -52,6 +52,14 @@ export function isXtzCoin(coin: BaseCoin): coin is Xtz {
5252
return isFamily(coin, CoinFamily.XTZ);
5353
}
5454

55+
export function isSolCoin(coin: BaseCoin): coin is Sol {
56+
return isFamily(coin, CoinFamily.SOL);
57+
}
58+
59+
export function isTrxCoin(coin: BaseCoin): coin is Trx {
60+
return isFamily(coin, CoinFamily.TRX);
61+
}
62+
5563
function isFamily(coin: BaseCoin, family: CoinFamily) {
5664
return Boolean(coin && coin.getFamily() === family);
5765
}

0 commit comments

Comments
 (0)