From 3cc55ff95dde1f87f53efb2496e995beeb943b00 Mon Sep 17 00:00:00 2001 From: Shinji Date: Sat, 20 Jul 2024 10:12:33 +0000 Subject: [PATCH] perf: improve token list performance by caching target tokens on load and config change --- tsconfig.base.json | 1 + .../src/containers/Wallets/Wallets.tsx | 9 +- widget/embedded/src/services/cacheService.ts | 35 ++++ widget/embedded/src/store/slices/config.ts | 40 +++- widget/embedded/src/store/slices/data.test.ts | 84 +++++++++ widget/embedded/src/store/slices/data.ts | 68 ++++--- widget/embedded/src/store/utils/data.test.ts | 175 ++++++++++++++++++ widget/embedded/src/store/utils/data.ts | 117 ++++++++++++ widget/embedded/src/store/utils/index.ts | 1 + widget/embedded/src/test-utils/fixtures.ts | 21 ++- widget/embedded/src/types/wallets.ts | 2 +- 11 files changed, 518 insertions(+), 35 deletions(-) create mode 100644 widget/embedded/src/services/cacheService.ts create mode 100644 widget/embedded/src/store/utils/data.test.ts create mode 100644 widget/embedded/src/store/utils/data.ts create mode 100644 widget/embedded/src/store/utils/index.ts diff --git a/tsconfig.base.json b/tsconfig.base.json index 7b278e076c..5510cb9153 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -2,6 +2,7 @@ "compilerOptions": { "module": "esnext", // use Node's module resolution algorithm, instead of the legacy TS one + "target": "ES2015", "moduleResolution": "node", // stricter type-checking for stronger correctness. Recommended by TS "strict": true, diff --git a/widget/embedded/src/containers/Wallets/Wallets.tsx b/widget/embedded/src/containers/Wallets/Wallets.tsx index 06f1581fb0..8a6a8913d0 100644 --- a/widget/embedded/src/containers/Wallets/Wallets.tsx +++ b/widget/embedded/src/containers/Wallets/Wallets.tsx @@ -31,7 +31,12 @@ export const WidgetContext = createContext({ }); function Main(props: PropsWithChildren) { - const { updateConfig, updateSettings, fetch: fetchMeta } = useAppStore(); + const { + updateConfig, + updateSettings, + fetch: fetchMeta, + fetchStatus, + } = useAppStore(); const blockchains = useAppStore().blockchains(); const { findToken } = useAppStore(); const config = useAppStore().config; @@ -61,7 +66,7 @@ function Main(props: PropsWithChildren) { dappConfig: props.config, }; } - }, [props.config]); + }, [props.config, fetchStatus]); const evmBasedChainNames = blockchains .filter(isEvmBlockchain) diff --git a/widget/embedded/src/services/cacheService.ts b/widget/embedded/src/services/cacheService.ts new file mode 100644 index 0000000000..c598b140aa --- /dev/null +++ b/widget/embedded/src/services/cacheService.ts @@ -0,0 +1,35 @@ +import type { Token } from 'rango-sdk'; + +interface CacheServiceInterface { + get(key: K): V[K] | undefined; + set(key: K, value: V[K]): void; + remove(key: K): void; + clear(): void; +} + +class CacheService implements CacheServiceInterface { + #cache: Map = new Map(); + + get(key: K): T[K] | undefined { + return this.#cache.get(key as string) as T[K]; + } + + set(key: K, value: T[K]): void { + this.#cache.set(key as string, value); + } + + remove(key: K): void { + this.#cache.delete(key as string); + } + + clear(): void { + this.#cache.clear(); + } +} + +export type CachedEntries = { + supportedSourceTokens: Token[]; + supportedDestinationTokens: Token[]; +}; + +export const cacheService = new CacheService(); diff --git a/widget/embedded/src/store/slices/config.ts b/widget/embedded/src/store/slices/config.ts index e7ab4c4812..f302f14d49 100644 --- a/widget/embedded/src/store/slices/config.ts +++ b/widget/embedded/src/store/slices/config.ts @@ -1,7 +1,11 @@ +import type { DataSlice } from './data'; import type { SettingsSlice } from './settings'; import type { WidgetConfig } from '../../types'; import type { StateCreatorWithInitialData } from '../app'; +import { cacheService } from '../../services/cacheService'; +import { matchTokensFromConfigWithMeta } from '../utils'; + export const DEFAULT_CONFIG: WidgetConfig = { apiKey: '', title: undefined, @@ -54,7 +58,7 @@ export interface ConfigSlice { export const createConfigSlice: StateCreatorWithInitialData< WidgetConfig, - ConfigSlice & SettingsSlice, + ConfigSlice & SettingsSlice & DataSlice, ConfigSlice > = (initialData, set, get) => { return { @@ -87,6 +91,40 @@ export const createConfigSlice: StateCreatorWithInitialData< // Actions updateConfig: (nextConfig: WidgetConfig) => { const currentConfig = get().config; + const { + _tokensMapByTokenHash: tokensMapByTokenHash, + _tokensMapByBlockchainName: tokensMapByBlockchainName, + } = get(); + + const supportedSourceTokens = matchTokensFromConfigWithMeta({ + type: 'source', + config: { + blockchains: nextConfig.from?.blockchains, + tokens: nextConfig.from?.tokens, + }, + meta: { + tokensMapByBlockchainName, + tokensMapByTokenHash, + }, + }); + + const supportedDestinationTokens = matchTokensFromConfigWithMeta({ + type: 'destination', + config: { + blockchains: nextConfig.to?.blockchains, + tokens: nextConfig.to?.tokens, + }, + meta: { + tokensMapByBlockchainName, + tokensMapByTokenHash, + }, + }); + + cacheService.set('supportedSourceTokens', supportedSourceTokens); + cacheService.set( + 'supportedDestinationTokens', + supportedDestinationTokens + ); set({ config: { diff --git a/widget/embedded/src/store/slices/data.test.ts b/widget/embedded/src/store/slices/data.test.ts index 83a0a22f23..025d7af8ef 100644 --- a/widget/embedded/src/store/slices/data.test.ts +++ b/widget/embedded/src/store/slices/data.test.ts @@ -3,6 +3,7 @@ import type { EvmBlockchainMeta, Token } from 'rango-sdk'; import { assert, beforeEach, describe, expect, test } from 'vitest'; +import { cacheService } from '../../services/cacheService'; import { createEvmBlockchain, createInitialAppStore, @@ -17,6 +18,7 @@ let customTokens: [Token, Token, Token]; let rangoBlockchain: EvmBlockchainMeta; beforeEach(() => { + cacheService.clear(); rangoBlockchain = createEvmBlockchain(); customTokens = [ @@ -52,6 +54,10 @@ beforeEach(() => { customTokens.forEach((token) => { const tokenHash = createTokenHash(token); initData._tokensMapByTokenHash.set(tokenHash, token); + if (!initData._tokensMapByBlockchainName[token.blockchain]) { + initData._tokensMapByBlockchainName[token.blockchain] = []; + } + initData._tokensMapByBlockchainName[token.blockchain].push(tokenHash); }); const appStore = createAppStore(); @@ -351,3 +357,81 @@ describe('search in tokens', () => { expect(secondResult).toBe('RNG'); }); }); + +describe('supported tokens from config', () => { + test('Should ensure tokens include only tokens from the config', () => { + const rangoToken = customTokens[0]; + const djangoToken = customTokens[1]; + + const configTokens = [rangoToken, djangoToken]; + + appStoreState = updateAppStoreConfig(appStoreState, { + from: { + tokens: configTokens, + }, + to: { tokens: configTokens }, + }); + + let sourceTokens = appStoreState.tokens({ + type: 'source', + }); + + let destinationTokens = appStoreState.tokens({ + type: 'destination', + }); + + expect(configTokens).toMatchObject(sourceTokens); + expect(configTokens).toMatchObject(destinationTokens); + + appStoreState = updateAppStoreConfig(appStoreState, { + from: { + blockchains: [rangoBlockchain.name], + tokens: { + [rangoBlockchain.name]: { + tokens: configTokens, + isExcluded: false, + }, + }, + }, + to: { + blockchains: [rangoBlockchain.name], + tokens: { + [rangoBlockchain.name]: { + tokens: configTokens, + isExcluded: false, + }, + }, + }, + }); + + sourceTokens = appStoreState.tokens({ + type: 'source', + }); + + destinationTokens = appStoreState.tokens({ + type: 'destination', + }); + + expect(sourceTokens).toMatchObject(sourceTokens); + expect(configTokens).toMatchObject(destinationTokens); + }); + + test('Check tokens calculation is caching', () => { + const rangoToken = customTokens[0]; + const djangoToken = customTokens[1]; + + appStoreState = updateAppStoreConfig(appStoreState, { + from: { + tokens: [rangoToken, djangoToken], + }, + }); + + expect(cacheService.get('supportedSourceTokens')?.length ?? 0).toBe(0); + + appStoreState.tokens({ + type: 'source', + }); + + expect(cacheService.get('supportedSourceTokens')?.length ?? 0).toBe(2); + }); +}); diff --git a/widget/embedded/src/store/slices/data.ts b/widget/embedded/src/store/slices/data.ts index 5c81daea91..df4768088a 100644 --- a/widget/embedded/src/store/slices/data.ts +++ b/widget/embedded/src/store/slices/data.ts @@ -1,16 +1,18 @@ // We keep all the received data from server in this slice import type { ConfigSlice } from './config'; -import type { Balance, TokenHash } from '../../types'; +import type { CachedEntries } from '../../services/cacheService'; +import type { Balance, Blockchain, TokenHash } from '../../types'; import type { Asset, BlockchainMeta, SwapperMeta, Token } from 'rango-sdk'; import type { StateCreator } from 'zustand'; +import { cacheService } from '../../services/cacheService'; import { httpService as sdk } from '../../services/httpService'; import { compareWithSearchFor, containsText } from '../../utils/common'; -import { isTokenExcludedInConfig } from '../../utils/configs'; import { createTokenHash, isTokenNative } from '../../utils/meta'; import { sortLiquiditySourcesByGroupTitle } from '../../utils/settings'; import { areTokensEqual, compareTokenBalance } from '../../utils/wallets'; +import { matchTokensFromConfigWithMeta } from '../utils'; type BlockchainOptions = { type?: 'source' | 'destination'; @@ -30,6 +32,7 @@ export type FetchStatus = 'loading' | 'success' | 'failed'; export interface DataSlice { _blockchainsMapByName: Map; _tokensMapByTokenHash: Map; + _tokensMapByBlockchainName: Record; _popularTokens: Token[]; _swappers: SwapperMeta[]; fetchStatus: FetchStatus; @@ -48,8 +51,9 @@ export const createDataSlice: StateCreator< DataSlice > = (set, get) => ({ // State - _blockchainsMapByName: new Map(), - _tokensMapByTokenHash: new Map(), + _blockchainsMapByName: new Map(), + _tokensMapByTokenHash: new Map(), + _tokensMapByBlockchainName: {}, _popularTokens: [], _swappers: [], fetchStatus: 'loading', @@ -84,31 +88,41 @@ export const createDataSlice: StateCreator< return list; }, tokens: (options) => { - const tokensMapByHashToken = get()._tokensMapByTokenHash; - const tokensFromState = Array.from(tokensMapByHashToken?.values() || []); + const { _tokensMapByTokenHash, _tokensMapByBlockchainName, config } = get(); + const tokensFromState = Array.from(_tokensMapByTokenHash.values()); const blockchainsMapByName = get()._blockchainsMapByName; - - if (!options || !options?.type) { + if (!options || !options.type) { return tokensFromState; } - const config = get().config; - const supportedTokensConfig = - (options.type === 'source' ? config.from?.tokens : config.to?.tokens) ?? - {}; + const configType = options.type === 'source' ? 'from' : 'to'; + const cacheKey: keyof CachedEntries = + options.type === 'source' + ? 'supportedSourceTokens' + : 'supportedDestinationTokens'; + + let supportedTokens = cacheService.get(cacheKey); + if (!supportedTokens) { + supportedTokens = matchTokensFromConfigWithMeta({ + type: options.type, + config: { + blockchains: config[configType]?.blockchains, + tokens: config[configType]?.tokens, + }, + meta: { + tokensMapByTokenHash: _tokensMapByTokenHash, + tokensMapByBlockchainName: _tokensMapByBlockchainName, + }, + }); + cacheService.set(cacheKey, supportedTokens); + } + const blockchains = get().blockchains({ type: options.type, }); - const list = tokensFromState + const list = supportedTokens .filter((token) => { - if ( - supportedTokensConfig && - isTokenExcludedInConfig(token, supportedTokensConfig) - ) { - return false; - } - // If a specific blockchain has passed, we only keep that blockchain's tokens. if (!!options.blockchain && token.blockchain !== options.blockchain) { return false; @@ -261,17 +275,18 @@ export const createDataSlice: StateCreator< const response = await sdk().getAllMetadata({ enableCentralizedSwappers, }); - set({ fetchStatus: 'success' }); const blockchainsMapByName: Map = new Map< string, BlockchainMeta >(); - const tokensMapByHashToken: Map = new Map< + const tokensMapByTokenHash: Map = new Map< TokenHash, Token >(); + const tokensMapByBlockchainName: DataSlice['_tokensMapByBlockchainName'] = + {}; const tokens: Token[] = []; const popularTokens: Token[] = response.popularTokens; const swappers: SwapperMeta[] = response.swappers; @@ -299,12 +314,17 @@ export const createDataSlice: StateCreator< tokens.forEach((token) => { const tokenHash = createTokenHash(token); - tokensMapByHashToken.set(tokenHash, token); + if (!tokensMapByBlockchainName[token.blockchain]) { + tokensMapByBlockchainName[token.blockchain] = []; + } + tokensMapByTokenHash.set(tokenHash, token); + tokensMapByBlockchainName[token.blockchain].push(tokenHash); }); set({ _blockchainsMapByName: blockchainsMapByName, - _tokensMapByTokenHash: tokensMapByHashToken, + _tokensMapByTokenHash: tokensMapByTokenHash, + _tokensMapByBlockchainName: tokensMapByBlockchainName, _popularTokens: popularTokens, _swappers: swappers, }); diff --git a/widget/embedded/src/store/utils/data.test.ts b/widget/embedded/src/store/utils/data.test.ts new file mode 100644 index 0000000000..2d04b1ffbf --- /dev/null +++ b/widget/embedded/src/store/utils/data.test.ts @@ -0,0 +1,175 @@ +import type { Token } from 'rango-sdk'; + +import { describe, expect, test } from 'vitest'; + +import { createToken } from '../../test-utils/fixtures'; +import { createTokenHash } from '../../utils/meta'; + +import { matchTokensFromConfigWithMeta } from './data'; // Adjust path as necessary + +type MatchTokensFromConfigWithMetaParam = Parameters< + typeof matchTokensFromConfigWithMeta +>[0]; + +const BLOCKCHAIN_A = 'blockchainA'; +const BLOCKCHAIN_B = 'blockchainB'; +const BLOCKCHAIN_C = 'blockchainC'; + +const tokens: Token[] = [ + { ...createToken(), blockchain: BLOCKCHAIN_A }, + { ...createToken(), blockchain: BLOCKCHAIN_A }, + { ...createToken(), blockchain: BLOCKCHAIN_B }, + { ...createToken(), blockchain: BLOCKCHAIN_C }, +]; + +function createMetaForMockData( + tokens: Token[] +): MatchTokensFromConfigWithMetaParam['meta'] { + const meta: MatchTokensFromConfigWithMetaParam['meta'] = { + tokensMapByTokenHash: new Map(), + tokensMapByBlockchainName: {}, + }; + tokens.forEach((token) => { + const tokenHash = createTokenHash(token); + meta.tokensMapByTokenHash.set(tokenHash, token); + if (!meta.tokensMapByBlockchainName[token.blockchain]) { + meta.tokensMapByBlockchainName[token.blockchain] = []; + } + meta.tokensMapByBlockchainName[token.blockchain].push(tokenHash); + }); + + return meta; +} + +describe('matchTokensFromConfigWithMeta', () => { + test('should include tokens from config.tokens array that exist in meta', () => { + const mockData: MatchTokensFromConfigWithMetaParam = { + type: 'source', + meta: createMetaForMockData(tokens), + config: { + blockchains: undefined, + tokens: [tokens[0], tokens[1]], + }, + }; + + const result = matchTokensFromConfigWithMeta(mockData); + expect(result).toEqual([tokens[0], tokens[1]]); + }); + + test('If config.blockchains is not defined or is empty, we should include all tokens from the meta for every blockchain that does not have tokens specified in the config, as well as tokens specified in the config that exist in the meta.', () => { + const mockData: MatchTokensFromConfigWithMetaParam = { + type: 'source', + meta: createMetaForMockData(tokens), + config: { + blockchains: undefined, + tokens: { + [BLOCKCHAIN_A]: { tokens: [tokens[0]], isExcluded: false }, + }, + }, + }; + + const runTestWithConfig = (config: { + blockchains: MatchTokensFromConfigWithMetaParam['config']['blockchains']; + }) => { + mockData.config.blockchains = config.blockchains; + const result = matchTokensFromConfigWithMeta(mockData); + /** + * In this case, the result might differ from what was expected. + * To pass the test, we reordered the expected result. + * Changing the order of tokens does not impact the overall behavior of our app, + * as we have a comprehensive sorting logic applied to tokens within the data slice. + */ + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + expect(result).toEqual([tokens[2], tokens[3], tokens[0]]); + }; + + runTestWithConfig({ blockchains: undefined }); + runTestWithConfig({ blockchains: [] }); + }); + + test('should include tokens from config.tokens object that exist in meta', () => { + const mockData: MatchTokensFromConfigWithMetaParam = { + type: 'source', + meta: createMetaForMockData(tokens), + config: { + blockchains: [BLOCKCHAIN_A, BLOCKCHAIN_B], + tokens: { + [BLOCKCHAIN_A]: { tokens: [tokens[0]], isExcluded: false }, + [BLOCKCHAIN_B]: { tokens: [tokens[2]], isExcluded: false }, + }, + }, + }; + + const result = matchTokensFromConfigWithMeta(mockData); + expect(result).toEqual([tokens[0], tokens[2]]); + }); + + test('should not include tokens from config.tokens whose blockchain does not exist in config.blockchains', () => { + const mockData: MatchTokensFromConfigWithMetaParam = { + type: 'source', + meta: createMetaForMockData(tokens), + config: { + blockchains: [BLOCKCHAIN_A], + tokens: { + [BLOCKCHAIN_B]: { tokens: [tokens[2]], isExcluded: false }, + }, + }, + }; + + const result = matchTokensFromConfigWithMeta(mockData); + expect(result).toEqual([tokens[0], tokens[1]]); + }); + + test('should include all tokens from a blockchain if that blockchain does not have any tokens in config.tokens', () => { + const mockData: MatchTokensFromConfigWithMetaParam = { + type: 'source', + meta: createMetaForMockData(tokens), + config: { + blockchains: [BLOCKCHAIN_A], + tokens: {}, + }, + }; + + const result = matchTokensFromConfigWithMeta(mockData); + expect(result).toEqual([tokens[0], tokens[1]]); + }); + + test('should include all tokens from config.blockchains that exist in the meta, except tokens excluded in config.token', () => { + const mockData: MatchTokensFromConfigWithMetaParam = { + type: 'source', + meta: createMetaForMockData(tokens), + config: { + blockchains: [BLOCKCHAIN_A], + tokens: { + [BLOCKCHAIN_A]: { tokens: [tokens[0]], isExcluded: true }, + }, + }, + }; + + const result = matchTokensFromConfigWithMeta(mockData); + expect(result).toEqual([tokens[1]]); + }); + + test('should include all tokens from the meta when config parameters are empty values', () => { + const mockData: MatchTokensFromConfigWithMetaParam = { + type: 'source', + meta: createMetaForMockData(tokens), + config: { + blockchains: [], + tokens: {}, + }, + }; + + const runTestWithConfig = ( + config: MatchTokensFromConfigWithMetaParam['config'] + ) => { + mockData.config = config; + const result = matchTokensFromConfigWithMeta(mockData); + expect(result).toEqual(tokens); + }; + + runTestWithConfig({ blockchains: [], tokens: {} }); + runTestWithConfig({ blockchains: undefined, tokens: undefined }); + runTestWithConfig({ blockchains: [], tokens: [] }); + }); +}); diff --git a/widget/embedded/src/store/utils/data.ts b/widget/embedded/src/store/utils/data.ts new file mode 100644 index 0000000000..85a71ad264 --- /dev/null +++ b/widget/embedded/src/store/utils/data.ts @@ -0,0 +1,117 @@ +import type { BlockchainAndTokenConfig, TokenHash } from '../../types'; +import type { DataSlice } from '../slices/data'; +import type { Asset, Token } from 'rango-sdk'; + +import { createTokenHash } from '../../utils/meta'; + +/** + * This function retrieves tokens and blockchains from the widget configuration and combines them with token data from the meta response to determine which tokens should be displayed in the widget. + * To optimize performance, the function minimizes unnecessary iterations over the meta tokens. + * The result of this function should be cached and recalculated whenever the widget configuration changes. + */ +export function matchTokensFromConfigWithMeta(params: { + type: 'source' | 'destination'; + config: { + blockchains: BlockchainAndTokenConfig['blockchains']; + tokens: BlockchainAndTokenConfig['tokens']; + }; + meta: { + tokensMapByTokenHash: DataSlice['_tokensMapByTokenHash']; + tokensMapByBlockchainName: DataSlice['_tokensMapByBlockchainName']; + }; +}): Token[] { + const { config, meta } = params; + const result: Record = {}; + const configTokens = config.tokens; + + // Helper function to add tokens to result + const addTokens = (tokens: (Asset | TokenHash)[]) => { + tokens.forEach((item) => { + if (typeof item !== 'string') { + item = createTokenHash(item); + } + const tokenFromMeta = meta.tokensMapByTokenHash.get(item); + if (tokenFromMeta) { + result[item] = tokenFromMeta; + } + }); + }; + + /** + * We support two types of configurations for passing tokens in the widget config. + * We check the type of configuration and calculate the result based on that. + */ + if (Array.isArray(configTokens)) { + /** + * If "config.tokens" is an array, + * we iterate through it and include any token that exists in the meta data in the result. + */ + if (configTokens.length > 0) { + addTokens(configTokens); + return Object.values(result); + } + return Array.from(meta.tokensMapByTokenHash.values()); + } + + /** + * From this point onward, + * the function handles the other type of widget configuration. + */ + if (!configTokens) { + return Array.from(meta.tokensMapByTokenHash.values()); + } + + let configBlockchains: string[]; + + if (config.blockchains?.length) { + configBlockchains = config.blockchains; + } else { + /** + * If config.blockchains does not exist, + * we include all tokens from every blockchain in the meta data that are not specified in the tokens from the config. + */ + configBlockchains = Object.keys(configTokens); + const configBlockchainsSet = new Set(configBlockchains); + + Object.keys(meta.tokensMapByBlockchainName).forEach((blockchain) => { + if (!configBlockchainsSet.has(blockchain)) { + addTokens(meta.tokensMapByBlockchainName[blockchain]); + } + }); + } + //We iterate over each blockchain and retrieve the related tokens for each one from the configuration. + configBlockchains.forEach((blockchain) => { + const targetTokensForBlockchain = configTokens[blockchain]; + /** + * If no tokens exist for a blockchain, + * we include all tokens for that blockchain from the meta response. + */ + if ( + !targetTokensForBlockchain && + meta.tokensMapByBlockchainName?.[blockchain] + ) { + addTokens(meta.tokensMapByBlockchainName[blockchain]); + return; + } + + if (targetTokensForBlockchain) { + if (targetTokensForBlockchain.isExcluded) { + /** + * If tokens are excluded, + * we first include all tokens from the meta for that blockchain, + * then remove the excluded tokens from the result. + */ + addTokens(meta.tokensMapByBlockchainName[blockchain]); + targetTokensForBlockchain.tokens.forEach((token) => { + const tokenHash = createTokenHash(token); + delete result[tokenHash]; + }); + } else { + //We retrieve the corresponding token from the meta and include it in the result. + addTokens(targetTokensForBlockchain.tokens); + } + } + }); + + return Object.values(result); +} diff --git a/widget/embedded/src/store/utils/index.ts b/widget/embedded/src/store/utils/index.ts new file mode 100644 index 0000000000..3707679222 --- /dev/null +++ b/widget/embedded/src/store/utils/index.ts @@ -0,0 +1 @@ +export * from './data'; diff --git a/widget/embedded/src/test-utils/fixtures.ts b/widget/embedded/src/test-utils/fixtures.ts index f28d11c47a..49dd4cc46d 100644 --- a/widget/embedded/src/test-utils/fixtures.ts +++ b/widget/embedded/src/test-utils/fixtures.ts @@ -1,5 +1,5 @@ import type { AppStoreState } from '../store/app'; -import type { TokenHash, WidgetConfig } from '../types'; +import type { Blockchain, TokenHash, WidgetConfig } from '../types'; import type { BlockchainMeta, EvmBlockchainMeta, Token } from 'rango-sdk'; import { faker } from '@faker-js/faker'; @@ -231,13 +231,20 @@ export function createInitialAppStore() { blockchains: blockchains.map((b) => b.name), }); + const tokensMapByTokenHash: Map = new Map(); + const tokensMapByBlockchainName: Record = {}; + tokens.forEach((token) => { + const tokenHash = createTokenHash(token); + if (!tokensMapByBlockchainName[token.blockchain]) { + tokensMapByBlockchainName[token.blockchain] = []; + } + tokensMapByTokenHash.set(tokenHash, token); + tokensMapByBlockchainName[token.blockchain].push(tokenHash); + }); + return { - _tokensMapByTokenHash: new Map( - tokens.map((token) => { - const tokenHash = createTokenHash(token); - return [tokenHash, token]; - }) - ), + _tokensMapByTokenHash: tokensMapByTokenHash, + _tokensMapByBlockchainName: tokensMapByBlockchainName, _blockchainsMapByName: new Map( blockchains.map((meta) => [meta.name, meta]) ), diff --git a/widget/embedded/src/types/wallets.ts b/widget/embedded/src/types/wallets.ts index 4024605624..b15dedcb2b 100644 --- a/widget/embedded/src/types/wallets.ts +++ b/widget/embedded/src/types/wallets.ts @@ -13,7 +13,7 @@ export type Balance = { usdValue: string; }; -type Blockchain = string; +export type Blockchain = string; type TokenSymbol = string; type Address = string;