Skip to content

Commit 7731736

Browse files
authored
WT-1697 - patched getIndexerBalance for un-indexed wallets (#859)
1 parent cd62b88 commit 7731736

File tree

3 files changed

+198
-25
lines changed

3 files changed

+198
-25
lines changed

packages/checkout/sdk/src/balances/balances.test.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,156 @@ describe('balances', () => {
415415
]);
416416
});
417417

418+
it('should call getIndexerBalance and return native balance on ERC20 404', async () => {
419+
getTokensByWalletAddressMock = jest.fn().mockRejectedValue(
420+
{ code: HttpStatusCode.NotFound, message: 'not found' },
421+
);
422+
423+
getNativeTokenByWalletAddressMock = jest.fn().mockResolvedValue({
424+
token: {
425+
name: 'IMX',
426+
symbol: 'IMX',
427+
decimals: '18',
428+
address: IMX_ADDRESS_ZKEVM,
429+
} as BlockscoutNativeTokenData,
430+
value: '777777777777777777',
431+
} as BlockscoutToken);
432+
433+
(Blockscout as unknown as jest.Mock).mockReturnValue({
434+
getTokensByWalletAddress: getTokensByWalletAddressMock,
435+
getNativeTokenByWalletAddress: getNativeTokenByWalletAddressMock,
436+
});
437+
438+
const chainId = Object.keys(BLOCKSCOUT_CHAIN_URL_MAP)[0] as unknown as ChainId;
439+
440+
const getAllBalancesResult = await getAllBalances(
441+
{
442+
remote: {
443+
getTokensConfig: () => ({
444+
blockscout: true,
445+
}),
446+
},
447+
networkMap: testCheckoutConfig.networkMap,
448+
} as unknown as CheckoutConfiguration,
449+
jest.fn() as unknown as Web3Provider,
450+
'abc123',
451+
chainId,
452+
);
453+
454+
expect(getNativeTokenByWalletAddressMock).toHaveBeenCalledTimes(1);
455+
expect(getTokensByWalletAddressMock).toHaveBeenCalledTimes(1);
456+
457+
expect(getAllBalancesResult.balances).toEqual([
458+
{
459+
balance: BigNumber.from('777777777777777777'),
460+
formattedBalance: '0.777777777777777777',
461+
token: {
462+
address: '0x0000000000000000000000000000000000001010',
463+
decimals: 18,
464+
name: 'IMX',
465+
symbol: 'IMX',
466+
},
467+
},
468+
]);
469+
});
470+
471+
it('should call getIndexerBalance and return ERC20 balances on native 404', async () => {
472+
getTokensByWalletAddressMock = jest.fn().mockResolvedValue({
473+
items: [
474+
{
475+
token: {
476+
address: '0x65AA7a21B0f3ce9B478aAC3408fE75b423939b1F',
477+
decimals: '18',
478+
name: 'Ether',
479+
symbol: 'ETH',
480+
type: BlockscoutTokenType.ERC20,
481+
},
482+
value: '330000000000000000',
483+
},
484+
],
485+
// eslint-disable-next-line @typescript-eslint/naming-convention
486+
next_page_params: null,
487+
} as BlockscoutTokens);
488+
489+
getNativeTokenByWalletAddressMock = jest.fn().mockRejectedValue(
490+
{ code: HttpStatusCode.NotFound, message: 'not found' },
491+
);
492+
493+
(Blockscout as unknown as jest.Mock).mockReturnValue({
494+
getTokensByWalletAddress: getTokensByWalletAddressMock,
495+
getNativeTokenByWalletAddress: getNativeTokenByWalletAddressMock,
496+
});
497+
498+
const chainId = Object.keys(BLOCKSCOUT_CHAIN_URL_MAP)[0] as unknown as ChainId;
499+
500+
const getAllBalancesResult = await getAllBalances(
501+
{
502+
remote: {
503+
getTokensConfig: () => ({
504+
blockscout: true,
505+
}),
506+
},
507+
networkMap: testCheckoutConfig.networkMap,
508+
} as unknown as CheckoutConfiguration,
509+
jest.fn() as unknown as Web3Provider,
510+
'abc123',
511+
chainId,
512+
);
513+
514+
expect(getNativeTokenByWalletAddressMock).toHaveBeenCalledTimes(1);
515+
expect(getTokensByWalletAddressMock).toHaveBeenCalledTimes(1);
516+
517+
expect(getAllBalancesResult.balances).toEqual([
518+
{
519+
balance: BigNumber.from('330000000000000000'),
520+
formattedBalance: '0.33',
521+
token: {
522+
address: '0x65AA7a21B0f3ce9B478aAC3408fE75b423939b1F',
523+
decimals: 18,
524+
name: 'Ether',
525+
symbol: 'ETH',
526+
type: 'ERC-20',
527+
},
528+
},
529+
]);
530+
});
531+
532+
it('should call getIndexerBalance and return empty balance due to 404', async () => {
533+
getTokensByWalletAddressMock = jest.fn().mockRejectedValue(
534+
{ code: HttpStatusCode.NotFound, message: 'not found' },
535+
);
536+
537+
getNativeTokenByWalletAddressMock = jest.fn().mockRejectedValue(
538+
{ code: HttpStatusCode.NotFound, message: 'not found' },
539+
);
540+
541+
(Blockscout as unknown as jest.Mock).mockReturnValue({
542+
getTokensByWalletAddress: getTokensByWalletAddressMock,
543+
getNativeTokenByWalletAddress: getNativeTokenByWalletAddressMock,
544+
});
545+
546+
const chainId = Object.keys(BLOCKSCOUT_CHAIN_URL_MAP)[0] as unknown as ChainId;
547+
548+
const getAllBalancesResult = await getAllBalances(
549+
{
550+
remote: {
551+
getTokensConfig: () => ({
552+
blockscout: true,
553+
}),
554+
},
555+
networkMap: testCheckoutConfig.networkMap,
556+
} as unknown as CheckoutConfiguration,
557+
jest.fn() as unknown as Web3Provider,
558+
'abc123',
559+
chainId,
560+
);
561+
562+
expect(getNativeTokenByWalletAddressMock).toHaveBeenCalledTimes(1);
563+
expect(getTokensByWalletAddressMock).toHaveBeenCalledTimes(1);
564+
565+
expect(getAllBalancesResult.balances).toEqual([]);
566+
});
567+
418568
const testcases = [{
419569
errorMessage: 'test',
420570
expectedErrorMessage: 'test',

packages/checkout/sdk/src/balances/balances.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Web3Provider } from '@ethersproject/providers';
22
import { BigNumber, Contract, utils } from 'ethers';
3+
import { HttpStatusCode } from 'axios';
34
import {
45
ChainId,
56
ERC20ABI,
@@ -119,22 +120,40 @@ export const getIndexerBalance = async (
119120
items.push(...resp.items);
120121
} while (resp.next_page_params);
121122
} catch (err: any) {
122-
throw new CheckoutError(
123-
err.message || 'InternalServerError | getTokensByWalletAddress',
124-
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
125-
err,
126-
);
123+
// In case of a 404, the wallet is a new wallet that hasn't been indexed by
124+
// the Blockscout just yet. This happens when a wallet hasn't had any
125+
// activity on the chain. In this case, simply ignore the error and return
126+
// no currencies.
127+
// In case of a malformed wallet address, Blockscout returns a 422, which
128+
// means we are safe to assume that a 404 is a missing wallet due to inactivity
129+
// or simply an incorrect wallet address was provided.
130+
if (err?.code !== HttpStatusCode.NotFound) {
131+
throw new CheckoutError(
132+
err.message || 'InternalServerError | getTokensByWalletAddress',
133+
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
134+
err,
135+
);
136+
}
127137
}
128138

129139
try {
130140
const respNative = await blockscoutClient.getNativeTokenByWalletAddress({ walletAddress });
131141
items.push(respNative);
132142
} catch (err: any) {
133-
throw new CheckoutError(
134-
err.message || 'InternalServerError | getNativeTokenByWalletAddress',
135-
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
136-
err,
137-
);
143+
// In case of a 404, the wallet is a new wallet that hasn't been indexed by
144+
// the Blockscout just yet. This happens when a wallet hasn't had any
145+
// activity on the chain. In this case, simply ignore the error and return
146+
// no currencies.
147+
// In case of a malformed wallet address, Blockscout returns a 422, which
148+
// means we are safe to assume that a 404 is a missing wallet due to inactivity
149+
// or simply an incorrect wallet address was provided.
150+
if (err?.code !== HttpStatusCode.NotFound) {
151+
throw new CheckoutError(
152+
err.message || 'InternalServerError | getNativeTokenByWalletAddress',
153+
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
154+
err,
155+
);
156+
}
138157
}
139158

140159
return {

packages/checkout/sdk/src/types/constants.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,6 @@ export const DEFAULT_SWAP_ENABLED = true;
4242
*/
4343
export const DEFAULT_BRIDGE_ENABLED = true;
4444

45-
/**
46-
* Blockscout API configuration per chain
47-
*/
48-
export const BLOCKSCOUT_CHAIN_URL_MAP: {
49-
[key: string]: {
50-
url: string,
51-
nativeToken: TokenInfo
52-
}
53-
} = {
54-
[ChainId.IMTBL_ZKEVM_TESTNET]: {
55-
url: 'https://explorer.testnet.immutable.com',
56-
nativeToken: ZKEVM_NATIVE_TOKEN,
57-
},
58-
};
59-
6045
export const TRANSAK_API_BASE_URL = {
6146
[Environment.SANDBOX]: 'https://global-stg.transak.com',
6247
[Environment.PRODUCTION]: 'https://global.transak.com/',
@@ -161,6 +146,25 @@ NetworkDetails
161146
],
162147
]);
163148

149+
/**
150+
* Blockscout API configuration per chain
151+
*/
152+
export const BLOCKSCOUT_CHAIN_URL_MAP: {
153+
[key: string]: {
154+
url: string,
155+
nativeToken: TokenInfo
156+
}
157+
} = {
158+
[ChainId.IMTBL_ZKEVM_TESTNET]: {
159+
url: 'https://explorer.testnet.immutable.com',
160+
nativeToken: SANDBOX_CHAIN_ID_NETWORK_MAP.get(ChainId.IMTBL_ZKEVM_TESTNET)!.nativeCurrency,
161+
},
162+
[ChainId.IMTBL_ZKEVM_MAINNET]: {
163+
url: 'https://explorer.mainnet.immutable.com',
164+
nativeToken: PRODUCTION_CHAIN_ID_NETWORK_MAP.get(ChainId.IMTBL_ZKEVM_MAINNET)!.nativeCurrency,
165+
},
166+
};
167+
164168
export const ERC20ABI = [
165169
{
166170
constant: true,

0 commit comments

Comments
 (0)