Skip to content

Commit f38a933

Browse files
feat: raise error on no recipients eth musig rec and some refactors
1 parent 2538178 commit f38a933

File tree

4 files changed

+76
-29
lines changed

4 files changed

+76
-29
lines changed

src/api/enclaved/handlers/recoveryMultisigTransaction.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { SignFinalOptions } from '@bitgo/abstract-eth';
2-
import { HalfSignedUtxoTransaction, MethodNotImplementedError } from 'bitgo';
2+
import { AbstractUtxoCoin } from '@bitgo/abstract-utxo';
3+
import { HalfSignedUtxoTransaction, MethodNotImplementedError, TransactionRecipient } from 'bitgo';
34
import { EnclavedApiSpecRouteRequest } from '../../../enclavedBitgoExpress/routers/enclavedApiSpec';
45
import logger from '../../../logger';
56
import {
@@ -9,12 +10,11 @@ import {
910
} from '../../../shared/coinUtils';
1011
import {
1112
addEthLikeRecoveryExtras,
12-
getDefaultMusigEthGasParams,
13+
DEFAULT_MUSIG_ETH_GAS_PARAMS,
1314
getReplayProtectionOptions,
1415
} from '../../../shared/recoveryUtils';
1516
import { SignedEthLikeRecoveryTx } from '../../../types/transaction';
1617
import { retrieveKmsPrvKey } from '../utils';
17-
import { AbstractUtxoCoin } from '@bitgo/abstract-utxo';
1818

1919
export async function recoveryMultisigTransaction(
2020
req: EnclavedApiSpecRouteRequest<'v1.multisig.recovery', 'post'>,
@@ -43,9 +43,13 @@ export async function recoveryMultisigTransaction(
4343
const walletKeys = unsignedSweepPrebuildTx.xpubxWithDerivationPath;
4444
const pubs = [walletKeys?.user?.xpub, walletKeys?.backup?.xpub, walletKeys?.bitgo?.xpub];
4545
const { gasPrice, gasLimit, maxFeePerGas, maxPriorityFeePerGas } =
46-
getDefaultMusigEthGasParams();
46+
DEFAULT_MUSIG_ETH_GAS_PARAMS;
4747

4848
try {
49+
checkIfNoRecipients({
50+
recipients: unsignedSweepPrebuildTx.recipients,
51+
coin: req.decoded.coin,
52+
});
4953
const halfSignedTxBase = await baseCoin.signTransaction({
5054
isLastSignature: false,
5155
prv: userPrv,
@@ -61,6 +65,7 @@ export async function recoveryMultisigTransaction(
6165
maxPriorityFeePerGas,
6266
},
6367
replayProtectionOptions: getReplayProtectionOptions(
68+
bitgo.env,
6469
unsignedSweepPrebuildTx.replayProtectionOptions,
6570
),
6671
txPrebuild: {
@@ -72,13 +77,15 @@ export async function recoveryMultisigTransaction(
7277
maxPriorityFeePerGas,
7378
},
7479
replayProtectionOptions: getReplayProtectionOptions(
80+
bitgo.env,
7581
unsignedSweepPrebuildTx.replayProtectionOptions,
7682
),
7783
},
7884
walletContractAddress,
7985
});
8086

8187
const halfSignedTx = addEthLikeRecoveryExtras({
88+
env: bitgo.env,
8289
signedTx: halfSignedTxBase as SignedEthLikeRecoveryTx,
8390
transaction: unsignedSweepPrebuildTx,
8491
isLastSignature: false,
@@ -108,6 +115,7 @@ export async function recoveryMultisigTransaction(
108115
maxPriorityFeePerGas,
109116
},
110117
replayProtectionOptions: getReplayProtectionOptions(
118+
bitgo.env,
111119
halfSignedTx?.replayProtectionOptions,
112120
),
113121
} as unknown as SignFinalOptions,
@@ -159,3 +167,17 @@ export async function recoveryMultisigTransaction(
159167
throw new MethodNotImplementedError('Unsupported coin type for recovery: ' + baseCoin);
160168
}
161169
}
170+
171+
function checkIfNoRecipients({
172+
recipients,
173+
coin,
174+
}: {
175+
recipients?: TransactionRecipient[];
176+
coin: string;
177+
}) {
178+
if (!recipients || recipients.length === 0) {
179+
const errorMsg = `Recovery tx for coin ${coin} must have at least one recipient.`;
180+
logger.error(errorMsg);
181+
throw new Error(errorMsg);
182+
}
183+
}

src/api/master/handlers/recoveryWallet.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import { BaseCoin, MethodNotImplementedError } from 'bitgo';
22

3-
import { AbstractUtxoCoin } from '@bitgo/abstract-utxo';
43
import { AbstractEthLikeNewCoins } from '@bitgo/abstract-eth';
4+
import { AbstractUtxoCoin } from '@bitgo/abstract-utxo';
55

66
import {
77
isEthLikeCoin,
88
isFormattedOfflineVaultTxInfo,
99
isUtxoCoin,
1010
} from '../../../shared/coinUtils';
1111
import {
12-
getDefaultMusigEthGasParams,
12+
DEFAULT_MUSIG_ETH_GAS_PARAMS,
1313
getReplayProtectionOptions,
1414
} from '../../../shared/recoveryUtils';
15-
import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec';
15+
import { EnvironmentName } from '../../../shared/types/index';
1616
import { EnclavedExpressClient } from '../clients/enclavedExpressClient';
17+
import { MasterApiSpecRouteRequest } from '../routers/masterApiSpec';
1718

1819
interface RecoveryParams {
1920
userKey: string;
@@ -37,10 +38,10 @@ async function handleEthLikeRecovery(
3738
commonRecoveryParams: RecoveryParams,
3839
enclavedExpressClient: any,
3940
params: EnclavedRecoveryParams,
41+
env: EnvironmentName,
4042
) {
4143
try {
42-
const { gasLimit, gasPrice, maxFeePerGas, maxPriorityFeePerGas } =
43-
getDefaultMusigEthGasParams();
44+
const { gasLimit, gasPrice, maxFeePerGas, maxPriorityFeePerGas } = DEFAULT_MUSIG_ETH_GAS_PARAMS;
4445
const unsignedSweepPrebuildTx = await (sdkCoin as AbstractEthLikeNewCoins).recover({
4546
...commonRecoveryParams,
4647
gasPrice,
@@ -49,7 +50,7 @@ async function handleEthLikeRecovery(
4950
maxFeePerGas,
5051
maxPriorityFeePerGas,
5152
},
52-
replayProtectionOptions: getReplayProtectionOptions(),
53+
replayProtectionOptions: getReplayProtectionOptions(env),
5354
});
5455

5556
const fullSignedRecoveryTx = await enclavedExpressClient.recoveryMultisig({
@@ -133,14 +134,20 @@ export async function handleRecoveryWalletOnPrem(
133134
}
134135

135136
if (isEthLikeCoin(sdkCoin)) {
136-
return handleEthLikeRecovery(sdkCoin, commonRecoveryParams, enclavedExpressClient, {
137-
userPub,
138-
backupPub,
139-
apiKey,
140-
unsignedSweepPrebuildTx: undefined,
141-
coinSpecificParams: undefined,
142-
walletContractAddress,
143-
});
137+
return handleEthLikeRecovery(
138+
sdkCoin,
139+
commonRecoveryParams,
140+
enclavedExpressClient,
141+
{
142+
userPub,
143+
backupPub,
144+
apiKey,
145+
unsignedSweepPrebuildTx: undefined,
146+
coinSpecificParams: undefined,
147+
walletContractAddress,
148+
},
149+
bitgo.env,
150+
);
144151
}
145152
if (!bitgoPub) {
146153
throw new Error('BitGo public key is required for recovery');

src/shared/recoveryUtils.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
import { type EnvironmentName } from '../shared/types/index';
12
import type { ReplayProtectionOptions, SignedEthLikeRecoveryTx } from '../types/transaction';
23

34
export function addEthLikeRecoveryExtras({
5+
env,
46
signedTx,
57
transaction,
68
isLastSignature,
79
replayProtectionOptions,
810
}: {
11+
env: EnvironmentName;
912
signedTx: SignedEthLikeRecoveryTx;
1013
transaction: any; // Same type as UnsignedSweepPrebuildTx
1114
isLastSignature: boolean;
@@ -50,28 +53,30 @@ export function addEthLikeRecoveryExtras({
5053
? transaction.backupKeyNonce
5154
: transaction.nextContractSequenceId;
5255
decoratedSignedTx.walletContractAddress = transaction.walletContractAddress;
53-
decoratedSignedTx.replayProtectionOptions = getReplayProtectionOptions(replayProtectionOptions);
56+
decoratedSignedTx.replayProtectionOptions = getReplayProtectionOptions(
57+
env,
58+
replayProtectionOptions,
59+
);
5460
}
5561

5662
return decoratedSignedTx;
5763
}
5864

5965
export function getReplayProtectionOptions(
66+
env: EnvironmentName,
6067
replayProtectionOptions: ReplayProtectionOptions | undefined = undefined,
6168
): ReplayProtectionOptions {
6269
return (
6370
replayProtectionOptions ?? {
64-
chain: 17000, // 1 if mainnet, 17000 if testnet
71+
chain: env === 'prod' ? 1 : 17000,
6572
hardfork: 'london',
6673
}
6774
);
6875
}
6976

70-
export function getDefaultMusigEthGasParams() {
71-
return {
72-
gasPrice: 20000000000,
73-
gasLimit: 200000,
74-
maxFeePerGas: 20000000000,
75-
maxPriorityFeePerGas: 10000000000,
76-
};
77-
}
77+
export const DEFAULT_MUSIG_ETH_GAS_PARAMS = {
78+
gasPrice: 20000000000,
79+
gasLimit: 200000,
80+
maxFeePerGas: 20000000000,
81+
maxPriorityFeePerGas: 10000000000,
82+
};

src/shared/types/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@ export enum AppMode {
88
MASTER_EXPRESS = 'master-express',
99
}
1010

11-
export type EnvironmentName = 'prod' | 'test' | 'staging' | 'dev' | 'local';
11+
export type EnvironmentName =
12+
| 'prod'
13+
| 'staging'
14+
| 'test'
15+
| 'dev'
16+
| 'local'
17+
| 'localNonSecure'
18+
| 'mock'
19+
| 'adminProd'
20+
| 'adminTest'
21+
| 'adminStaging'
22+
| 'adminDev'
23+
| 'custom'
24+
| 'branch';
1225

1326
// Common base configuration shared by both modes
1427
interface BaseConfig {

0 commit comments

Comments
 (0)