diff --git a/apps/web/src/components/NavBar/PoolSelect/index.tsx b/apps/web/src/components/NavBar/PoolSelect/index.tsx index 9befef6dd65..5d8959d1bf2 100644 --- a/apps/web/src/components/NavBar/PoolSelect/index.tsx +++ b/apps/web/src/components/NavBar/PoolSelect/index.tsx @@ -4,6 +4,7 @@ import CurrencySearchModal from 'components/SearchModal/CurrencySearchModal' import styled from 'lib/styled-components' import React, { useCallback, useEffect, useState } from 'react'; import { useActiveSmartPool, useSelectActiveSmartPool } from 'state/application/hooks'; +import { PoolWithChain } from 'state/application/reducer'; import { CurrencyInfo } from 'uniswap/src/features/dataApi/types'; const PoolSelectButton = styled(ButtonGray)<{ @@ -65,7 +66,7 @@ const StyledTokenName = styled.span<{ active?: boolean }>` `; interface PoolSelectProps { - operatedPools: Token[]; + operatedPools: PoolWithChain[]; } const PoolSelect: React.FC = ({ operatedPools }) => { @@ -73,37 +74,58 @@ const PoolSelect: React.FC = ({ operatedPools }) => { const activeSmartPool = useActiveSmartPool(); const onPoolSelect = useSelectActiveSmartPool(); - // on chain switch revert to default pool if selected does not exist on new chain - const activePoolExistsOnChain = operatedPools?.some(pool => pool.address === activeSmartPool?.address); + // Create a map for quick chainId lookup by address + const poolChainMap = React.useMemo(() => + new Map(operatedPools.map(pool => [pool.address, pool.chainId])), + [operatedPools] + ); + + // Convert PoolWithChain[] to Token[] for display + const poolsAsTokens = React.useMemo(() => + operatedPools.map((pool) => + new Token(pool.chainId, pool.address, pool.decimals, pool.symbol, pool.name) + ), + [operatedPools] + ); + + // on chain switch revert to default pool if selected does not exist + const activePoolExistsInList = operatedPools?.some(pool => pool.address === activeSmartPool?.address); // initialize selected pool - use ref to prevent re-initialization const hasInitialized = React.useRef(false); useEffect(() => { - if (!hasInitialized.current && (!activeSmartPool?.name || !activePoolExistsOnChain)) { - onPoolSelect(operatedPools[0]); + if (!hasInitialized.current && (!activeSmartPool?.name || !activePoolExistsInList)) { + if (poolsAsTokens.length > 0 && operatedPools.length > 0) { + const firstPool = operatedPools[0]; + onPoolSelect(poolsAsTokens[0], firstPool.chainId); + } hasInitialized.current = true; } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activePoolExistsOnChain, activeSmartPool?.name]) + }, [activePoolExistsInList, activeSmartPool?.name, poolsAsTokens, operatedPools, onPoolSelect]) // Memoize poolsAsCurrrencies to prevent recreation on every render const poolsAsCurrrencies = React.useMemo(() => - operatedPools.map((pool: Token) => ({ + poolsAsTokens.map((pool: Token) => ({ currency: pool, currencyId: pool.address, safetyLevel: null, safetyInfo: null, spamCode: null, logoUrl: null, - isSpam: null + isSpam: null, + // Store chainId using our map for safer access + chainId: poolChainMap.get(pool.address), })) as CurrencyInfo[] - , [operatedPools]); + , [poolsAsTokens, poolChainMap]); const handleSelectPool = useCallback((pool: Currency) => { - onPoolSelect(pool); + // Find the chain ID for the selected pool using our map + const poolAddress = pool.isToken ? pool.address : ''; + const chainId = poolChainMap.get(poolAddress); + onPoolSelect(pool, chainId); setShowModal(false); - }, [onPoolSelect]); + }, [onPoolSelect, poolChainMap]); return ( <> diff --git a/apps/web/src/components/NavBar/index.tsx b/apps/web/src/components/NavBar/index.tsx index 60137c82cc5..c0869964ad2 100644 --- a/apps/web/src/components/NavBar/index.tsx +++ b/apps/web/src/components/NavBar/index.tsx @@ -16,8 +16,8 @@ import { PageType, useIsPage } from 'hooks/useIsPage' import deprecatedStyled, { css } from 'lib/styled-components' import { useProfilePageState } from 'nft/hooks' import { ProfilePageStateType } from 'nft/types' -import { useEffect, useMemo, useRef } from 'react' -import { useOperatedPools } from 'state/pool/hooks' +import { useMemo } from 'react' +import { useInitializeMultiChainPools, useOperatedPoolsMultiChain } from 'state/pool/hooks' import { Flex, Nav as TamaguiNav, styled, useMedia } from 'ui/src' import { INTERFACE_NAV_HEIGHT, breakpoints, zIndexes } from 'ui/src/theme' import { useEnabledChains } from 'uniswap/src/features/chains/hooks/useEnabledChains' @@ -140,27 +140,15 @@ export default function Navbar() { const isSignInExperimentControl = !isEmbeddedWalletEnabled && isControl const shouldDisplayCreateAccountButton = false - const rawOperatedPools = useOperatedPools() - const cachedPoolsRef = useRef(undefined) - const prevChainIdRef = useRef(account.chainId) - useEffect(() => { - if (account.chainId !== prevChainIdRef.current) { - cachedPoolsRef.current = undefined - prevChainIdRef.current = account.chainId - } - }, [account.chainId]) + // Initialize multi-chain pools + useInitializeMultiChainPools() - useEffect(() => { - if (rawOperatedPools && rawOperatedPools.length > 0) { - cachedPoolsRef.current = rawOperatedPools - } - }, [rawOperatedPools]) - - const cachedOperatedPools = cachedPoolsRef.current ?? rawOperatedPools - const cachedUserIsOperator = useMemo(() => - Boolean(cachedOperatedPools && cachedOperatedPools.length > 0), - [cachedOperatedPools] + // Get all operated pools from state (no chain filtering) + const operatedPools = useOperatedPoolsMultiChain() + const userIsOperator = useMemo(() => + Boolean(operatedPools && operatedPools.length > 0), + [operatedPools] ) return ( @@ -168,13 +156,13 @@ export default function Navbar() { - {areTabsVisible && } + {areTabsVisible && } - {!collapseSearchBar && cachedOperatedPools && cachedOperatedPools.length > 0 && ( + {!collapseSearchBar && operatedPools && operatedPools.length > 0 && ( - + )} {!collapseSearchBar && ( @@ -188,9 +176,9 @@ export default function Navbar() { {collapseSearchBar && ( - {cachedOperatedPools && cachedOperatedPools.length > 0 && ( + {operatedPools && operatedPools.length > 0 && ( - + )} {!hideChainSelector && } diff --git a/apps/web/src/index.tsx b/apps/web/src/index.tsx index 9e932c3bd1e..fcf98c49bc7 100644 --- a/apps/web/src/index.tsx +++ b/apps/web/src/index.tsx @@ -15,6 +15,7 @@ import { useAccount } from 'hooks/useAccount' import { LanguageProvider } from 'i18n/LanguageProvider' import { BlockNumberProvider } from 'lib/hooks/useBlockNumber' import { MulticallUpdater } from 'lib/state/multicall' +import { PoolMulticallUpdater } from 'lib/state/multicallForPools' import App from 'pages/App' import { PropsWithChildren, StrictMode, useEffect, useMemo } from 'react' import { createRoot } from 'react-dom/client' @@ -74,6 +75,7 @@ function Updaters() { + diff --git a/apps/web/src/lib/hooks/multicallForPools.ts b/apps/web/src/lib/hooks/multicallForPools.ts new file mode 100644 index 00000000000..e9cec3420ee --- /dev/null +++ b/apps/web/src/lib/hooks/multicallForPools.ts @@ -0,0 +1,33 @@ +import { usePoolCallContext } from 'lib/hooks/usePoolCallContext' +import poolMulticall from 'lib/state/multicallForPools' +import { SkipFirst } from 'types/tuple' + +export { NEVER_RELOAD } from '@uniswap/redux-multicall' // re-export for convenience +export type { CallStateResult } from '@uniswap/redux-multicall' // re-export for convenience + +// Create wrappers for pool-specific multicall hooks that use a separate multicall instance +// dedicated to pool queries. This instance uses the wallet's chainId and is not affected +// by multicallUpdaterSwapChainId from the swap context. +// +// This prevents pool queries from being invalidated when users swap tokens on different chains. + +type SkipFirstTwoParams any> = SkipFirst, 2> + +export function useMultipleContractSingleDataForPools( + ...args: SkipFirstTwoParams +) { + const { chainId, latestBlock } = usePoolCallContext() + return poolMulticall.hooks.useMultipleContractSingleData(chainId, latestBlock, ...args) +} + +export function useSingleCallResultForPools(...args: SkipFirstTwoParams) { + const { chainId, latestBlock } = usePoolCallContext() + return poolMulticall.hooks.useSingleCallResult(chainId, latestBlock, ...args) +} + +export function useSingleContractMultipleDataForPools( + ...args: SkipFirstTwoParams +) { + const { chainId, latestBlock } = usePoolCallContext() + return poolMulticall.hooks.useSingleContractMultipleData(chainId, latestBlock, ...args) +} diff --git a/apps/web/src/lib/hooks/usePoolCallContext.ts b/apps/web/src/lib/hooks/usePoolCallContext.ts new file mode 100644 index 00000000000..481dfe101d2 --- /dev/null +++ b/apps/web/src/lib/hooks/usePoolCallContext.ts @@ -0,0 +1,34 @@ +import { useAccount } from 'hooks/useAccount' +import { useContext } from 'react' +import { BlockNumberContext } from 'lib/hooks/useBlockNumber' + +/** + * Get block number for the wallet's actual chainId. + * This accesses the BlockNumberContext which tracks blocks independently, + * so we get the current block for whatever chain is active in the context. + */ +function useWalletBlockNumber(): number | undefined { + const blockNumberContext = useContext(BlockNumberContext) + if (!blockNumberContext) { + return undefined + } + + // Return the current block number from context + // The PoolMulticallUpdater ensures this is for the wallet's actual chainId + const latestBlockNumber = typeof blockNumberContext === 'object' ? blockNumberContext?.block : undefined + return latestBlockNumber +} + +/** + * Hook to provide call context for pool-related multicall queries. + * This is used by pool-specific multicall hooks to ensure they always query + * the wallet's actual chainId, not the swap context's chainId. + * + * The key is that pool queries use PoolMulticallUpdater which never looks at + * multicallUpdaterSwapChainId, so pool data remains stable when swapping across chains. + */ +export function usePoolCallContext() { + const account = useAccount() + const latestBlock = useWalletBlockNumber() + return { chainId: account.chainId, latestBlock } +} diff --git a/apps/web/src/lib/state/multicallForPools.tsx b/apps/web/src/lib/state/multicallForPools.tsx new file mode 100644 index 00000000000..7ae82009a46 --- /dev/null +++ b/apps/web/src/lib/state/multicallForPools.tsx @@ -0,0 +1,44 @@ +import { createMulticall, ListenerOptions } from '@uniswap/redux-multicall' +import { useAccount } from 'hooks/useAccount' +import { useInterfaceMulticall } from 'hooks/useContract' +import { useContext, useMemo } from 'react' +import { BlockNumberContext } from 'lib/hooks/useBlockNumber' +import { getChainInfo } from 'uniswap/src/features/chains/chainInfo' + +// Create a separate multicall instance specifically for pool queries +// This is independent from the main multicall system and won't be affected +// by chain switches in the swap context +const poolMulticall = createMulticall() + +export default poolMulticall + +/** + * Multicall updater specifically for pool queries. + * Unlike the main MulticallUpdater, this one ONLY uses the wallet's actual chainId + * and is not affected by multicallUpdaterSwapChainId from the swap context. + * + * This prevents pool queries from being invalidated when users swap tokens on different chains. + */ +export function PoolMulticallUpdater() { + const account = useAccount() + // CRITICAL: Only use account.chainId, never use multicallUpdaterSwapChainId + const chainId = account.chainId + + // Get block number from context + const blockNumberContext = useContext(BlockNumberContext) + const latestBlockNumber = typeof blockNumberContext === 'object' ? blockNumberContext?.block : undefined + const contract = useInterfaceMulticall(chainId) + const listenerOptions: ListenerOptions = useMemo( + () => ({ blocksPerFetch: chainId ? getChainInfo(chainId).blockPerMainnetEpochForChainId : 1 }), + [chainId], + ) + + return ( + + ) +} diff --git a/apps/web/src/pages/CreatePool/index.tsx b/apps/web/src/pages/CreatePool/index.tsx index a8159421f20..46f64cf424c 100644 --- a/apps/web/src/pages/CreatePool/index.tsx +++ b/apps/web/src/pages/CreatePool/index.tsx @@ -11,7 +11,7 @@ import styled from 'lib/styled-components' import { Trans } from 'react-i18next' import { useCloseModal, useModalIsOpen, useToggleCreateModal } from 'state/application/hooks' import { ApplicationModal } from 'state/application/reducer' -import { useAllPoolsData } from 'state/pool/hooks' +import { useInitializeMultiChainPools, useOperatedPoolsDataFromCache } from 'state/pool/hooks' import { ThemedText } from 'theme/components/text' import Trace from 'uniswap/src/features/telemetry/Trace' @@ -74,7 +74,12 @@ export default function CreatePool() { const open = useModalIsOpen(ApplicationModal.CREATE) const closeModal = useCloseModal(ApplicationModal.CREATE) const toggleCreateModal = useToggleCreateModal() - const { data: allPools } = useAllPoolsData() + + // Initialize multi-chain pools + useInitializeMultiChainPools() + + // Use cached operated pools instead of querying + const { data: allPools } = useOperatedPoolsDataFromCache() return ( diff --git a/apps/web/src/pages/Stake/index.tsx b/apps/web/src/pages/Stake/index.tsx index b6562f059b6..423960eff16 100644 --- a/apps/web/src/pages/Stake/index.tsx +++ b/apps/web/src/pages/Stake/index.tsx @@ -14,7 +14,7 @@ import { Trans } from 'react-i18next' import JSBI from 'jsbi' import { useMemo, useState } from 'react' import InfiniteScroll from 'react-infinite-scroll-component' -import { PoolRegisteredLog, useAllPoolsData, useStakingPools } from 'state/pool/hooks' +import { PoolRegisteredLog, useAllPoolsDataStable, useInitializeMultiChainPools, useStakingPools } from 'state/pool/hooks' import { useFreeStakeBalance, useUnclaimedRewards, useUserStakeBalances } from 'state/stake/hooks' import styled from 'lib/styled-components' import { ThemedText } from 'theme/components/text' @@ -86,8 +86,11 @@ export default function Stake() { const [hasMore, setHasMore] = useState(true) const [records, setRecords] = useState(itemsPerPage) - // we retrieve logs again as we want to be able to load pools when switching chain from stake page. - const { data: allPools } = useAllPoolsData() + // Initialize multi-chain pools to ensure pools persist across chain switches + useInitializeMultiChainPools() + + // Get all registered pools with stable chainId (won't break on swap chain changes) + const { data: allPools } = useAllPoolsDataStable() const account = useAccount() const accountDrawer = useAccountDrawer() diff --git a/apps/web/src/state/application/hooks.tsx b/apps/web/src/state/application/hooks.tsx index 6b632989125..0e4f47bca4b 100644 --- a/apps/web/src/state/application/hooks.tsx +++ b/apps/web/src/state/application/hooks.tsx @@ -5,16 +5,21 @@ import { ApplicationModal, CloseModalParams, OpenModalParams, + PoolWithChain, addSuppressedPopups, removeSuppressedPopups, setCloseModal, setOpenModal, + setOperatedPools, setSmartPoolValue, } from 'state/application/reducer' import { useAppDispatch, useAppSelector } from 'state/hooks' import { InterfaceState } from 'state/webReducer' import { ModalNameType } from 'uniswap/src/features/telemetry/constants' +// Re-export PoolWithChain for external use +export type { PoolWithChain } from 'state/application/reducer' + export function useModalIsOpen(modal: ApplicationModal | ModalNameType): boolean { const openModal = useAppSelector((state: InterfaceState) => state.application.openModal?.name) return openModal === modal @@ -81,15 +86,16 @@ export function useTogglePrivacyPolicy(): () => void { return useToggleModal(ApplicationModal.PRIVACY_POLICY) } -export function useSelectActiveSmartPool(): (smartPoolValue?: Currency) => void { +export function useSelectActiveSmartPool(): (smartPoolValue?: Currency, chainId?: number) => void { const dispatch = useAppDispatch() return useCallback( - (smartPoolValue?: Currency) => { + (smartPoolValue?: Currency, chainId?: number) => { dispatch( setSmartPoolValue({ smartPool: { address: smartPoolValue?.isToken ? smartPoolValue.address : undefined, name: smartPoolValue?.isToken && smartPoolValue.name ? smartPoolValue.name : undefined, + chainId, }, }) ) @@ -98,6 +104,20 @@ export function useSelectActiveSmartPool(): (smartPoolValue?: Currency) => void ) } +export function useSetOperatedPools(): (pools: PoolWithChain[]) => void { + const dispatch = useAppDispatch() + return useCallback( + (pools: PoolWithChain[]) => { + dispatch(setOperatedPools(pools)) + }, + [dispatch] + ) +} + +export function useOperatedPoolsFromState(): PoolWithChain[] { + return useAppSelector((state: InterfaceState) => state.application.operatedPools) +} + // returns functions to suppress and unsuppress popups by type export function useSuppressPopups(popupTypes: PopupType[]): { suppressPopups: () => void diff --git a/apps/web/src/state/application/reducer.test.ts b/apps/web/src/state/application/reducer.test.ts index 2fbfcc2d618..c4f35bbc412 100644 --- a/apps/web/src/state/application/reducer.test.ts +++ b/apps/web/src/state/application/reducer.test.ts @@ -17,6 +17,7 @@ describe('application reducer', () => { openModal: null, smartPool: { address: null, name: '' }, suppressedPopups: [], + operatedPools: [], }) }) diff --git a/apps/web/src/state/application/reducer.ts b/apps/web/src/state/application/reducer.ts index f9510ff2b31..a703d449973 100644 --- a/apps/web/src/state/application/reducer.ts +++ b/apps/web/src/state/application/reducer.ts @@ -50,18 +50,30 @@ export type OpenModalParams = export type CloseModalParams = ModalNameType | ApplicationModal +export interface PoolWithChain { + address: string + chainId: number + name: string + symbol: string + decimals: number + id: string + group?: string +} + export interface ApplicationState { readonly chainId: number | null readonly openModal: OpenModalParams | null - readonly smartPool: { address?: string | null; name: string | null } + readonly smartPool: { address?: string | null; name: string | null; chainId?: number | null } readonly suppressedPopups: PopupType[] + readonly operatedPools: PoolWithChain[] } const initialState: ApplicationState = { chainId: null, openModal: null, - smartPool: { address: null, name: '' }, + smartPool: { address: null, name: '', chainId: null }, suppressedPopups: [], + operatedPools: [], } const applicationSlice = createSlice({ @@ -76,6 +88,10 @@ const applicationSlice = createSlice({ const { smartPool } = action.payload state.smartPool.address = smartPool.address state.smartPool.name = smartPool.name + state.smartPool.chainId = smartPool.chainId + }, + setOperatedPools(state, action: PayloadAction) { + state.operatedPools = action.payload }, setOpenModal(state, action: PayloadAction) { state.openModal = action.payload @@ -102,5 +118,6 @@ export const { setSmartPoolValue, addSuppressedPopups, removeSuppressedPopups, + setOperatedPools, } = applicationSlice.actions export default applicationSlice.reducer diff --git a/apps/web/src/state/pool/hooks.ts b/apps/web/src/state/pool/hooks.ts index 5dd8cf93410..48e83dbd0ae 100644 --- a/apps/web/src/state/pool/hooks.ts +++ b/apps/web/src/state/pool/hooks.ts @@ -12,11 +12,15 @@ import { useAccount } from 'hooks/useAccount' import { useContract } from 'hooks/useContract' import usePrevious from 'hooks/usePrevious' import { useTotalSupply } from 'hooks/useTotalSupply' -import { CallStateResult, useMultipleContractSingleData, useSingleContractMultipleData } from 'lib/hooks/multicall' +import { CallStateResult } from 'lib/hooks/multicall' +import { + useMultipleContractSingleDataForPools as useMultipleContractSingleData, + useSingleContractMultipleDataForPools as useSingleContractMultipleData, +} from 'lib/hooks/multicallForPools' //import useBlockNumber from 'lib/hooks/useBlockNumber' -import { useCallback, useEffect, useMemo } from 'react' +import { useCallback, useEffect, useMemo, useRef } from 'react' import { useParams } from 'react-router-dom' -import { useSelectActiveSmartPool } from 'state/application/hooks' +import { PoolWithChain, useOperatedPoolsFromState, useSelectActiveSmartPool, useSetOperatedPools } from 'state/application/hooks' import { useStakingContract } from 'state/governance/hooks' import { useLogs } from 'state/logs/hooks' import { useTransactionAdder } from 'state/transactions/hooks' @@ -521,3 +525,163 @@ export function useOperatedPools(): Token[] | undefined { return operatedPools } + +/** + * Hook to initialize and load operated pools for all supported chains + * This should be called at app initialization to load all operated pools + */ +export function useInitializeMultiChainPools() { + const account = useAccount() + const setOperatedPools = useSetOperatedPools() + const existingPools = useOperatedPoolsFromState() + + // Get pools data with id field from logs + const { data: poolsLogs } = useAllPoolsData() + + // TODO: For now, we'll fetch pools only for the current chain + // In the future, this should be extended to fetch from all chains using an API or multi-chain RPC + const currentChainPools = useOperatedPools() + + useEffect(() => { + if (!account.address || !account.chainId || !currentChainPools || !poolsLogs) { + return + } + + // Create a map of pool address to pool log data for quick lookup + const poolLogMap = new Map(poolsLogs.map(log => [log.pool, log])) + + // Convert Token[] to PoolWithChain[] including id from logs + const poolsWithChain: PoolWithChain[] = currentChainPools.map((pool) => { + const logData = poolLogMap.get(pool.address) + return { + address: pool.address, + chainId: pool.chainId ?? account.chainId ?? UniverseChainId.Mainnet, + name: pool.name ?? '', + symbol: pool.symbol ?? '', + decimals: pool.decimals, + id: logData?.id ?? '', + group: logData?.group, + } + }) + + // Merge with existing pools from other chains (don't override pools from other chains) + const otherChainPools = existingPools.filter((p) => p.chainId !== account.chainId) + const mergedPools = [...otherChainPools, ...poolsWithChain] + + // Only update if pools have changed - use shallow comparison for performance + const hasChanged = + mergedPools.length !== existingPools.length || + mergedPools.some((pool, index) => + !existingPools[index] || + pool.address !== existingPools[index].address || + pool.chainId !== existingPools[index].chainId + ) + + if (hasChanged) { + setOperatedPools(mergedPools) + } + }, [account.address, account.chainId, currentChainPools, poolsLogs, existingPools, setOperatedPools]) +} + +/** + * Hook to get operated pools from state with optional chain filter + */ +export function useOperatedPoolsMultiChain(chainId?: number): PoolWithChain[] { + const pools = useOperatedPoolsFromState() + + return useMemo(() => { + if (!chainId) { + return pools + } + return pools.filter((p) => p.chainId === chainId) + }, [pools, chainId]) +} + +/** + * Hook to get all pools data with stable chainId + * Returns pools in PoolRegisteredLog format for compatibility + * Maintains stable chainId even when swap context changes chain + */ +export function useAllPoolsDataStable(): { data?: PoolRegisteredLog[] } { + const account = useAccount() + + // Store the initial chainId when the hook is first called + const initialChainIdRef = useRef(account.chainId) + const initialAddressRef = useRef(account.address) + + // Only update the chainId ref when the address changes (user switched accounts) + // This preserves the chainId even when swap context changes + useEffect(() => { + if (account.address !== initialAddressRef.current) { + initialChainIdRef.current = account.chainId + initialAddressRef.current = account.address + } + }, [account.address, account.chainId]) + + // Get pools for the stable chain, not the current chain + //const stableAccount = useMemo(() => ({ + // ...account, + // chainId: initialChainIdRef.current ?? account.chainId + //}), [account]) + + // Use a custom hook that queries with stable chainId + // For now, return cached data if available, otherwise return undefined + // This prevents the loading issue + const cachedPools = useOperatedPoolsMultiChain(initialChainIdRef.current) + + return useMemo(() => { + if (cachedPools && cachedPools.length > 0) { + const poolsAsLogs: PoolRegisteredLog[] = cachedPools.map(pool => ({ + pool: pool.address, + name: pool.name, + symbol: pool.symbol, + id: pool.id, + group: pool.group, + })) + return { data: poolsAsLogs } + } + + return { data: undefined } + }, [cachedPools]) +} + +/** + * Hook to get operated pools data from cache for CreatePool page + * Returns pools in PoolRegisteredLog format for compatibility + * Uses ref to maintain stable chainId even when account.chainId changes + */ +export function useOperatedPoolsDataFromCache(): { data?: PoolRegisteredLog[] } { + const account = useAccount() + + // Store the initial chainId when the hook is first called + const initialChainIdRef = useRef(account.chainId) + + // Update the ref if chainId changes while user is connected (intentional chain switch) + // but preserve it if the account disconnects/reconnects + useEffect(() => { + if (account.chainId && account.address) { + initialChainIdRef.current = account.chainId + } + }, [account.chainId, account.address]) + + // Use the stable chainId from ref instead of current account.chainId + const stableChainId = initialChainIdRef.current + const cachedPools = useOperatedPoolsMultiChain(stableChainId) + + return useMemo(() => { + if (!cachedPools || cachedPools.length === 0) { + return { data: undefined } + } + + // Convert PoolWithChain to PoolRegisteredLog format + const poolsAsLogs: PoolRegisteredLog[] = cachedPools.map(pool => ({ + pool: pool.address, + name: pool.name, + symbol: pool.symbol, + id: pool.id, + group: pool.group, + })) + + return { data: poolsAsLogs } + }, [cachedPools]) +} diff --git a/apps/web/src/state/webReducer.ts b/apps/web/src/state/webReducer.ts index a27ad14f350..fba3ecf93f2 100644 --- a/apps/web/src/state/webReducer.ts +++ b/apps/web/src/state/webReducer.ts @@ -1,5 +1,6 @@ import { combineReducers } from '@reduxjs/toolkit' import multicall from 'lib/state/multicall' +import poolMulticall from 'lib/state/multicallForPools' import application from 'state/application/reducer' import fiatOnRampTransactions from 'state/fiatOnRampTransactions/reducer' import poolsList from 'state/lists/poolsList/reducer' @@ -28,6 +29,7 @@ const interfaceReducers = { mint, mintV3, multicall: multicall.reducer, + poolMulticall: poolMulticall.reducer, logs, [routingApi.reducerPath]: routingApi.reducer, [quickRouteApi.reducerPath]: quickRouteApi.reducer,