Skip to content

Commit c36ba04

Browse files
test: musig recovery eth for mbe tests
1 parent 0d70111 commit c36ba04

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import 'should';
2+
import sinon from 'sinon';
3+
4+
import { AbstractEthLikeNewCoins } from '@bitgo/abstract-eth';
5+
import nock from 'nock';
6+
import * as request from 'supertest';
7+
import { app as expressApp } from '../../../masterExpressApp';
8+
import { AppMode, MasterExpressConfig, TlsMode } from '../../../shared/types';
9+
import { data as ethRecoveryData } from '../../mocks/ethRecoveryMusigMockData';
10+
11+
describe('POST /api/:coin/wallet/recovery', () => {
12+
let agent: request.SuperAgentTest;
13+
const enclavedExpressUrl = 'http://enclaved.invalid';
14+
const coin = 'hteth';
15+
const accessToken = 'test-token';
16+
17+
before(() => {
18+
nock.disableNetConnect();
19+
nock.enableNetConnect('127.0.0.1');
20+
21+
const config: MasterExpressConfig = {
22+
appMode: AppMode.MASTER_EXPRESS,
23+
port: 0, // Let OS assign a free port
24+
bind: 'localhost',
25+
timeout: 60000,
26+
logFile: '',
27+
env: 'test',
28+
disableEnvCheck: true,
29+
authVersion: 2,
30+
enclavedExpressUrl: enclavedExpressUrl,
31+
enclavedExpressCert: 'dummy-cert',
32+
tlsMode: TlsMode.DISABLED,
33+
mtlsRequestCert: false,
34+
allowSelfSigned: true,
35+
};
36+
37+
const app = expressApp(config);
38+
agent = request.agent(app);
39+
});
40+
41+
afterEach(() => {
42+
nock.cleanAll();
43+
sinon.restore();
44+
});
45+
46+
it('should get the tx hex for broadcasting from eve on musig recovery ', async () => {
47+
// sdk call mock on mbe
48+
const recoverStub = sinon
49+
.stub(AbstractEthLikeNewCoins.prototype, 'recover')
50+
.resolves(ethRecoveryData.unsignedSweepPrebuildTx);
51+
52+
// the call to eve.recoverWallet(...)
53+
// that contains the calls to sdk.signTransaction
54+
const eveRecoverWalletNock = nock(enclavedExpressUrl)
55+
.post(`/api/${coin}/multisig/recovery`, {
56+
userPub: ethRecoveryData.userKey,
57+
backupPub: ethRecoveryData.backupKey,
58+
apiKey: 'etherscan-api-token',
59+
unsignedSweepPrebuildTx: ethRecoveryData.unsignedSweepPrebuildTx,
60+
coinSpecificParams: undefined,
61+
walletContractAddress: ethRecoveryData.walletContractAddress,
62+
})
63+
.reply(200, {
64+
txHex: ethRecoveryData.txHexFullSigned,
65+
});
66+
67+
// the call to our own master api express endpoint
68+
const response = await agent
69+
.post(`/api/${coin}/wallet/recovery`, (body) => {
70+
console.log('Nock received body:', body);
71+
return true;
72+
})
73+
.set('Authorization', `Bearer ${accessToken}`)
74+
.send({
75+
userPub: ethRecoveryData.userKey,
76+
backupPub: ethRecoveryData.backupKey,
77+
apiKey: 'etherscan-api-token',
78+
walletContractAddress: ethRecoveryData.walletContractAddress,
79+
recoveryDestinationAddress: ethRecoveryData.recoveryDestinationAddress,
80+
});
81+
82+
response.status.should.equal(200);
83+
response.body.should.have.property('txHex', ethRecoveryData.txHexFullSigned);
84+
sinon.assert.calledOnce(recoverStub);
85+
eveRecoverWalletNock.done();
86+
});
87+
88+
it('should fail when walletContractAddress (origin) not provided', async () => {
89+
const response = await agent
90+
.post(`/api/${coin}/wallet/recovery`)
91+
.set('Authorization', `Bearer ${accessToken}`)
92+
.send({
93+
userPub: ethRecoveryData.userKey,
94+
backupPub: ethRecoveryData.backupKey,
95+
apiKey: 'etherscan-api-token',
96+
walletContractAddress: undefined,
97+
recoveryDestinationAddress: ethRecoveryData.recoveryDestinationAddress,
98+
});
99+
100+
response.status.should.equal(400);
101+
response.body.should.have.property('error');
102+
response.body.error.should.match(/walletContractAddress/i);
103+
});
104+
it('should fail when recoveryDestinationAddress (destiny) not provided', async () => {
105+
const response = await agent
106+
.post(`/api/${coin}/wallet/recovery`)
107+
.set('Authorization', `Bearer ${accessToken}`)
108+
.send({
109+
userPub: ethRecoveryData.userKey,
110+
backupPub: ethRecoveryData.backupKey,
111+
apiKey: 'etherscan-api-token',
112+
walletContractAddress: ethRecoveryData.walletContractAddress,
113+
recoveryDestinationAddress: undefined,
114+
});
115+
116+
response.status.should.equal(400);
117+
response.body.should.have.property('error');
118+
response.body.error.should.match(/recoveryDestinationAddress/i);
119+
});
120+
it('should fail when userPub or backupPub not provided', async () => {
121+
const responseNoUserKey = await agent
122+
.post(`/api/${coin}/wallet/recovery`)
123+
.set('Authorization', `Bearer ${accessToken}`)
124+
.send({
125+
backupPub: ethRecoveryData.backupKey,
126+
apiKey: 'etherscan-api-token',
127+
walletContractAddress: ethRecoveryData.walletContractAddress,
128+
recoveryDestinationAddress: undefined,
129+
});
130+
131+
const responseNoBackupKey = await agent
132+
.post(`/api/${coin}/wallet/recovery`)
133+
.set('Authorization', `Bearer ${accessToken}`)
134+
.send({
135+
userPub: ethRecoveryData.userKey,
136+
apiKey: 'etherscan-api-token',
137+
walletContractAddress: ethRecoveryData.walletContractAddress,
138+
recoveryDestinationAddress: undefined,
139+
});
140+
141+
responseNoUserKey.status.should.equal(400);
142+
responseNoUserKey.body.should.have.property('error');
143+
responseNoUserKey.body.error.should.match(/userPub/i);
144+
145+
responseNoBackupKey.status.should.equal(400);
146+
responseNoBackupKey.body.should.have.property('error');
147+
responseNoBackupKey.body.error.should.match(/backupPub/i);
148+
});
149+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const walletContractAddress = '0x223fe2adcc8f28d8a46f72f7f355117d2727554d';
2+
3+
export const data = {
4+
walletContractAddress,
5+
userKey:
6+
'xpub661MyMwAqRbcFigezGWEYSbCPVuaUmvnp1u7iEpH9YsKU6uYQtPANvudjgAo82QRHXsUieMqKeB1xEj89VUKU1ugtmyAZ3xzNEbHPexxgKK',
7+
backupKey:
8+
'xpub661MyMwAqRbcGbCirzmQsUJT2eidt9tFLw2m77w6FiKco6TKu49CP3GkHF88xGCpvqkP93SYMAarfyWAn8UWevQtNT6pDo8xH7xmf6GqK6e',
9+
unsignedSweepPrebuildTx: {
10+
tx: 'f9012b808504a817c8008307a12094223fe2adcc8f28d8a46f72f7f355117d2727554d80b9010439125215000000000000000000000000e7d07af8e3e7472ea8391a3372ab98d04ac4df200000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000006851a693000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080',
11+
userKey:
12+
'xpub661MyMwAqRbcFigezGWEYSbCPVuaUmvnp1u7iEpH9YsKU6uYQtPANvudjgAo82QRHXsUieMqKeB1xEj89VUKU1ugtmyAZ3xzNEbHPexxgKK',
13+
backupKey:
14+
'xpub661MyMwAqRbcGbCirzmQsUJT2eidt9tFLw2m77w6FiKco6TKu49CP3GkHF88xGCpvqkP93SYMAarfyWAn8UWevQtNT6pDo8xH7xmf6GqK6e',
15+
coin: 'hteth',
16+
gasPrice: 20000000000,
17+
gasLimit: 500000,
18+
recipients: [
19+
{
20+
address: '0xe7d07af8e3e7472ea8391a3372ab98d04ac4df20',
21+
amount: '1000000000000000000',
22+
},
23+
],
24+
walletContractAddress,
25+
amount: '1000000000000000000',
26+
backupKeyNonce: 0,
27+
recipient: {
28+
address: '0xe7d07af8e3e7472ea8391a3372ab98d04ac4df20',
29+
amount: '1000000000000000000',
30+
},
31+
expireTime: 1750181523,
32+
contractSequenceId: '1',
33+
nextContractSequenceId: '1',
34+
},
35+
txHexFullSigned:
36+
'xpub661MyMwAqRbcGLBsaNVtc8bhB7dN8fzf3JTEKhviDUMDz11HkcHNXRSV6tk2jsqPhXnqLJPUHy8VMSjTr4hPFimRWdFk3eaLEM8VBUbZdQE',
37+
recoveryDestinationAddress: '0x927324f364a6fd1bf4648310a445b58063f5bb64',
38+
};

0 commit comments

Comments
 (0)