-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhandleSendMany.ts
More file actions
126 lines (108 loc) · 4.23 KB
/
Copy pathhandleSendMany.ts
File metadata and controls
126 lines (108 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { RequestTracer, PrebuildTransactionOptions, Memo, KeyIndices } from '@bitgo/sdk-core';
import { createEnclavedExpressClient } from './enclavedExpressClient';
import logger from '../logger';
import { MasterApiSpecRouteRequest } from './routers/masterApiSpec';
import { isMasterExpressConfig } from '../initConfig';
/**
* Defines the structure for a single recipient in a send-many transaction.
* This provides strong typing and autocompletion within the handler.
*/
interface Recipient {
address: string;
amount: string | number;
feeLimit?: string;
data?: string;
tokenName?: string;
tokenData?: any;
}
export async function handleSendMany(req: MasterApiSpecRouteRequest<'v1.wallet.sendMany', 'post'>) {
if (!isMasterExpressConfig(req.config)) {
throw new Error('Configuration must be in master express mode');
}
const enclavedExpressClient = createEnclavedExpressClient(req.config, req.params.coin);
if (!enclavedExpressClient) {
throw new Error('Please configure enclaved express configs to sign the transactions.');
}
const reqId = new RequestTracer();
const bitgo = req.bitgo;
const baseCoin = bitgo.coin(req.params.coin);
const params = req.decoded;
params.recipients = params.recipients as Recipient[];
const walletId = req.params.walletId;
const wallet = await baseCoin.wallets().get({ id: walletId, reqId });
if (!wallet) {
throw new Error(`Wallet ${walletId} not found`);
}
// TODO: uncomment when on-prem type is added to SDK
// if (wallet.type() !== 'cold' || wallet.subType() !== 'onPrem') {
// throw new Error('Wallet is not an on-prem wallet');
// }
const keyIdIndex = params.source === 'user' ? KeyIndices.USER : KeyIndices.BACKUP;
logger.info(`Key ID index: ${keyIdIndex}`);
logger.info(`Key IDs: ${JSON.stringify(wallet.keyIds(), null, 2)}`);
// Get the signing keychains
const signingKeychain = await baseCoin.keychains().get({
id: wallet.keyIds()[keyIdIndex],
});
if (!signingKeychain || !signingKeychain.pub) {
throw new Error(`Signing keychain for ${params.source} not found`);
}
if (params.pubkey && params.pubkey !== signingKeychain.pub) {
throw new Error(`Pub provided does not match the keychain on wallet for ${params.source}`);
}
logger.info(`Signing with ${params.source} keychain, pub: ${signingKeychain.pub}`);
logger.debug(`Signing keychain: ${JSON.stringify(signingKeychain, null, 2)}`);
try {
const prebuildParams: PrebuildTransactionOptions = {
...params,
// Convert memo string to Memo object if present
memo: params.memo ? ({ type: 'text', value: params.memo } as Memo) : undefined,
};
// First build the transaction with bitgo
const txPrebuilt = await wallet.prebuildTransaction({
...prebuildParams,
reqId,
});
// verify transaction prebuild
try {
const verified = await baseCoin.verifyTransaction({
txParams: { ...prebuildParams },
txPrebuild: txPrebuilt,
wallet,
verification: {},
reqId: reqId,
walletType: 'onchain',
});
if (!verified) {
throw new Error('Transaction prebuild failed local validation');
}
logger.debug('Transaction prebuild verified');
} catch (e) {
const err = e as Error;
logger.error('transaction prebuild failed local validation:', err.message);
logger.error('transaction prebuild:', JSON.stringify(txPrebuilt, null, 2));
logger.error(err);
}
logger.debug('Tx prebuild: %s', JSON.stringify(txPrebuilt, null, 2));
// Then sign it using the enclaved express client
const signedTx = await enclavedExpressClient.signMultisig({
txPrebuild: txPrebuilt,
source: params.source,
pub: signingKeychain.pub,
});
// Get extra prebuild parameters
const extraParams = await baseCoin.getExtraPrebuildParams({
...params,
wallet,
});
// Combine the signed transaction with extra parameters
const finalTxParams = { ...signedTx, ...extraParams };
// Submit the half signed transaction
const result = (await wallet.submitTransaction(finalTxParams, reqId)) as any;
return result;
} catch (error) {
const err = error as Error;
logger.error('Failed to send many: %s', err.message);
throw err;
}
}