Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WT-1808 Use blockscout to get the L1 balances if enabled #1042

Merged
merged 5 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/checkout/sdk/src/Checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ export class Checkout {
}

const tokenList = await tokens.getTokenAllowList(this.config, { type: TokenFilterTypes.ONRAMP });
const token = tokenList.tokens.find((t) => t.address?.toLowerCase() === params.tokenAddress?.toLowerCase());
const token = tokenList.tokens?.find((t) => t.address?.toLowerCase() === params.tokenAddress?.toLowerCase());
if (token) {
tokenAmount = params.tokenAmount;
tokenSymbol = token.symbol;
Expand Down
2 changes: 2 additions & 0 deletions packages/checkout/sdk/src/balances/balances.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
getBalance,
getBalances,
getERC20Balance,
resetBlockscoutClientMap,
} from './balances';
import {
BLOCKSCOUT_CHAIN_URL_MAP,
Expand Down Expand Up @@ -218,6 +219,7 @@ describe('balances', () => {

beforeEach(() => {
jest.restoreAllMocks();
resetBlockscoutClientMap();
getTokenAllowListMock = jest.fn().mockReturnValue({
tokens: [
{
Expand Down
65 changes: 42 additions & 23 deletions packages/checkout/sdk/src/balances/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BigNumber, Contract, utils } from 'ethers';
import { HttpStatusCode } from 'axios';
import {
ChainId,
DEFAULT_TOKEN_DECIMALS,
ERC20ABI,
GetAllBalancesResult,
GetBalanceResult,
Expand All @@ -14,7 +15,7 @@ import {
import { CheckoutError, CheckoutErrorType, withCheckoutError } from '../errors';
import { getNetworkInfo } from '../network';
import { getTokenAllowList } from '../tokens';
import { CheckoutConfiguration } from '../config';
import { CheckoutConfiguration, getL1ChainId } from '../config';
import {
Blockscout,
BlockscoutTokens,
Expand Down Expand Up @@ -87,20 +88,29 @@ export async function getERC20Balance(
);
}

// Blockscout client singleton
let blockscoutClient: Blockscout;
// Blockscout client singleton per chain id
const blockscoutClientMap: Map<ChainId, Blockscout> = new Map();

// This function is a utility function that can be used to reset the
// blockscout map and therefore clear all the cache.
export const resetBlockscoutClientMap = () => blockscoutClientMap.clear();

export const getIndexerBalance = async (
walletAddress: string,
chainId: ChainId,
rename: TokenInfo[],
filterTokens: TokenInfo[],
): Promise<GetAllBalancesResult> => {
// Shuffle the mapping of the tokens configuration so it is a hashmap
// for faster access to tokens config objects.
const mapRename = Object.assign({}, ...(rename.map((t) => ({ [t.address || '']: t }))));
const shouldFilter = filterTokens.length > 0;
const mapFilterTokens = Object.assign({}, ...(filterTokens.map((t) => ({ [t.address || '']: t }))));

// Ensure singleton is present and match the selected chain
if (!blockscoutClient || blockscoutClient.chainId !== chainId) blockscoutClient = new Blockscout({ chainId });
// Get blockscout client for the given chain
let blockscoutClient = blockscoutClientMap.get(chainId);
if (!blockscoutClient) {
blockscoutClient = new Blockscout({ chainId });
blockscoutClientMap.set(chainId, blockscoutClient);
}

// Hold the items in an array for post-fetching processing
const items = [];
Expand Down Expand Up @@ -156,25 +166,28 @@ export const getIndexerBalance = async (
}
}

return {
balances: items.map((item) => {
const tokenData = item.token || {};
const balances: GetBalanceResult[] = [];
items.forEach((item) => {
if (shouldFilter && !mapFilterTokens[item.token.address]) return;

const balance = BigNumber.from(item.value);
const tokenData = item.token || {};

const renamed = (mapRename[tokenData.address] || {}) as TokenInfo;
const token = {
...tokenData,
name: renamed.name ?? tokenData.name,
symbol: renamed.symbol ?? tokenData.symbol,
decimals: parseInt(tokenData.decimals, 10),
};
const balance = BigNumber.from(item.value);

const formattedBalance = utils.formatUnits(item.value, token.decimals);
let decimals = parseInt(tokenData.decimals, 10);
if (Number.isNaN(decimals)) decimals = DEFAULT_TOKEN_DECIMALS;

const token = {
...tokenData,
decimals,
};

return { balance, formattedBalance, token } as GetBalanceResult;
}),
};
const formattedBalance = utils.formatUnits(item.value, token.decimals);

balances.push({ balance, formattedBalance, token } as GetBalanceResult);
});

return { balances };
};

export const getBalances = async (
Expand Down Expand Up @@ -233,7 +246,13 @@ export const getAllBalances = async (
}

if (flag && Blockscout.isChainSupported(chainId)) {
return await getIndexerBalance(walletAddress, chainId, tokens);
// This is a hack because the widgets are still using the tokens symbol
// to drive the conversions. If we remove all the token symbols from e.g. zkevm
// then we would not have fiat conversions.
// Please remove this hack once https://immutable.atlassian.net/browse/WT-1710
// is done.
const isL1Chain = getL1ChainId(config) === chainId;
return await getIndexerBalance(walletAddress, chainId, isL1Chain ? tokens : []);
}

// This fallback to use ERC20s calls which is a best effort solution
Expand Down
2 changes: 1 addition & 1 deletion packages/checkout/sdk/src/client/blockscout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('Blockscout', () => {
});
});
it('not supported', () => {
expect(Blockscout.isChainSupported(ChainId.SEPOLIA)).toBe(false);
expect(Blockscout.isChainSupported('aaa' as unknown as ChainId)).toBe(false);
});
});

Expand Down
8 changes: 8 additions & 0 deletions packages/checkout/sdk/src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ export const BLOCKSCOUT_CHAIN_URL_MAP: {
url: 'https://explorer.mainnet.immutable.com',
nativeToken: PRODUCTION_CHAIN_ID_NETWORK_MAP.get(ChainId.IMTBL_ZKEVM_MAINNET)!.nativeCurrency,
},
[ChainId.SEPOLIA]: {
url: 'https://eth-sepolia.blockscout.com',
nativeToken: SANDBOX_CHAIN_ID_NETWORK_MAP.get(ChainId.SEPOLIA)!.nativeCurrency,
},
[ChainId.ETHEREUM]: {
url: 'https://eth.blockscout.com/',
nativeToken: PRODUCTION_CHAIN_ID_NETWORK_MAP.get(ChainId.ETHEREUM)!.nativeCurrency,
},
};

export const ERC20ABI = [
Expand Down