Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ export const BFF_BALANCES_SWR_CONFIG: SWRConfiguration = {
// 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 }) => {
onErrorRetry: (error: unknown, _key, config, revalidate, { retryCount }) => {
// Don't retry if error is "Unsupported chain"
if (error instanceof Error && error.message.toLowerCase().includes('unsupported chain')) {
Copy link
Collaborator

@shoom3301 shoom3301 Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should isUnsupportedChainMessage be used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, fixed

return
}

const timeout = config.errorRetryInterval * Math.pow(2, retryCount - 1)

setTimeout(() => revalidate({ retryCount }), timeout)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ 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'
import { useSetIsBffFailed, useAddUnsupportedChainId } from '../state/isBffFailedAtom'
import { useIsBffSupportedNetwork } from '../utils/isBffSupportedNetwork'

type BalanceResponse = {
balances: Record<string, string> | null
message?: string
}

export interface PersistBalancesFromBffParams {
Expand All @@ -25,15 +26,45 @@ export interface PersistBalancesFromBffParams {
tokenAddresses: string[]
}

function isUnsupportedChainError(errorMessage: string): boolean {
return errorMessage.toLowerCase().includes('unsupported chain')
}

function parseErrorResponse(data: unknown, statusText: string): string {
if (typeof data === 'object' && data !== null && 'message' in data) {
return String(data.message)
}
return statusText
}

async function parseBffResponse(res: Response): Promise<BalanceResponse | { message?: string }> {
try {
return await res.json()
} catch {
return { message: res.statusText }
}
}

function handleBffError(res: Response, data: BalanceResponse | { message?: string }): never {
const errorMessage = parseErrorResponse(data, res.statusText)

if (isUnsupportedChainError(errorMessage)) {
throw new Error('Unsupported chain')
}

throw new Error(`BFF error: ${res.status} ${res.statusText}`)
}

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 isSupportedNetwork = useIsBffSupportedNetwork(targetChainId)

const setIsBffFailed = useSetIsBffFailed()
const addUnsupportedChainId = useAddUnsupportedChainId()

const lastTriggerRef = useRef(invalidateCacheTrigger)

Expand All @@ -59,8 +90,15 @@ export function usePersistBalancesFromBff(params: PersistBalancesFromBffParams):
}, [setBalances, isBalancesLoading, targetChainId, targetAccount])

useEffect(() => {
const hasUnsupportedChainError = error instanceof Error &&
isUnsupportedChainError(error.message)

if (hasUnsupportedChainError) {
addUnsupportedChainId(targetChainId)
}

setIsBffFailed(!!error)
}, [error, setIsBffFailed])
}, [error, setIsBffFailed, addUnsupportedChainId, targetChainId])

useEffect(() => {
if (!targetAccount || !data || error) return
Expand Down Expand Up @@ -114,18 +152,21 @@ export async function getBffBalances(

try {
const res = await fetch(fullUrl)
const data: BalanceResponse = await res.json()
const data = await parseBffResponse(res)

if (!res.ok) {
return Promise.reject(new Error(`BFF error: ${res.status} ${res.statusText}`))
handleBffError(res, data)
}

if (!data.balances) {
if (!('balances' in data) || !data.balances) {
return null
}

return data.balances
} catch (error) {
return Promise.reject(error)
if (error instanceof Error && isUnsupportedChainError(error.message)) {
throw new Error('Unsupported chain')
}
throw error
}
}
16 changes: 16 additions & 0 deletions libs/balances-and-allowances/src/state/isBffFailedAtom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useSetAtom } from 'jotai'
import { atom, useAtomValue } from 'jotai/index'

import { SupportedChainId } from '@cowprotocol/cow-sdk'

export const isBffFailedAtom = atom(false)

export function useIsBffFailed(): boolean {
Expand All @@ -10,3 +12,17 @@ export function useIsBffFailed(): boolean {
export function useSetIsBffFailed(): (value: boolean) => void {
return useSetAtom(isBffFailedAtom)
}

export const bffUnsupportedChainsAtom = atom(new Set<SupportedChainId>())

export function useAddUnsupportedChainId(): (chainId: SupportedChainId) => void {
const setAtom = useSetAtom(bffUnsupportedChainsAtom)
return (chainId) => {
setAtom((prev) => {
if (prev.has(chainId)) {
return prev
}
return new Set([...prev, chainId])
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { BASIC_MULTICALL_SWR_CONFIG } from '../consts'
import { useNativeTokenBalance } from '../hooks/useNativeTokenBalance'
import { useSwrConfigWithPauseForNetwork } from '../hooks/useSwrConfigWithPauseForNetwork'
import { useUpdateTokenBalance } from '../hooks/useUpdateTokenBalance'
import { useIsBffSupportedNetwork } from '../utils/isBffSupportedNetwork'

// 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` }
Expand All @@ -40,6 +41,7 @@ export function BalancesAndAllowancesUpdater({
isBffEnabled,
}: BalancesAndAllowancesUpdaterProps): ReactNode {
const updateTokenBalance = useUpdateTokenBalance()
const isBffSupported = useIsBffSupportedNetwork(chainId)

const allTokens = useAllActiveTokens()
const { data: nativeTokenBalance } = useNativeTokenBalance(account, chainId)
Expand Down Expand Up @@ -71,7 +73,7 @@ export function BalancesAndAllowancesUpdater({

return (
<>
{isBffEnabled && (
{isBffEnabled && isBffSupported && (
<BalancesBffUpdater
account={account}
chainId={chainId}
Expand Down
11 changes: 10 additions & 1 deletion libs/balances-and-allowances/src/utils/isBffSupportedNetwork.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { useAtomValue } from 'jotai'

import { SupportedChainId } from '@cowprotocol/cow-sdk'

import { bffUnsupportedChainsAtom } from '../state/isBffFailedAtom'

// TODO: check before Plasma launch. Currently unsupported on 2025/10/20
const UNSUPPORTED_BFF_NETWORKS = [SupportedChainId.LENS, SupportedChainId.SEPOLIA, SupportedChainId.PLASMA]
const UNSUPPORTED_BFF_NETWORKS = [SupportedChainId.PLASMA]

export function isBffSupportedNetwork(chainId: SupportedChainId): boolean {
return !UNSUPPORTED_BFF_NETWORKS.includes(chainId)
}

export function useIsBffSupportedNetwork(chainId: SupportedChainId): boolean {
const unsupportedChains = useAtomValue(bffUnsupportedChainsAtom)
return isBffSupportedNetwork(chainId) && !unsupportedChains.has(chainId)
}
Loading