Skip to content

Commit 737fedd

Browse files
committed
feat: add multisig generate wallet API for MBE
Ticket: WP-4378
1 parent dfd264d commit 737fedd

File tree

3 files changed

+161
-25
lines changed

3 files changed

+161
-25
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import {
2+
GenerateWalletOptions,
3+
promiseProps,
4+
RequestTracer,
5+
SupplementGenerateWalletOptions,
6+
Keychain,
7+
KeychainsTriplet,
8+
Wallet,
9+
WalletWithKeychains,
10+
AddKeychainOptions,
11+
} from '@bitgo/sdk-core';
12+
import { createEnclavedExpressClient } from './enclavedExpressClient';
13+
import _ from 'lodash';
14+
import { BitGoRequest } from '../types/request';
15+
16+
/**
17+
* This route is used to generate a multisig wallet when enclaved express is enabled
18+
*/
19+
export async function handleGenerateWalletOnPrem(req: BitGoRequest) {
20+
const bitgo = req.bitgo;
21+
const baseCoin = bitgo.coin(req.params.coin);
22+
23+
const enclavedExpressClient = createEnclavedExpressClient(req.params.coin);
24+
if (!enclavedExpressClient) {
25+
throw new Error(
26+
'Enclaved express client not configured - enclaved express features will be disabled',
27+
);
28+
}
29+
30+
const params = req.body as GenerateWalletOptions;
31+
const reqId = new RequestTracer();
32+
33+
// Assign the default multiSig type value based on the coin
34+
if (!params.multisigType) {
35+
params.multisigType = baseCoin.getDefaultMultisigType();
36+
}
37+
38+
if (typeof params.label !== 'string') {
39+
throw new Error('missing required string parameter label');
40+
}
41+
42+
const { label, enterprise } = params;
43+
44+
// Create wallet parameters with type assertion to allow 'onprem' subtype
45+
const walletParams = {
46+
label: label,
47+
m: 2,
48+
n: 3,
49+
keys: [],
50+
type: 'cold',
51+
subType: 'onprem',
52+
multisigType: 'onchain',
53+
} as unknown as SupplementGenerateWalletOptions; // TODO: Add onprem to the SDK subType and remove "unknown" type casting
54+
55+
if (!_.isUndefined(enterprise)) {
56+
if (!_.isString(enterprise)) {
57+
throw new Error('invalid enterprise argument, expecting string');
58+
}
59+
walletParams.enterprise = enterprise;
60+
}
61+
62+
const userKeychainPromise = async (): Promise<Keychain> => {
63+
const userKeychain = await enclavedExpressClient.createIndependentKeychain({
64+
source: 'user',
65+
coin: req.params.coin,
66+
type: 'independent',
67+
});
68+
const userKeychainParams: AddKeychainOptions = {
69+
pub: userKeychain.pub,
70+
keyType: userKeychain.type,
71+
source: userKeychain.source,
72+
reqId,
73+
};
74+
75+
const newUserKeychain = await baseCoin.keychains().add(userKeychainParams);
76+
return _.extend({}, newUserKeychain, userKeychain);
77+
};
78+
79+
const backupKeychainPromise = async (): Promise<Keychain> => {
80+
const backupKeychain = await enclavedExpressClient.createIndependentKeychain({
81+
source: 'backup',
82+
coin: req.params.coin,
83+
type: 'independent',
84+
});
85+
const backupKeychainParams: AddKeychainOptions = {
86+
pub: backupKeychain.pub,
87+
keyType: backupKeychain.type,
88+
source: backupKeychain.source,
89+
reqId,
90+
};
91+
92+
const newBackupKeychain = await baseCoin.keychains().add(backupKeychainParams);
93+
return _.extend({}, newBackupKeychain, backupKeychain);
94+
};
95+
96+
const { userKeychain, backupKeychain, bitgoKeychain }: KeychainsTriplet = await promiseProps({
97+
userKeychain: userKeychainPromise(),
98+
backupKeychain: backupKeychainPromise(),
99+
bitgoKeychain: baseCoin.keychains().createBitGo({
100+
enterprise: params.enterprise,
101+
reqId,
102+
isDistributedCustody: params.isDistributedCustody,
103+
}),
104+
});
105+
106+
walletParams.keys = [userKeychain.id, backupKeychain.id, bitgoKeychain.id];
107+
108+
const keychains = {
109+
userKeychain,
110+
backupKeychain,
111+
bitgoKeychain,
112+
};
113+
114+
const finalWalletParams = await baseCoin.supplementGenerateWallet(walletParams, keychains);
115+
116+
bitgo.setRequestTracer(reqId);
117+
const newWallet = await bitgo.post(baseCoin.url('/wallet/add')).send(finalWalletParams).result();
118+
119+
const result: WalletWithKeychains = {
120+
wallet: new Wallet(bitgo, baseCoin, newWallet),
121+
userKeychain: userKeychain,
122+
backupKeychain: backupKeychain,
123+
bitgoKeychain: bitgoKeychain,
124+
responseType: 'WalletWithKeychains',
125+
};
126+
127+
return { ...result, wallet: result.wallet.toJSON() };
128+
}

src/masterExpressApp.ts

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import https from 'https';
77
import http from 'http';
88
import superagent from 'superagent';
99
import { BitGo, BitGoOptions } from 'bitgo';
10+
import { BitGoBase } from '@bitgo/sdk-core';
11+
import { version } from 'bitgo/package.json';
1012

1113
import { MasterExpressConfig, config, isMasterExpressConfig } from './config';
14+
import { BitGoRequest } from './types/request';
1215
import {
1316
setupLogging,
1417
setupDebugNamespaces,
@@ -25,18 +28,11 @@ import bodyParser from 'body-parser';
2528
import { ProxyAgent } from 'proxy-agent';
2629
import { promiseWrapper } from './routes';
2730
import pjson from '../package.json';
28-
import { createEnclavedExpressClient } from './masterBitgoExpress/enclavedExpressClient';
31+
import { handleGenerateWalletOnPrem } from './masterBitgoExpress/generateWallet';
2932

3033
const debugLogger = debug('master-express:express');
31-
const { version } = require('bitgo/package.json');
3234
const BITGOEXPRESS_USER_AGENT = `BitGoExpress/${pjson.version} BitGoJS/${version}`;
3335

34-
// Add this interface before the startup function
35-
interface BitGoRequest extends express.Request {
36-
bitgo: BitGo;
37-
config: MasterExpressConfig;
38-
}
39-
4036
/**
4137
* Create a startup function which will be run upon server initialization
4238
*/
@@ -58,16 +54,6 @@ function isSSL(config: MasterExpressConfig): boolean {
5854
return Boolean((keyPath && crtPath) || (sslKey && sslCert));
5955
}
6056

61-
/**
62-
*
63-
* @param status
64-
* @param result
65-
* @param message
66-
*/
67-
function apiResponse(status: number, result: any, message: string): ApiResponseError {
68-
return new ApiResponseError(message, status, result);
69-
}
70-
7157
const expressJSONParser = bodyParser.json({ limit: '20mb' });
7258

7359
/**
@@ -120,7 +106,7 @@ function prepareBitGo(config: MasterExpressConfig) {
120106
: {}),
121107
};
122108

123-
(req as BitGoRequest).bitgo = new BitGo(bitgoConstructorParams);
109+
(req as BitGoRequest).bitgo = new BitGo(bitgoConstructorParams) as unknown as BitGoBase;
124110
(req as BitGoRequest).config = config;
125111

126112
next();
@@ -179,16 +165,18 @@ function setupMasterExpressRoutes(app: express.Application): void {
179165
// Setup common health check routes
180166
setupHealthCheckRoutes(app, 'master express');
181167

168+
const cfg = config() as MasterExpressConfig;
169+
console.log('SSL Enabled:', cfg.enableSSL);
170+
console.log('Enclaved Express URL:', cfg.enclavedExpressUrl);
171+
console.log('Certificate exists:', Boolean(cfg.enclavedExpressSSLCert));
172+
console.log('Certificate length:', cfg.enclavedExpressSSLCert.length);
173+
console.log('Certificate content:', cfg.enclavedExpressSSLCert);
174+
182175
// Add enclaved express ping route
183176
app.get('/ping/enclavedExpress', async (req, res) => {
184-
const cfg = config() as MasterExpressConfig;
185177
try {
186178
console.log('Pinging enclaved express');
187-
console.log('SSL Enabled:', cfg.enableSSL);
188-
console.log('Enclaved Express URL:', cfg.enclavedExpressUrl);
189-
console.log('Certificate exists:', Boolean(cfg.enclavedExpressSSLCert));
190-
console.log('Certificate length:', cfg.enclavedExpressSSLCert.length);
191-
console.log('Certificate content:', cfg.enclavedExpressSSLCert);
179+
192180
const response = await superagent
193181
.get(`${cfg.enclavedExpressUrl}/ping`)
194182
.ca(cfg.enclavedExpressSSLCert)
@@ -213,6 +201,14 @@ function setupMasterExpressRoutes(app: express.Application): void {
213201
}
214202
});
215203

204+
// TODO: Add api-ts to these new API routes
205+
app.post(
206+
'/api/:coin/wallet/generate',
207+
parseBody,
208+
prepareBitGo(config() as MasterExpressConfig),
209+
promiseWrapper(handleGenerateWalletOnPrem),
210+
);
211+
216212
// Add a catch-all for unsupported routes
217213
app.use('*', (_req, res) => {
218214
res.status(404).json({

src/types/request.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @prettier
3+
*/
4+
import express from 'express';
5+
import { BitGoBase } from '@bitgo/sdk-core';
6+
import { MasterExpressConfig } from '../types';
7+
8+
// Extended request type for BitGo Express
9+
export interface BitGoRequest extends express.Request {
10+
bitgo: BitGoBase;
11+
config: MasterExpressConfig;
12+
}

0 commit comments

Comments
 (0)