Skip to content

Commit

Permalink
WT-1697 - patched getIndexerBalance for un-indexed wallets (#859)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrearampin authored Sep 13, 2023
1 parent cd62b88 commit 7731736
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 25 deletions.
150 changes: 150 additions & 0 deletions packages/checkout/sdk/src/balances/balances.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,156 @@ describe('balances', () => {
]);
});

it('should call getIndexerBalance and return native balance on ERC20 404', async () => {
getTokensByWalletAddressMock = jest.fn().mockRejectedValue(
{ code: HttpStatusCode.NotFound, message: 'not found' },
);

getNativeTokenByWalletAddressMock = jest.fn().mockResolvedValue({
token: {
name: 'IMX',
symbol: 'IMX',
decimals: '18',
address: IMX_ADDRESS_ZKEVM,
} as BlockscoutNativeTokenData,
value: '777777777777777777',
} as BlockscoutToken);

(Blockscout as unknown as jest.Mock).mockReturnValue({
getTokensByWalletAddress: getTokensByWalletAddressMock,
getNativeTokenByWalletAddress: getNativeTokenByWalletAddressMock,
});

const chainId = Object.keys(BLOCKSCOUT_CHAIN_URL_MAP)[0] as unknown as ChainId;

const getAllBalancesResult = await getAllBalances(
{
remote: {
getTokensConfig: () => ({
blockscout: true,
}),
},
networkMap: testCheckoutConfig.networkMap,
} as unknown as CheckoutConfiguration,
jest.fn() as unknown as Web3Provider,
'abc123',
chainId,
);

expect(getNativeTokenByWalletAddressMock).toHaveBeenCalledTimes(1);
expect(getTokensByWalletAddressMock).toHaveBeenCalledTimes(1);

expect(getAllBalancesResult.balances).toEqual([
{
balance: BigNumber.from('777777777777777777'),
formattedBalance: '0.777777777777777777',
token: {
address: '0x0000000000000000000000000000000000001010',
decimals: 18,
name: 'IMX',
symbol: 'IMX',
},
},
]);
});

it('should call getIndexerBalance and return ERC20 balances on native 404', async () => {
getTokensByWalletAddressMock = jest.fn().mockResolvedValue({
items: [
{
token: {
address: '0x65AA7a21B0f3ce9B478aAC3408fE75b423939b1F',
decimals: '18',
name: 'Ether',
symbol: 'ETH',
type: BlockscoutTokenType.ERC20,
},
value: '330000000000000000',
},
],
// eslint-disable-next-line @typescript-eslint/naming-convention
next_page_params: null,
} as BlockscoutTokens);

getNativeTokenByWalletAddressMock = jest.fn().mockRejectedValue(
{ code: HttpStatusCode.NotFound, message: 'not found' },
);

(Blockscout as unknown as jest.Mock).mockReturnValue({
getTokensByWalletAddress: getTokensByWalletAddressMock,
getNativeTokenByWalletAddress: getNativeTokenByWalletAddressMock,
});

const chainId = Object.keys(BLOCKSCOUT_CHAIN_URL_MAP)[0] as unknown as ChainId;

const getAllBalancesResult = await getAllBalances(
{
remote: {
getTokensConfig: () => ({
blockscout: true,
}),
},
networkMap: testCheckoutConfig.networkMap,
} as unknown as CheckoutConfiguration,
jest.fn() as unknown as Web3Provider,
'abc123',
chainId,
);

expect(getNativeTokenByWalletAddressMock).toHaveBeenCalledTimes(1);
expect(getTokensByWalletAddressMock).toHaveBeenCalledTimes(1);

expect(getAllBalancesResult.balances).toEqual([
{
balance: BigNumber.from('330000000000000000'),
formattedBalance: '0.33',
token: {
address: '0x65AA7a21B0f3ce9B478aAC3408fE75b423939b1F',
decimals: 18,
name: 'Ether',
symbol: 'ETH',
type: 'ERC-20',
},
},
]);
});

it('should call getIndexerBalance and return empty balance due to 404', async () => {
getTokensByWalletAddressMock = jest.fn().mockRejectedValue(
{ code: HttpStatusCode.NotFound, message: 'not found' },
);

getNativeTokenByWalletAddressMock = jest.fn().mockRejectedValue(
{ code: HttpStatusCode.NotFound, message: 'not found' },
);

(Blockscout as unknown as jest.Mock).mockReturnValue({
getTokensByWalletAddress: getTokensByWalletAddressMock,
getNativeTokenByWalletAddress: getNativeTokenByWalletAddressMock,
});

const chainId = Object.keys(BLOCKSCOUT_CHAIN_URL_MAP)[0] as unknown as ChainId;

const getAllBalancesResult = await getAllBalances(
{
remote: {
getTokensConfig: () => ({
blockscout: true,
}),
},
networkMap: testCheckoutConfig.networkMap,
} as unknown as CheckoutConfiguration,
jest.fn() as unknown as Web3Provider,
'abc123',
chainId,
);

expect(getNativeTokenByWalletAddressMock).toHaveBeenCalledTimes(1);
expect(getTokensByWalletAddressMock).toHaveBeenCalledTimes(1);

expect(getAllBalancesResult.balances).toEqual([]);
});

const testcases = [{
errorMessage: 'test',
expectedErrorMessage: 'test',
Expand Down
39 changes: 29 additions & 10 deletions packages/checkout/sdk/src/balances/balances.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Web3Provider } from '@ethersproject/providers';
import { BigNumber, Contract, utils } from 'ethers';
import { HttpStatusCode } from 'axios';
import {
ChainId,
ERC20ABI,
Expand Down Expand Up @@ -119,22 +120,40 @@ export const getIndexerBalance = async (
items.push(...resp.items);
} while (resp.next_page_params);
} catch (err: any) {
throw new CheckoutError(
err.message || 'InternalServerError | getTokensByWalletAddress',
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
err,
);
// In case of a 404, the wallet is a new wallet that hasn't been indexed by
// the Blockscout just yet. This happens when a wallet hasn't had any
// activity on the chain. In this case, simply ignore the error and return
// no currencies.
// In case of a malformed wallet address, Blockscout returns a 422, which
// means we are safe to assume that a 404 is a missing wallet due to inactivity
// or simply an incorrect wallet address was provided.
if (err?.code !== HttpStatusCode.NotFound) {
throw new CheckoutError(
err.message || 'InternalServerError | getTokensByWalletAddress',
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
err,
);
}
}

try {
const respNative = await blockscoutClient.getNativeTokenByWalletAddress({ walletAddress });
items.push(respNative);
} catch (err: any) {
throw new CheckoutError(
err.message || 'InternalServerError | getNativeTokenByWalletAddress',
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
err,
);
// In case of a 404, the wallet is a new wallet that hasn't been indexed by
// the Blockscout just yet. This happens when a wallet hasn't had any
// activity on the chain. In this case, simply ignore the error and return
// no currencies.
// In case of a malformed wallet address, Blockscout returns a 422, which
// means we are safe to assume that a 404 is a missing wallet due to inactivity
// or simply an incorrect wallet address was provided.
if (err?.code !== HttpStatusCode.NotFound) {
throw new CheckoutError(
err.message || 'InternalServerError | getNativeTokenByWalletAddress',
CheckoutErrorType.GET_INDEXER_BALANCE_ERROR,
err,
);
}
}

return {
Expand Down
34 changes: 19 additions & 15 deletions packages/checkout/sdk/src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,6 @@ export const DEFAULT_SWAP_ENABLED = true;
*/
export const DEFAULT_BRIDGE_ENABLED = true;

/**
* Blockscout API configuration per chain
*/
export const BLOCKSCOUT_CHAIN_URL_MAP: {
[key: string]: {
url: string,
nativeToken: TokenInfo
}
} = {
[ChainId.IMTBL_ZKEVM_TESTNET]: {
url: 'https://explorer.testnet.immutable.com',
nativeToken: ZKEVM_NATIVE_TOKEN,
},
};

export const TRANSAK_API_BASE_URL = {
[Environment.SANDBOX]: 'https://global-stg.transak.com',
[Environment.PRODUCTION]: 'https://global.transak.com/',
Expand Down Expand Up @@ -161,6 +146,25 @@ NetworkDetails
],
]);

/**
* Blockscout API configuration per chain
*/
export const BLOCKSCOUT_CHAIN_URL_MAP: {
[key: string]: {
url: string,
nativeToken: TokenInfo
}
} = {
[ChainId.IMTBL_ZKEVM_TESTNET]: {
url: 'https://explorer.testnet.immutable.com',
nativeToken: SANDBOX_CHAIN_ID_NETWORK_MAP.get(ChainId.IMTBL_ZKEVM_TESTNET)!.nativeCurrency,
},
[ChainId.IMTBL_ZKEVM_MAINNET]: {
url: 'https://explorer.mainnet.immutable.com',
nativeToken: PRODUCTION_CHAIN_ID_NETWORK_MAP.get(ChainId.IMTBL_ZKEVM_MAINNET)!.nativeCurrency,
},
};

export const ERC20ABI = [
{
constant: true,
Expand Down

0 comments on commit 7731736

Please sign in to comment.