From 1cc29222120075628ebcb02139810b0540f80743 Mon Sep 17 00:00:00 2001 From: Christian Baroni <7061887+christianbaroni@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:35:52 -0500 Subject: [PATCH] Optimize backendNetworks store, remove worklet functions --- .../components/AnimatedChainImage.android.tsx | 17 +- .../components/AnimatedChainImage.ios.tsx | 23 +- .../screens/Swap/components/ReviewPanel.tsx | 8 +- .../components/TokenList/ChainSelection.tsx | 14 +- .../Swap/hooks/useSwapOutputQuotesDisabled.ts | 13 +- .../screens/Swap/providers/swap-provider.tsx | 9 +- src/state/backendNetworks/backendNetworks.ts | 701 ++++++------------ 7 files changed, 269 insertions(+), 516 deletions(-) diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx index 64a5d9080ae..8a67533d6ea 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.android.tsx @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import React, { useMemo } from 'react'; -import { Image, StyleSheet, View } from 'react-native'; +import { Image, View } from 'react-native'; import { getChainBadgeStyles } from '@/components/coin-icon/ChainImage'; -import { globalColors, useColorMode } from '@/design-system'; +import { useColorMode } from '@/design-system'; import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; import { ChainId } from '@/state/backendNetworks/types'; import { useSwapsStore } from '@/state/swaps/swapsStore'; @@ -41,16 +41,3 @@ export function AnimatedChainImage({ ); } - -const sx = StyleSheet.create({ - badge: { - position: 'absolute', - shadowColor: globalColors.grey100, - shadowOffset: { - height: 4, - width: 0, - }, - shadowOpacity: 0.2, - shadowRadius: 6, - }, -}); diff --git a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx index 14002f33bc7..2459b3b03a1 100644 --- a/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx +++ b/src/__swaps__/screens/Swap/components/AnimatedChainImage.ios.tsx @@ -1,12 +1,12 @@ import React, { useMemo } from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; import { useAnimatedProps, useDerivedValue } from 'react-native-reanimated'; import { AnimatedFasterImage } from '@/components/AnimatedComponents/AnimatedFasterImage'; import { BLANK_BASE64_PIXEL } from '@/components/DappBrowser/constants'; import { getChainBadgeStyles } from '@/components/coin-icon/ChainImage'; import { DEFAULT_FASTER_IMAGE_CONFIG } from '@/components/images/ImgixImage'; -import { globalColors, useColorMode } from '@/design-system'; -import { getChainsBadgeWorklet, useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; +import { useColorMode } from '@/design-system'; +import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; import { ChainId } from '@/state/backendNetworks/types'; import { useSwapContext } from '../providers/swap-provider'; @@ -20,7 +20,7 @@ export function AnimatedChainImage({ size?: number; }) { const { internalSelectedInputAsset, internalSelectedOutputAsset } = useSwapContext(); - const backendNetworks = useBackendNetworksStore(state => state.backendNetworksSharedValue); + const networkBadges = useBackendNetworksStore(state => state.getChainsBadge()); const url = useDerivedValue(() => { const asset = assetType === 'input' ? internalSelectedInputAsset : internalSelectedOutputAsset; @@ -28,7 +28,7 @@ export function AnimatedChainImage({ let url = 'eth'; if (chainId !== undefined && !(!showMainnetBadge && chainId === ChainId.mainnet)) { - url = getChainsBadgeWorklet(backendNetworks)[chainId]; + url = networkBadges[chainId]; } return url; }); @@ -55,16 +55,3 @@ export function AnimatedChainImage({ ); } - -const sx = StyleSheet.create({ - badge: { - position: 'absolute', - shadowColor: globalColors.grey100, - shadowOffset: { - height: 4, - width: 0, - }, - shadowOpacity: 0.2, - shadowRadius: 6, - }, -}); diff --git a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx index 3403463373f..132255121cd 100644 --- a/src/__swaps__/screens/Swap/components/ReviewPanel.tsx +++ b/src/__swaps__/screens/Swap/components/ReviewPanel.tsx @@ -41,7 +41,7 @@ import { useSelectedGasSpeed } from '../hooks/useSelectedGas'; import { NavigationSteps, useSwapContext } from '../providers/swap-provider'; import { EstimatedSwapGasFee, EstimatedSwapGasFeeSlot } from './EstimatedSwapGasFee'; import { UnmountOnAnimatedReaction } from './UnmountOnAnimatedReaction'; -import { getChainsLabelWorklet, useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; +import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; import { ChainId } from '@/state/backendNetworks/types'; const UNKNOWN_LABEL = i18n.t(i18n.l.swap.unknown); @@ -245,17 +245,15 @@ export const SlippageRow = () => { export function ReviewPanel() { const { navigate } = useNavigation(); const { isDarkMode } = useColorMode(); - const backendNetworks = useBackendNetworksStore(state => state.backendNetworksSharedValue); const { configProgress, lastTypedInput, internalSelectedInputAsset, internalSelectedOutputAsset, quote } = useSwapContext(); + const chainLabels = useBackendNetworksStore(state => state.getChainsLabel()); const labelTertiary = useForegroundColor('labelTertiary'); const separator = useForegroundColor('separator'); const unknown = i18n.t(i18n.l.swap.unknown); - const chainName = useDerivedValue( - () => getChainsLabelWorklet(backendNetworks)[internalSelectedInputAsset.value?.chainId ?? ChainId.mainnet] - ); + const chainName = useDerivedValue(() => chainLabels[internalSelectedInputAsset.value?.chainId ?? ChainId.mainnet]); const minReceivedOrMaxSoldLabel = useDerivedValue(() => { const isInputBasedTrade = lastTypedInput.value === 'inputAmount' || lastTypedInput.value === 'inputNativeValue'; diff --git a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx index 3620f694527..bbebe399b6c 100644 --- a/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx +++ b/src/__swaps__/screens/Swap/components/TokenList/ChainSelection.tsx @@ -15,7 +15,7 @@ import { useAccountAccentColor } from '@/hooks'; import { useSharedValueState } from '@/hooks/reanimated/useSharedValueState'; import { userAssetsStore, useUserAssetsStore } from '@/state/assets/userAssets'; import { swapsStore } from '@/state/swaps/swapsStore'; -import { getChainsBadgeWorklet, getChainsLabelWorklet, useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; +import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; import { DropdownMenu, MenuItem } from '@/components/DropdownMenu'; type ChainSelectionProps = { @@ -27,7 +27,9 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: const { isDarkMode } = useColorMode(); const { accentColor: accountColor } = useAccountAccentColor(); const { selectedOutputChainId, setSelectedOutputChainId } = useSwapContext(); - const backendNetworks = useBackendNetworksStore(state => state.backendNetworksSharedValue); + + const chainLabels = useBackendNetworksStore(state => state.getChainsLabel()); + const networkBadges = useBackendNetworksStore(state => state.getChainsBadge()); // chains sorted by balance on output, chains without balance hidden on input const balanceSortedChainList = useUserAssetsStore(state => (output ? state.getBalanceSortedChainList() : state.getChainsWithBalance())); @@ -45,8 +47,6 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: }, [accountColor, isDarkMode]); const chainName = useDerivedValue(() => { - const chainLabels = getChainsLabelWorklet(backendNetworks); - return output ? chainLabels[selectedOutputChainId.value] : inputListFilter.value === 'all' @@ -79,11 +79,11 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: supportedChains = balanceSortedChainList.map(chainId => { return { actionKey: `${chainId}`, - actionTitle: getChainsLabelWorklet(backendNetworks)[chainId], + actionTitle: chainLabels[chainId], icon: { iconType: 'REMOTE', iconValue: { - uri: getChainsBadgeWorklet(backendNetworks)[chainId], + uri: networkBadges[chainId], }, }, }; @@ -103,7 +103,7 @@ export const ChainSelection = memo(function ChainSelection({ allText, output }: return { menuItems: supportedChains, }; - }, [backendNetworks, balanceSortedChainList, output]); + }, [balanceSortedChainList, chainLabels, networkBadges, output]); return ( diff --git a/src/__swaps__/screens/Swap/hooks/useSwapOutputQuotesDisabled.ts b/src/__swaps__/screens/Swap/hooks/useSwapOutputQuotesDisabled.ts index cf3259ab0eb..58ad5d501df 100644 --- a/src/__swaps__/screens/Swap/hooks/useSwapOutputQuotesDisabled.ts +++ b/src/__swaps__/screens/Swap/hooks/useSwapOutputQuotesDisabled.ts @@ -1,10 +1,6 @@ import { SharedValue, useDerivedValue } from 'react-native-reanimated'; import { ExtendedAnimatedAssetWithColors } from '@/__swaps__/types/assets'; -import { - useBackendNetworksStore, - getSwapExactOutputSupportedChainIdsWorklet, - getBridgeExactOutputSupportedChainIdsWorklet, -} from '@/state/backendNetworks/backendNetworks'; +import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; export const useSwapOutputQuotesDisabled = ({ inputAsset, @@ -13,15 +9,16 @@ export const useSwapOutputQuotesDisabled = ({ inputAsset: SharedValue; outputAsset: SharedValue; }): SharedValue => { - const backendNetworks = useBackendNetworksStore(state => state.backendNetworksSharedValue); + const swapSupportedChainIds = useBackendNetworksStore(state => state.getSwapExactOutputSupportedChainIds()); + const bridgeSupportedChainIds = useBackendNetworksStore(state => state.getBridgeExactOutputSupportedChainIds()); const outputQuotesAreDisabled = useDerivedValue(() => { if (!inputAsset.value || !outputAsset.value) return false; if (inputAsset.value.chainId === outputAsset.value.chainId) { - return !getSwapExactOutputSupportedChainIdsWorklet(backendNetworks).includes(inputAsset.value.chainId); + return !swapSupportedChainIds.includes(inputAsset.value.chainId); } else { - return !getBridgeExactOutputSupportedChainIdsWorklet(backendNetworks).includes(inputAsset.value.chainId); + return !bridgeSupportedChainIds.includes(inputAsset.value.chainId); } }); diff --git a/src/__swaps__/screens/Swap/providers/swap-provider.tsx b/src/__swaps__/screens/Swap/providers/swap-provider.tsx index a1637a2dd40..09bb906f737 100644 --- a/src/__swaps__/screens/Swap/providers/swap-provider.tsx +++ b/src/__swaps__/screens/Swap/providers/swap-provider.tsx @@ -1,5 +1,5 @@ // @refresh -import React, { createContext, ReactNode, useCallback, useContext, useEffect, useRef } from 'react'; +import React, { createContext, ReactNode, useCallback, useContext, useEffect, useRef, useState } from 'react'; import { InteractionManager, NativeModules, StyleProp, TextInput, TextStyle } from 'react-native'; import { AnimatedRef, @@ -49,7 +49,6 @@ import { haptics } from '@/utils'; import { CrosschainQuote, Quote, QuoteError, SwapType } from '@rainbow-me/swaps'; import { IS_IOS } from '@/env'; -import { Address } from 'viem'; import { clearCustomGasSettings } from '../hooks/useCustomGas'; import { getGasSettingsBySpeed, getSelectedGas } from '../hooks/useSelectedGas'; import { useSwapOutputQuotesDisabled } from '../hooks/useSwapOutputQuotesDisabled'; @@ -57,7 +56,7 @@ import { SyncGasStateToSharedValues, SyncQuoteSharedValuesToState } from './Sync import { performanceTracking, Screens, TimeToSignOperation } from '@/state/performance/performance'; import { getRemoteConfig } from '@/model/remoteConfig'; import { useConnectedToAnvilStore } from '@/state/connectedToAnvil'; -import { useBackendNetworksStore, getChainsNativeAssetWorklet } from '@/state/backendNetworks/backendNetworks'; +import { useBackendNetworksStore } from '@/state/backendNetworks/backendNetworks'; import { getSwapsNavigationParams } from '../navigateToSwaps'; import { LedgerSigner } from '@/handlers/LedgerSigner'; import showWalletErrorAlert from '@/helpers/support'; @@ -154,7 +153,7 @@ const getInitialSliderXPosition = ({ export const SwapProvider = ({ children }: SwapProviderProps) => { const { nativeCurrency } = useAccountSettings(); - const backendNetworks = useBackendNetworksStore(state => state.backendNetworksSharedValue); + const [nativeChainAssets] = useState(useBackendNetworksStore.getState().getChainsNativeAsset()); const initialValues = getSwapsNavigationParams(); const isFetching = useSharedValue(false); @@ -787,7 +786,7 @@ export const SwapProvider = ({ children }: SwapProviderProps) => { } if (hasEnoughFundsForGas.value === false) { - const nativeCurrency = getChainsNativeAssetWorklet(backendNetworks)[sellAsset?.chainId || ChainId.mainnet]; + const nativeCurrency = nativeChainAssets[sellAsset?.chainId || ChainId.mainnet]; return { label: `${insufficient} ${nativeCurrency.symbol}`, disabled: true, diff --git a/src/state/backendNetworks/backendNetworks.ts b/src/state/backendNetworks/backendNetworks.ts index 43a74079195..2d9e1c8e257 100644 --- a/src/state/backendNetworks/backendNetworks.ts +++ b/src/state/backendNetworks/backendNetworks.ts @@ -1,22 +1,22 @@ -import { makeMutable, SharedValue } from 'react-native-reanimated'; +import isEqual from 'react-fast-compare'; +import { Chain } from 'viem/chains'; +import { IS_TEST } from '@/env'; import { queryClient } from '@/react-query'; import buildTimeNetworks from '@/references/networks.json'; import { backendNetworksQueryKey, BackendNetworksResponse } from '@/resources/metadata/backendNetworks'; -import { createRainbowStore } from '@/state/internal/createRainbowStore'; -import { Chain } from 'viem/chains'; import { transformBackendNetworksToChains } from '@/state/backendNetworks/utils'; -import { IS_TEST } from '@/env'; import { BackendNetwork, BackendNetworkServices, chainAnvil, chainAnvilOptimism, ChainId } from '@/state/backendNetworks/types'; -import { GasSpeed } from '@/__swaps__/types/gas'; import { useConnectedToAnvilStore } from '@/state/connectedToAnvil'; +import { createRainbowStore } from '@/state/internal/createRainbowStore'; import { colors as globalColors } from '@/styles'; +import { GasSpeed } from '@/__swaps__/types/gas'; +import { time } from '@/utils'; const INITIAL_BACKEND_NETWORKS = queryClient.getQueryData(backendNetworksQueryKey()) ?? buildTimeNetworks; -const DEFAULT_PRIVATE_MEMPOOL_TIMEOUT = 2 * 60 * 1_000; // 2 minutes +const DEFAULT_PRIVATE_MEMPOOL_TIMEOUT = time.minutes(2); export interface BackendNetworksState { backendNetworks: BackendNetworksResponse; - backendNetworksSharedValue: SharedValue; getBackendChains: () => Chain[]; getSupportedChains: () => Chain[]; @@ -66,137 +66,186 @@ export interface BackendNetworksState { setBackendNetworks: (backendNetworks: BackendNetworksResponse) => void; } -export const useBackendNetworksStore = createRainbowStore((set, get) => ({ - backendNetworks: INITIAL_BACKEND_NETWORKS, - backendNetworksSharedValue: makeMutable(INITIAL_BACKEND_NETWORKS), - - getBackendChains: () => { - const { backendNetworks } = get(); - return transformBackendNetworksToChains(backendNetworks.networks); - }, +let lastNetworks: BackendNetworksResponse | null = null; +let storeGetter: (() => BackendNetworksResponse) | null = null; - getSupportedChains: () => { - const backendChains = get().getBackendChains(); - return IS_TEST ? [...backendChains, chainAnvil, chainAnvilOptimism] : backendChains; - }, +function createSelector(selectorFn: (networks: BackendNetworksResponse) => () => T): () => T { + let cachedResult: T | undefined = undefined; + let memoizedFn: (() => T) | null = null; - getSortedSupportedChainIds: () => { - const supportedChains = get().getSupportedChains(); - return supportedChains.sort((a, b) => a.name.localeCompare(b.name)).map(c => c.id); - }, + return () => { + if (!storeGetter) storeGetter = () => useBackendNetworksStore.getState().backendNetworks; + const backendNetworks = storeGetter(); - getDefaultChains: () => { - const supportedChains = get().getSupportedChains(); - return supportedChains.reduce( - (acc, chain) => { - acc[chain.id] = chain; - return acc; - }, - {} as Record - ); - }, + if (cachedResult !== undefined && lastNetworks === backendNetworks) { + return cachedResult; + } - getSupportedChainIds: () => { - const supportedChains = get().getSupportedChains(); - return supportedChains.map(chain => chain.id); - }, + if (!memoizedFn || lastNetworks !== backendNetworks) { + memoizedFn = selectorFn(backendNetworks); + lastNetworks = backendNetworks; + } - getSupportedMainnetChains: () => { - const supportedChains = get().getSupportedChains(); - return supportedChains.filter(chain => !chain.testnet); - }, + cachedResult = memoizedFn(); + return cachedResult; + }; +} - getSupportedMainnetChainIds: () => { - const supportedMainnetChains = get().getSupportedMainnetChains(); - return supportedMainnetChains.map(chain => chain.id); - }, +function createParameterizedSelector( + selectorFn: (networks: BackendNetworksResponse) => (...args: Args) => T +): (...args: Args) => T { + let cachedResult: T | undefined = undefined; + let lastArgs: Args | null = null; + let memoizedFn: ((...args: Args) => T) | null = null; - getNeedsL1SecurityFeeChains: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks - .filter((backendNetwork: BackendNetwork) => backendNetwork.opStack) - .map((backendNetwork: BackendNetwork) => parseInt(backendNetwork.id, 10)); - }, + return (...args: Args) => { + if (!storeGetter) storeGetter = () => useBackendNetworksStore.getState().backendNetworks; + const backendNetworks = storeGetter(); + const argsChanged = !lastArgs || args.length !== lastArgs.length || args.some((arg, i) => arg !== lastArgs?.[i]); - getChainsNativeAsset: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.nativeAsset; - return acc; - }, - {} as Record - ); - }, + if (cachedResult !== undefined && lastNetworks === backendNetworks && !argsChanged) { + return cachedResult; + } - getChainsLabel: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.label; - return acc; - }, - {} as Record - ); - }, + if (!memoizedFn || lastNetworks !== backendNetworks) { + memoizedFn = selectorFn(backendNetworks); + lastNetworks = backendNetworks; + } - getChainsPrivateMempoolTimeout: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.privateMempoolTimeout || DEFAULT_PRIVATE_MEMPOOL_TIMEOUT; - return acc; - }, - {} as Record - ); - }, + lastArgs = args; + cachedResult = memoizedFn(...args); + return cachedResult; + }; +} - getChainsName: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.name; - return acc; - }, - {} as Record - ); - }, +export const useBackendNetworksStore = createRainbowStore((set, get) => ({ + backendNetworks: INITIAL_BACKEND_NETWORKS, - getChainsBadge: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.icons.badgeURL; - return acc; - }, - {} as Record - ); - }, + getBackendChains: createSelector(networks => () => transformBackendNetworksToChains(networks.networks)), - getChainsIdByName: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( - (acc, backendNetwork) => { - acc[backendNetwork.name] = parseInt(backendNetwork.id, 10); + getSupportedChains: createSelector(networks => () => { + const backendChains = transformBackendNetworksToChains(networks.networks); + return IS_TEST ? [...backendChains, chainAnvil, chainAnvilOptimism] : backendChains; + }), + + getSortedSupportedChainIds: createSelector(networks => () => { + const chains = transformBackendNetworksToChains(networks.networks); + const allChains = IS_TEST ? [...chains, chainAnvil, chainAnvilOptimism] : chains; + return allChains.sort((a, b) => a.name.localeCompare(b.name)).map(c => c.id); + }), + + getDefaultChains: createSelector(networks => () => { + const chains = transformBackendNetworksToChains(networks.networks); + const allChains = IS_TEST ? [...chains, chainAnvil, chainAnvilOptimism] : chains; + return allChains.reduce( + (acc, chain) => { + acc[chain.id] = chain; return acc; }, - {} as Record + {} as Record ); - }, - - getColorsForChainId: (chainId: ChainId, isDarkMode: boolean) => { - const { backendNetworks } = get(); - - const colors = backendNetworks.networks.find(chain => +chain.id === chainId)?.colors; + }), + + getSupportedChainIds: createSelector(networks => () => { + const chains = transformBackendNetworksToChains(networks.networks); + const allChains = IS_TEST ? [...chains, chainAnvil, chainAnvilOptimism] : chains; + return allChains.map(chain => chain.id); + }), + + getSupportedMainnetChains: createSelector(networks => () => { + const chains = transformBackendNetworksToChains(networks.networks); + const allChains = IS_TEST ? [...chains, chainAnvil, chainAnvilOptimism] : chains; + return allChains.filter(chain => !chain.testnet); + }), + + getSupportedMainnetChainIds: createSelector(networks => () => { + const chains = transformBackendNetworksToChains(networks.networks); + const allChains = IS_TEST ? [...chains, chainAnvil, chainAnvilOptimism] : chains; + return allChains.filter(chain => !chain.testnet).map(chain => chain.id); + }), + + getNeedsL1SecurityFeeChains: createSelector( + networks => () => + networks.networks + .filter((backendNetwork: BackendNetwork) => backendNetwork.opStack) + .map((backendNetwork: BackendNetwork) => parseInt(backendNetwork.id, 10)) + ), + + getChainsNativeAsset: createSelector( + networks => () => + networks.networks.reduce( + (acc, backendNetwork) => { + acc[parseInt(backendNetwork.id, 10)] = backendNetwork.nativeAsset; + return acc; + }, + {} as Record + ) + ), + + getChainsLabel: createSelector( + networks => () => + networks.networks.reduce( + (acc, backendNetwork) => { + acc[parseInt(backendNetwork.id, 10)] = backendNetwork.label; + return acc; + }, + {} as Record + ) + ), + + getChainsPrivateMempoolTimeout: createSelector( + networks => () => + networks.networks.reduce( + (acc, backendNetwork) => { + acc[parseInt(backendNetwork.id, 10)] = backendNetwork.privateMempoolTimeout || DEFAULT_PRIVATE_MEMPOOL_TIMEOUT; + return acc; + }, + {} as Record + ) + ), + + getChainsName: createSelector( + networks => () => + networks.networks.reduce( + (acc, backendNetwork) => { + acc[parseInt(backendNetwork.id, 10)] = backendNetwork.name; + return acc; + }, + {} as Record + ) + ), + + getChainsBadge: createSelector( + networks => () => + networks.networks.reduce( + (acc, backendNetwork) => { + acc[parseInt(backendNetwork.id, 10)] = backendNetwork.icons.badgeURL; + return acc; + }, + {} as Record + ) + ), + + getChainsIdByName: createSelector( + networks => () => + networks.networks.reduce( + (acc, backendNetwork) => { + acc[backendNetwork.name] = parseInt(backendNetwork.id, 10); + return acc; + }, + {} as Record + ) + ), + + getColorsForChainId: createParameterizedSelector(networks => (chainId: ChainId, isDarkMode: boolean) => { + const colors = networks.networks.find(chain => +chain.id === chainId)?.colors; if (!colors) { return isDarkMode ? globalColors.white : globalColors.black; } - return isDarkMode ? colors.dark : colors.light; - }, + }), - // TODO: This should come from the backend at some point - defaultGasSpeeds: chainId => { + defaultGasSpeeds: createParameterizedSelector(() => (chainId: ChainId) => { switch (chainId) { case ChainId.bsc: case ChainId.goerli: @@ -207,20 +256,31 @@ export const useBackendNetworksStore = createRainbowStore( default: return [GasSpeed.NORMAL, GasSpeed.FAST, GasSpeed.URGENT, GasSpeed.CUSTOM]; } - }, + }), - getChainsGasSpeeds: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( + getChainsGasSpeeds: createSelector(networks => () => { + return networks.networks.reduce( (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = get().defaultGasSpeeds(parseInt(backendNetwork.id, 10)); + const chainId = parseInt(backendNetwork.id, 10); + switch (chainId) { + case ChainId.bsc: + case ChainId.goerli: + case ChainId.polygon: + acc[chainId] = [GasSpeed.NORMAL, GasSpeed.FAST, GasSpeed.URGENT]; + break; + case ChainId.gnosis: + acc[chainId] = [GasSpeed.NORMAL]; + break; + default: + acc[chainId] = [GasSpeed.NORMAL, GasSpeed.FAST, GasSpeed.URGENT, GasSpeed.CUSTOM]; + } return acc; }, {} as Record ); - }, + }), - defaultPollingInterval: chainId => { + defaultPollingInterval: createParameterizedSelector(() => (chainId: ChainId) => { switch (chainId) { case ChainId.polygon: return 2_000; @@ -230,21 +290,30 @@ export const useBackendNetworksStore = createRainbowStore( default: return 5_000; } - }, + }), - getChainsPollingInterval: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( + getChainsPollingInterval: createSelector(networks => () => { + return networks.networks.reduce( (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = get().defaultPollingInterval(parseInt(backendNetwork.id, 10)); + const chainId = parseInt(backendNetwork.id, 10); + switch (chainId) { + case ChainId.polygon: + acc[chainId] = 2_000; + break; + case ChainId.arbitrum: + case ChainId.bsc: + acc[chainId] = 3_000; + break; + default: + acc[chainId] = 5_000; + } return acc; }, {} as Record ); - }, + }), - // TODO: This should come from the backend - defaultSimplehashNetwork: chainId => { + defaultSimplehashNetwork: createParameterizedSelector(() => (chainId: ChainId) => { switch (chainId) { case ChainId.apechain: return 'apechain'; @@ -264,42 +333,60 @@ export const useBackendNetworksStore = createRainbowStore( return 'gnosis'; case ChainId.goerli: return 'ethereum-goerli'; - // case ChainId.gravity: // FIXME: Unsupported as of now https://docs.simplehash.com/reference/supported-chains-testnets#mainnets - // return 'gravity'; - // case ChainId.ink: // FIXME: Unsupported as of now https://docs.simplehash.com/reference/supported-chains-testnets#mainnets - // return 'ink'; case ChainId.mainnet: return 'ethereum'; case ChainId.optimism: return 'optimism'; case ChainId.polygon: return 'polygon'; - // case ChainId.sanko: // FIXME: Unsupported as of now https://docs.simplehash.com/reference/supported-chains-testnets#mainnets - // return 'sanko'; - case ChainId.scroll: - return 'scroll'; - case ChainId.zksync: - return 'zksync-era'; case ChainId.zora: return 'zora'; - case ChainId.linea: - return 'linea'; - default: return ''; } - }, + }), - getChainsSimplehashNetwork: () => { - const backendNetworks = get().backendNetworks; - return backendNetworks.networks.reduce( + getChainsSimplehashNetwork: createSelector(networks => () => { + return networks.networks.reduce( (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = get().defaultSimplehashNetwork(parseInt(backendNetwork.id, 10)); + const chainId = parseInt(backendNetwork.id, 10); + acc[chainId] = (() => { + switch (chainId) { + case ChainId.apechain: + return 'apechain'; + case ChainId.arbitrum: + return 'arbitrum'; + case ChainId.avalanche: + return 'avalanche'; + case ChainId.base: + return 'base'; + case ChainId.blast: + return 'blast'; + case ChainId.bsc: + return 'bsc'; + case ChainId.degen: + return 'degen'; + case ChainId.gnosis: + return 'gnosis'; + case ChainId.goerli: + return 'ethereum-goerli'; + case ChainId.mainnet: + return 'ethereum'; + case ChainId.optimism: + return 'optimism'; + case ChainId.polygon: + return 'polygon'; + case ChainId.zora: + return 'zora'; + default: + return ''; + } + })(); return acc; }, {} as Record ); - }, + }), filterChainIdsByService: servicePath => { const backendNetworks = get().backendNetworks; @@ -358,9 +445,8 @@ export const useBackendNetworksStore = createRainbowStore( return [ChainId.mainnet, ChainId.polygon, ChainId.goerli]; }, - getChainGasUnits: chainId => { - const backendNetworks = get().backendNetworks; - const chainsGasUnits = backendNetworks.networks.reduce( + getChainGasUnits: createParameterizedSelector(networks => (chainId?: ChainId) => { + const chainsGasUnits = networks.networks.reduce( (acc, backendNetwork: BackendNetwork) => { acc[parseInt(backendNetwork.id, 10)] = backendNetwork.gasUnits; return acc; @@ -369,7 +455,7 @@ export const useBackendNetworksStore = createRainbowStore( ); return (chainId ? chainsGasUnits[chainId] : undefined) || chainsGasUnits[ChainId.mainnet]; - }, + }), getChainDefaultRpc: chainId => { const defaultChains = get().getDefaultChains(); @@ -385,308 +471,7 @@ export const useBackendNetworksStore = createRainbowStore( setBackendNetworks: backendNetworks => set(state => { - state.backendNetworksSharedValue.value = backendNetworks; - return { - ...state, - backendNetworks: backendNetworks, - }; + if (isEqual(state.backendNetworks, backendNetworks)) return state; + return { backendNetworks }; }), })); - -// ------ WORKLET FUNCTIONS ------ - -export const getBackendChainsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return transformBackendNetworksToChains(backendNetworks.value.networks); -}; - -export const getSupportedChainsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - const backendChains = getBackendChainsWorklet(backendNetworks); - return IS_TEST ? [...backendChains, chainAnvil, chainAnvilOptimism] : backendChains; -}; - -export const getDefaultChainsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - const supportedChains = getSupportedChainsWorklet(backendNetworks); - return supportedChains.reduce( - (acc, chain) => { - acc[chain.id] = chain; - return acc; - }, - {} as Record - ); -}; - -export const getSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - const supportedChains = getSupportedChainsWorklet(backendNetworks); - return supportedChains.map(chain => chain.id); -}; - -export const getSupportedMainnetChainsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - const supportedChains = getSupportedChainsWorklet(backendNetworks); - return supportedChains.filter(chain => !chain.testnet); -}; - -export const getSupportedMainnetChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - const supportedMainnetChains = getSupportedMainnetChainsWorklet(backendNetworks); - return supportedMainnetChains.map(chain => chain.id); -}; - -export const getNeedsL1SecurityFeeChainsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks - .filter((backendNetwork: BackendNetwork) => backendNetwork.opStack) - .map((backendNetwork: BackendNetwork) => parseInt(backendNetwork.id, 10)); -}; - -export const getChainsNativeAssetWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.nativeAsset; - return acc; - }, - {} as Record - ); -}; - -export const getChainsLabelWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork: BackendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.label; - return acc; - }, - {} as Record - ); -}; - -export const getChainsNameWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork: BackendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.name; - return acc; - }, - {} as Record - ); -}; - -export const getChainsBadgeWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.icons.badgeURL; - return acc; - }, - {} as Record - ); -}; - -export const getChainsIdByNameWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork) => { - acc[backendNetwork.name] = parseInt(backendNetwork.id, 10); - return acc; - }, - {} as Record - ); -}; - -export const defaultGasSpeedsWorklet = (chainId: ChainId) => { - 'worklet'; - switch (chainId) { - case ChainId.bsc: - case ChainId.goerli: - case ChainId.polygon: - return [GasSpeed.NORMAL, GasSpeed.FAST, GasSpeed.URGENT]; - case ChainId.gnosis: - return [GasSpeed.NORMAL]; - default: - return [GasSpeed.NORMAL, GasSpeed.FAST, GasSpeed.URGENT, GasSpeed.CUSTOM]; - } -}; - -export const getChainsGasSpeedsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = defaultGasSpeedsWorklet(parseInt(backendNetwork.id, 10)); - return acc; - }, - {} as Record - ); -}; - -export const defaultPollingIntervalWorklet = (chainId: ChainId) => { - 'worklet'; - switch (chainId) { - case ChainId.polygon: - return 2_000; - case ChainId.arbitrum: - case ChainId.bsc: - return 3_000; - default: - return 5_000; - } -}; - -export const getChainsPollingIntervalWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = defaultPollingIntervalWorklet(parseInt(backendNetwork.id, 10)); - return acc; - }, - {} as Record - ); -}; - -export const defaultSimplehashNetworkWorklet = (chainId: ChainId) => { - 'worklet'; - switch (chainId) { - case ChainId.apechain: - return 'apechain'; - case ChainId.arbitrum: - return 'arbitrum'; - case ChainId.avalanche: - return 'avalanche'; - case ChainId.base: - return 'base'; - case ChainId.blast: - return 'blast'; - case ChainId.bsc: - return 'bsc'; - case ChainId.degen: - return 'degen'; - case ChainId.gnosis: - return 'gnosis'; - case ChainId.goerli: - return 'ethereum-goerli'; - case ChainId.mainnet: - return 'ethereum'; - case ChainId.optimism: - return 'optimism'; - case ChainId.polygon: - return 'polygon'; - case ChainId.zora: - return 'zora'; - default: - return ''; - } -}; - -export const getChainsSimplehashNetworkWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return backendNetworks.value.networks.reduce( - (acc, backendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = defaultSimplehashNetworkWorklet(parseInt(backendNetwork.id, 10)); - return acc; - }, - {} as Record - ); -}; - -export const filterChainIdsByServiceWorklet = ( - backendNetworks: SharedValue, - servicePath: (services: BackendNetworkServices) => boolean -) => { - 'worklet'; - return backendNetworks.value.networks.filter(network => servicePath(network.enabledServices)).map(network => parseInt(network.id, 10)); -}; - -export const getMeteorologySupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.meteorology.enabled); -}; - -export const getSwapSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.swap.enabled); -}; - -export const getSwapExactOutputSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.swap.swapExactOutput); -}; - -export const getBridgeExactOutputSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.swap.bridgeExactOutput); -}; - -export const getNotificationsSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.notifications.enabled); -}; - -export const getApprovalsSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.addys.approvals); -}; - -export const getTransactionsSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.addys.transactions); -}; - -export const getSupportedAssetsChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.addys.assets); -}; - -export const getSupportedPositionsChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.addys.positions); -}; - -export const getTokenSearchSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.tokenSearch.enabled); -}; - -export const getNftSupportedChainIdsWorklet = (backendNetworks: SharedValue) => { - 'worklet'; - return filterChainIdsByServiceWorklet(backendNetworks, services => services.nftProxy.enabled); -}; - -export const getFlashbotsSupportedChainIdsWorklet = (_?: SharedValue) => { - 'worklet'; - return [ChainId.mainnet]; -}; - -export const getShouldDefaultToFastGasChainIdsWorklet = (_?: SharedValue) => { - 'worklet'; - return [ChainId.mainnet, ChainId.polygon, ChainId.goerli]; -}; - -export const getChainGasUnitsWorklet = (backendNetworks: SharedValue, chainId?: ChainId) => { - 'worklet'; - const chainsGasUnits = backendNetworks.value.networks.reduce( - (acc, backendNetwork: BackendNetwork) => { - acc[parseInt(backendNetwork.id, 10)] = backendNetwork.gasUnits; - return acc; - }, - {} as Record - ); - - return (chainId ? chainsGasUnits[chainId] : undefined) || chainsGasUnits[ChainId.mainnet]; -}; - -export const getChainDefaultRpcWorklet = (backendNetworks: SharedValue, chainId: ChainId) => { - 'worklet'; - const defaultChains = getDefaultChainsWorklet(backendNetworks); - switch (chainId) { - case ChainId.mainnet: - return useConnectedToAnvilStore.getState().connectedToAnvil - ? 'http://127.0.0.1:8545' - : defaultChains[ChainId.mainnet].rpcUrls.default.http[0]; - default: - return defaultChains[chainId].rpcUrls.default.http[0]; - } -};