From 129ae27002218633123497f35023d426c1b4d8b1 Mon Sep 17 00:00:00 2001 From: limitofzero Date: Mon, 5 Jan 2026 01:53:08 +0400 Subject: [PATCH 1/2] feat: use sse to get balances --- ...onPriorityBalancesAndAllowancesUpdater.tsx | 1 + .../src/hooks/useSseBalances.ts | 171 ++++++++++++++++++ libs/balances-and-allowances/src/index.ts | 18 +- .../src/state/isSseFailedAtom.ts | 12 ++ .../updaters/BalancesAndAllowancesUpdater.tsx | 49 ++++- .../src/updaters/BalancesSseUpdater.tsx | 112 ++++++++++++ libs/common-const/src/bff.ts | 3 + 7 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 libs/balances-and-allowances/src/hooks/useSseBalances.ts create mode 100644 libs/balances-and-allowances/src/state/isSseFailedAtom.ts create mode 100644 libs/balances-and-allowances/src/updaters/BalancesSseUpdater.tsx diff --git a/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx b/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx index fab0d6d0513..ad516b23a68 100644 --- a/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx @@ -91,6 +91,7 @@ export function CommonPriorityBalancesAndAllowancesUpdater(): ReactNode { chainId={sourceChainId} isBffSwitchedOn={isBffSwitchedOn} isBffEnabled={isBffEnabled} + isSseEnabled={true} excludedTokens={priorityTokenAddresses} invalidateCacheTrigger={invalidateCacheTrigger} /> diff --git a/libs/balances-and-allowances/src/hooks/useSseBalances.ts b/libs/balances-and-allowances/src/hooks/useSseBalances.ts new file mode 100644 index 00000000000..6a763e6cfe4 --- /dev/null +++ b/libs/balances-and-allowances/src/hooks/useSseBalances.ts @@ -0,0 +1,171 @@ +import { useCallback, useEffect, useRef, useState } from 'react' + +import { BALANCES_SSE_URL } from '@cowprotocol/common-const' +import { SupportedChainId } from '@cowprotocol/cow-sdk' + +const RECONNECT_DELAY_MS = 3000 +const MAX_RECONNECT_ATTEMPTS = 5 + +export interface SseBalancesState { + isConnected: boolean + isLoading: boolean + error: Error | null +} + +export interface UseSseBalancesParams { + account: string | undefined + chainId: SupportedChainId + enabled: boolean + tokensListsUrls: string[] + customTokens?: string[] + onAllBalances: (balances: Record) => void + onBalanceUpdate: (address: string, balance: string) => void + onError?: (error: Error) => void +} + +const INITIAL_STATE: SseBalancesState = { isConnected: false, isLoading: false, error: null } + +async function createSession( + chainId: SupportedChainId, + account: string, + tokensListsUrls: string[], + customTokens?: string[], +): Promise { + const response = await fetch(`${BALANCES_SSE_URL}/${chainId}/sessions/${account}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tokensListsUrls, customTokens }), + }) + + if (!response.ok) { + throw new Error(`Session creation failed: ${response.status}`) + } +} + +interface EventHandlers { + onAllBalances: (balances: Record) => void + onBalanceUpdate: (address: string, balance: string) => void + onError?: (error: Error) => void + onOpen: () => void + onClose: () => void +} + +function setupEventSource(url: string, handlers: EventHandlers): EventSource { + const { onAllBalances, onBalanceUpdate, onError, onOpen, onClose } = handlers + const es = new EventSource(url) + + es.onopen = onOpen + + es.addEventListener('all_balances', (e: MessageEvent): void => { + try { + const { balances } = JSON.parse(e.data) + if (balances && Object.keys(balances).length > 0) onAllBalances(balances) + } catch { + /* ignore */ + } + }) + + es.addEventListener('balance_update', (e: MessageEvent): void => { + try { + const { address, balance } = JSON.parse(e.data) + if (address && balance != null) onBalanceUpdate(address.toLowerCase(), balance) + } catch { + /* ignore */ + } + }) + + es.addEventListener('error', (e: MessageEvent): void => { + try { + const { message, code } = JSON.parse(e.data) + onError?.(new Error(`SSE Error ${code}: ${message}`)) + } catch { + /* ignore */ + } + }) + + es.onerror = (): void => { + if (es.readyState === EventSource.CLOSED) onClose() + } + + return es +} + +export function useSseBalances(params: UseSseBalancesParams): SseBalancesState { + const { + account, + chainId, + enabled, + tokensListsUrls, + customTokens = [], + onAllBalances, + onBalanceUpdate, + onError, + } = params + + const [state, setState] = useState(INITIAL_STATE) + const esRef = useRef(null) + const attemptsRef = useRef(0) + const timeoutRef = useRef>() + + const cleanup = useCallback((): void => { + esRef.current?.close() + esRef.current = null + clearTimeout(timeoutRef.current) + }, []) + + useEffect(() => { + if (!enabled || !account || tokensListsUrls.length === 0) { + cleanup() + setState(INITIAL_STATE) + return cleanup + } + + let cancelled = false + + const connect = async (): Promise => { + if (cancelled) return + cleanup() + setState((s) => ({ ...s, isLoading: true, error: null })) + + try { + await createSession(chainId, account, tokensListsUrls, customTokens) + } catch (e) { + const error = e instanceof Error ? e : new Error('Session failed') + setState({ ...INITIAL_STATE, error }) + onError?.(error) + return + } + + if (cancelled) return + + esRef.current = setupEventSource(`${BALANCES_SSE_URL}/sse/${chainId}/balances/${account}`, { + onAllBalances, + onBalanceUpdate, + onError, + onOpen: (): void => { + attemptsRef.current = 0 + setState({ isConnected: true, isLoading: false, error: null }) + }, + onClose: (): void => { + setState((s) => ({ ...s, isConnected: false })) + if (attemptsRef.current < MAX_RECONNECT_ATTEMPTS) { + attemptsRef.current++ + timeoutRef.current = setTimeout(() => void connect(), RECONNECT_DELAY_MS * 2 ** (attemptsRef.current - 1)) + } else { + const err = new Error('SSE: Max reconnect attempts') + setState((s) => ({ ...s, error: err })) + onError?.(err) + } + }, + }) + } + + void connect() + return (): void => { + cancelled = true + cleanup() + } + }, [account, chainId, enabled, tokensListsUrls, customTokens, cleanup, onAllBalances, onBalanceUpdate, onError]) + + return state +} diff --git a/libs/balances-and-allowances/src/index.ts b/libs/balances-and-allowances/src/index.ts index 684441ecaec..bc729fe4fb7 100644 --- a/libs/balances-and-allowances/src/index.ts +++ b/libs/balances-and-allowances/src/index.ts @@ -1,6 +1,9 @@ -// Updater +// Updaters export { BalancesAndAllowancesUpdater } from './updaters/BalancesAndAllowancesUpdater' export { PriorityTokensUpdater, PRIORITY_TOKENS_REFRESH_INTERVAL } from './updaters/PriorityTokensUpdater' +export { BalancesBffUpdater } from './updaters/BalancesBffUpdater' +export { BalancesRpcCallUpdater } from './updaters/BalancesRpcCallUpdater' +export { BalancesSseUpdater } from './updaters/BalancesSseUpdater' // Hooks export { useTokensBalances } from './hooks/useTokensBalances' @@ -14,15 +17,20 @@ export { useUpdateTokenBalance } from './hooks/useUpdateTokenBalance' export { useTokenAllowances } from './hooks/useTokenAllowances' export { useBalancesAndAllowances } from './hooks/useBalancesAndAllowances' export { useTradeSpenderAddress } from './hooks/useTradeSpenderAddress' +export { useSseBalances } from './hooks/useSseBalances' + +// State hooks export { useIsBffFailed } from './state/isBffFailedAtom' -export { BalancesBffUpdater } from './updaters/BalancesBffUpdater' -export { BalancesRpcCallUpdater } from './updaters/BalancesRpcCallUpdater' -export type { BalancesAndAllowances } from './types/balances-and-allowances' -export * from './utils/isBffSupportedNetwork' +export { useIsSseFailed } from './state/isSseFailedAtom' // Types +export type { BalancesAndAllowances } from './types/balances-and-allowances' export type { BalancesState } from './state/balancesAtom' export type { AllowancesState } from './hooks/useTokenAllowances' +export type { SseBalancesState, UseSseBalancesParams } from './hooks/useSseBalances' + +// Utils +export * from './utils/isBffSupportedNetwork' // Consts export { DEFAULT_BALANCES_STATE } from './state/balancesAtom' diff --git a/libs/balances-and-allowances/src/state/isSseFailedAtom.ts b/libs/balances-and-allowances/src/state/isSseFailedAtom.ts new file mode 100644 index 00000000000..185f356166d --- /dev/null +++ b/libs/balances-and-allowances/src/state/isSseFailedAtom.ts @@ -0,0 +1,12 @@ +import { useSetAtom } from 'jotai' +import { atom, useAtomValue } from 'jotai/index' + +export const isSseFailedAtom = atom(false) + +export function useIsSseFailed(): boolean { + return useAtomValue(isSseFailedAtom) +} + +export function useSetIsSseFailed(): (value: boolean) => void { + return useSetAtom(isSseFailedAtom) +} diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index 01848130ed1..6fe9fe9c27d 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -2,7 +2,7 @@ import { ReactNode, useEffect, useMemo } from 'react' import { LpToken, NATIVE_CURRENCIES } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' -import { useAllActiveTokens } from '@cowprotocol/tokens' +import { useAllActiveTokens, useListsEnabledState } from '@cowprotocol/tokens' import ms from 'ms.macro' import { SWRConfiguration } from 'swr' @@ -11,11 +11,13 @@ import { BalancesBffUpdater } from './BalancesBffUpdater' import { BalancesCacheUpdater } from './BalancesCacheUpdater' import { BalancesResetUpdater } from './BalancesResetUpdater' import { BalancesRpcCallUpdater } from './BalancesRpcCallUpdater' +import { BalancesSseUpdater } from './BalancesSseUpdater' import { BASIC_MULTICALL_SWR_CONFIG } from '../consts' import { useNativeTokenBalance } from '../hooks/useNativeTokenBalance' import { useSwrConfigWithPauseForNetwork } from '../hooks/useSwrConfigWithPauseForNetwork' import { useUpdateTokenBalance } from '../hooks/useUpdateTokenBalance' +import { useIsSseFailed } from '../state/isSseFailedAtom' // A small gap between balances and allowances refresh intervals is needed to avoid high load to the node at the same time const RPC_BALANCES_SWR_CONFIG: SWRConfiguration = { ...BASIC_MULTICALL_SWR_CONFIG, refreshInterval: ms`31s` } @@ -27,8 +29,12 @@ export interface BalancesAndAllowancesUpdaterProps { chainId: SupportedChainId invalidateCacheTrigger: number excludedTokens: Set + /** @deprecated Use isSseEnabled instead */ isBffSwitchedOn: boolean + /** @deprecated Use isSseEnabled instead */ isBffEnabled?: boolean + /** Enable SSE-based real-time balance updates */ + isSseEnabled?: boolean } export function BalancesAndAllowancesUpdater({ @@ -38,8 +44,10 @@ export function BalancesAndAllowancesUpdater({ isBffSwitchedOn, excludedTokens, isBffEnabled, + isSseEnabled = false, }: BalancesAndAllowancesUpdaterProps): ReactNode { const updateTokenBalance = useUpdateTokenBalance() + const isSseFailed = useIsSseFailed() const allTokens = useAllActiveTokens() const { data: nativeTokenBalance } = useNativeTokenBalance(account, chainId) @@ -55,23 +63,47 @@ export function BalancesAndAllowancesUpdater({ }, []) }, [allTokens, chainId]) + // Get enabled token list URLs for SSE + const listsEnabledState = useListsEnabledState() + const tokensListsUrls = useMemo(() => { + return Object.entries(listsEnabledState) + .filter(([, isEnabled]) => isEnabled === true) + .map(([url]) => url) + }, [listsEnabledState]) + const rpcBalancesSwrConfig = useSwrConfigWithPauseForNetwork(chainId, account, RPC_BALANCES_SWR_CONFIG) + + // Determine which updater to use + const hasSseTokenLists = tokensListsUrls.length > 0 + const useSse = isSseEnabled && !isSseFailed && hasSseTokenLists + const useBff = !isSseEnabled && isBffEnabled + const useRpcFallback = (!isBffSwitchedOn || !isBffEnabled) && !useSse + // Add native token balance to the store as well useEffect(() => { - if (isBffSwitchedOn) return + if (isBffSwitchedOn || isSseEnabled) return const nativeToken = NATIVE_CURRENCIES[chainId] if (nativeToken && nativeTokenBalance) { updateTokenBalance(nativeToken.address, nativeTokenBalance) } - }, [isBffSwitchedOn, nativeTokenBalance, chainId, updateTokenBalance]) - - const enableRpcFallback = !isBffSwitchedOn || !isBffEnabled + }, [isBffSwitchedOn, isSseEnabled, nativeTokenBalance, chainId, updateTokenBalance]) return ( <> - {isBffEnabled && ( + {/* SSE-based real-time updates (preferred) */} + {useSse && ( + + )} + + {/* Legacy BFF polling (deprecated, for backward compatibility) */} + {useBff && ( )} - {enableRpcFallback && ( + + {/* RPC fallback when SSE/BFF fails or is disabled */} + {(useRpcFallback || isSseFailed) && ( )} + diff --git a/libs/balances-and-allowances/src/updaters/BalancesSseUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesSseUpdater.tsx new file mode 100644 index 00000000000..17a5ee64de7 --- /dev/null +++ b/libs/balances-and-allowances/src/updaters/BalancesSseUpdater.tsx @@ -0,0 +1,112 @@ +import { useSetAtom } from 'jotai' +import { useCallback, useEffect } from 'react' + +import { SupportedChainId } from '@cowprotocol/cow-sdk' +import { BigNumber } from '@ethersproject/bignumber' + +import { useSseBalances } from '../hooks/useSseBalances' +import { balancesAtom, BalancesState, balancesUpdateAtom } from '../state/balancesAtom' +import { useSetIsSseFailed } from '../state/isSseFailedAtom' + +export interface BalancesSseUpdaterProps { + account: string | undefined + chainId: SupportedChainId + tokenAddresses: string[] + tokensListsUrls: string[] +} + +function parseBalances(balances: Record): BalancesState['values'] { + return Object.entries(balances).reduce((acc, [address, balance]) => { + if (balance && balance !== '0') { + try { + acc[address.toLowerCase()] = BigNumber.from(balance) + } catch { + // Skip invalid balances + } + } + return acc + }, {}) +} + +export function BalancesSseUpdater({ + account, + chainId, + tokenAddresses, + tokensListsUrls, +}: BalancesSseUpdaterProps): null { + const setBalances = useSetAtom(balancesAtom) + const setBalancesUpdate = useSetAtom(balancesUpdateAtom) + const setIsSseFailed = useSetIsSseFailed() + + const onAllBalances = useCallback( + (balances: Record) => { + if (!account) return + + const parsed = parseBalances(balances) + if (Object.keys(parsed).length === 0) return + + setBalances((state) => ({ + ...state, + chainId, + fromCache: false, + values: { ...state.values, ...parsed }, + isLoading: false, + })) + + setBalancesUpdate((state) => ({ + ...state, + [chainId]: { + ...state[chainId], + [account.toLowerCase()]: Date.now(), + }, + })) + }, + [account, chainId, setBalances, setBalancesUpdate], + ) + + const onBalanceUpdate = useCallback( + (address: string, balance: string) => { + if (!balance) return + + try { + setBalances((state) => ({ + ...state, + values: { + ...state.values, + [address]: BigNumber.from(balance), + }, + })) + } catch { + // Skip invalid balance + } + }, + [setBalances], + ) + + const onError = useCallback(() => { + setIsSseFailed(true) + }, [setIsSseFailed]) + + const { isConnected, isLoading } = useSseBalances({ + account, + chainId, + enabled: !!account, + tokensListsUrls, + customTokens: tokenAddresses, + onAllBalances, + onBalanceUpdate, + onError, + }) + + // Sync connection state + useEffect(() => { + setIsSseFailed(!isConnected && !isLoading) + }, [isConnected, isLoading, setIsSseFailed]) + + // Sync loading state + useEffect(() => { + setBalances((state) => ({ ...state, isLoading, chainId })) + }, [isLoading, chainId, setBalances]) + + return null +} diff --git a/libs/common-const/src/bff.ts b/libs/common-const/src/bff.ts index 248632aafe4..8cf6809f0d2 100644 --- a/libs/common-const/src/bff.ts +++ b/libs/common-const/src/bff.ts @@ -1 +1,4 @@ export const BFF_BASE_URL = process.env.REACT_APP_BFF_BASE_URL || 'https://bff.barn.cow.fi' + +// SSE Balances Service URL (token-balances-updater) +export const BALANCES_SSE_URL = process.env.REACT_APP_BALANCES_SSE_URL || 'http://localhost:4000' From cb6e023c90acf00c32c954f4bf7700d63d4e1ee1 Mon Sep 17 00:00:00 2001 From: limitofzero Date: Wed, 7 Jan 2026 03:03:14 +0400 Subject: [PATCH 2/2] fix: remove unstable condition --- .../hooks/useOrdersFilledEventsTrigger.ts | 46 ----- ...onPriorityBalancesAndAllowancesUpdater.tsx | 40 +--- .../src/constants/bff-balances-swr-config.ts | 54 ----- .../hooks/usePersistBalancesFromBff.test.tsx | 192 ------------------ .../src/hooks/usePersistBalancesFromBff.ts | 131 ------------ .../src/hooks/useSseBalances.ts | 28 ++- libs/balances-and-allowances/src/index.ts | 5 - .../src/state/isBffFailedAtom.ts | 12 -- .../updaters/BalancesAndAllowancesUpdater.tsx | 49 +---- .../src/updaters/BalancesBffUpdater.tsx | 24 --- .../src/updaters/BalancesSseUpdater.tsx | 34 +++- .../src/utils/isBffSupportedNetwork.ts | 8 - 12 files changed, 59 insertions(+), 564 deletions(-) delete mode 100644 apps/cowswap-frontend/src/modules/balancesAndAllowances/hooks/useOrdersFilledEventsTrigger.ts delete mode 100644 libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts delete mode 100644 libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx delete mode 100644 libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts delete mode 100644 libs/balances-and-allowances/src/state/isBffFailedAtom.ts delete mode 100644 libs/balances-and-allowances/src/updaters/BalancesBffUpdater.tsx delete mode 100644 libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts diff --git a/apps/cowswap-frontend/src/modules/balancesAndAllowances/hooks/useOrdersFilledEventsTrigger.ts b/apps/cowswap-frontend/src/modules/balancesAndAllowances/hooks/useOrdersFilledEventsTrigger.ts deleted file mode 100644 index 56212c49546..00000000000 --- a/apps/cowswap-frontend/src/modules/balancesAndAllowances/hooks/useOrdersFilledEventsTrigger.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useEffect, useState } from 'react' - -import { useDebounce } from '@cowprotocol/common-hooks' -import { CowEventListener, CowWidgetEventPayloadMap, CowWidgetEvents } from '@cowprotocol/events' - -import ms from 'ms.macro' -import { WIDGET_EVENT_EMITTER } from 'widgetEventEmitter' - -const DEBOUNCE_FOR_PENDING_ORDERS_MS = ms`1s` - -type OrderFilledListener = CowEventListener -type BridgingSuccessListener = CowEventListener - -/** - * Invalidate cache trigger that only updates when the number of pending orders decreases - * This useful to force a refresh when orders are being fulfilled or bridging is completed - * */ -export function useOrdersFilledEventsTrigger(): number { - const [triggerValue, setTriggerValue] = useState(0) - - useEffect(() => { - const incrementTrigger = (): void => { - setTriggerValue((prev) => prev + 1) - } - - const onFulfilledListener: OrderFilledListener = { - event: CowWidgetEvents.ON_FULFILLED_ORDER, - handler: incrementTrigger, - } - - const onBridgingListener: BridgingSuccessListener = { - event: CowWidgetEvents.ON_BRIDGING_SUCCESS, - handler: incrementTrigger, - } - - WIDGET_EVENT_EMITTER.on(onFulfilledListener) - WIDGET_EVENT_EMITTER.on(onBridgingListener) - - return (): void => { - WIDGET_EVENT_EMITTER.off(onFulfilledListener) - WIDGET_EVENT_EMITTER.off(onBridgingListener) - } - }, []) - - return useDebounce(triggerValue, DEBOUNCE_FOR_PENDING_ORDERS_MS) -} diff --git a/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx b/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx index ad516b23a68..2171c1a3559 100644 --- a/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx +++ b/apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx @@ -2,12 +2,10 @@ import { ReactNode, useEffect, useMemo, useState } from 'react' import { BalancesAndAllowancesUpdater, - isBffSupportedNetwork, PRIORITY_TOKENS_REFRESH_INTERVAL, PriorityTokensUpdater, - useIsBffFailed, + useIsSseFailed, } from '@cowprotocol/balances-and-allowances' -import { useFeatureFlags } from '@cowprotocol/common-hooks' import { useWalletInfo } from '@cowprotocol/wallet' import { useBalancesContext } from 'entities/balancesContext/useBalancesContext' @@ -15,22 +13,6 @@ import { useBalancesContext } from 'entities/balancesContext/useBalancesContext' import { useSourceChainId } from 'modules/tokensList' import { usePriorityTokenAddresses } from 'modules/trade' -import { useOrdersFilledEventsTrigger } from '../hooks/useOrdersFilledEventsTrigger' - -function shouldApplyBffBalances(account: string | undefined, percentage: number | boolean | undefined): boolean { - // Early exit for 100%, meaning should be enabled for everyone - if (percentage === 100) { - return true - } - - // Falsy conditions - if (typeof percentage !== 'number' || !account || percentage < 0 || percentage > 100) { - return false - } - - return BigInt(account) % 100n < percentage -} - export function CommonPriorityBalancesAndAllowancesUpdater(): ReactNode { const sourceChainId = useSourceChainId().chainId const { account } = useWalletInfo() @@ -44,6 +26,7 @@ export function CommonPriorityBalancesAndAllowancesUpdater(): ReactNode { const priorityTokenCount = priorityTokenAddressesAsArray.length const [skipFirstPriorityUpdate, setSkipFirstPriorityUpdate] = useState(true) + const isSseFailed = useIsSseFailed() /** * Reset skipFirstPriorityUpdate on every network change @@ -67,33 +50,20 @@ export function CommonPriorityBalancesAndAllowancesUpdater(): ReactNode { } }, [account, priorityTokenCount]) - const { bffBalanceEnabledPercentage } = useFeatureFlags() - const isBffFailed = useIsBffFailed() - const isBffSupportNetwork = isBffSupportedNetwork(sourceChainId) - const isBffEnabled = shouldApplyBffBalances(account, bffBalanceEnabledPercentage) - const isBffSwitchedOn = isBffEnabled && !isBffFailed && isBffSupportNetwork - const invalidateCacheTrigger = useOrdersFilledEventsTrigger() - return ( <> - {!isBffSwitchedOn ? ( + {/* Priority tokens use RPC when SSE fails */} + {isSseFailed && ( - ) : null} + )} ) diff --git a/libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts b/libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts deleted file mode 100644 index 65077cb12de..00000000000 --- a/libs/balances-and-allowances/src/constants/bff-balances-swr-config.ts +++ /dev/null @@ -1,54 +0,0 @@ -import ms from 'ms.macro' -import { SWRConfiguration } from 'swr' - -import { BASIC_MULTICALL_SWR_CONFIG } from '../consts' - -let focusLostTimestamp: number | null = null -const FOCUS_HIDDEN_DELAY = ms`20s` - -function initializeFocusListeners(): void { - if (typeof document === 'undefined') { - return - } - - document.addEventListener('visibilitychange', () => { - focusLostTimestamp = document.hidden ? Date.now() : null - }) - - window.addEventListener('blur', () => { - focusLostTimestamp = Date.now() - }) - - window.addEventListener('focus', () => { - focusLostTimestamp = null - }) -} - -export const BFF_BALANCES_SWR_CONFIG: SWRConfiguration = { - ...BASIC_MULTICALL_SWR_CONFIG, - revalidateIfStale: true, - refreshInterval: ms`8s`, - errorRetryCount: 3, - errorRetryInterval: ms`30s`, - isPaused() { - initializeFocusListeners() - - if (document.hasFocus()) { - focusLostTimestamp = null - return false - } - - if (!focusLostTimestamp) { - focusLostTimestamp = Date.now() - return false - } - - // Pause only if focus has been lost for more than ${FOCUS_HIDDEN_DELAY} seconds - return Date.now() - focusLostTimestamp > FOCUS_HIDDEN_DELAY - }, - onErrorRetry: (_: unknown, __key, config, revalidate, { retryCount }) => { - const timeout = config.errorRetryInterval * Math.pow(2, retryCount - 1) - - setTimeout(() => revalidate({ retryCount }), timeout) - }, -} diff --git a/libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx b/libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx deleted file mode 100644 index 49ed1ebe902..00000000000 --- a/libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.test.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { Provider } from 'jotai' -import { useHydrateAtoms } from 'jotai/utils' -import React, { ReactNode } from 'react' - -import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk' -import { PersistentStateByChain } from '@cowprotocol/types' - -import { renderHook } from '@testing-library/react' -import fetchMock from 'jest-fetch-mock' -import useSWR from 'swr' - -// Import the function to test after all mocks are set up -import { PersistBalancesFromBffParams, usePersistBalancesFromBff } from './usePersistBalancesFromBff' - -import { BFF_BALANCES_SWR_CONFIG } from '../constants/bff-balances-swr-config' -import { balancesAtom, BalancesState, balancesUpdateAtom } from '../state/balancesAtom' -import * as isBffFailedAtom from '../state/isBffFailedAtom' -import * as bffUtils from '../utils/isBffSupportedNetwork' - -// Enable fetch mocking -fetchMock.enableMocks() - -// Mock modules -jest.mock('swr') -jest.mock('../utils/isBffSupportedNetwork') -jest.mock('../state/isBffFailedAtom') - -// Create mock for useWalletInfo -const mockUseWalletInfo = jest.fn() - -// Mock the wallet module -jest.mock('@cowprotocol/wallet', () => ({ - useWalletInfo: () => mockUseWalletInfo(), -})) - -describe('usePersistBalancesFromBff - invalidateCacheTrigger', () => { - const mockSetIsBffFailed = jest.fn() - const mockWalletInfo = { - chainId: SupportedChainId.MAINNET, - account: '0x1234567890123456789012345678901234567890', - } - - const defaultParams: PersistBalancesFromBffParams = { - account: '0x1234567890123456789012345678901234567890', - chainId: SupportedChainId.MAINNET, - invalidateCacheTrigger: 0, - tokenAddresses: ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'], - } - - const mockBalancesData = { - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': '1000000', - } - - // Create complete mock data for balancesUpdateAtom - const mockBalancesUpdate: PersistentStateByChain> = mapSupportedNetworks({}) - - const wrapper = ({ children }: { children: ReactNode }): ReactNode => { - const HydrateAtoms = ({ children }: { children: ReactNode }): ReactNode => { - useHydrateAtoms([ - [ - balancesAtom, - { - isLoading: false, - chainId: SupportedChainId.MAINNET, - values: {}, - fromCache: false, - } as BalancesState, - ], - [balancesUpdateAtom, mockBalancesUpdate], - ]) - return <>{children} - } - - return ( - - {children} - - ) - } - - beforeEach(() => { - jest.clearAllMocks() - fetchMock.resetMocks() - mockUseWalletInfo.mockReturnValue(mockWalletInfo) - ;(isBffFailedAtom.useSetIsBffFailed as jest.Mock).mockReturnValue(mockSetIsBffFailed) - ;(bffUtils.isBffSupportedNetwork as jest.Mock).mockReturnValue(true) - }) - - describe('hardcoded SWR config', () => { - it('should use hardcoded BFF_BALANCES_SWR_CONFIG', () => { - const mockUseSWR = useSWR as jest.MockedFunction - mockUseSWR.mockReturnValue({ - data: mockBalancesData, - error: undefined, - isLoading: false, - isValidating: false, - mutate: jest.fn(), - } as ReturnType) - - renderHook(() => usePersistBalancesFromBff(defaultParams), { wrapper }) - - expect(mockUseSWR).toHaveBeenCalledWith( - expect.any(Array), - expect.any(Function), - BFF_BALANCES_SWR_CONFIG, // Verify hardcoded config is used - ) - }) - }) - - describe('invalidateCacheTrigger parameter', () => { - it('should trigger cache invalidation when value changes', async () => { - const mockUseSWR = useSWR as jest.MockedFunction - - mockUseSWR.mockImplementation((key, fetcher, _config) => { - if (key && fetcher) { - Promise.resolve(fetcher(key as [string, SupportedChainId])).catch(() => {}) - } - return { - data: mockBalancesData, - error: undefined, - isLoading: false, - isValidating: false, - mutate: jest.fn(), - } as ReturnType - }) - fetchMock.mockResolvedValue({ - ok: true, - json: async () => ({ balances: mockBalancesData }), - } as Response) - - const { rerender } = renderHook( - ({ trigger }: { trigger: number }) => - usePersistBalancesFromBff({ ...defaultParams, invalidateCacheTrigger: trigger }), - { - wrapper, - initialProps: { trigger: 0 }, - }, - ) - - // Initial call with trigger = 0 - expect(mockUseSWR).toHaveBeenCalledWith( - [defaultParams.account, defaultParams.chainId, 0, 'bff-balances'], - expect.any(Function), - BFF_BALANCES_SWR_CONFIG, - ) - - // Change trigger to force cache invalidation - rerender({ trigger: 1 }) - - expect(mockUseSWR).toHaveBeenCalledWith( - [defaultParams.account, defaultParams.chainId, 1, 'bff-balances'], - expect.any(Function), - BFF_BALANCES_SWR_CONFIG, - ) - - // Change trigger again - rerender({ trigger: 5 }) - - expect(mockUseSWR).toHaveBeenCalledWith( - [defaultParams.account, defaultParams.chainId, 5, 'bff-balances'], - expect.any(Function), - BFF_BALANCES_SWR_CONFIG, - ) - }) - - it('should handle undefined invalidateCacheTrigger', () => { - const mockUseSWR = useSWR as jest.MockedFunction - mockUseSWR.mockReturnValue({ - data: mockBalancesData, - error: undefined, - isLoading: false, - isValidating: false, - mutate: jest.fn(), - } as ReturnType) - - const paramsWithoutTrigger: Omit = { - account: defaultParams.account, - chainId: defaultParams.chainId, - tokenAddresses: defaultParams.tokenAddresses, - } - - renderHook(() => usePersistBalancesFromBff(paramsWithoutTrigger as PersistBalancesFromBffParams), { wrapper }) - - expect(mockUseSWR).toHaveBeenCalledWith( - [defaultParams.account, defaultParams.chainId, undefined, 'bff-balances'], - expect.any(Function), - BFF_BALANCES_SWR_CONFIG, - ) - }) - }) - -}) diff --git a/libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts b/libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts deleted file mode 100644 index af37a304932..00000000000 --- a/libs/balances-and-allowances/src/hooks/usePersistBalancesFromBff.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { useSetAtom } from 'jotai' -import { useEffect, useRef } from 'react' - -import { BFF_BASE_URL } from '@cowprotocol/common-const' -import { SupportedChainId } from '@cowprotocol/cow-sdk' -import { useWalletInfo } from '@cowprotocol/wallet' -import { BigNumber } from '@ethersproject/bignumber' - -import useSWR, { SWRConfiguration } from 'swr' - -import { BFF_BALANCES_SWR_CONFIG } from '../constants/bff-balances-swr-config' -import { balancesAtom, BalancesState, balancesUpdateAtom } from '../state/balancesAtom' -import { useSetIsBffFailed } from '../state/isBffFailedAtom' -import { isBffSupportedNetwork } from '../utils/isBffSupportedNetwork' - -type BalanceResponse = { - balances: Record | null -} - -export interface PersistBalancesFromBffParams { - account?: string - chainId: SupportedChainId - balancesSwrConfig?: SWRConfiguration - invalidateCacheTrigger?: number - tokenAddresses: string[] -} - -export function usePersistBalancesFromBff(params: PersistBalancesFromBffParams): void { - const { account, chainId, invalidateCacheTrigger, tokenAddresses } = params - - const { chainId: activeChainId, account: connectedAccount } = useWalletInfo() - const targetAccount = account ?? connectedAccount - const targetChainId = chainId ?? activeChainId - const isSupportedNetwork = isBffSupportedNetwork(targetChainId) - - const setIsBffFailed = useSetIsBffFailed() - - const lastTriggerRef = useRef(invalidateCacheTrigger) - - const { - isLoading: isBalancesLoading, - data, - error, - } = useSWR( - targetAccount && isSupportedNetwork ? [targetAccount, targetChainId, invalidateCacheTrigger, 'bff-balances'] : null, - ([walletAddress, chainId]) => { - const skipCache = lastTriggerRef.current !== invalidateCacheTrigger - lastTriggerRef.current = invalidateCacheTrigger - return getBffBalances(walletAddress, chainId, skipCache) - }, - BFF_BALANCES_SWR_CONFIG, - ) - - const setBalances = useSetAtom(balancesAtom) - const setBalancesUpdate = useSetAtom(balancesUpdateAtom) - - useEffect(() => { - setBalances((state) => ({ ...state, isLoading: isBalancesLoading, chainId: targetChainId })) - }, [setBalances, isBalancesLoading, targetChainId, targetAccount]) - - useEffect(() => { - setIsBffFailed(!!error) - }, [error, setIsBffFailed]) - - useEffect(() => { - if (!targetAccount || !data || error) return - - const balancesState = tokenAddresses.reduce((acc, address) => { - address = address.toLowerCase() - const balance = data[address] || '0' - acc[address] = BigNumber.from(balance) - return acc - }, {}) - - setBalances((state) => { - return { - ...state, - chainId: targetChainId, - fromCache: false, - values: balancesState, - isLoading: false, - } - }) - - setBalancesUpdate((state) => ({ - ...state, - [targetChainId]: { - ...state[targetChainId], - [targetAccount.toLowerCase()]: Date.now(), - }, - })) - }, [ - targetChainId, - account, - data, - setBalances, - setBalancesUpdate, - error, - tokenAddresses, - isBalancesLoading, - chainId, - targetAccount, - ]) -} - -export async function getBffBalances( - address: string, - chainId: SupportedChainId, - skipCache = false, -): Promise | null> { - const url = `${BFF_BASE_URL}/${chainId}/address/${address}/balances` - const queryParams = skipCache ? '?ignoreCache=true' : '' - const fullUrl = url + queryParams - - try { - const res = await fetch(fullUrl) - const data: BalanceResponse = await res.json() - - if (!res.ok) { - return Promise.reject(new Error(`BFF error: ${res.status} ${res.statusText}`)) - } - - if (!data.balances) { - return null - } - - return data.balances - } catch (error) { - return Promise.reject(error) - } -} diff --git a/libs/balances-and-allowances/src/hooks/useSseBalances.ts b/libs/balances-and-allowances/src/hooks/useSseBalances.ts index 6a763e6cfe4..58ec7900ef8 100644 --- a/libs/balances-and-allowances/src/hooks/useSseBalances.ts +++ b/libs/balances-and-allowances/src/hooks/useSseBalances.ts @@ -31,7 +31,10 @@ async function createSession( tokensListsUrls: string[], customTokens?: string[], ): Promise { - const response = await fetch(`${BALANCES_SSE_URL}/${chainId}/sessions/${account}`, { + const url = `${BALANCES_SSE_URL}/${chainId}/sessions/${account}` + console.debug('[SSE] Creating session:', url) + + const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tokensListsUrls, customTokens }), @@ -40,6 +43,7 @@ async function createSession( if (!response.ok) { throw new Error(`Session creation failed: ${response.status}`) } + console.debug('[SSE] Session created successfully') } interface EventHandlers { @@ -52,9 +56,13 @@ interface EventHandlers { function setupEventSource(url: string, handlers: EventHandlers): EventSource { const { onAllBalances, onBalanceUpdate, onError, onOpen, onClose } = handlers + console.debug('[SSE] Connecting to:', url) const es = new EventSource(url) - es.onopen = onOpen + es.onopen = (): void => { + console.debug('[SSE] Connection opened') + onOpen() + } es.addEventListener('all_balances', (e: MessageEvent): void => { try { @@ -83,8 +91,12 @@ function setupEventSource(url: string, handlers: EventHandlers): EventSource { } }) - es.onerror = (): void => { - if (es.readyState === EventSource.CLOSED) onClose() + es.onerror = (event): void => { + console.debug('[SSE] Error event, readyState:', es.readyState, event) + if (es.readyState === EventSource.CLOSED) { + console.debug('[SSE] Connection closed') + onClose() + } } return es @@ -105,7 +117,7 @@ export function useSseBalances(params: UseSseBalancesParams): SseBalancesState { const [state, setState] = useState(INITIAL_STATE) const esRef = useRef(null) const attemptsRef = useRef(0) - const timeoutRef = useRef>() + const timeoutRef = useRef>(undefined) const cleanup = useCallback((): void => { esRef.current?.close() @@ -115,6 +127,11 @@ export function useSseBalances(params: UseSseBalancesParams): SseBalancesState { useEffect(() => { if (!enabled || !account || tokensListsUrls.length === 0) { + console.debug('[SSE] Skipping connection:', { + enabled, + account: !!account, + tokensListsUrls: tokensListsUrls.length, + }) cleanup() setState(INITIAL_STATE) return cleanup @@ -131,6 +148,7 @@ export function useSseBalances(params: UseSseBalancesParams): SseBalancesState { await createSession(chainId, account, tokensListsUrls, customTokens) } catch (e) { const error = e instanceof Error ? e : new Error('Session failed') + console.debug('[SSE] Session creation failed:', error.message) setState({ ...INITIAL_STATE, error }) onError?.(error) return diff --git a/libs/balances-and-allowances/src/index.ts b/libs/balances-and-allowances/src/index.ts index bc729fe4fb7..dcdc0d4428a 100644 --- a/libs/balances-and-allowances/src/index.ts +++ b/libs/balances-and-allowances/src/index.ts @@ -1,7 +1,6 @@ // Updaters export { BalancesAndAllowancesUpdater } from './updaters/BalancesAndAllowancesUpdater' export { PriorityTokensUpdater, PRIORITY_TOKENS_REFRESH_INTERVAL } from './updaters/PriorityTokensUpdater' -export { BalancesBffUpdater } from './updaters/BalancesBffUpdater' export { BalancesRpcCallUpdater } from './updaters/BalancesRpcCallUpdater' export { BalancesSseUpdater } from './updaters/BalancesSseUpdater' @@ -20,7 +19,6 @@ export { useTradeSpenderAddress } from './hooks/useTradeSpenderAddress' export { useSseBalances } from './hooks/useSseBalances' // State hooks -export { useIsBffFailed } from './state/isBffFailedAtom' export { useIsSseFailed } from './state/isSseFailedAtom' // Types @@ -29,8 +27,5 @@ export type { BalancesState } from './state/balancesAtom' export type { AllowancesState } from './hooks/useTokenAllowances' export type { SseBalancesState, UseSseBalancesParams } from './hooks/useSseBalances' -// Utils -export * from './utils/isBffSupportedNetwork' - // Consts export { DEFAULT_BALANCES_STATE } from './state/balancesAtom' diff --git a/libs/balances-and-allowances/src/state/isBffFailedAtom.ts b/libs/balances-and-allowances/src/state/isBffFailedAtom.ts deleted file mode 100644 index cb0192f338b..00000000000 --- a/libs/balances-and-allowances/src/state/isBffFailedAtom.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useSetAtom } from 'jotai' -import { atom, useAtomValue } from 'jotai/index' - -export const isBffFailedAtom = atom(false) - -export function useIsBffFailed(): boolean { - return useAtomValue(isBffFailedAtom) -} - -export function useSetIsBffFailed(): (value: boolean) => void { - return useSetAtom(isBffFailedAtom) -} diff --git a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx index 6fe9fe9c27d..7b0f69a20f8 100644 --- a/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx +++ b/libs/balances-and-allowances/src/updaters/BalancesAndAllowancesUpdater.tsx @@ -1,22 +1,19 @@ -import { ReactNode, useEffect, useMemo } from 'react' +import { ReactNode, useMemo } from 'react' -import { LpToken, NATIVE_CURRENCIES } from '@cowprotocol/common-const' +import { LpToken } from '@cowprotocol/common-const' import type { SupportedChainId } from '@cowprotocol/cow-sdk' import { useAllActiveTokens, useListsEnabledState } from '@cowprotocol/tokens' import ms from 'ms.macro' import { SWRConfiguration } from 'swr' -import { BalancesBffUpdater } from './BalancesBffUpdater' import { BalancesCacheUpdater } from './BalancesCacheUpdater' import { BalancesResetUpdater } from './BalancesResetUpdater' import { BalancesRpcCallUpdater } from './BalancesRpcCallUpdater' import { BalancesSseUpdater } from './BalancesSseUpdater' import { BASIC_MULTICALL_SWR_CONFIG } from '../consts' -import { useNativeTokenBalance } from '../hooks/useNativeTokenBalance' import { useSwrConfigWithPauseForNetwork } from '../hooks/useSwrConfigWithPauseForNetwork' -import { useUpdateTokenBalance } from '../hooks/useUpdateTokenBalance' import { useIsSseFailed } from '../state/isSseFailedAtom' // A small gap between balances and allowances refresh intervals is needed to avoid high load to the node at the same time @@ -27,30 +24,17 @@ const EMPTY_TOKENS: string[] = [] export interface BalancesAndAllowancesUpdaterProps { account: string | undefined chainId: SupportedChainId - invalidateCacheTrigger: number excludedTokens: Set - /** @deprecated Use isSseEnabled instead */ - isBffSwitchedOn: boolean - /** @deprecated Use isSseEnabled instead */ - isBffEnabled?: boolean - /** Enable SSE-based real-time balance updates */ - isSseEnabled?: boolean } export function BalancesAndAllowancesUpdater({ account, chainId, - invalidateCacheTrigger, - isBffSwitchedOn, excludedTokens, - isBffEnabled, - isSseEnabled = false, }: BalancesAndAllowancesUpdaterProps): ReactNode { - const updateTokenBalance = useUpdateTokenBalance() const isSseFailed = useIsSseFailed() const allTokens = useAllActiveTokens() - const { data: nativeTokenBalance } = useNativeTokenBalance(account, chainId) const tokenAddresses = useMemo(() => { if (allTokens.chainId !== chainId) return EMPTY_TOKENS @@ -75,20 +59,7 @@ export function BalancesAndAllowancesUpdater({ // Determine which updater to use const hasSseTokenLists = tokensListsUrls.length > 0 - const useSse = isSseEnabled && !isSseFailed && hasSseTokenLists - const useBff = !isSseEnabled && isBffEnabled - const useRpcFallback = (!isBffSwitchedOn || !isBffEnabled) && !useSse - - // Add native token balance to the store as well - useEffect(() => { - if (isBffSwitchedOn || isSseEnabled) return - - const nativeToken = NATIVE_CURRENCIES[chainId] - - if (nativeToken && nativeTokenBalance) { - updateTokenBalance(nativeToken.address, nativeTokenBalance) - } - }, [isBffSwitchedOn, isSseEnabled, nativeTokenBalance, chainId, updateTokenBalance]) + const useSse = !isSseFailed && hasSseTokenLists return ( <> @@ -102,18 +73,8 @@ export function BalancesAndAllowancesUpdater({ /> )} - {/* Legacy BFF polling (deprecated, for backward compatibility) */} - {useBff && ( - - )} - - {/* RPC fallback when SSE/BFF fails or is disabled */} - {(useRpcFallback || isSseFailed) && ( + {/* RPC fallback when SSE fails */} + {isSseFailed && ( ): BalancesState['values'] { return Object.entries(balances).reduce((acc, [address, balance]) => { - if (balance && balance !== '0') { + if (balance) { try { acc[address.toLowerCase()] = BigNumber.from(balance) } catch { @@ -28,6 +30,17 @@ function parseBalances(balances: Record): BalancesState['values' }, {}) } +/** + * Initialize all token addresses with 0 balance + * This is needed because SSE only returns non-zero balances + */ +function initializeZeroBalances(tokenAddresses: string[]): BalancesState['values'] { + return tokenAddresses.reduce((acc, address) => { + acc[address.toLowerCase()] = ZERO + return acc + }, {}) +} + export function BalancesSseUpdater({ account, chainId, @@ -42,14 +55,17 @@ export function BalancesSseUpdater({ (balances: Record) => { if (!account) return + // Initialize all known tokens with 0 balance first + // SSE only returns non-zero balances, so tokens not in the response have 0 balance + const zeroBalances = initializeZeroBalances(tokenAddresses) const parsed = parseBalances(balances) - if (Object.keys(parsed).length === 0) return setBalances((state) => ({ ...state, chainId, fromCache: false, - values: { ...state.values, ...parsed }, + // First apply zero balances, then overwrite with actual balances + values: { ...state.values, ...zeroBalances, ...parsed }, isLoading: false, })) @@ -61,7 +77,7 @@ export function BalancesSseUpdater({ }, })) }, - [account, chainId, setBalances, setBalancesUpdate], + [account, chainId, tokenAddresses, setBalances, setBalancesUpdate], ) const onBalanceUpdate = useCallback( @@ -87,7 +103,7 @@ export function BalancesSseUpdater({ setIsSseFailed(true) }, [setIsSseFailed]) - const { isConnected, isLoading } = useSseBalances({ + const { isLoading, error } = useSseBalances({ account, chainId, enabled: !!account, @@ -98,10 +114,12 @@ export function BalancesSseUpdater({ onError, }) - // Sync connection state + // Only mark SSE as failed when there's an actual error, not on initial state useEffect(() => { - setIsSseFailed(!isConnected && !isLoading) - }, [isConnected, isLoading, setIsSseFailed]) + if (error) { + setIsSseFailed(true) + } + }, [error, setIsSseFailed]) // Sync loading state useEffect(() => { diff --git a/libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts b/libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts deleted file mode 100644 index 41b5a6e358e..00000000000 --- a/libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SupportedChainId } from '@cowprotocol/cow-sdk' - -// TODO: check before Plasma launch. Currently unsupported on 2025/10/20 -const UNSUPPORTED_BFF_NETWORKS = [SupportedChainId.LENS, SupportedChainId.SEPOLIA, SupportedChainId.PLASMA] - -export function isBffSupportedNetwork(chainId: SupportedChainId): boolean { - return !UNSUPPORTED_BFF_NETWORKS.includes(chainId) -}