Skip to content

Commit cd97ca3

Browse files
committed
chore: fix types for sendmany
Ticket: WP-4634
1 parent 38e0a70 commit cd97ca3

File tree

3 files changed

+61
-54
lines changed

3 files changed

+61
-54
lines changed

src/masterBitgoExpress/enclavedExpressClient.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import https from 'https';
33
import debug from 'debug';
44
import { MasterExpressConfig } from '../types';
55
import { TlsMode } from '../types';
6-
import { SignMultisigOptions } from '../types/masterApiTypes';
7-
import { SignedTransaction } from '@bitgo/sdk-core';
6+
import { SignedTransaction, TransactionPrebuild } from '@bitgo/sdk-core';
87

98
const debugLogger = debug('bitgo:express:enclavedExpressClient');
109

@@ -24,6 +23,12 @@ export interface IndependentKeychainResponse {
2423
coin: string;
2524
}
2625

26+
interface SignMultisigOptions {
27+
txPrebuild: TransactionPrebuild;
28+
source: 'user' | 'backup';
29+
pub: string;
30+
}
31+
2732
export class EnclavedExpressClient {
2833
private readonly baseUrl: string;
2934
private readonly enclavedExpressCert: string;
@@ -119,7 +124,7 @@ export class EnclavedExpressClient {
119124

120125
try {
121126
const res = await this.configureRequest(
122-
superagent.post(`${this.baseUrl}/api/${this.coin}/signMultisig`).type('json'),
127+
superagent.post(`${this.baseUrl}/api/${this.coin}/multisig/sign`).type('json'),
123128
).send(params);
124129

125130
return res.body;

src/masterBitgoExpress/handleSendMany.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@ import { RequestTracer, PrebuildTransactionOptions, Memo } from '@bitgo/sdk-core
22
import { BitGoRequest } from '../types/request';
33
import { createEnclavedExpressClient } from './enclavedExpressClient';
44
import logger from '../logger';
5-
import { SendManyRequest } from './routers/masterApiSpec';
6-
import { TypeOf } from 'io-ts';
5+
import { MasterApiSpecRouteRequest } from './routers/masterApiSpec';
76

8-
export async function handleSendMany(req: BitGoRequest) {
7+
/**
8+
* Defines the structure for a single recipient in a send-many transaction.
9+
* This provides strong typing and autocompletion within the handler.
10+
*/
11+
interface Recipient {
12+
address: string;
13+
amount: string | number;
14+
feeLimit?: string;
15+
data?: string;
16+
tokenName?: string;
17+
tokenData?: any;
18+
}
19+
20+
export async function handleSendMany(req: MasterApiSpecRouteRequest<'v1.wallet.sendMany', 'post'>) {
921
const enclavedExpressClient = createEnclavedExpressClient(req.config, req.params.coin);
1022
if (!enclavedExpressClient) {
1123
throw new Error('Please configure enclaved express configs to sign the transactions.');
@@ -14,13 +26,16 @@ export async function handleSendMany(req: BitGoRequest) {
1426
const bitgo = req.bitgo;
1527
const baseCoin = bitgo.coin(req.params.coin);
1628

17-
const params = req.body as TypeOf<typeof SendManyRequest>;
29+
const params = req.decoded;
30+
params.recipients = params.recipients as Recipient[];
31+
1832
const walletId = req.params.walletId;
1933
const wallet = await baseCoin.wallets().get({ id: walletId, reqId });
2034
if (!wallet) {
2135
throw new Error(`Wallet ${walletId} not found`);
2236
}
2337

38+
// @ts-ignore
2439
if (wallet.type() !== 'cold' || wallet.subType() !== 'onPrem') {
2540
throw new Error('Wallet is not an on-prem wallet');
2641
}
@@ -33,7 +48,7 @@ export async function handleSendMany(req: BitGoRequest) {
3348

3449
// Find the user keychain for signing
3550
const signingKeychain = signingKeychains.find((k) => k.source === params.source);
36-
if (!signingKeychain) {
51+
if (!signingKeychain || !signingKeychain.pub) {
3752
throw new Error(`Signing keychain for ${params.source} not found`);
3853
}
3954

src/masterBitgoExpress/routers/masterApiSpec.ts

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '@api-ts/typed-express-router';
1414
import { Response } from '@api-ts/response';
1515
import express from 'express';
16-
import { BitGoRequest, isBitGoRequest } from '../../types/request';
16+
import { BitGoRequest } from '../../types/request';
1717
import { MasterExpressConfig } from '../../config';
1818
import { handleGenerateWalletOnPrem } from '../generateWallet';
1919
import { prepareBitGo, responseHandler } from '../../shared/middleware';
@@ -44,50 +44,37 @@ const GenerateWalletRequest = {
4444
isDistributedCustody: t.union([t.undefined, t.boolean]),
4545
};
4646

47-
export const SendManyRequest = t.intersection([
48-
t.type({
49-
pubkey: t.string,
50-
source: t.union([t.literal('user'), t.literal('backup')]),
51-
recipients: t.array(
52-
t.type({
53-
address: t.string,
54-
amount: t.union([t.string, t.number]),
55-
feeLimit: t.union([t.undefined, t.string]),
56-
data: t.union([t.undefined, t.string]),
57-
tokenName: t.union([t.undefined, t.string]),
58-
tokenData: t.union([t.undefined, t.any]),
59-
}),
60-
),
61-
}),
62-
t.partial({
63-
numBlocks: t.number,
64-
feeRate: t.number,
65-
feeMultiplier: t.number,
66-
maxFeeRate: t.number,
67-
minConfirms: t.number,
68-
enforceMinConfirmsForChange: t.boolean,
69-
targetWalletUnspents: t.number,
70-
message: t.string,
71-
minValue: t.union([t.number, t.string]),
72-
maxValue: t.union([t.number, t.string]),
73-
sequenceId: t.string,
74-
lastLedgerSequence: t.number,
75-
ledgerSequenceDelta: t.number,
76-
gasPrice: t.number,
77-
noSplitChange: t.boolean,
78-
unspents: t.array(t.string),
79-
comment: t.string,
80-
otp: t.string,
81-
changeAddress: t.string,
82-
allowExternalChangeAddress: t.boolean,
83-
instant: t.boolean,
84-
memo: t.string,
85-
transferId: t.number,
86-
eip1559: t.any,
87-
gasLimit: t.number,
88-
custodianTransactionId: t.string,
89-
}),
90-
]);
47+
export const SendManyRequest = {
48+
pubkey: t.string,
49+
source: t.union([t.literal('user'), t.literal('backup')]),
50+
recipients: t.array(t.any),
51+
numBlocks: t.union([t.undefined, t.number]),
52+
feeRate: t.union([t.undefined, t.number]),
53+
feeMultiplier: t.union([t.undefined, t.number]),
54+
maxFeeRate: t.union([t.undefined, t.number]),
55+
minConfirms: t.union([t.undefined, t.number]),
56+
enforceMinConfirmsForChange: t.union([t.undefined, t.boolean]),
57+
targetWalletUnspents: t.union([t.undefined, t.number]),
58+
message: t.union([t.undefined, t.string]),
59+
minValue: t.union([t.undefined, t.union([t.number, t.string])]),
60+
maxValue: t.union([t.undefined, t.union([t.number, t.string])]),
61+
sequenceId: t.union([t.undefined, t.string]),
62+
lastLedgerSequence: t.union([t.undefined, t.number]),
63+
ledgerSequenceDelta: t.union([t.undefined, t.number]),
64+
gasPrice: t.union([t.undefined, t.number]),
65+
noSplitChange: t.union([t.undefined, t.boolean]),
66+
unspents: t.union([t.undefined, t.array(t.string)]),
67+
comment: t.union([t.undefined, t.string]),
68+
otp: t.union([t.undefined, t.string]),
69+
changeAddress: t.union([t.undefined, t.string]),
70+
allowExternalChangeAddress: t.union([t.undefined, t.boolean]),
71+
instant: t.union([t.undefined, t.boolean]),
72+
memo: t.union([t.undefined, t.string]),
73+
transferId: t.union([t.undefined, t.number]),
74+
eip1559: t.union([t.undefined, t.any]),
75+
gasLimit: t.union([t.undefined, t.number]),
76+
custodianTransactionId: t.union([t.undefined, t.string]),
77+
};
9178

9279
export const SendManyResponse: HttpResponse = {
9380
// TODO: Get type from public types repo / Wallet Platform
@@ -102,7 +89,7 @@ export const SendManyResponse: HttpResponse = {
10289
export const MasterApiSpec = apiSpec({
10390
'v1.wallet.generate': {
10491
post: httpRoute({
105-
method: 'POST',
92+
method: 'POST' as const,
10693
path: '/{coin}/wallet/generate',
10794
request: httpRequest({
10895
params: {

0 commit comments

Comments
 (0)