From 436ddca5dd8896c16f141ed82005bf6ef71f1b82 Mon Sep 17 00:00:00 2001 From: Pearce Date: Thu, 18 Dec 2025 20:36:23 +0100 Subject: [PATCH 1/4] refactor: reuse constant DAY_IN_MS and banner duration --- apps/main/src/dex/components/PoolAlertBanner.tsx | 5 ++--- apps/main/src/dex/store/createPoolsSlice.ts | 3 ++- apps/main/src/loan/entities/scrvusd-yield.ts | 9 +++++---- packages/curve-ui-kit/src/themes/design/0_primitives.ts | 6 ++++++ .../src/widgets/Header/PhishingWarningBanner.tsx | 4 ++-- tests/cypress/e2e/all/header.cy.ts | 3 +-- tests/cypress/support/generators.ts | 3 ++- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/apps/main/src/dex/components/PoolAlertBanner.tsx b/apps/main/src/dex/components/PoolAlertBanner.tsx index a1033087a2..2429d707ff 100644 --- a/apps/main/src/dex/components/PoolAlertBanner.tsx +++ b/apps/main/src/dex/components/PoolAlertBanner.tsx @@ -3,10 +3,9 @@ import { createPortal } from 'react-dom' import { useIsDesktop } from '@ui-kit/hooks/useBreakpoints' import { useDismissBanner } from '@ui-kit/hooks/useLocalStorage' import { Banner, BannerProps } from '@ui-kit/shared/ui/Banner' +import { Duration } from '@ui-kit/themes/design/0_primitives' import { AlertType, PoolAlert } from '../types/main.types' -const ONE_DAY_MS = 24 * 60 * 60 * 1000 // 24 hours in milliseconds - /** Maps AlertType to BannerSeverity */ const alertTypeToBannerSeverity: Record = { error: 'alert', @@ -25,7 +24,7 @@ export const PoolAlertBanner = ({ poolAlertBannerKey: string alertType: AlertType }) => { - const { shouldShowBanner, dismissBanner } = useDismissBanner(poolAlertBannerKey, ONE_DAY_MS) + const { shouldShowBanner, dismissBanner } = useDismissBanner(poolAlertBannerKey, Duration.Banner.Daily) const isDesktop = useIsDesktop() // eslint-disable-next-line react-hooks/exhaustive-deps -- isDesktop triggers re-query when header changes (desktop ↔ mobile) const portalTarget = useMemo(() => document.getElementsByTagName('header')[0], [isDesktop]) diff --git a/apps/main/src/dex/store/createPoolsSlice.ts b/apps/main/src/dex/store/createPoolsSlice.ts index 1307759a0b..c2483e3e28 100644 --- a/apps/main/src/dex/store/createPoolsSlice.ts +++ b/apps/main/src/dex/store/createPoolsSlice.ts @@ -44,6 +44,7 @@ import { convertToLocaleTimestamp } from '@ui-kit/features/candle-chart/utils' import { requireLib } from '@ui-kit/features/connect-wallet' import { log } from '@ui-kit/lib/logging' import { fetchTokenUsdRate, getTokenUsdRateQueryData } from '@ui-kit/lib/model/entities/token-usd-rate' +import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' import { fetchNetworks } from '../entities/networks' import { getPools } from '../lib/pools' @@ -526,7 +527,7 @@ const createPoolsSlice = (set: StoreApi['setState'], get: StoreApi fetchPricesPoolSnapshots: async (chainId: ChainId, poolAddress: string) => { const networks = await fetchNetworks() if (networks[chainId].pricesApi) { - const startTime = Math.floor((Date.now() - 24 * 60 * 60 * 1000) / 1000) + const startTime = Math.floor((Date.now() - DAY_IN_MS) / 1000) const endTime = Math.floor(Date.now() / 1000) const network = networks[chainId].id.toLowerCase() diff --git a/apps/main/src/loan/entities/scrvusd-yield.ts b/apps/main/src/loan/entities/scrvusd-yield.ts index e8f02937ad..e407317322 100644 --- a/apps/main/src/loan/entities/scrvusd-yield.ts +++ b/apps/main/src/loan/entities/scrvusd-yield.ts @@ -3,15 +3,16 @@ import type { Yield } from '@curvefi/prices-api/savings/models' import { queryFactory } from '@ui-kit/lib/model/query' import { timeOptionValidationSuite } from '@ui-kit/lib/model/query/time-option-validation' import type { TimeOption } from '@ui-kit/lib/types/scrvusd' +import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' export type ScrvUsdYieldWithAverages = Yield & { proj_apy_7d_avg: number; proj_apy_total_avg: number } export const _getScrvUsdYield = async (params: { timeOption: TimeOption }) => { // calcs starting timestamp const timeOptionCalc: Record = { - '1M': 30 * 24 * 60 * 60 * 1000, // 30 days - '6M': 180 * 24 * 60 * 60 * 1000, // 180 days - '1Y': 365 * 24 * 60 * 60 * 1000, // 365 days + '1M': 30 * DAY_IN_MS, // 30 days + '6M': 180 * DAY_IN_MS, // 180 days + '1Y': 365 * DAY_IN_MS, // 365 days } // sets number of aggregations const aggNumbers: Record = { '1M': 4, '6M': 16, '1Y': 32 } @@ -31,7 +32,7 @@ export const _getScrvUsdYield = async (params: { timeOption: TimeOption }) => { const totalAverage = array.reduce((sum, curr) => sum + curr.apyProjected, 0) / array.length // Calculate 7-day moving average - const SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000 + const SEVEN_DAYS_IN_MILLISECONDS = 7 * DAY_IN_MS const currentTimestamp = item.timestamp.getTime() const sevenDaysAgoTimestamp = currentTimestamp - SEVEN_DAYS_IN_MILLISECONDS diff --git a/packages/curve-ui-kit/src/themes/design/0_primitives.ts b/packages/curve-ui-kit/src/themes/design/0_primitives.ts index f227de53b5..e326dbd4b2 100644 --- a/packages/curve-ui-kit/src/themes/design/0_primitives.ts +++ b/packages/curve-ui-kit/src/themes/design/0_primitives.ts @@ -133,6 +133,8 @@ export const Sizing = { '800': '5.5rem', // 88px } as const +export const DAY_IN_MS = 24 * 60 * 60 * 1000 + export const Duration = { Delay: 100, Flicker: 1000, @@ -141,6 +143,10 @@ export const Duration = { Snackbar: 6000, Tooltip: { Enter: 500, Exit: 500 }, Transition: 256, + Banner: { + Daily: DAY_IN_MS, + Monthly: 30 * DAY_IN_MS, + }, } export const TransitionFunction = `ease-out ${Duration.Transition}ms` diff --git a/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx b/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx index d8ffc37c62..6f0e324071 100644 --- a/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx +++ b/packages/curve-ui-kit/src/widgets/Header/PhishingWarningBanner.tsx @@ -1,16 +1,16 @@ import { useDismissBanner } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' import { Banner } from '@ui-kit/shared/ui/Banner' +import { Duration } from '@ui-kit/themes/design/0_primitives' const URL = 'https://www.curve.finance' -const ONE_MONTH_MS = 30 * 24 * 60 * 60 * 1000 // 30 days in milliseconds /** * Displays a banner warning users about phishing risks and encourages them to verify they are on the official Curve domains. * The banner will reappear after one month if dismissed. */ export const PhishingWarningBanner = () => { - const { shouldShowBanner, dismissBanner } = useDismissBanner('phishing-warning-dismissed', ONE_MONTH_MS) + const { shouldShowBanner, dismissBanner } = useDismissBanner('phishing-warning-dismissed', Duration.Banner.Monthly) return ( shouldShowBanner && ( diff --git a/tests/cypress/e2e/all/header.cy.ts b/tests/cypress/e2e/all/header.cy.ts index ddf318d7db..253321df35 100644 --- a/tests/cypress/e2e/all/header.cy.ts +++ b/tests/cypress/e2e/all/header.cy.ts @@ -8,6 +8,7 @@ import { SCROLL_WIDTH, TABLET_BREAKPOINT, } from '@cy/support/ui' +import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' const expectedMainNavHeight = 56 const expectedSubNavHeight = 42 // 40 + 2px border @@ -18,8 +19,6 @@ const expectedFooterXMargin = { mobile: 32, tablet: 48, desktop: 48 } const expectedFooterMinWidth = 273 const expectedFooterMaxWidth = 1536 -const DAY_IN_MS = 24 * 60 * 60 * 1000 - describe('Header', () => { let viewport: readonly [number, number] diff --git a/tests/cypress/support/generators.ts b/tests/cypress/support/generators.ts index 0ed2ac4342..a5758af498 100644 --- a/tests/cypress/support/generators.ts +++ b/tests/cypress/support/generators.ts @@ -1,5 +1,6 @@ import type { Address } from '@curvefi/prices-api' import { range, recordValues } from '@curvefi/prices-api/objects.util' +import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' export const MAX_USD_VALUE = 400_000_000 @@ -36,7 +37,7 @@ export const oneTokenType = () => oneOf('collateral', 'borrowed') export type TokenType = ReturnType export const oneDate = ({ - minDate = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000), // 1 year ago + minDate = new Date(Date.now() - 365 * DAY_IN_MS), // 1 year ago maxDate = new Date(Date.now()), }: { minDate?: Date From c958ce4b1e7e080e19e7cfe3ed42c6cf2ced71f7 Mon Sep 17 00:00:00 2001 From: Pearce Date: Thu, 18 Dec 2025 20:39:52 +0100 Subject: [PATCH 2/4] feat: banner for aave v2 deprecation on polygon and avalanche --- .../src/shared/ui/GlobalBanner.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx b/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx index d0c946ca28..272b97a6b5 100644 --- a/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx +++ b/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx @@ -1,10 +1,14 @@ import { useChainId, useConnection, useSwitchChain } from 'wagmi' import Box from '@mui/material/Box' import { isFailure, useCurve, type WagmiChainId } from '@ui-kit/features/connect-wallet' -import { useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' +import { usePathname } from '@ui-kit/hooks/router' +import { useDismissBanner, useReleaseChannel } from '@ui-kit/hooks/useLocalStorage' import { t } from '@ui-kit/lib/i18n' +import { getCurrentApp } from '@ui-kit/shared/routes' import { Banner } from '@ui-kit/shared/ui/Banner' +import { Duration } from '@ui-kit/themes/design/0_primitives' import { isCypress, ReleaseChannel } from '@ui-kit/utils' +import { Chain } from '@ui-kit/utils/network' import { PhishingWarningBanner } from '@ui-kit/widgets/Header/PhishingWarningBanner' export type GlobalBannerProps = { @@ -21,11 +25,18 @@ export const GlobalBanner = ({ networkId, chainId }: GlobalBannerProps) => { const { switchChain } = useSwitchChain() const { connectState } = useCurve() const walletChainId = useChainId() + const pathname = usePathname() + const currentApp = getCurrentApp(pathname) + const showSwitchNetworkMessage = isConnected && chainId && walletChainId != chainId const showConnectApiErrorMessage = !showSwitchNetworkMessage && isFailure(connectState) + + const { shouldShowBanner: showAaveBanner, dismissBanner: dismissAaveBanner } = useDismissBanner( + 'aave-v2-frozen-avalanche-polygon', + Duration.Banner.Monthly, + ) return ( - {releaseChannel !== ReleaseChannel.Stable && !isCypress && ( { {t`${releaseChannel} Mode Enabled`} )} + {maintenanceMessage && {maintenanceMessage}} {showSwitchNetworkMessage && ( { {t`There is an issue connecting to the API. Please try to switch your RPC in your wallet settings.`} )} + {showAaveBanner && currentApp === 'dex' && (chainId === Chain.Polygon || chainId === Chain.Avalanche) && ( + + {t`Aave V2 Frozen aTokens`} + + )} ) } From 725f7545d95e42f9a5877c33d91fec4a001f4c94 Mon Sep 17 00:00:00 2001 From: Pearce Date: Fri, 19 Dec 2025 15:13:16 +0100 Subject: [PATCH 3/4] refactor: use TIME_FRAMES for MS time constants --- apps/main/src/dex/store/createPoolsSlice.ts | 4 ++-- apps/main/src/loan/entities/scrvusd-yield.ts | 10 +++++----- packages/curve-ui-kit/src/lib/model/time.ts | 3 +++ .../curve-ui-kit/src/themes/design/0_primitives.ts | 8 ++++---- tests/cypress/e2e/all/header.cy.ts | 6 +++--- tests/cypress/support/generators.ts | 4 ++-- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/apps/main/src/dex/store/createPoolsSlice.ts b/apps/main/src/dex/store/createPoolsSlice.ts index c2483e3e28..353945bb66 100644 --- a/apps/main/src/dex/store/createPoolsSlice.ts +++ b/apps/main/src/dex/store/createPoolsSlice.ts @@ -44,7 +44,7 @@ import { convertToLocaleTimestamp } from '@ui-kit/features/candle-chart/utils' import { requireLib } from '@ui-kit/features/connect-wallet' import { log } from '@ui-kit/lib/logging' import { fetchTokenUsdRate, getTokenUsdRateQueryData } from '@ui-kit/lib/model/entities/token-usd-rate' -import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' import { fetchNetworks } from '../entities/networks' import { getPools } from '../lib/pools' @@ -527,7 +527,7 @@ const createPoolsSlice = (set: StoreApi['setState'], get: StoreApi fetchPricesPoolSnapshots: async (chainId: ChainId, poolAddress: string) => { const networks = await fetchNetworks() if (networks[chainId].pricesApi) { - const startTime = Math.floor((Date.now() - DAY_IN_MS) / 1000) + const startTime = Math.floor((Date.now() - TIME_FRAMES.DAY_MS) / 1000) const endTime = Math.floor(Date.now() / 1000) const network = networks[chainId].id.toLowerCase() diff --git a/apps/main/src/loan/entities/scrvusd-yield.ts b/apps/main/src/loan/entities/scrvusd-yield.ts index e407317322..0a6f665389 100644 --- a/apps/main/src/loan/entities/scrvusd-yield.ts +++ b/apps/main/src/loan/entities/scrvusd-yield.ts @@ -2,17 +2,17 @@ import { getYield } from '@curvefi/prices-api/savings' import type { Yield } from '@curvefi/prices-api/savings/models' import { queryFactory } from '@ui-kit/lib/model/query' import { timeOptionValidationSuite } from '@ui-kit/lib/model/query/time-option-validation' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' import type { TimeOption } from '@ui-kit/lib/types/scrvusd' -import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' export type ScrvUsdYieldWithAverages = Yield & { proj_apy_7d_avg: number; proj_apy_total_avg: number } export const _getScrvUsdYield = async (params: { timeOption: TimeOption }) => { // calcs starting timestamp const timeOptionCalc: Record = { - '1M': 30 * DAY_IN_MS, // 30 days - '6M': 180 * DAY_IN_MS, // 180 days - '1Y': 365 * DAY_IN_MS, // 365 days + '1M': 30 * TIME_FRAMES.DAY_MS, // 30 days + '6M': 180 * TIME_FRAMES.DAY_MS, // 180 days + '1Y': 365 * TIME_FRAMES.DAY_MS, // 365 days } // sets number of aggregations const aggNumbers: Record = { '1M': 4, '6M': 16, '1Y': 32 } @@ -32,7 +32,7 @@ export const _getScrvUsdYield = async (params: { timeOption: TimeOption }) => { const totalAverage = array.reduce((sum, curr) => sum + curr.apyProjected, 0) / array.length // Calculate 7-day moving average - const SEVEN_DAYS_IN_MILLISECONDS = 7 * DAY_IN_MS + const SEVEN_DAYS_IN_MILLISECONDS = 7 * TIME_FRAMES.DAY_MS const currentTimestamp = item.timestamp.getTime() const sevenDaysAgoTimestamp = currentTimestamp - SEVEN_DAYS_IN_MILLISECONDS diff --git a/packages/curve-ui-kit/src/lib/model/time.ts b/packages/curve-ui-kit/src/lib/model/time.ts index 40c0960f46..8d4048acd8 100644 --- a/packages/curve-ui-kit/src/lib/model/time.ts +++ b/packages/curve-ui-kit/src/lib/model/time.ts @@ -15,6 +15,9 @@ export const REFRESH_INTERVAL = { } as const export const TIME_FRAMES = { + DAY_MS: 24 * 60 * 60 * 1000, WEEK: 7 * 24 * 60 * 60, MONTH: 30 * 24 * 60 * 60, + MONTH_MS: 30 * 24 * 60 * 60 * 1000, + YEAR_MS: 365 * 24 * 60 * 60 * 1000, } as const diff --git a/packages/curve-ui-kit/src/themes/design/0_primitives.ts b/packages/curve-ui-kit/src/themes/design/0_primitives.ts index e326dbd4b2..c76ccc2074 100644 --- a/packages/curve-ui-kit/src/themes/design/0_primitives.ts +++ b/packages/curve-ui-kit/src/themes/design/0_primitives.ts @@ -1,3 +1,5 @@ +import { TIME_FRAMES } from '@ui-kit/lib/model/time' + export const Grays = { '10': '#fdfcfc', '25': '#fafafa', @@ -133,8 +135,6 @@ export const Sizing = { '800': '5.5rem', // 88px } as const -export const DAY_IN_MS = 24 * 60 * 60 * 1000 - export const Duration = { Delay: 100, Flicker: 1000, @@ -144,8 +144,8 @@ export const Duration = { Tooltip: { Enter: 500, Exit: 500 }, Transition: 256, Banner: { - Daily: DAY_IN_MS, - Monthly: 30 * DAY_IN_MS, + Daily: TIME_FRAMES.DAY_MS, + Monthly: TIME_FRAMES.MONTH_MS, }, } diff --git a/tests/cypress/e2e/all/header.cy.ts b/tests/cypress/e2e/all/header.cy.ts index 253321df35..f912b50493 100644 --- a/tests/cypress/e2e/all/header.cy.ts +++ b/tests/cypress/e2e/all/header.cy.ts @@ -8,7 +8,7 @@ import { SCROLL_WIDTH, TABLET_BREAKPOINT, } from '@cy/support/ui' -import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' const expectedMainNavHeight = 56 const expectedSubNavHeight = 42 // 40 + 2px border @@ -190,14 +190,14 @@ describe('Header', () => { it('should reappear after one month', () => { // Set dismissal date to 31 days ago (more than one month) - const oneMonthAgo = Date.now() - 31 * DAY_IN_MS + const oneMonthAgo = Date.now() - 31 * TIME_FRAMES.DAY_MS visitWithDismissedBanner(oneMonthAgo) cy.get("[data-testid='phishing-warning-banner']", LOAD_TIMEOUT).should('be.visible') }) it('should remain hidden within one month', () => { // Set dismissal date to 15 days ago (less than one month) - const fifteenDaysAgo = Date.now() - 15 * DAY_IN_MS + const fifteenDaysAgo = Date.now() - 15 * TIME_FRAMES.DAY_MS visitWithDismissedBanner(fifteenDaysAgo) cy.get("[data-testid='phishing-warning-banner']", LOAD_TIMEOUT).should('not.exist') }) diff --git a/tests/cypress/support/generators.ts b/tests/cypress/support/generators.ts index a5758af498..640a64488e 100644 --- a/tests/cypress/support/generators.ts +++ b/tests/cypress/support/generators.ts @@ -1,6 +1,6 @@ import type { Address } from '@curvefi/prices-api' import { range, recordValues } from '@curvefi/prices-api/objects.util' -import { DAY_IN_MS } from '@ui-kit/themes/design/0_primitives' +import { TIME_FRAMES } from '@ui-kit/lib/model/time' export const MAX_USD_VALUE = 400_000_000 @@ -37,7 +37,7 @@ export const oneTokenType = () => oneOf('collateral', 'borrowed') export type TokenType = ReturnType export const oneDate = ({ - minDate = new Date(Date.now() - 365 * DAY_IN_MS), // 1 year ago + minDate = new Date(Date.now() - TIME_FRAMES.YEAR_MS), // 1 year ago maxDate = new Date(Date.now()), }: { minDate?: Date From 347a7c3a61b8bd49c0d531d23336058f98d65c70 Mon Sep 17 00:00:00 2001 From: Pearce Date: Fri, 19 Dec 2025 15:13:49 +0100 Subject: [PATCH 4/4] feat: aave v2 banner with info severity instead of warning --- packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx b/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx index 272b97a6b5..5d204dc8b7 100644 --- a/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx +++ b/packages/curve-ui-kit/src/shared/ui/GlobalBanner.tsx @@ -63,12 +63,12 @@ export const GlobalBanner = ({ networkId, chainId }: GlobalBannerProps) => { {t`There is an issue connecting to the API. Please try to switch your RPC in your wallet settings.`} )} - {showAaveBanner && currentApp === 'dex' && (chainId === Chain.Polygon || chainId === Chain.Avalanche) && ( + {showAaveBanner && currentApp === 'dex' && [Chain.Polygon, Chain.Avalanche].includes(chainId) && ( {t`Aave V2 Frozen aTokens`}