Skip to content

Commit 52e6a5a

Browse files
feat(mbe): update api spec
feat(mbp): swagger update
1 parent 2b79643 commit 52e6a5a

File tree

8 files changed

+281
-117
lines changed

8 files changed

+281
-117
lines changed

masterBitgoExpress.json

Lines changed: 159 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,171 @@
7373
}
7474
}
7575
},
76-
"202": {
77-
"description": "Accepted",
76+
"400": {
77+
"description": "Bad Request",
7878
"content": {
7979
"application/json": {
8080
"schema": {}
8181
}
8282
}
8383
},
84+
"500": {
85+
"description": "Internal Server Error",
86+
"content": {
87+
"application/json": {
88+
"schema": {
89+
"type": "object",
90+
"properties": {
91+
"error": {
92+
"type": "string"
93+
},
94+
"details": {
95+
"type": "string"
96+
}
97+
},
98+
"required": [
99+
"error",
100+
"details"
101+
]
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}
108+
},
109+
"/api/{coin}/wallet/{walletId}/consolidateunspents": {
110+
"post": {
111+
"parameters": [
112+
{
113+
"name": "walletId",
114+
"in": "path",
115+
"required": true,
116+
"schema": {
117+
"type": "string"
118+
}
119+
},
120+
{
121+
"name": "coin",
122+
"in": "path",
123+
"required": true,
124+
"schema": {
125+
"type": "string"
126+
}
127+
}
128+
],
129+
"requestBody": {
130+
"content": {
131+
"application/json": {
132+
"schema": {
133+
"type": "object",
134+
"properties": {
135+
"pubkey": {
136+
"type": "string"
137+
},
138+
"source": {
139+
"type": "string",
140+
"enum": [
141+
"user",
142+
"backup"
143+
]
144+
},
145+
"walletPassphrase": {
146+
"type": "string"
147+
},
148+
"feeRate": {
149+
"type": "number"
150+
},
151+
"maxFeeRate": {
152+
"type": "number"
153+
},
154+
"maxFeePercentage": {
155+
"type": "number"
156+
},
157+
"feeTxConfirmTarget": {
158+
"type": "number"
159+
},
160+
"bulk": {
161+
"type": "boolean"
162+
},
163+
"minValue": {
164+
"oneOf": [
165+
{
166+
"type": "string"
167+
},
168+
{
169+
"type": "number"
170+
}
171+
]
172+
},
173+
"maxValue": {
174+
"oneOf": [
175+
{
176+
"type": "string"
177+
},
178+
{
179+
"type": "number"
180+
}
181+
]
182+
},
183+
"minHeight": {
184+
"type": "number"
185+
},
186+
"minConfirms": {
187+
"type": "number"
188+
},
189+
"enforceMinConfirmsForChange": {
190+
"type": "boolean"
191+
},
192+
"limit": {
193+
"type": "number"
194+
},
195+
"numUnspentsToMake": {
196+
"type": "number"
197+
},
198+
"targetAddress": {
199+
"type": "string"
200+
},
201+
"txFormat": {
202+
"type": "string",
203+
"enum": [
204+
"legacy",
205+
"psbt",
206+
"psbt-lite"
207+
]
208+
}
209+
},
210+
"required": [
211+
"pubkey",
212+
"source"
213+
]
214+
}
215+
}
216+
}
217+
},
218+
"responses": {
219+
"200": {
220+
"description": "OK",
221+
"content": {
222+
"application/json": {
223+
"schema": {
224+
"type": "object",
225+
"properties": {
226+
"tx": {
227+
"type": "string"
228+
},
229+
"txid": {
230+
"type": "string"
231+
}
232+
},
233+
"required": [
234+
"tx",
235+
"txid"
236+
]
237+
}
238+
}
239+
}
240+
},
84241
"400": {
85242
"description": "Bad Request",
86243
"content": {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"test:watch": "mocha --require ts-node/register --watch 'src/**/__tests__/**/*.test.ts'",
1515
"test:coverage": "nyc mocha --require ts-node/register 'src/**/__tests__/**/*.test.ts'",
1616
"lint": "eslint --quiet .",
17+
"lint:fix": "eslint --quiet . --fix",
1718
"generate-test-ssl": "openssl req -x509 -newkey rsa:2048 -keyout test-ssl-key.pem -out test-ssl-cert.pem -days 365 -nodes -subj '/CN=localhost'",
1819
"generate:openapi:masterExpress": "npx @api-ts/openapi-generator --name @bitgo/master-bitgo-express ./src/api/master/routers/index.ts > masterBitgoExpress.json",
1920
"container:build": "podman build -t bitgo-onprem-express ."

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ describe('POST /api/:coin/wallet/:walletId/consolidateunspents', () => {
129129
const mockError = {
130130
error: 'Internal Server Error',
131131
name: 'ApiResponseError',
132-
details: 'There are too few unspents that meet the given parameters to consolidate (1 available).',
132+
details:
133+
'There are too few unspents that meet the given parameters to consolidate (1 available).',
133134
};
134135

135136
const consolidateUnspentsStub = sinon
@@ -188,4 +189,4 @@ describe('POST /api/:coin/wallet/:walletId/consolidateunspents', () => {
188189
walletGetNock.done();
189190
keychainGetNock.done();
190191
});
191-
});
192+
});

src/api/master/handlerUtils.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { BitGo, RequestTracer } from 'bitgo';
2+
import { EnclavedExpressClient } from './clients/enclavedExpressClient';
3+
4+
/**
5+
* Fetch wallet and signing keychain, with validation for source and pubkey.
6+
* Throws with a clear error if not found or mismatched.
7+
*/
8+
9+
export async function getWalletAndSigningKeychain({
10+
bitgo,
11+
coin,
12+
walletId,
13+
params,
14+
reqId,
15+
KeyIndices,
16+
}: {
17+
bitgo: BitGo;
18+
coin: string;
19+
walletId: string;
20+
params: { source: 'user' | 'backup'; pubkey?: string };
21+
reqId: RequestTracer;
22+
KeyIndices: { USER: number; BACKUP: number; BITGO: number };
23+
}) {
24+
const baseCoin = bitgo.coin(coin);
25+
26+
const wallet = await baseCoin.wallets().get({ id: walletId, reqId });
27+
28+
if (!wallet) {
29+
throw new Error(`Wallet ${walletId} not found`);
30+
}
31+
32+
const keyIdIndex = params.source === 'user' ? KeyIndices.USER : KeyIndices.BACKUP;
33+
const signingKeychain = await baseCoin.keychains().get({
34+
id: wallet.keyIds()[keyIdIndex],
35+
});
36+
37+
if (!signingKeychain || !signingKeychain.pub) {
38+
throw new Error(`Signing keychain for ${params.source} not found`);
39+
}
40+
41+
if (params.pubkey && params.pubkey !== signingKeychain.pub) {
42+
throw new Error(`Pub provided does not match the keychain on wallet for ${params.source}`);
43+
}
44+
45+
return { baseCoin, wallet, signingKeychain };
46+
}
47+
/**
48+
* Create a custom signing function that delegates to enclavedExpressClient.signMultisig.
49+
*/
50+
51+
export function makeCustomSigningFunction({
52+
enclavedExpressClient,
53+
source,
54+
pub,
55+
}: {
56+
enclavedExpressClient: EnclavedExpressClient;
57+
source: 'user' | 'backup';
58+
pub: string;
59+
}) {
60+
return async function customSigningFunction(signParams: any) {
61+
return enclavedExpressClient.signMultisig({
62+
txPrebuild: signParams.txPrebuild,
63+
source,
64+
pub,
65+
});
66+
};
67+
}

src/api/master/handlers/handleAccelerate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { RequestTracer, KeyIndices } from '@bitgo/sdk-core';
22
import logger from '../../../logger';
33
import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec';
4-
import { getWalletAndSigningKeychain, makeCustomSigningFunction } from '../../../shared/coinUtils';
4+
import { getWalletAndSigningKeychain, makeCustomSigningFunction } from '../handlerUtils';
55

66
export async function handleAccelerate(
77
req: MasterApiSpecRouteRequest<'v1.wallet.accelerate', 'post'>,
Lines changed: 19 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,34 @@
11
import { RequestTracer, KeyIndices } from '@bitgo/sdk-core';
22
import logger from '../../../logger';
33
import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec';
4+
import { getWalletAndSigningKeychain, makeCustomSigningFunction } from '../handlerUtils';
45

56
export async function handleConsolidateUnspents(
6-
req: MasterApiSpecRouteRequest<'v1.wallet.consolidateUnspents', 'post'>,
7+
req: MasterApiSpecRouteRequest<'v1.wallet.consolidateunspents', 'post'>,
78
) {
89
const enclavedExpressClient = req.enclavedExpressClient;
910
const reqId = new RequestTracer();
1011
const bitgo = req.bitgo;
11-
const baseCoin = bitgo.coin((req as any).params.coin);
12-
const params = (req as any).decoded;
13-
const walletId = (req as any).params.walletId;
14-
const wallet = await baseCoin.wallets().get({ id: walletId, reqId });
15-
16-
if (!wallet) {
17-
throw new Error(`Wallet ${walletId} not found`);
18-
}
19-
20-
// Get the signing keychain based on source
21-
const keyIdIndex = params.source === 'user' ? KeyIndices.USER : KeyIndices.BACKUP;
22-
const signingKeychain = await baseCoin.keychains().get({
23-
id: wallet.keyIds()[keyIdIndex],
12+
const params = req.decoded;
13+
const walletId = req.params.walletId;
14+
const coin = req.params.coin;
15+
16+
const { wallet, signingKeychain } = await getWalletAndSigningKeychain({
17+
bitgo,
18+
coin,
19+
walletId,
20+
params,
21+
reqId,
22+
KeyIndices,
2423
});
2524

26-
if (!signingKeychain || !signingKeychain.pub) {
27-
throw new Error(`Signing keychain for ${params.source} not found`);
28-
}
29-
30-
if (params.pubkey && params.pubkey !== signingKeychain.pub) {
31-
throw new Error(`Pub provided does not match the keychain on wallet for ${params.source}`);
32-
}
33-
3425
try {
3526
// Create custom signing function that delegates to EBE
36-
const customSigningFunction = async (signParams: any) => {
37-
const signedTx = await enclavedExpressClient.signMultisig({
38-
txPrebuild: signParams.txPrebuild,
39-
source: params.source,
40-
pub: signingKeychain.pub!,
41-
});
42-
return signedTx;
43-
};
27+
const customSigningFunction = makeCustomSigningFunction({
28+
enclavedExpressClient,
29+
source: params.source,
30+
pub: signingKeychain.pub!,
31+
});
4432

4533
// Prepare consolidation parameters
4634
const consolidationParams = {
@@ -57,4 +45,4 @@ export async function handleConsolidateUnspents(
5745
logger.error('Failed to consolidate unspents: %s', err.message);
5846
throw err;
5947
}
60-
}
48+
}

0 commit comments

Comments
 (0)