Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
20 changes: 12 additions & 8 deletions apps/cowswap-frontend/src/locales/en-US.po
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ msgstr "Easily set and manage your orders in USD"
msgid "Click \"Wrap {nativeSymbol}\" to try again."
msgstr "Click \"Wrap {nativeSymbol}\" to try again."

#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/index.tsx
#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/useSearchRows.ts
msgid "Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists."
msgstr "Tokens from inactive lists. Import specific tokens below or click Manage to activate more lists."

Expand Down Expand Up @@ -840,8 +840,8 @@ msgid "Copied"
msgstr "Copied"

#: apps/cowswap-frontend/src/modules/tokensList/containers/TokenSearchResults/index.tsx
msgid "Can't find your token on the list?"
msgstr "Can't find your token on the list?"
#~ msgid "Can't find your token on the list?"
#~ msgstr "Can't find your token on the list?"

#: apps/cowswap-frontend/src/modules/trade/pure/ReceiveAmountTitle/index.tsx
msgid "icon"
Expand All @@ -860,8 +860,8 @@ msgid "Please connect your wallet to one of our supported networks."
msgstr "Please connect your wallet to one of our supported networks."

#: apps/cowswap-frontend/src/modules/tokensList/containers/TokenSearchResults/index.tsx
msgid "<0>Read our guide</0> on how to add custom tokens."
msgstr "<0>Read our guide</0> on how to add custom tokens."
#~ msgid "<0>Read our guide</0> on how to add custom tokens."
#~ msgstr "<0>Read our guide</0> on how to add custom tokens."

#: apps/cowswap-frontend/src/modules/hooksStore/containers/TenderlySimulate/index.tsx
msgid "Retry"
Expand Down Expand Up @@ -1166,7 +1166,7 @@ msgstr "Select an {accountProxyLabelString} to check for available refunds {chai
msgid "Unsupported wallet"
msgstr "Unsupported wallet"

#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/index.tsx
#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/useSearchRows.ts
msgid "Expanded results from inactive Token Lists"
msgstr "Expanded results from inactive Token Lists"

Expand Down Expand Up @@ -2961,7 +2961,7 @@ msgstr "Safe confirmed signatures"
msgid "Winning solver"
msgstr "Winning solver"

#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/index.tsx
#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/useSearchRows.ts
msgid "Tokens from external sources."
msgstr "Tokens from external sources."

Expand Down Expand Up @@ -3911,6 +3911,10 @@ msgstr "User rejected approval transaction"
msgid "Swap on"
msgstr "Swap on"

#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/GuideBanner.tsx
msgid "Can't find your token on the list? <0>Read our guide</0> on how to add custom tokens."
msgstr "Can't find your token on the list? <0>Read our guide</0> on how to add custom tokens."

#: apps/cowswap-frontend/src/modules/trade/pure/ProtocolFeeRow/index.tsx
#~ msgid "The fee is {protocolFeeBps} BPS ({protocolFeeAsPercent}%), applied only if the trade is executed.<0/><1/>Solver rewards are taken from this fee amount."
#~ msgstr "The fee is {protocolFeeBps} BPS ({protocolFeeAsPercent}%), applied only if the trade is executed.<0/><1/>Solver rewards are taken from this fee amount."
Expand Down Expand Up @@ -4225,7 +4229,7 @@ msgstr "Version"
msgid "All tokens"
msgstr "All tokens"

#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/index.tsx
#: apps/cowswap-frontend/src/modules/tokensList/pure/TokenSearchContent/useSearchRows.ts
msgid "Additional Results from External Sources"
msgstr "Additional Results from External Sources"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Dispatch, ReactNode, SetStateAction, useMemo, useState } from 'react'

import { Currency, CurrencyAmount, Price } from '@uniswap/sdk-core'

import { Nullish } from 'types'

import { useUsdAmount } from 'modules/usdAmount'

import { ReceiveAmountInfo } from '../../types'
import { getLimitPriceFromReceiveAmount } from '../../utils/getLimitPriceFromReceiveAmount'
import { getOrderTypeReceiveAmounts } from '../../utils/getOrderTypeReceiveAmounts'

interface UseTradeBasicConfirmDetailsDataParams {
receiveAmountInfo: ReceiveAmountInfo
hideUsdValues?: boolean
networkCostsSuffix?: ReactNode
networkCostsTooltipSuffix?: ReactNode
}

interface UseTradeBasicConfirmDetailsDataResult {
isInvertedState: [boolean, Dispatch<SetStateAction<boolean>>]
amountAfterFees: CurrencyAmount<Currency>
amountAfterSlippage: CurrencyAmount<Currency>
amountAfterSlippageUsd: Nullish<CurrencyAmount<Currency>>
amountAfterFeesUsd: Nullish<CurrencyAmount<Currency>>
limitPrice: Price<Currency, Currency> | null
networkCostsSuffix?: ReactNode
networkCostsTooltipSuffix?: ReactNode
}

export function useTradeBasicConfirmDetailsData(
Copy link
Collaborator

Choose a reason for hiding this comment

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

The file is probably should be in this PR. I don't see any places where it is used

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lol, it's from another pr, thank you for catch

params: UseTradeBasicConfirmDetailsDataParams
): UseTradeBasicConfirmDetailsDataResult {
const { receiveAmountInfo, hideUsdValues, networkCostsSuffix, networkCostsTooltipSuffix } = params

const isInvertedState = useState(false)
const { amountAfterFees, amountAfterSlippage } = getOrderTypeReceiveAmounts(receiveAmountInfo)

const amountAfterSlippageUsd = useUsdAmount(hideUsdValues ? null : amountAfterSlippage).value
const amountAfterFeesUsd = useUsdAmount(hideUsdValues ? null : amountAfterFees).value

const limitPrice = useMemo(() => getLimitPriceFromReceiveAmount(receiveAmountInfo), [receiveAmountInfo])

return {
isInvertedState,
amountAfterFees,
amountAfterSlippage,
amountAfterSlippageUsd,
amountAfterFeesUsd,
limitPrice,
networkCostsSuffix,
networkCostsTooltipSuffix,
}
}

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
@@ -1,11 +1,11 @@
import { Provider } from 'jotai'
import { Provider, useAtomValue } 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 { renderHook, waitFor } from '@testing-library/react'
import fetchMock from 'jest-fetch-mock'
import useSWR from 'swr'

Expand All @@ -14,16 +14,14 @@ import { PersistBalancesFromBffParams, usePersistBalancesFromBff } from './usePe

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'
import { bffUnsupportedChainsAtom } from '../state/isBffFailedAtom'
import { UnsupportedChainError } from '../utils/UnsupportedChainError'

// 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()
Expand All @@ -34,7 +32,6 @@ jest.mock('@cowprotocol/wallet', () => ({
}))

describe('usePersistBalancesFromBff - invalidateCacheTrigger', () => {
const mockSetIsBffFailed = jest.fn()
const mockWalletInfo = {
chainId: SupportedChainId.MAINNET,
account: '0x1234567890123456789012345678901234567890',
Expand Down Expand Up @@ -67,6 +64,7 @@ describe('usePersistBalancesFromBff - invalidateCacheTrigger', () => {
} as BalancesState,
],
[balancesUpdateAtom, mockBalancesUpdate],
[bffUnsupportedChainsAtom, new Set<SupportedChainId>()],
])
return <>{children}</>
}
Expand All @@ -82,8 +80,6 @@ describe('usePersistBalancesFromBff - invalidateCacheTrigger', () => {
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', () => {
Expand Down Expand Up @@ -189,4 +185,104 @@ describe('usePersistBalancesFromBff - invalidateCacheTrigger', () => {
})
})

describe('unsupported chain handling', () => {
it('should not make requests for unsupported chains', () => {
const mockUseSWR = useSWR as jest.MockedFunction<typeof useSWR>
mockUseSWR.mockReturnValue({
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: jest.fn(),
} as ReturnType<typeof useSWR>)

const unsupportedChainParams: PersistBalancesFromBffParams = {
...defaultParams,
chainId: SupportedChainId.SEPOLIA, // Unsupported network
}

renderHook(() => usePersistBalancesFromBff(unsupportedChainParams), { wrapper })

// Should not make SWR call for unsupported network
expect(mockUseSWR).toHaveBeenCalledWith(
null, // Key should be null for unsupported network
expect.any(Function),
BFF_BALANCES_SWR_CONFIG,
)
})

it('should add chain to unsupported list when "Unsupported chain" error occurs', async () => {
const mockUseSWR = useSWR as jest.MockedFunction<typeof useSWR>
const unsupportedChainError = new UnsupportedChainError()

mockUseSWR.mockReturnValue({
data: undefined,
error: unsupportedChainError,
isLoading: false,
isValidating: false,
mutate: jest.fn(),
} as ReturnType<typeof useSWR>)

const useUnsupportedChains = (): Set<SupportedChainId> => {
usePersistBalancesFromBff(defaultParams)
return useAtomValue(bffUnsupportedChainsAtom)
}

const { result } = renderHook(() => useUnsupportedChains(), { wrapper })

// Wait for effect to run and add chain to unsupported list
await waitFor(
() => {
expect(result.current.has(defaultParams.chainId)).toBe(true)
},
{ timeout: 3000 },
)
})

it('should stop making requests after chain is added to unsupported list', () => {
const mockUseSWR = useSWR as jest.MockedFunction<typeof useSWR>

const wrapperWithUnsupportedChain = ({ children }: { children: ReactNode }): ReactNode => {
const HydrateAtoms = ({ children }: { children: ReactNode }): ReactNode => {
useHydrateAtoms([
[
balancesAtom,
{
isLoading: false,
chainId: SupportedChainId.MAINNET,
values: {},
fromCache: false,
} as BalancesState,
],
[balancesUpdateAtom, mockBalancesUpdate],
[bffUnsupportedChainsAtom, new Set([SupportedChainId.MAINNET])], // Chain is in unsupported list
])
return <>{children}</>
}

return (
<Provider>
<HydrateAtoms>{children}</HydrateAtoms>
</Provider>
)
}

mockUseSWR.mockReturnValue({
data: undefined,
error: undefined,
isLoading: false,
isValidating: false,
mutate: jest.fn(),
} as ReturnType<typeof useSWR>)

renderHook(() => usePersistBalancesFromBff(defaultParams), { wrapper: wrapperWithUnsupportedChain })

// Should not make SWR call because chain is in unsupported list
expect(mockUseSWR).toHaveBeenCalledWith(
null, // Key should be null
expect.any(Function),
BFF_BALANCES_SWR_CONFIG,
)
})
})
})
Loading