Skip to content

Commit b4b8112

Browse files
feat(root): enable passing apiKey for recovery on eth likes
Ticket: WP-5348
1 parent abc39d4 commit b4b8112

File tree

15 files changed

+268
-95
lines changed

15 files changed

+268
-95
lines changed

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 94 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ export type RecoverOptions = {
253253
intendedChain?: string;
254254
common?: EthLikeCommon.default;
255255
derivationSeed?: string;
256+
apiKey?: string; // optional API key to use instead of the one from the environment
256257
} & TSSRecoverOptions;
257258

258259
export type GetBatchExecutionInfoRT = {
@@ -529,15 +530,19 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
529530
/**
530531
* Query explorer for the balance of an address
531532
* @param {String} address - the ETHLike address
533+
* @param {String} apiKey - optional API key to use instead of the one from the environment
532534
* @returns {BigNumber} address balance
533535
*/
534-
async queryAddressBalance(address: string): Promise<any> {
535-
const result = await this.recoveryBlockchainExplorerQuery({
536-
chainid: this.getChainId().toString(),
537-
module: 'account',
538-
action: 'balance',
539-
address: address,
540-
});
536+
async queryAddressBalance(address: string, apiKey?: string): Promise<any> {
537+
const result = await this.recoveryBlockchainExplorerQuery(
538+
{
539+
chainid: this.getChainId().toString(),
540+
module: 'account',
541+
action: 'balance',
542+
address: address,
543+
},
544+
apiKey
545+
);
541546
// throw if the result does not exist or the result is not a valid number
542547
if (!result || !result.result || isNaN(result.result)) {
543548
throw new Error(`Could not obtain address balance for ${address} from the explorer, got: ${result.result}`);
@@ -627,21 +632,25 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
627632
/**
628633
* Queries the contract (via explorer API) for the next sequence ID
629634
* @param {String} address - address of the contract
635+
* @param {String} apiKey - optional API key to use instead of the one from the environment
630636
* @returns {Promise<Number>} sequence ID
631637
*/
632-
async querySequenceId(address: string): Promise<number> {
638+
async querySequenceId(address: string, apiKey?: string): Promise<number> {
633639
// Get sequence ID using contract call
634640
const sequenceIdMethodSignature = optionalDeps.ethAbi.methodID('getNextSequenceId', []);
635641
const sequenceIdArgs = optionalDeps.ethAbi.rawEncode([], []);
636642
const sequenceIdData = Buffer.concat([sequenceIdMethodSignature, sequenceIdArgs]).toString('hex');
637-
const result = await this.recoveryBlockchainExplorerQuery({
638-
chainid: this.getChainId().toString(),
639-
module: 'proxy',
640-
action: 'eth_call',
641-
to: address,
642-
data: sequenceIdData,
643-
tag: 'latest',
644-
});
643+
const result = await this.recoveryBlockchainExplorerQuery(
644+
{
645+
chainid: this.getChainId().toString(),
646+
module: 'proxy',
647+
action: 'eth_call',
648+
to: address,
649+
data: sequenceIdData,
650+
tag: 'latest',
651+
},
652+
apiKey
653+
);
645654
if (!result || !result.result) {
646655
throw new Error('Could not obtain sequence ID from explorer, got: ' + result.result);
647656
}
@@ -837,18 +846,22 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
837846
/**
838847
* Queries public block explorer to get the next ETHLike coin's nonce that should be used for the given ETH address
839848
* @param {string} address
849+
* @param {string} apiKey - optional API key to use instead of the one from the environment
840850
* @returns {Promise<number>}
841851
*/
842-
async getAddressNonce(address: string): Promise<number> {
852+
async getAddressNonce(address: string, apiKey?: string): Promise<number> {
843853
// Get nonce for backup key (should be 0)
844854
let nonce = 0;
845855

846-
const result = await this.recoveryBlockchainExplorerQuery({
847-
chainid: this.getChainId().toString(),
848-
module: 'account',
849-
action: 'txlist',
850-
address,
851-
});
856+
const result = await this.recoveryBlockchainExplorerQuery(
857+
{
858+
chainid: this.getChainId().toString(),
859+
module: 'account',
860+
action: 'txlist',
861+
address,
862+
},
863+
apiKey
864+
);
852865
if (!result || !Array.isArray(result.result)) {
853866
throw new Error('Unable to find next nonce from Etherscan, got: ' + JSON.stringify(result));
854867
}
@@ -1630,24 +1643,32 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
16301643
* Query explorer for the balance of an address for a token
16311644
* @param {string} tokenContractAddress - address where the token smart contract is hosted
16321645
* @param {string} walletContractAddress - address of the wallet
1646+
* @param {string} apiKey - optional API key to use instead of the one from the environment
16331647
* @returns {BigNumber} token balaance in base units
16341648
*/
1635-
async queryAddressTokenBalance(tokenContractAddress: string, walletContractAddress: string): Promise<any> {
1649+
async queryAddressTokenBalance(
1650+
tokenContractAddress: string,
1651+
walletContractAddress: string,
1652+
apiKey?: string
1653+
): Promise<any> {
16361654
if (!optionalDeps.ethUtil.isValidAddress(tokenContractAddress)) {
16371655
throw new Error('cannot get balance for invalid token address');
16381656
}
16391657
if (!optionalDeps.ethUtil.isValidAddress(walletContractAddress)) {
16401658
throw new Error('cannot get token balance for invalid wallet address');
16411659
}
16421660

1643-
const result = await this.recoveryBlockchainExplorerQuery({
1644-
chainid: this.getChainId().toString(),
1645-
module: 'account',
1646-
action: 'tokenbalance',
1647-
contractaddress: tokenContractAddress,
1648-
address: walletContractAddress,
1649-
tag: 'latest',
1650-
});
1661+
const result = await this.recoveryBlockchainExplorerQuery(
1662+
{
1663+
chainid: this.getChainId().toString(),
1664+
module: 'account',
1665+
action: 'tokenbalance',
1666+
contractaddress: tokenContractAddress,
1667+
address: walletContractAddress,
1668+
tag: 'latest',
1669+
},
1670+
apiKey
1671+
);
16511672
// throw if the result does not exist or the result is not a valid number
16521673
if (!result || !result.result || isNaN(result.result)) {
16531674
throw new Error(
@@ -2164,8 +2185,8 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
21642185
}
21652186

21662187
private async buildTssRecoveryTxn(baseAddress: string, gasPrice: any, gasLimit: any, params: RecoverOptions) {
2167-
const nonce = await this.getAddressNonce(baseAddress);
2168-
const txAmount = await this.validateBalanceAndGetTxAmount(baseAddress, gasPrice, gasLimit);
2188+
const nonce = await this.getAddressNonce(baseAddress, params.apiKey);
2189+
const txAmount = await this.validateBalanceAndGetTxAmount(baseAddress, gasPrice, gasLimit, params.apiKey);
21692190
const recipients = [
21702191
{
21712192
address: params.recoveryDestination,
@@ -2194,8 +2215,8 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
21942215
return { txInfo, tx, nonce };
21952216
}
21962217

2197-
async validateBalanceAndGetTxAmount(baseAddress: string, gasPrice: BN, gasLimit: BN) {
2198-
const baseAddressBalance = await this.queryAddressBalance(baseAddress);
2218+
async validateBalanceAndGetTxAmount(baseAddress: string, gasPrice: BN, gasLimit: BN, apiKey?: string) {
2219+
const baseAddressBalance = await this.queryAddressBalance(baseAddress, apiKey);
21992220
const totalGasNeeded = gasPrice.mul(gasLimit);
22002221
const weiToGwei = new BN(10 ** 9);
22012222
if (baseAddressBalance.lt(totalGasNeeded)) {
@@ -2209,7 +2230,13 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
22092230
return txAmount;
22102231
}
22112232

2212-
async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<any> {
2233+
/**
2234+
* Make a query to blockchain explorer for information such as balance, token balance, solidity calls
2235+
* @param query {Object} key-value pairs of parameters to append after /api
2236+
* @param apiKey {string} optional API key to use instead of the one from the environment
2237+
* @returns {Object} response from the blockchain explorer
2238+
*/
2239+
async recoveryBlockchainExplorerQuery(query: Record<string, string>, apiKey?: string): Promise<any> {
22132240
throw new Error('method not implemented');
22142241
}
22152242

@@ -2782,14 +2809,19 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27822809

27832810
/**
27842811
* Fetch the gas price from the explorer
2812+
* @param {string} wrongChainCoin - the coin that we're getting gas price for
2813+
* @param {string} apiKey - optional API key to use instead of the one from the environment
27852814
*/
2786-
async getGasPriceFromExternalAPI(wrongChainCoin: string): Promise<BN> {
2815+
async getGasPriceFromExternalAPI(wrongChainCoin: string, apiKey?: string): Promise<BN> {
27872816
try {
2788-
const res = await this.recoveryBlockchainExplorerQuery({
2789-
chainid: this.getChainId().toString(),
2790-
module: 'proxy',
2791-
action: 'eth_gasPrice',
2792-
});
2817+
const res = await this.recoveryBlockchainExplorerQuery(
2818+
{
2819+
chainid: this.getChainId().toString(),
2820+
module: 'proxy',
2821+
action: 'eth_gasPrice',
2822+
},
2823+
apiKey
2824+
);
27932825
const gasPrice = new BN(res.result.slice(2), 16);
27942826
console.log(` Got gas price: ${gasPrice}`);
27952827
return gasPrice;
@@ -2804,17 +2836,27 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28042836
* @param from
28052837
* @param to
28062838
* @param data
2839+
* @param {string} apiKey - optional API key to use instead of the one from the environment
28072840
*/
2808-
async getGasLimitFromExternalAPI(intendedChain: string, from: string, to: string, data: string): Promise<BN> {
2841+
async getGasLimitFromExternalAPI(
2842+
intendedChain: string,
2843+
from: string,
2844+
to: string,
2845+
data: string,
2846+
apiKey?: string
2847+
): Promise<BN> {
28092848
try {
2810-
const res = await this.recoveryBlockchainExplorerQuery({
2811-
chainid: this.getChainId().toString(),
2812-
module: 'proxy',
2813-
action: 'eth_estimateGas',
2814-
from,
2815-
to,
2816-
data,
2817-
});
2849+
const res = await this.recoveryBlockchainExplorerQuery(
2850+
{
2851+
chainid: this.getChainId().toString(),
2852+
module: 'proxy',
2853+
action: 'eth_estimateGas',
2854+
from,
2855+
to,
2856+
data,
2857+
},
2858+
apiKey
2859+
);
28182860
const gasLimit = new BN(res.result.slice(2), 16);
28192861
console.log(`Got gas limit: ${gasLimit}`);
28202862
return gasLimit;

modules/sdk-coin-arbeth/src/arbeth.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@ export class Arbeth extends AbstractEthLikeNewCoins {
3535
/**
3636
* Make a query to Arbiscan for information such as balance, token balance, solidity calls
3737
* @param {Object} query key-value pairs of parameters to append after /api
38+
* @param {string} apiKey optional API key to use instead of the one from the environment
3839
* @returns {Promise<Object>} response from Arbiscan
3940
*/
40-
async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<Record<string, unknown>> {
41-
const apiToken = common.Environments[this.bitgo.getEnv()].arbiscanApiToken;
41+
async recoveryBlockchainExplorerQuery(
42+
query: Record<string, string>,
43+
apiKey?: string
44+
): Promise<Record<string, unknown>> {
45+
const apiToken = apiKey || common.Environments[this.bitgo.getEnv()].arbiscanApiToken;
4246
const explorerUrl = common.Environments[this.bitgo.getEnv()].arbiscanBaseUrl;
4347
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken);
4448
}

modules/sdk-coin-avaxc/src/avaxc.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,10 @@ export class AvaxC extends AbstractEthLikeNewCoins {
307307
/**
308308
* Make a query to avax.network for information such as balance, token balance, solidity calls
309309
* @param {Object} query — key-value pairs of parameters to append after /api
310+
* @param {string} apiKey - optional API key to use instead of the one from the environment
310311
* @returns {Promise<Object>} response from avax.network
311312
*/
312-
async recoveryBlockchainExplorerQuery(query: Record<string, any>): Promise<any> {
313+
async recoveryBlockchainExplorerQuery(query: Record<string, any>, apiKey?: string): Promise<any> {
313314
const response = await request
314315
.post(common.Environments[this.bitgo.getEnv()].avaxcNetworkBaseUrl + '/ext/bc/C/rpc')
315316
.send(query);

modules/sdk-coin-bsc/src/bsc.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,17 @@ export class Bsc extends AbstractEthLikeNewCoins {
4040
return 'ecdsa';
4141
}
4242

43-
async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<Record<string, unknown>> {
44-
const apiToken = common.Environments[this.bitgo.getEnv()].bscscanApiToken;
43+
/**
44+
* Make a query to BSCScan for information such as balance, token balance, solidity calls
45+
* @param {Object} query key-value pairs of parameters to append after /api
46+
* @param {string} apiKey optional API key to use instead of the one from the environment
47+
* @returns {Promise<Object>} response from BSCScan
48+
*/
49+
async recoveryBlockchainExplorerQuery(
50+
query: Record<string, string>,
51+
apiKey?: string
52+
): Promise<Record<string, unknown>> {
53+
const apiToken = apiKey || common.Environments[this.bitgo.getEnv()].bscscanApiToken;
4554
const explorerUrl = common.Environments[this.bitgo.getEnv()].bscscanBaseUrl;
4655
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken);
4756
}

modules/sdk-coin-coredao/src/coredao.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,17 @@ export class Coredao extends AbstractEthLikeNewCoins {
3939
return 'ecdsa';
4040
}
4141

42-
async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<Record<string, unknown>> {
43-
const apiToken = common.Environments[this.bitgo.getEnv()].coredaoExplorerApiToken;
42+
/**
43+
* Make a query to Coredao explorer for information such as balance, token balance, solidity calls
44+
* @param {Object} query key-value pairs of parameters to append after /api
45+
* @param {string} apiKey optional API key to use instead of the one from the environment
46+
* @returns {Promise<Object>} response from Coredao explorer
47+
*/
48+
async recoveryBlockchainExplorerQuery(
49+
query: Record<string, string>,
50+
apiKey?: string
51+
): Promise<Record<string, unknown>> {
52+
const apiToken = apiKey || common.Environments[this.bitgo.getEnv()].coredaoExplorerApiToken;
4453
const explorerUrl = common.Environments[this.bitgo.getEnv()].coredaoExplorerBaseUrl;
4554
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken);
4655
}

modules/sdk-coin-eth/src/erc20Token.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,15 @@ export class Erc20Token extends Eth {
207207
// Get nonce for backup key (should be 0)
208208
let backupKeyNonce = 0;
209209

210-
const result = await this.recoveryBlockchainExplorerQuery({
211-
chainid: this.getChainId().toString(),
212-
module: 'account',
213-
action: 'txlist',
214-
address: backupKeyAddress,
215-
});
210+
const result = await this.recoveryBlockchainExplorerQuery(
211+
{
212+
chainid: this.getChainId().toString(),
213+
module: 'account',
214+
action: 'txlist',
215+
address: backupKeyAddress,
216+
},
217+
params.apiKey
218+
);
216219
const backupKeyTxList = result.result;
217220
if (backupKeyTxList.length > 0) {
218221
// Calculate last nonce used
@@ -221,7 +224,7 @@ export class Erc20Token extends Eth {
221224
}
222225

223226
// get balance of backup key and make sure we can afford gas
224-
const backupKeyBalance = await this.queryAddressBalance(backupKeyAddress);
227+
const backupKeyBalance = await this.queryAddressBalance(backupKeyAddress, params.apiKey);
225228

226229
if (backupKeyBalance.lt(gasPrice.mul(gasLimit))) {
227230
throw new Error(
@@ -232,7 +235,11 @@ export class Erc20Token extends Eth {
232235
}
233236

234237
// get token balance of wallet
235-
const txAmount = await this.queryAddressTokenBalance(this.tokenContractAddress, params.walletContractAddress);
238+
const txAmount = await this.queryAddressTokenBalance(
239+
this.tokenContractAddress,
240+
params.walletContractAddress,
241+
params.apiKey
242+
);
236243
if (new BigNumber(txAmount).isLessThanOrEqualTo(0)) {
237244
throw new Error('Wallet does not have enough funds to recover');
238245
}
@@ -246,7 +253,7 @@ export class Erc20Token extends Eth {
246253
];
247254

248255
// Get sequence ID using contract call
249-
const sequenceId = await this.querySequenceId(params.walletContractAddress);
256+
const sequenceId = await this.querySequenceId(params.walletContractAddress, params.apiKey);
250257

251258
let operationHash, signature;
252259
if (!isUnsignedSweep) {
@@ -288,7 +295,17 @@ export class Erc20Token extends Eth {
288295
});
289296

290297
if (isUnsignedSweep) {
291-
return this.formatForOfflineVault(txInfo, tx, userKey, backupKey, gasPrice, gasLimit, params.eip1559) as any;
298+
return this.formatForOfflineVault(
299+
txInfo,
300+
tx,
301+
userKey,
302+
backupKey,
303+
gasPrice,
304+
gasLimit,
305+
params.eip1559,
306+
params.replayProtectionOptions,
307+
params.apiKey
308+
) as any;
292309
}
293310

294311
if (!isKrsRecovery) {

0 commit comments

Comments
 (0)