diff --git a/.changeset/wet-poets-knock.md b/.changeset/wet-poets-knock.md new file mode 100644 index 0000000000..7a14fd7fbf --- /dev/null +++ b/.changeset/wet-poets-knock.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": minor +--- + +add geo-block diff --git a/apps/evm/src/__mocks__/api/pools.json b/apps/evm/src/__mocks__/api/pools.json index 5fb1080486..a9dbf1e955 100644 --- a/apps/evm/src/__mocks__/api/pools.json +++ b/apps/evm/src/__mocks__/api/pools.json @@ -5729,6 +5729,7 @@ "name": "USDT", "symbol": "USDT", "decimals": 6, + "gatedCountries": ["FR"], "createdAt": "2025-07-08T13:47:41.000Z", "updatedAt": "2025-07-08T13:47:41.000Z", "tokenPrices": [ @@ -5921,6 +5922,7 @@ "name": "BNB", "symbol": "BNB", "decimals": 18, + "restrictedCountries": ["FR"], "createdAt": "2025-07-08T13:47:41.000Z", "updatedAt": "2025-07-08T13:47:41.000Z", "tokenPrices": [ diff --git a/apps/evm/src/__mocks__/models/asset.ts b/apps/evm/src/__mocks__/models/asset.ts index 03f42dcc5d..4aba8cbd20 100644 --- a/apps/evm/src/__mocks__/models/asset.ts +++ b/apps/evm/src/__mocks__/models/asset.ts @@ -84,6 +84,8 @@ export const assetData: Asset[] = [ extraInfoUrl: 'https://fake.url', }, ], + isGated: false, + isRestricted: false, }, { vToken: vUsdc, @@ -180,6 +182,8 @@ export const assetData: Asset[] = [ ], supplyPointDistributions: [], borrowPointDistributions: [], + isRestricted: false, + isGated: false, }, { vToken: vUsdtCorePool, @@ -272,6 +276,8 @@ export const assetData: Asset[] = [ extraInfoUrl: 'https://fake.url', }, ], + isGated: true, + isRestricted: false, }, { vToken: vBusdCorePool, @@ -376,5 +382,7 @@ export const assetData: Asset[] = [ }, ], borrowPointDistributions: [], + isGated: false, + isRestricted: true, }, ]; diff --git a/apps/evm/src/__mocks__/models/trade.ts b/apps/evm/src/__mocks__/models/trade.ts index d1e3e79afd..967a5b606b 100644 --- a/apps/evm/src/__mocks__/models/trade.ts +++ b/apps/evm/src/__mocks__/models/trade.ts @@ -6,10 +6,21 @@ import fakeAddress, { altAddress } from './address'; import { poolData } from './pools'; const pool = poolData[0]; -const xvsAsset = pool.assets[0]; -const usdcAsset = pool.assets[1]; -const usdtAsset = pool.assets[2]; -const busdAsset = pool.assets[3]; +const tradePool = { + ...pool, + assets: pool.assets.map(asset => + asset.vToken.address === pool.assets[3].vToken.address + ? { + ...asset, + isRestricted: false, + } + : asset, + ), +}; +const xvsAsset = tradePool.assets[0]; +const usdcAsset = tradePool.assets[1]; +const usdtAsset = tradePool.assets[2]; +const busdAsset = tradePool.assets[3]; export const apiTradePositions: ApiTradePosition[] = [ { @@ -109,7 +120,7 @@ export const apiTradePositions: ApiTradePosition[] = [ export const tradePositions: TradePosition[] = [ formatToTradePosition({ - pool, + pool: tradePool, chainId: busdAsset.vToken.underlyingToken.chainId, positionAccountAddress: fakeAddress, dsaVTokenAddress: xvsAsset.vToken.address, @@ -128,7 +139,7 @@ export const tradePositions: TradePosition[] = [ unrealizedPnlPercentage: 0, })!, formatToTradePosition({ - pool, + pool: tradePool, chainId: busdAsset.vToken.underlyingToken.chainId, positionAccountAddress: altAddress, dsaVTokenAddress: usdcAsset.vToken.address, @@ -147,7 +158,7 @@ export const tradePositions: TradePosition[] = [ unrealizedPnlPercentage: 1.2, })!, formatToTradePosition({ - pool, + pool: tradePool, chainId: usdtAsset.vToken.underlyingToken.chainId, positionAccountAddress: altAddress, dsaVTokenAddress: usdcAsset.vToken.address, diff --git a/apps/evm/src/clients/api/__mocks__/index.ts b/apps/evm/src/clients/api/__mocks__/index.ts index 1c83f3e6dd..8679967a29 100644 --- a/apps/evm/src/clients/api/__mocks__/index.ts +++ b/apps/evm/src/clients/api/__mocks__/index.ts @@ -233,6 +233,17 @@ export const useGetXvsVaultUserPendingWithdrawalsFromBeforeUpgrade = vi.fn(() => }), ); +export const getIpLocation = vi.fn(async () => ({ + countryCode: 'US', +})); +export const useGetIpLocation = vi.fn((options?: Partial) => + useQuery({ + queryKey: [FunctionKey.GET_IP_LOCATION], + queryFn: getIpLocation, + ...options, + }), +); + export const useGetPools = vi.fn(() => ({ isLoading: false, data: { diff --git a/apps/evm/src/clients/api/index.ts b/apps/evm/src/clients/api/index.ts index 190d4b6063..8372a0e2a2 100644 --- a/apps/evm/src/clients/api/index.ts +++ b/apps/evm/src/clients/api/index.ts @@ -86,6 +86,7 @@ export * from './queries/getVenusVaiVaultDailyRate/useGetVenusVaiVaultDailyRate' export * from './queries/useGetAsset'; export * from './queries/useGetPools'; +export * from './queries/useGetIpLocation'; export * from './queries/useGetPool'; diff --git a/apps/evm/src/clients/api/queries/getPendingRewards/getApiTokenPrice/index.ts b/apps/evm/src/clients/api/queries/getPendingRewards/getApiTokenPrice/index.ts index ae61f25d7e..f1b78e0391 100644 --- a/apps/evm/src/clients/api/queries/getPendingRewards/getApiTokenPrice/index.ts +++ b/apps/evm/src/clients/api/queries/getPendingRewards/getApiTokenPrice/index.ts @@ -1,7 +1,6 @@ -import type { ChainId } from 'types'; +import type { ApiTokenPrice, ChainId } from 'types'; import { restService } from 'utilities'; import type { Address } from 'viem'; -import type { ApiTokenPrice } from '../../useGetPools/getPools/getApiPools'; export interface GetApiTokenPriceInput { tokenAddresses: string[]; diff --git a/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts b/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts index fbae086832..9d93faa2ec 100644 --- a/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts +++ b/apps/evm/src/clients/api/queries/getRawTradePositions/__tests__/index.spec.ts @@ -10,10 +10,10 @@ import { logError } from 'libs/errors'; import { ChainId } from 'types'; import { formatToTradePosition, restService } from 'utilities'; import { type GetRawTradePositionsInput, getRawTradePositions } from '..'; -import { getPools } from '../../useGetPools/getPools'; +import { getPools } from '../../useGetPools/useGetPoolsQuery/getPools'; vi.mock('utilities/restService'); -vi.mock('../../useGetPools/getPools', () => ({ +vi.mock('../../useGetPools/useGetPoolsQuery/getPools', () => ({ getPools: vi.fn(), })); vi.mock('libs/errors', async () => { diff --git a/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts b/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts index 31e2f636b5..bf69cffdbf 100644 --- a/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts +++ b/apps/evm/src/clients/api/queries/getRawTradePositions/index.ts @@ -8,7 +8,7 @@ import { formatToTradePosition, restService, } from 'utilities'; -import { getPools } from '../useGetPools/getPools'; +import { getPools } from '../useGetPools/useGetPoolsQuery/getPools'; import type { GetApiTradePositionsOutput, GetRawTradePositionsInput, diff --git a/apps/evm/src/clients/api/queries/getSimulatedPool/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/clients/api/queries/getSimulatedPool/__tests__/__snapshots__/index.spec.ts.snap index a82f31b57d..d1a3e35ccf 100644 --- a/apps/evm/src/clients/api/queries/getSimulatedPool/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/clients/api/queries/getSimulatedPool/__tests__/__snapshots__/index.spec.ts.snap @@ -42,7 +42,9 @@ exports[`getSimulatedPool > recalculates Prime APYs when a Prime market is mutat "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -168,7 +170,9 @@ exports[`getSimulatedPool > recalculates Prime APYs when a Prime market is mutat "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -310,7 +314,9 @@ exports[`getSimulatedPool > recalculates Prime APYs when a Prime market is mutat "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -444,7 +450,9 @@ exports[`getSimulatedPool > recalculates Prime APYs when a Prime market is mutat "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -794,7 +802,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -920,7 +930,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -1062,7 +1074,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -1196,7 +1210,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -1546,7 +1562,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -1672,7 +1690,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -1814,7 +1834,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -1948,7 +1970,9 @@ exports[`getSimulatedPool > returns simulated pool with updated asset balances w "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -2310,7 +2334,9 @@ exports[`getSimulatedPool > updates simulated VAI borrow balances when VAI mutat "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -2436,7 +2462,9 @@ exports[`getSimulatedPool > updates simulated VAI borrow balances when VAI mutat "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -2578,7 +2606,9 @@ exports[`getSimulatedPool > updates simulated VAI borrow balances when VAI mutat "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -2712,7 +2742,9 @@ exports[`getSimulatedPool > updates simulated VAI borrow balances when VAI mutat "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", diff --git a/apps/evm/src/clients/api/queries/useGetAsset/__snapshots__/index.spec.tsx.snap b/apps/evm/src/clients/api/queries/useGetAsset/__snapshots__/index.spec.tsx.snap index 83b1bb20de..3508665ce6 100644 --- a/apps/evm/src/clients/api/queries/useGetAsset/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/clients/api/queries/useGetAsset/__snapshots__/index.spec.tsx.snap @@ -40,7 +40,9 @@ exports[`useGetAsset > returns the correct asset 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", diff --git a/apps/evm/src/clients/api/queries/useGetIpLocation/__tests__/index.spec.ts b/apps/evm/src/clients/api/queries/useGetIpLocation/__tests__/index.spec.ts new file mode 100644 index 0000000000..54c1c9dfd0 --- /dev/null +++ b/apps/evm/src/clients/api/queries/useGetIpLocation/__tests__/index.spec.ts @@ -0,0 +1,26 @@ +import { waitFor } from '@testing-library/dom'; +import type { Mock } from 'vitest'; + +import { renderHook } from 'testUtils/render'; +import { useGetIpLocation } from '..'; + +const mockFetch = global.fetch as Mock; + +describe('useGetIpLocation', () => { + it('fetches the user country code', async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: async () => ({ + countryCode: 'FR', + }), + } satisfies Partial); + + const { result } = renderHook(() => useGetIpLocation()); + + await waitFor(() => + expect(result.current.data).toEqual({ + countryCode: 'FR', + }), + ); + }); +}); diff --git a/apps/evm/src/clients/api/queries/useGetIpLocation/getIpLocation/__tests__/index.spec.ts b/apps/evm/src/clients/api/queries/useGetIpLocation/getIpLocation/__tests__/index.spec.ts new file mode 100644 index 0000000000..4ea8749b19 --- /dev/null +++ b/apps/evm/src/clients/api/queries/useGetIpLocation/getIpLocation/__tests__/index.spec.ts @@ -0,0 +1,71 @@ +import type { Mock } from 'vitest'; + +import { VError } from 'libs/errors'; +import { IP_API_URL, getIpLocation } from '..'; + +const mockFetch = global.fetch as Mock; + +describe('getIpLocation', () => { + it('returns the user country code when the request succeeds', async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: async () => ({ + countryCode: 'FR', + }), + } satisfies Partial); + + await expect(getIpLocation()).resolves.toEqual({ + countryCode: 'FR', + }); + + expect(mockFetch).toHaveBeenCalledWith(IP_API_URL); + }); + + it('throws a VError when the request fails', async () => { + const error = new Error('Network error'); + mockFetch.mockRejectedValue(error); + + await expect(getIpLocation()).rejects.toEqual( + new VError({ + type: 'unexpected', + code: 'somethingWentWrong', + data: { exception: error }, + }), + ); + }); + + it('throws a VError when the response is not ok', async () => { + mockFetch.mockResolvedValue({ + ok: false, + json: async () => ({}), + } satisfies Partial); + + await expect(getIpLocation()).rejects.toEqual( + new VError({ + type: 'unexpected', + code: 'somethingWentWrong', + data: { + exception: { + ok: false, + json: expect.any(Function), + }, + }, + }), + ); + }); + + it('throws a VError when the response payload is invalid', async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: async () => ({}), + } satisfies Partial); + + await expect(getIpLocation()).rejects.toEqual( + new VError({ + type: 'unexpected', + code: 'somethingWentWrong', + data: { exception: {} }, + }), + ); + }); +}); diff --git a/apps/evm/src/clients/api/queries/useGetIpLocation/getIpLocation/index.ts b/apps/evm/src/clients/api/queries/useGetIpLocation/getIpLocation/index.ts new file mode 100644 index 0000000000..99beb5c026 --- /dev/null +++ b/apps/evm/src/clients/api/queries/useGetIpLocation/getIpLocation/index.ts @@ -0,0 +1,57 @@ +import { VError } from 'libs/errors'; + +type GetIpLocationApiResponse = { + countryCode?: string; +}; + +export interface GetIpLocationOutput { + countryCode: string; +} + +export const IP_API_URL = 'https://free.freeipapi.com/api/json'; + +export const getIpLocation = async (): Promise => { + let response: Response; + + try { + response = await fetch(IP_API_URL); + } catch (error) { + throw new VError({ + type: 'unexpected', + code: 'somethingWentWrong', + data: { exception: error }, + }); + } + + if (!response.ok) { + throw new VError({ + type: 'unexpected', + code: 'somethingWentWrong', + data: { exception: response }, + }); + } + + let payload: GetIpLocationApiResponse; + + try { + payload = await response.json(); + } catch (error) { + throw new VError({ + type: 'unexpected', + code: 'somethingWentWrong', + data: { exception: error }, + }); + } + + if (!payload.countryCode) { + throw new VError({ + type: 'unexpected', + code: 'somethingWentWrong', + data: { exception: payload }, + }); + } + + return { + countryCode: payload.countryCode, + }; +}; diff --git a/apps/evm/src/clients/api/queries/useGetIpLocation/index.ts b/apps/evm/src/clients/api/queries/useGetIpLocation/index.ts new file mode 100644 index 0000000000..90232b858d --- /dev/null +++ b/apps/evm/src/clients/api/queries/useGetIpLocation/index.ts @@ -0,0 +1,26 @@ +import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; + +import FunctionKey from 'constants/functionKey'; +import { type GetIpLocationOutput, getIpLocation } from './getIpLocation'; + +export type UseGetIpLocationQueryKey = [FunctionKey.GET_IP_LOCATION]; + +type Options = QueryObserverOptions< + GetIpLocationOutput, + Error, + GetIpLocationOutput, + GetIpLocationOutput, + UseGetIpLocationQueryKey +>; + +export const useGetIpLocation = (options?: Partial) => + useQuery({ + queryKey: [FunctionKey.GET_IP_LOCATION], + queryFn: getIpLocation, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + staleTime: Number.POSITIVE_INFINITY, + gcTime: Number.POSITIVE_INFINITY, + ...options, + }); diff --git a/apps/evm/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap b/apps/evm/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap index 45f3062435..a78dc96856 100644 --- a/apps/evm/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/clients/api/queries/useGetPool/__snapshots__/index.spec.tsx.snap @@ -42,7 +42,9 @@ exports[`useGetPool > returns the correct asset 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -168,7 +170,9 @@ exports[`useGetPool > returns the correct asset 1`] = ` "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -310,7 +314,9 @@ exports[`useGetPool > returns the correct asset 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -444,7 +450,9 @@ exports[`useGetPool > returns the correct asset 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", diff --git a/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.eMode.spec.ts.snap b/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.eMode.spec.ts.snap index e5ff65ac47..a9337aa948 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.eMode.spec.ts.snap +++ b/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.eMode.spec.ts.snap @@ -27,7 +27,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -108,7 +110,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -205,7 +209,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -287,7 +293,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -368,7 +376,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -464,7 +474,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -714,7 +726,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -805,7 +819,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -881,7 +897,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -954,7 +972,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -1030,7 +1050,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -1121,7 +1143,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -1194,7 +1218,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -1285,7 +1311,9 @@ exports[`useGetPools > fetches and formats E-mode groups associated with each po "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", @@ -1374,7 +1402,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -1455,7 +1485,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -1552,7 +1584,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -1634,7 +1668,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -1715,7 +1751,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -1811,7 +1849,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -2109,7 +2149,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -2200,7 +2242,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -2276,7 +2320,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -2349,7 +2395,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -2425,7 +2473,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -2516,7 +2566,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -2589,7 +2641,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -2680,7 +2734,9 @@ exports[`useGetPools > returns pools with user data and E-mode group enabled in "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", @@ -2769,7 +2825,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -2850,7 +2908,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -2947,7 +3007,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -3029,7 +3091,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -3110,7 +3174,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -3206,7 +3272,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -3503,7 +3571,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -3594,7 +3664,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -3670,7 +3742,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -3743,7 +3817,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -3819,7 +3895,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -3910,7 +3988,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -3983,7 +4063,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -4074,7 +4156,9 @@ exports[`useGetPools > returns pools with user data and isolated E-mode group en "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", @@ -4163,7 +4247,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -4244,7 +4330,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -4341,7 +4429,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -4423,7 +4513,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -4504,7 +4596,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -4600,7 +4694,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": false, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -4898,7 +4994,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -4989,7 +5087,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -5065,7 +5165,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -5138,7 +5240,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -5214,7 +5318,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -5305,7 +5411,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -5378,7 +5486,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -5469,7 +5579,9 @@ exports[`useGetPools > uses pool settings if user has an inactive E-mode group e "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", diff --git a/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.prime.spec.ts.snap b/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.prime.spec.ts.snap index ca837b6fc3..03de8bd181 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.prime.spec.ts.snap +++ b/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.prime.spec.ts.snap @@ -45,7 +45,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -144,7 +146,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -241,7 +245,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -323,7 +329,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -421,7 +429,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -551,7 +561,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -820,7 +832,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -911,7 +925,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -1005,7 +1021,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -1096,7 +1114,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -1172,7 +1192,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -1263,7 +1285,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -1354,7 +1378,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -1481,7 +1507,9 @@ exports[`useGetPools > does not fetch Prime distributions if user is not Prime 1 "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", @@ -1618,7 +1646,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -1729,7 +1759,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -1826,7 +1858,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -1908,7 +1942,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -2018,7 +2054,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -2172,7 +2210,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -2453,7 +2493,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -2544,7 +2586,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -2650,7 +2694,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -2753,7 +2799,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -2829,7 +2877,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -2920,7 +2970,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -3023,7 +3075,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -3174,7 +3228,9 @@ exports[`useGetPools > fetches and formats Prime distributions and Prime distrib "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", @@ -3306,7 +3362,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -3400,7 +3458,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -3497,7 +3557,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -3579,7 +3641,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -3672,7 +3736,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -3792,7 +3858,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -4056,7 +4124,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -4147,7 +4217,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -4236,7 +4308,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -4322,7 +4396,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -4398,7 +4474,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -4489,7 +4567,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -4575,7 +4655,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -4692,7 +4774,9 @@ exports[`useGetPools > filters out Prime simulations that are 0 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", diff --git a/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.spec.ts.snap index 1b1f0afc03..c187130971 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/clients/api/queries/useGetPools/__tests__/__snapshots__/index.spec.ts.snap @@ -27,7 +27,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -108,7 +110,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -205,7 +209,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -287,7 +293,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -368,7 +376,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -464,7 +474,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -714,7 +726,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -805,7 +819,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -881,7 +897,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -954,7 +972,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -1030,7 +1050,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -1121,7 +1143,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -1194,7 +1218,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -1285,7 +1311,9 @@ exports[`useGetPools > returns pools in the correct format 1`] = ` "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", @@ -1374,7 +1402,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -1455,7 +1485,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -1552,7 +1584,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -1634,7 +1668,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -1715,7 +1751,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -1811,7 +1849,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -2051,7 +2091,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -2142,7 +2184,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -2218,7 +2262,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -2291,7 +2337,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -2367,7 +2415,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -2458,7 +2508,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -2531,7 +2583,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -2622,7 +2676,9 @@ exports[`useGetPools > returns pools with time based reward rates in the correct "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", @@ -2711,7 +2767,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 0, "liquidityCents": "9.998358808049033258268439270234862259568416992e+22", @@ -2792,7 +2850,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "0", @@ -2889,7 +2949,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "367322.427136472011309675565022", @@ -2971,7 +3033,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 66, "liquidityCents": "1332106415.658306127885385733550383", @@ -3052,7 +3116,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1.000003658230657184839939048025550587141377317e+36", @@ -3148,7 +3214,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 89.10000000000001, "liquidityCents": "1.01017679492420109825145006688858448e+23", @@ -3400,7 +3468,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "264910628.128578051", @@ -3491,7 +3561,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "10115894.213933673", @@ -3567,7 +3639,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "1103022783", @@ -3640,7 +3714,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 49.50000000000001, "liquidityCents": "46.627361564366928016606869", @@ -3716,7 +3792,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 82.50000000000001, "liquidityCents": "600557.958", @@ -3807,7 +3885,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 71.5, "liquidityCents": "3256964.0339677929523638", @@ -3880,7 +3960,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "99810466.29625374", @@ -3971,7 +4053,9 @@ exports[`useGetPools > returns pools with user data in the correct format 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 88.00000000000001, "liquidityCents": "999930", diff --git a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.eMode.spec.ts b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.eMode.spec.ts index 748d62e7eb..80435ffab0 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.eMode.spec.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.eMode.spec.ts @@ -5,6 +5,7 @@ import apiPoolsResponse from '__mocks__/api/pools.json'; import fakeAccountAddress from '__mocks__/models/address'; import BigNumber from 'bignumber.js'; import { type GetTokenBalancesInput, getTokenBalances } from 'clients/api/queries/getTokenBalances'; +import { useGetIpLocation } from 'clients/api/queries/useGetIpLocation'; import { type UseIsFeatureEnabledInput, useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import { @@ -29,9 +30,19 @@ vi.mock('utilities/restService'); vi.mock('clients/api/queries/getTokenBalances', () => ({ getTokenBalances: vi.fn(), })); +vi.mock('clients/api/queries/useGetIpLocation', () => ({ + useGetIpLocation: vi.fn(), +})); describe('useGetPools', () => { beforeEach(() => { + (useGetIpLocation as Mock).mockReturnValue({ + data: { + countryCode: 'US', + }, + error: null, + }); + (useIsFeatureEnabled as Mock).mockImplementation( ({ name }: UseIsFeatureEnabledInput) => name === 'eMode', ); diff --git a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.prime.spec.ts b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.prime.spec.ts index 9296818a7e..a46714ef62 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.prime.spec.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.prime.spec.ts @@ -5,6 +5,7 @@ import apiPoolsResponse from '__mocks__/api/pools.json'; import fakeAccountAddress from '__mocks__/models/address'; import BigNumber from 'bignumber.js'; import { type GetTokenBalancesInput, getTokenBalances } from 'clients/api/queries/getTokenBalances'; +import { useGetIpLocation } from 'clients/api/queries/useGetIpLocation'; import { type UseIsFeatureEnabledInput, useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; import { @@ -30,9 +31,19 @@ vi.mock('utilities/restService'); vi.mock('clients/api/queries/getTokenBalances', () => ({ getTokenBalances: vi.fn(), })); +vi.mock('clients/api/queries/useGetIpLocation', () => ({ + useGetIpLocation: vi.fn(), +})); describe('useGetPools', () => { beforeEach(() => { + (useGetIpLocation as Mock).mockReturnValue({ + data: { + countryCode: 'US', + }, + error: null, + }); + (useIsFeatureEnabled as Mock).mockImplementation( ({ name }: UseIsFeatureEnabledInput) => name === 'prime', ); diff --git a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts index 80b6adbb71..3d292cd481 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/__tests__/index.spec.ts @@ -5,6 +5,7 @@ import apiPoolsResponse from '__mocks__/api/pools.json'; import fakeAccountAddress from '__mocks__/models/address'; import BigNumber from 'bignumber.js'; import { type GetTokenBalancesInput, getTokenBalances } from 'clients/api/queries/getTokenBalances'; +import { useGetIpLocation } from 'clients/api/queries/useGetIpLocation'; import { type UseGetContractAddressInput, useGetContractAddress, @@ -27,9 +28,35 @@ vi.mock('utilities/restService'); vi.mock('clients/api/queries/getTokenBalances', () => ({ getTokenBalances: vi.fn(), })); +vi.mock('clients/api/queries/useGetIpLocation', () => ({ + useGetIpLocation: vi.fn(), +})); + +const findAssetByVTokenSymbol = ({ + symbol, + pools, +}: { + symbol: string; + pools?: { + assets: { + vToken: { + symbol: string; + }; + isRestricted: boolean; + isGated: boolean; + }[]; + }[]; +}) => pools?.flatMap(pool => pool.assets).find(asset => asset.vToken.symbol === symbol); describe('useGetPools', () => { beforeEach(() => { + (useGetIpLocation as Mock).mockReturnValue({ + data: { + countryCode: 'US', + }, + error: null, + }); + (usePublicClient as Mock).mockImplementation(() => ({ publicClient: fakePublicClient, })); @@ -103,4 +130,95 @@ describe('useGetPools', () => { await waitFor(() => expect(result.current.data).toBeDefined()); expect(result.current.data).toMatchSnapshot(); }); + + it('marks restricted and gated assets based on the user country code', async () => { + (useGetIpLocation as Mock).mockReturnValue({ + data: { + countryCode: 'FR', + }, + error: null, + }); + + const { result } = renderHook(() => useGetPools()); + + await waitFor(() => expect(result.current.data).toBeDefined()); + + const restrictedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vBNB', + }); + const gatedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vUSDT', + }); + + expect(restrictedAsset?.isRestricted).toBe(true); + expect(restrictedAsset?.isGated).toBe(false); + expect(gatedAsset?.isRestricted).toBe(false); + expect(gatedAsset?.isGated).toBe(true); + }); + + it('leaves restricted and gated flags disabled for allowed countries', async () => { + const { result } = renderHook(() => useGetPools()); + + await waitFor(() => expect(result.current.data).toBeDefined()); + + const restrictedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vBNB', + }); + const gatedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vUSDT', + }); + + expect(restrictedAsset?.isRestricted).toBe(false); + expect(restrictedAsset?.isGated).toBe(false); + expect(gatedAsset?.isRestricted).toBe(false); + expect(gatedAsset?.isGated).toBe(false); + }); + + it('returns pools before country data is available and updates geo flags after rerender', async () => { + (useGetIpLocation as Mock).mockReturnValue({ + data: undefined, + error: null, + }); + + const { result, rerender } = renderHook(() => useGetPools()); + + await waitFor(() => expect(result.current.data).toBeDefined()); + + let restrictedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vBNB', + }); + let gatedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vUSDT', + }); + + expect(restrictedAsset?.isRestricted).toBe(false); + expect(gatedAsset?.isGated).toBe(false); + + (useGetIpLocation as Mock).mockReturnValue({ + data: { + countryCode: 'FR', + }, + error: null, + }); + + rerender(); + + restrictedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vBNB', + }); + gatedAsset = findAssetByVTokenSymbol({ + pools: result.current.data?.pools, + symbol: 'vUSDT', + }); + + expect(restrictedAsset?.isRestricted).toBe(true); + expect(gatedAsset?.isGated).toBe(true); + }); }); diff --git a/apps/evm/src/clients/api/queries/useGetPools/applyCountryCodeToPools/index.ts b/apps/evm/src/clients/api/queries/useGetPools/applyCountryCodeToPools/index.ts new file mode 100644 index 0000000000..079ad42407 --- /dev/null +++ b/apps/evm/src/clients/api/queries/useGetPools/applyCountryCodeToPools/index.ts @@ -0,0 +1,32 @@ +import type { GetPoolsOutput } from '../types'; +import type { ApiTokenMetadata } from '../useGetPoolsQuery/getPools/getApiPools'; + +export const applyCountryCodeToPools = ({ + countryCode, + pools, + tokenMetadataMapping, +}: { + countryCode?: string; + pools: GetPoolsOutput['pools']; + tokenMetadataMapping: Record; +}): GetPoolsOutput['pools'] => { + if (!countryCode) { + return pools; + } + + return pools.map(pool => ({ + ...pool, + assets: pool.assets.map(asset => { + const tokenMetadata = + tokenMetadataMapping[asset.vToken.underlyingToken.address.toLowerCase()]; + const isRestricted = !!tokenMetadata?.restrictedCountries?.includes(countryCode); + const isGated = !!tokenMetadata?.gatedCountries?.includes(countryCode); + + return { + ...asset, + isRestricted, + isGated, + }; + }), + })); +}; diff --git a/apps/evm/src/clients/api/queries/useGetPools/index.ts b/apps/evm/src/clients/api/queries/useGetPools/index.ts index 34f59e68e7..ae53696448 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/index.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/index.ts @@ -1,105 +1,45 @@ -import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; - -import FunctionKey from 'constants/functionKey'; -import { useGetContractAddress } from 'hooks/useGetContractAddress'; -import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; -import { useGetTokens } from 'libs/tokens'; -import { useChainId, usePublicClient } from 'libs/wallet'; -import type { ChainId } from 'types'; -import { generatePseudoRandomRefetchInterval } from 'utilities/generatePseudoRandomRefetchInterval'; - -import { getPools } from './getPools'; -import type { GetPoolsInput, GetPoolsOutput } from './types'; - -type TrimmedInput = Omit< - GetPoolsInput, - | 'chainId' - | 'tokens' - | 'publicClient' - | 'primeContractAddress' - | 'poolLensContractAddress' - | 'legacyPoolComptrollerContractAddress' - | 'venusLensContractAddress' - | 'vaiControllerContractAddress' - | 'resilientOracleContractAddress' - | 'isEModeFeatureEnabled' ->; - -export type UseGetPoolsQueryKey = [ - FunctionKey.GET_POOLS, - TrimmedInput & { - chainId: ChainId; - }, -]; - -type Options = QueryObserverOptions< - GetPoolsOutput, - Error, - GetPoolsOutput, - GetPoolsOutput, - UseGetPoolsQueryKey ->; - -const refetchInterval = generatePseudoRandomRefetchInterval(); - -export const useGetPools = (input?: TrimmedInput, options?: Options) => { - const isPrimeEnabled = useIsFeatureEnabled({ - name: 'prime', - }); - - const isEModeFeatureEnabled = useIsFeatureEnabled({ - name: 'eMode', - }); - - const accountAddress = input?.accountAddress; - const { chainId } = useChainId(); - const tokens = useGetTokens(); - - const { publicClient } = usePublicClient(); - - const { address: primeContractAddress } = useGetContractAddress({ - name: 'Prime', - }); - const { address: poolLensContractAddress } = useGetContractAddress({ - name: 'PoolLens', - }); - const { address: legacyPoolComptrollerContractAddress } = useGetContractAddress({ - name: 'LegacyPoolComptroller', - }); - const { address: venusLensContractAddress } = useGetContractAddress({ - name: 'VenusLens', - }); - const { address: vaiControllerContractAddress } = useGetContractAddress({ - name: 'VaiController', - }); - const { address: resilientOracleContractAddress } = useGetContractAddress({ - name: 'ResilientOracle', - }); - - return useQuery({ - queryKey: [FunctionKey.GET_POOLS, { ...input, chainId, accountAddress }], - queryFn: () => - getPools({ - publicClient, - chainId, - tokens, - legacyPoolComptrollerContractAddress, - venusLensContractAddress, - poolLensContractAddress, - vaiControllerContractAddress, - resilientOracleContractAddress, - primeContractAddress: isPrimeEnabled ? primeContractAddress : undefined, - isEModeFeatureEnabled, - ...input, +import { useEffect, useMemo } from 'react'; + +import { useGetIpLocation } from 'clients/api/queries/useGetIpLocation'; + +import { logError } from 'libs/errors'; +import { applyCountryCodeToPools } from './applyCountryCodeToPools'; +import type { GetPoolsOutput } from './types'; +import { + type TrimmedInput, + type UseGetPoolsQueryOptions, + useGetPoolsQuery, +} from './useGetPoolsQuery'; + +export const useGetPools = (input?: TrimmedInput, options?: UseGetPoolsQueryOptions) => { + const poolsQuery = useGetPoolsQuery(input, options); + + const { data: getIpLocationData, error: getIpLocationError } = useGetIpLocation({ + enabled: options?.enabled !== false, + }); + + useEffect(() => { + if (getIpLocationError) { + logError(getIpLocationError); + } + }, [getIpLocationError]); + + const data = useMemo(() => { + if (!poolsQuery.data) { + return undefined; + } + + return { + pools: applyCountryCodeToPools({ + countryCode: getIpLocationData?.countryCode, + pools: poolsQuery.data.pools, + tokenMetadataMapping: poolsQuery.data.tokenMetadataMapping, }), - placeholderData: (previousOutput, previousInput) => { - // Return previous data if chain ID param hasn't changed and user address was undefined - const previousChainId = previousInput?.queryKey[1]?.chainId; - return previousChainId === chainId && !previousInput?.queryKey[1].accountAddress - ? previousOutput - : undefined; - }, - refetchInterval, - ...options, - }); + }; + }, [getIpLocationData?.countryCode, poolsQuery.data]); + + return { + ...poolsQuery, + data, + }; }; diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/appendPrimeSimulationDistributions/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/appendPrimeSimulationDistributions/index.ts similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/appendPrimeSimulationDistributions/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/appendPrimeSimulationDistributions/index.ts diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatDistributions/formatRewardDistribution/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatDistributions/formatRewardDistribution/index.ts similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatDistributions/formatRewardDistribution/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatDistributions/formatRewardDistribution/index.ts diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatDistributions/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatDistributions/index.ts similarity index 94% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatDistributions/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatDistributions/index.ts index 1b28765db6..675067168e 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatDistributions/index.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatDistributions/index.ts @@ -5,19 +5,19 @@ import { calculateDailyTokenRate } from 'utilities/calculateDailyTokenRate'; import findTokenByAddress from 'utilities/findTokenByAddress'; import formatRewardDistribution from './formatRewardDistribution'; +import { convertPriceMantissaToDollars } from 'utilities'; +import type { PrimeApy } from '../../../../types'; import type { ApiPointsDistribution, ApiRewardDistributor, - ApiTokenPrice, -} from 'clients/api/queries/useGetPools/getPools/getApiPools'; -import { convertPriceMantissaToDollars } from 'utilities'; -import type { PrimeApy } from '../../../types'; + ApiTokenMetadata, +} from '../../getApiPools'; import { isDistributingRewards } from './isDistributingRewards'; export type FormatDistributionsInput = { underlyingTokenPriceDollars: BigNumber; tokens: Token[]; - tokenPricesMapping: Record; + tokenMetadataMapping: Record; apiRewardsDistributors: ApiRewardDistributor[]; apiPointsDistributions: ApiPointsDistribution[]; currentBlockNumber: bigint; @@ -32,7 +32,7 @@ export const formatDistributions = ({ blocksPerDay, underlyingTokenPriceDollars, tokens, - tokenPricesMapping, + tokenMetadataMapping, apiRewardsDistributors, apiPointsDistributions, currentBlockNumber, @@ -69,8 +69,10 @@ export const formatDistributions = ({ return; } - const tokenPriceMapping = tokenPricesMapping[rewardTokenAddress.toLowerCase()]; - tokenPriceMapping.sort((tp01, tp02) => { + const tokenPriceMapping = + tokenMetadataMapping[rewardTokenAddress.toLowerCase()]?.tokenPrices || []; + + tokenPriceMapping?.sort((tp01, tp02) => { if (tp01.priceSource === tp02.priceSource) { return 0; } diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatDistributions/isDistributingRewards/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatDistributions/isDistributingRewards/index.ts similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatDistributions/isDistributingRewards/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatDistributions/isDistributingRewards/index.ts diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatEModeGroups/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatEModeGroups/index.ts similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/formatEModeGroups/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/formatEModeGroups/index.ts diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/index.ts similarity index 96% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/index.ts index 2a9cb774ec..f89933322c 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/getPools/formatOutput/index.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/formatOutput/index.ts @@ -18,14 +18,14 @@ import { getDisabledTokenActions, isPoolIsolated, } from 'utilities'; -import type { PrimeApy, VTokenBalance } from '../../types'; -import type { ApiPool, ApiTokenPrice } from '../getApiPools'; +import type { PrimeApy, VTokenBalance } from '../../../types'; +import type { ApiPool, ApiTokenMetadata } from '../getApiPools'; import { formatDistributions } from './formatDistributions'; import { formatEModeGroups } from './formatEModeGroups'; export const formatOutput = ({ apiPools, - tokenPricesMapping, + tokenMetadataMapping, chainId, tokens, currentBlockNumber, @@ -41,7 +41,7 @@ export const formatOutput = ({ }: { chainId: ChainId; tokens: Token[]; - tokenPricesMapping: Record; + tokenMetadataMapping: Record; currentBlockNumber: bigint; apiPools: ApiPool[]; userPoolEModeGroupIdMapping: Record; @@ -81,10 +81,10 @@ export const formatOutput = ({ return acc; } + const tokenMetadata = tokenMetadataMapping[market.underlyingAddress.toLowerCase()]; + // Get underlyingPriceMantissa from the tokens metadata - const correspondingOraclePrice = tokenPricesMapping[ - market.underlyingAddress.toLowerCase() - ].find( + const correspondingOraclePrice = tokenMetadata?.tokenPrices.find( p => p.priceOracleAddress && areAddressesEqual(apiPool.priceOracleAddress, p.priceOracleAddress), @@ -232,7 +232,7 @@ export const formatOutput = ({ borrowPointDistributions, } = formatDistributions({ blocksPerDay, - tokenPricesMapping, + tokenMetadataMapping, underlyingToken: vToken.underlyingToken, underlyingTokenPriceDollars: tokenPriceDollars, primeApy: userPrimeApyMap?.get(vToken.address), @@ -331,6 +331,9 @@ export const formatOutput = ({ userLiquidationThresholdPercentage, isBorrowable, isBorrowableByUser, + // These will be determined after fetching the IP location + isRestricted: false, + isGated: false, // This will be calculated after all assets have been formatted userBorrowLimitSharePercentage: 0, isCollateralOfUser, diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/index.ts similarity index 86% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/index.ts index cebf49057c..6fdbcf9956 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/index.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/index.ts @@ -1,5 +1,5 @@ import { VError } from 'libs/errors'; -import type { ChainId } from 'types'; +import type { ApiTokenPrice, ChainId } from 'types'; import { restService } from 'utilities/restService'; import type { Address } from 'viem'; import { formatPointDistribution } from './pointDistributions'; @@ -138,24 +138,14 @@ export interface ApiPool { eModeGroups?: ApiEModeGroup[]; } -export interface ApiTokenPrice { - tokenWrappedAddress: Address | null; - priceMantissa: string; - priceSource: 'oracle' | 'merkl' | 'coingecko'; - priceOracleAddress: Address | null; - isPriceInvalid: boolean; - hasErrorFetchingPrice: boolean; - isPriceProtected: boolean; - supplyPriceMantissa: string | null; - borrowPriceMantissa: string | null; -} - export interface ApiTokenMetadata { address: Address; name: string; symbol: string; decimals: number; tokenPrices: ApiTokenPrice[]; + gatedCountries?: string[]; + restrictedCountries?: string[]; } export interface GetApiPoolsResponse { @@ -199,17 +189,17 @@ export const getApiPools = async ({ }); } - const tokenMetadata = payload.tokens || []; - const tokenPricesMapping: Record = tokenMetadata.reduce<{ - [address: string]: ApiTokenPrice[]; - }>((acc, tokenMetadata) => { - const { address: tokenAddress, tokenPrices } = tokenMetadata; - - return { + const tokenMetadatas = payload.tokens || []; + const tokenMetadataMapping = tokenMetadatas.reduce<{ + [address: string]: ApiTokenMetadata; + }>( + (acc, tokenMetadata) => ({ ...acc, - [tokenAddress.toLowerCase()]: tokenPrices, - }; - }, {}); + [tokenMetadata.address.toLowerCase()]: tokenMetadata, + }), + {}, + ); + const pools = (payload?.result || []).map(pool => ({ ...pool, markets: pool.markets.map(market => ({ @@ -220,6 +210,6 @@ export const getApiPools = async ({ return { pools, - tokenPricesMapping, + tokenMetadataMapping, }; }; diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/asterPoints.svg b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/asterPoints.svg similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/asterPoints.svg rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/asterPoints.svg diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/ethenaPoints.svg b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/ethenaPoints.svg similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/ethenaPoints.svg rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/ethenaPoints.svg diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/etherfiPoints.svg b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/etherfiPoints.svg similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/etherfiPoints.svg rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/etherfiPoints.svg diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/index.ts similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/index.ts diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/kelpMiles.svg b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/kelpMiles.svg similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/kelpMiles.svg rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/kelpMiles.svg diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/solvPoints.svg b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/solvPoints.svg similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getApiPools/pointDistributions/solvPoints.svg rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getApiPools/pointDistributions/solvPoints.svg diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getUserCollateralAddresses/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserCollateralAddresses/index.ts similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getUserCollateralAddresses/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserCollateralAddresses/index.ts diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getUserPrimeApys/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserPrimeApys/index.ts similarity index 95% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getUserPrimeApys/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserPrimeApys/index.ts index d17ab962b4..202d6e02ab 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/getPools/getUserPrimeApys/index.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserPrimeApys/index.ts @@ -1,7 +1,7 @@ import { primeAbi } from 'libs/contracts'; import { convertAprBipsToApy } from 'utilities'; import type { Address, PublicClient } from 'viem'; -import type { PrimeApy } from '../../types'; +import type { PrimeApy } from '../../../types'; export const getUserPrimeApys = async ({ publicClient, diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getUserTokenBalances/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserTokenBalances/index.ts similarity index 98% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getUserTokenBalances/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserTokenBalances/index.ts index 9bfc609c44..eb67df3d27 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/getPools/getUserTokenBalances/index.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserTokenBalances/index.ts @@ -5,7 +5,7 @@ import { poolLensAbi, venusLensAbi } from 'libs/contracts'; import type { ChainId, Token } from 'types'; import { findTokenByAddress, isPoolIsolated } from 'utilities'; import type { Address, PublicClient } from 'viem'; -import type { VTokenBalance } from '../../types'; +import type { VTokenBalance } from '../../../types'; import type { ApiPool } from '../getApiPools'; export const getUserTokenBalances = async ({ diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/getUserVaiBorrowBalance/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserVaiBorrowBalance/index.ts similarity index 100% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/getUserVaiBorrowBalance/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/getUserVaiBorrowBalance/index.ts diff --git a/apps/evm/src/clients/api/queries/useGetPools/getPools/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/index.ts similarity index 81% rename from apps/evm/src/clients/api/queries/useGetPools/getPools/index.ts rename to apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/index.ts index ebf9d1914b..7158dbbcfc 100644 --- a/apps/evm/src/clients/api/queries/useGetPools/getPools/index.ts +++ b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/getPools/index.ts @@ -8,15 +8,19 @@ import { vaiControllerAbi, } from 'libs/contracts'; import type { Asset, TokenBalance } from 'types'; -import type { GetPoolsInput, GetPoolsOutput, PrimeApy, VTokenBalance } from '../types'; +import type { GetPoolsInput, GetPoolsOutput, PrimeApy, VTokenBalance } from '../../types'; import { appendPrimeSimulationDistributions } from './appendPrimeSimulationDistributions'; import { formatOutput } from './formatOutput'; -import { getApiPools } from './getApiPools'; +import { type ApiTokenMetadata, getApiPools } from './getApiPools'; import { getUserCollateralAddresses } from './getUserCollateralAddresses'; import { getUserPrimeApys } from './getUserPrimeApys'; import { getUserTokenBalances } from './getUserTokenBalances'; import { getUserVaiBorrowBalance } from './getUserVaiBorrowBalance'; +export interface GetPoolsQueryOutput extends GetPoolsOutput { + tokenMetadataMapping: Record; +} + export const getPools = async ({ publicClient, chainId, @@ -29,17 +33,17 @@ export const getPools = async ({ venusLensContractAddress, tokens, isEModeFeatureEnabled, -}: GetPoolsInput) => { +}: GetPoolsInput): Promise => { const vai = tokens.find(token => token.symbol === 'VAI'); const [ - { pools: apiPools, tokenPricesMapping }, - currentBlockNumber, - unsafePrimeVTokenAddresses, - primeMinimumXvsToStakeMantissa, - userPrimeToken, - vaiPriceMantissa, - vaiRepayRateMantissa, + apiPoolsResult, + currentBlockNumberResult, + unsafePrimeVTokenAddressesResult, + primeMinimumXvsToStakeMantissaResult, + userPrimeTokenResult, + vaiPriceMantissaResult, + vaiRepayRateMantissaResult, ] = await Promise.all([ getApiPools({ chainId }), // Fetch current block number @@ -85,8 +89,8 @@ export const getPools = async ({ : undefined, ]); - const primeVTokenAddresses = unsafePrimeVTokenAddresses ?? []; - const isUserPrime = userPrimeToken?.[0] || false; + const primeVTokenAddresses = unsafePrimeVTokenAddressesResult ?? []; + const isUserPrime = userPrimeTokenResult?.[0] || false; let userCollateralVTokenAddresses: string[] | undefined; let userTokenBalances: TokenBalance[] | undefined; @@ -107,12 +111,12 @@ export const getPools = async ({ chainId, accountAddress, legacyPoolComptrollerContractAddress, - apiPools, + apiPools: apiPoolsResult.pools, publicClient, }), getUserTokenBalances({ accountAddress, - apiPools, + apiPools: apiPoolsResult.pools, chainId, tokens, publicClient, @@ -166,37 +170,40 @@ export const getPools = async ({ chainId, isUserConnected: !!accountAddress, tokens, - currentBlockNumber, - apiPools, - tokenPricesMapping, + currentBlockNumber: currentBlockNumberResult, + apiPools: apiPoolsResult.pools, + tokenMetadataMapping: apiPoolsResult.tokenMetadataMapping, userPrimeApyMap, userCollateralVTokenAddresses, userVTokenBalances, userTokenBalances, userVaiBorrowBalanceMantissa, userPoolEModeGroupIdMapping, - vaiRepayRateMantissa, - vaiPriceMantissa, + vaiRepayRateMantissa: vaiRepayRateMantissaResult, + vaiPriceMantissa: vaiPriceMantissaResult, }); // Add Prime simulations // TODO: get Prime simulations from API const xvs = tokens.find(token => token.symbol === 'XVS'); - if (primeContractAddress && primeMinimumXvsToStakeMantissa && xvs) { + if (primeContractAddress && primeMinimumXvsToStakeMantissaResult && xvs) { await appendPrimeSimulationDistributions({ assets: pools.reduce((acc, pool) => acc.concat(pool.assets), []), primeContractAddress, primeVTokenAddresses, - primeMinimumXvsToStakeMantissa: new BigNumber(primeMinimumXvsToStakeMantissa.toString()), + primeMinimumXvsToStakeMantissa: new BigNumber( + primeMinimumXvsToStakeMantissaResult.toString(), + ), xvs, chainId, publicClient, }); } - const output: GetPoolsOutput = { + const output: GetPoolsQueryOutput = { pools, + tokenMetadataMapping: apiPoolsResult.tokenMetadataMapping, }; return output; diff --git a/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/index.ts b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/index.ts new file mode 100644 index 0000000000..2b9a31f75c --- /dev/null +++ b/apps/evm/src/clients/api/queries/useGetPools/useGetPoolsQuery/index.ts @@ -0,0 +1,106 @@ +import { type QueryObserverOptions, useQuery } from '@tanstack/react-query'; + +import FunctionKey from 'constants/functionKey'; +import { useGetContractAddress } from 'hooks/useGetContractAddress'; +import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; +import { useGetTokens } from 'libs/tokens'; +import { useChainId, usePublicClient } from 'libs/wallet'; +import type { ChainId } from 'types'; +import { generatePseudoRandomRefetchInterval } from 'utilities/generatePseudoRandomRefetchInterval'; + +import type { GetPoolsInput } from '../types'; +import { getPools } from './getPools'; +import type { GetPoolsQueryOutput } from './getPools'; + +export type TrimmedInput = Omit< + GetPoolsInput, + | 'chainId' + | 'tokens' + | 'publicClient' + | 'primeContractAddress' + | 'poolLensContractAddress' + | 'legacyPoolComptrollerContractAddress' + | 'venusLensContractAddress' + | 'vaiControllerContractAddress' + | 'resilientOracleContractAddress' + | 'isEModeFeatureEnabled' +>; + +export type UseGetPoolsQueryKey = [ + FunctionKey.GET_POOLS, + TrimmedInput & { + chainId: ChainId; + }, +]; + +export type UseGetPoolsQueryOptions = QueryObserverOptions< + GetPoolsQueryOutput, + Error, + GetPoolsQueryOutput, + GetPoolsQueryOutput, + UseGetPoolsQueryKey +>; + +const refetchInterval = generatePseudoRandomRefetchInterval(); + +export const useGetPoolsQuery = (input?: TrimmedInput, options?: UseGetPoolsQueryOptions) => { + const isPrimeEnabled = useIsFeatureEnabled({ + name: 'prime', + }); + + const isEModeFeatureEnabled = useIsFeatureEnabled({ + name: 'eMode', + }); + + const accountAddress = input?.accountAddress; + const { chainId } = useChainId(); + const tokens = useGetTokens(); + + const { publicClient } = usePublicClient(); + + const { address: primeContractAddress } = useGetContractAddress({ + name: 'Prime', + }); + const { address: poolLensContractAddress } = useGetContractAddress({ + name: 'PoolLens', + }); + const { address: legacyPoolComptrollerContractAddress } = useGetContractAddress({ + name: 'LegacyPoolComptroller', + }); + const { address: venusLensContractAddress } = useGetContractAddress({ + name: 'VenusLens', + }); + const { address: vaiControllerContractAddress } = useGetContractAddress({ + name: 'VaiController', + }); + const { address: resilientOracleContractAddress } = useGetContractAddress({ + name: 'ResilientOracle', + }); + + return useQuery({ + queryKey: [FunctionKey.GET_POOLS, { ...input, chainId, accountAddress }], + queryFn: () => + getPools({ + publicClient, + chainId, + tokens, + legacyPoolComptrollerContractAddress, + venusLensContractAddress, + poolLensContractAddress, + vaiControllerContractAddress, + resilientOracleContractAddress, + primeContractAddress: isPrimeEnabled ? primeContractAddress : undefined, + isEModeFeatureEnabled, + ...input, + }), + placeholderData: (previousOutput, previousInput) => { + // Return previous data if chain ID param hasn't changed and user address was undefined + const previousChainId = previousInput?.queryKey[1]?.chainId; + return previousChainId === chainId && !previousInput?.queryKey[1].accountAddress + ? previousOutput + : undefined; + }, + refetchInterval, + ...options, + }); +}; diff --git a/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/__snapshots__/index.spec.tsx.snap index 751dcb91d9..260b3c347a 100644 --- a/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/__snapshots__/index.spec.tsx.snap @@ -19,7 +19,9 @@ exports[`useGetFormattedFixedRatedVaults > fetches and returns pendle and matrix "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 5, "liquidationThresholdPercentage": 75, "liquidityCents": "742673002", diff --git a/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/index.spec.tsx b/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/index.spec.tsx index 2fac9b40ce..0e78424d4a 100644 --- a/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/index.spec.tsx +++ b/apps/evm/src/clients/api/queries/useGetVaults/useGetFormattedFixedRatedVaults/__tests__/index.spec.tsx @@ -239,6 +239,8 @@ const pendleVaultAsset: Asset = { borrowTokenDistributions: [], supplyPointDistributions: [], borrowPointDistributions: [], + isRestricted: false, + isGated: true, }; const fakePoolsData = { diff --git a/apps/evm/src/components/Modal/index.tsx b/apps/evm/src/components/Modal/index.tsx index 3e6f81488a..041dee48a3 100644 --- a/apps/evm/src/components/Modal/index.tsx +++ b/apps/evm/src/components/Modal/index.tsx @@ -19,7 +19,7 @@ export interface ModalProps extends Omit { className?: string; buttonClassName?: string; isOpen: boolean; - handleClose: () => void; + handleClose?: () => void; handleBackAction?: () => void; title?: string | ReactElement | ReactElement[]; noHorizontalPadding?: boolean; @@ -60,14 +60,16 @@ export const Modal: React.FC = ({ )}
{title}
- + {handleClose && ( + + )}
{children as React.ReactNode}
diff --git a/apps/evm/src/components/SelectTokenTextField/__testUtils__/testUtils.ts b/apps/evm/src/components/SelectTokenTextField/__testUtils__/testUtils.ts index 8ebeab6dee..e3f4a03d7e 100644 --- a/apps/evm/src/components/SelectTokenTextField/__testUtils__/testUtils.ts +++ b/apps/evm/src/components/SelectTokenTextField/__testUtils__/testUtils.ts @@ -1,7 +1,7 @@ import { fireEvent, getByTestId } from '@testing-library/react'; +import { getTokenListItemTestId } from 'containers/TokenListWrapper/getTokenListItemTestId'; import type { Token } from 'types'; -import { getTokenListItemTestId } from '../../TokenListWrapper/testIdGetters'; import { getTokenSelectButtonTestId } from '../testIdGetters'; export const selectToken = ({ diff --git a/apps/evm/src/components/SelectTokenTextField/index.tsx b/apps/evm/src/components/SelectTokenTextField/index.tsx index c474e50065..5c375dabb3 100644 --- a/apps/evm/src/components/SelectTokenTextField/index.tsx +++ b/apps/evm/src/components/SelectTokenTextField/index.tsx @@ -3,10 +3,10 @@ import { Typography } from '@mui/material'; import { useState } from 'react'; import { TertiaryButton } from '@venusprotocol/ui'; +import { type OptionalTokenBalance, TokenListWrapper } from 'containers/TokenListWrapper'; import type { Token } from 'types'; import { Icon } from '../Icon'; import { TokenIcon } from '../TokenIcon'; -import { type OptionalTokenBalance, TokenListWrapper } from '../TokenListWrapper'; import { TokenTextField, type TokenTextFieldProps } from '../TokenTextField'; import { useStyles } from './styles'; import { @@ -56,30 +56,32 @@ export const SelectTokenTextField: React.FC = ({ token={selectedToken} disabled={disabled} value={value} - displayTokenIcon={false} + displayTokenIcon={tokenBalances.length <= 1} leftAdornment={ - -
- + tokenBalances.length > 1 ? ( + +
+ -
{selectedToken.symbol}
-
+
{selectedToken.symbol}
+
- -
+ + + ) : undefined } rightAdornment={ rightMaxButton && ( diff --git a/apps/evm/src/components/TokenListWrapper/index.tsx b/apps/evm/src/components/TokenListWrapper/index.tsx deleted file mode 100644 index 9f5449328e..0000000000 --- a/apps/evm/src/components/TokenListWrapper/index.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { Typography } from '@mui/material'; -import { type InputHTMLAttributes, useMemo, useState } from 'react'; - -import { SenaryButton, cn } from '@venusprotocol/ui'; -import { Icon } from 'components'; -import { TokenIconWithSymbol } from 'components/TokenIconWithSymbol'; -import { useTranslation } from 'libs/translations'; -import type { Token, TokenBalance } from 'types'; -import { areTokensEqual, convertMantissaToTokens } from 'utilities'; -import { TextField } from '../TextField'; -import { getTokenListItemTestId } from './testIdGetters'; - -export interface OptionalTokenBalance extends Omit { - isDeemed?: boolean; - balanceMantissa?: BigNumber; -} - -export interface TokenListWrapperProps { - children: React.ReactNode; - tokenBalances: OptionalTokenBalance[]; - onTokenClick: (token: Token) => void; - onClose: () => void; - isListShown: boolean; - selectedToken: Token; - className?: string; - displayCommonTokenButtons?: boolean; - 'data-testid'?: string; -} - -const commonTokenSymbols = ['XVS', 'BNB', 'USDT', 'BTCB']; - -export const TokenListWrapper: React.FC = ({ - children, - tokenBalances, - onTokenClick, - onClose, - isListShown, - selectedToken, - displayCommonTokenButtons = true, - className, - 'data-testid': testId, -}) => { - const { t } = useTranslation(); - - const commonTokenBalances = displayCommonTokenButtons - ? tokenBalances.filter(tokenBalance => commonTokenSymbols.includes(tokenBalance.token.symbol)) - : []; - - const [searchValue, onSearchValueChange] = useState(''); - - const handleSearchInputChange: InputHTMLAttributes['onChange'] = event => - onSearchValueChange(event.currentTarget.value); - - // Sort tokens by balance (if it exists) - const sortedTokenBalances = useMemo( - () => - [...tokenBalances].sort((a, b) => { - const aIsNonNegative = !!a.balanceMantissa?.isGreaterThan(0); - const bIsNonNegative = !!b.balanceMantissa?.isGreaterThan(0); - - // Both are non-negative - if (aIsNonNegative === bIsNonNegative) { - return 0; - } - - // If a is non-negative and b is negative, a comes first - if (aIsNonNegative) { - return -1; - } - - // If b is non-negative and a is negative, b comes first - return 1; - }) as OptionalTokenBalance[], - [tokenBalances], - ); - - // Filter tokens based on search - const filteredTokenBalances = useMemo(() => { - if (!searchValue) { - return sortedTokenBalances; - } - - const formattedSearchValue = searchValue.toLowerCase(); - - // Enable user to search by symbol or address - return sortedTokenBalances.filter( - tokenBalance => - tokenBalance.token.symbol.toLowerCase().includes(formattedSearchValue) || - tokenBalance.token.address.toLowerCase().includes(formattedSearchValue), - ); - }, [sortedTokenBalances, searchValue]); - - const handleTokenClick = (token: Token) => { - onTokenClick(token); - - // Reset search value - onSearchValueChange(''); - - // Close list - onClose(); - }; - - return ( -
- {children} - - {isListShown && ( -
-
- {tokenBalances.length > 5 && ( -
- - - {commonTokenBalances.length > 2 && ( -
- {commonTokenBalances.map(commonTokenBalance => ( - handleTokenClick(commonTokenBalance.token)} - className="shrink-0" - key={`select-token-text-field-common-token-${commonTokenBalance.token.symbol}`} - > - - - ))} -
- )} -
- )} - -
- {filteredTokenBalances.map(tokenBalance => ( -
handleTokenClick(tokenBalance.token)} - key={`select-token-text-field-item-${tokenBalance.token.address}`} - data-testid={ - !!testId && - getTokenListItemTestId({ - parentTestId: testId, - tokenAddress: tokenBalance.token.address, - }) - } - > - - - {tokenBalance.balanceMantissa && ( - - {convertMantissaToTokens({ - value: tokenBalance.balanceMantissa, - token: tokenBalance.token, - returnInReadableFormat: true, - addSymbol: false, - })} - - )} - - {!tokenBalance.balanceMantissa && - areTokensEqual(tokenBalance.token, selectedToken) && ( - - )} -
- ))} -
-
-
- )} - -
-
- ); -}; diff --git a/apps/evm/src/components/index.ts b/apps/evm/src/components/index.ts index a506100d50..6fe4ea37e4 100644 --- a/apps/evm/src/components/index.ts +++ b/apps/evm/src/components/index.ts @@ -66,7 +66,6 @@ export * from './IsolatedEModeGroupTooltip'; export * from './TokenIcon'; export * from './BalanceUpdates'; export * from './LabeledValueUpdate'; -export * from './TokenListWrapper'; export * from './TransactionsList'; export * from './Wrapper'; export * from './AvailableBalance'; diff --git a/apps/evm/src/constants/functionKey.ts b/apps/evm/src/constants/functionKey.ts index 3837c2442b..31fcb6b380 100644 --- a/apps/evm/src/constants/functionKey.ts +++ b/apps/evm/src/constants/functionKey.ts @@ -73,6 +73,7 @@ enum FunctionKey { GET_SIMULATED_POOL = 'GET_SIMULATED_POOL', GET_SWAP_QUOTE = 'GET_SWAP_QUOTE', GET_PENDLE_SWAP_QUOTE = 'GET_PENDLE_SWAP_QUOTE', + GET_IP_LOCATION = 'GET_IP_LOCATION', GET_PROPOSAL_COUNT = 'GET_PROPOSAL_COUNT', GET_MARKETS_TVL = 'GET_MARKETS_TVL', GET_TOP_MARKETS = 'GET_TOP_MARKETS', diff --git a/apps/evm/src/containers/AssetAccessor/__tests__/index.spec.tsx b/apps/evm/src/containers/AssetAccessor/__tests__/index.spec.tsx index 52b932d4ec..d5b07939ef 100644 --- a/apps/evm/src/containers/AssetAccessor/__tests__/index.spec.tsx +++ b/apps/evm/src/containers/AssetAccessor/__tests__/index.spec.tsx @@ -1,4 +1,5 @@ import { waitFor } from '@testing-library/react'; +import BigNumber from 'bignumber.js'; import type { Mock } from 'vitest'; import fakeAddress from '__mocks__/models/address'; @@ -62,6 +63,11 @@ const mockGetPool = (pool: Pool = fakePool) => { }; describe('containers/AssetAccessor', () => { + beforeEach(() => { + mockGetPool(); + mockTokenAnnouncement.mockImplementation(() => null); + }); + it('renders without crashing', async () => { const { getByText } = renderComponent( {() => }, @@ -91,6 +97,25 @@ describe('containers/AssetAccessor', () => { expect(queryByText(fakeChildrenContent)).toBeNull(); }); + it('renders a restricted asset notice when the asset is unavailable in the user country', async () => { + mockGetPool( + getCustomFakePool({ + asset: { + isRestricted: true, + userBorrowBalanceCents: new BigNumber(1), + }, + }), + ); + + const { getByText, queryByText } = renderComponent( + {() => }, + ); + + await waitFor(() => expect(getByText(en.assetAccessor.assetNotAvailable)).toBeInTheDocument()); + + expect(queryByText(fakeChildrenContent)).toBeNull(); + }); + it('renders default token announcement if a disabled borrow action has no token announcement', async () => { mockGetPool( getCustomFakePool({ diff --git a/apps/evm/src/containers/AssetAccessor/index.tsx b/apps/evm/src/containers/AssetAccessor/index.tsx index 85651f9f9a..142e7e7f74 100644 --- a/apps/evm/src/containers/AssetAccessor/index.tsx +++ b/apps/evm/src/containers/AssetAccessor/index.tsx @@ -23,7 +23,7 @@ const AssetAccessor: React.FC = ({ action, }) => { const { accountAddress } = useAccountAddress(); - const { Trans } = useTranslation(); + const { t, Trans } = useTranslation(); const { data: getPools } = useGetPool({ poolComptrollerAddress, @@ -36,6 +36,10 @@ const AssetAccessor: React.FC = ({ return ; } + if (asset.isRestricted) { + return ; + } + const isBorrowAction = action === 'borrow' || action === 'boost'; if (isBorrowAction && !asset.isBorrowableByUser) { diff --git a/apps/evm/src/containers/GatedAssetAcknowledgementModal/__tests__/index.spec.tsx b/apps/evm/src/containers/GatedAssetAcknowledgementModal/__tests__/index.spec.tsx new file mode 100644 index 0000000000..dbdb0eb7ed --- /dev/null +++ b/apps/evm/src/containers/GatedAssetAcknowledgementModal/__tests__/index.spec.tsx @@ -0,0 +1,66 @@ +import { fireEvent, screen } from '@testing-library/react'; +import type { Mock } from 'vitest'; + +import { defaultUserChainSettings, useUserChainSettings } from 'hooks/useUserChainSettings'; +import { en } from 'libs/translations'; +import { renderComponent } from 'testUtils/render'; +import { GatedAssetAcknowledgementModal } from '..'; + +const mockSetUserChainSettings = vi.fn(); + +describe('GatedAssetAcknowledgementModal', () => { + beforeEach(() => { + (useUserChainSettings as Mock).mockReturnValue([ + defaultUserChainSettings, + mockSetUserChainSettings, + ]); + }); + + it('does not render when the notice has already been acknowledged', () => { + (useUserChainSettings as Mock).mockReturnValue([ + { + ...defaultUserChainSettings, + doNotShowGatedAssetModal: true, + }, + mockSetUserChainSettings, + ]); + + renderComponent(); + + expect(screen.queryByText(en.gatedAssetAcknowledgementModal.title)).not.toBeInTheDocument(); + }); + + it('acknowledges the notice and calls onAccept when accepting', () => { + const onAccept = vi.fn(); + + renderComponent(); + + fireEvent.click( + screen.getByRole('button', { + name: en.gatedAssetAcknowledgementModal.acceptButtonLabel, + }), + ); + + expect(mockSetUserChainSettings).toHaveBeenCalledWith({ + doNotShowGatedAssetModal: true, + }); + expect(onAccept).toHaveBeenCalledTimes(1); + }); + + it('resets the acknowledgement and calls onReject when rejecting', () => { + const onReject = vi.fn(); + + renderComponent(); + + fireEvent.click( + screen.getByRole('button', { + name: en.gatedAssetAcknowledgementModal.rejectButtonLabel, + }), + ); + + expect(mockSetUserChainSettings).toHaveBeenCalledWith({ + doNotShowGatedAssetModal: false, + }); + expect(onReject).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/evm/src/containers/GatedAssetAcknowledgementModal/index.tsx b/apps/evm/src/containers/GatedAssetAcknowledgementModal/index.tsx new file mode 100644 index 0000000000..3230a5ec8c --- /dev/null +++ b/apps/evm/src/containers/GatedAssetAcknowledgementModal/index.tsx @@ -0,0 +1,72 @@ +import { Button, Modal, type ModalProps, Notice, TextButton } from 'components'; +import { Link } from 'containers/Link'; +import { useUserChainSettings } from 'hooks/useUserChainSettings'; +import { useTranslation } from 'libs/translations'; + +const BSTOCKS_URL = 'https://www.bstocks.finance/en/prospectus'; + +export interface GatedAssetAcknowledgementModalProps + extends Omit { + onAccept?: () => void; + onReject?: () => void; +} + +export const GatedAssetAcknowledgementModal: React.FC = ({ + onAccept, + onReject, + ...otherProps +}) => { + const { t, Trans } = useTranslation(); + const [userChainSettings, setUserChainSettings] = useUserChainSettings(); + + const handleAccept = () => { + setUserChainSettings({ + doNotShowGatedAssetModal: true, + }); + + onAccept?.(); + }; + + const handleReject = () => { + setUserChainSettings({ + doNotShowGatedAssetModal: false, + }); + + onReject?.(); + }; + + return ( + +
+ , + Link: , + }} + /> + } + /> + +
+ + + + {t('gatedAssetAcknowledgementModal.rejectButtonLabel')} + +
+
+
+ ); +}; diff --git a/apps/evm/src/containers/MarketTable/__tests__/index.spec.tsx b/apps/evm/src/containers/MarketTable/__tests__/index.spec.tsx index c14507e54d..7006761bf4 100644 --- a/apps/evm/src/containers/MarketTable/__tests__/index.spec.tsx +++ b/apps/evm/src/containers/MarketTable/__tests__/index.spec.tsx @@ -27,6 +27,10 @@ const fakePausedPool: Pool = { }; describe('MarketTable', () => { + beforeEach(() => { + (useUserChainSettings as Mock).mockReturnValue([defaultUserChainSettings, vi.fn()]); + }); + it('renders with pool data', () => { const { container } = renderComponent( { expect(await screen.findByTestId('operation-form')).toBeInTheDocument(); }); + + it('opens the acknowledgement modal instead of the operation form for gated assets', async () => { + const gatedAssets = poolData[0].assets.map((asset, index) => + index === 0 + ? { + ...asset, + isGated: true, + } + : asset, + ); + + const { container } = renderComponent( + , + ); + + fireEvent.click(container.querySelector('tbody button') as HTMLButtonElement); + + expect(await screen.findByText(en.gatedAssetAcknowledgementModal.title)).toBeInTheDocument(); + expect(screen.queryByTestId('operation-form')).not.toBeInTheDocument(); + }); }); diff --git a/apps/evm/src/containers/MarketTable/index.tsx b/apps/evm/src/containers/MarketTable/index.tsx index 4a19ac9b3a..49b675acdf 100644 --- a/apps/evm/src/containers/MarketTable/index.tsx +++ b/apps/evm/src/containers/MarketTable/index.tsx @@ -13,9 +13,11 @@ import { } from 'components'; import { routes } from 'constants/routing'; import { Controls } from 'containers/Controls'; +import { GatedAssetAcknowledgementModal } from 'containers/GatedAssetAcknowledgementModal'; import { SwitchChainNotice } from 'containers/SwitchChainNotice'; import { useBreakpointUp } from 'hooks/responsive'; import { useCollateral } from 'hooks/useCollateral'; +import { useUserChainSettings } from 'hooks/useUserChainSettings'; import { handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import { useAccountChainId, useChainId } from 'libs/wallet'; @@ -82,6 +84,10 @@ export const MarketTable: React.FC = ({ const { toggleCollateral } = useCollateral(); + const [userChainSettings] = useUserChainSettings(); + const shouldDisplayGatedAssetsAcknowledgementModal = + selectedAsset?.isGated && !userChainSettings.doNotShowGatedAssetModal; + // The fallback breakpoint is just to satisfy TS here, it is not actually used const _isBreakpointUp = useBreakpointUp(breakpoint || '2xl'); const isBreakpointUp = !!breakpoint && _isBreakpointUp; @@ -231,9 +237,13 @@ export const MarketTable: React.FC = ({ {...otherTableProps} /> - {selectedAsset && ( + {shouldDisplayGatedAssetsAcknowledgementModal && ( + setSelectedAsset(undefined)} /> + )} + + {selectedAsset && !shouldDisplayGatedAssetsAcknowledgementModal && ( {selectedAsset.isProtectionModeEnabled && ( diff --git a/apps/evm/src/containers/TokenListWrapper/__tests__/index.spec.tsx b/apps/evm/src/containers/TokenListWrapper/__tests__/index.spec.tsx new file mode 100644 index 0000000000..ce600c02f4 --- /dev/null +++ b/apps/evm/src/containers/TokenListWrapper/__tests__/index.spec.tsx @@ -0,0 +1,275 @@ +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import BigNumber from 'bignumber.js'; +import type { Mock } from 'vitest'; + +import { bnb, busd, usdc, usdt, vai, xvs } from '__mocks__/models/tokens'; +import { defaultUserChainSettings, useUserChainSettings } from 'hooks/useUserChainSettings'; +import { en } from 'libs/translations'; +import { renderComponent } from 'testUtils/render'; +import type { Token } from 'types'; +import { type OptionalTokenBalance, TokenListWrapper } from '..'; +import { getTokenListItemTestId } from '../getTokenListItemTestId'; + +const testId = 'token-list-wrapper'; +const mockOnTokenClick = vi.fn(); +const mockOnClose = vi.fn(); +const mockSetUserChainSettings = vi.fn(); + +const getTokenBalanceMantissa = (token: Token) => new BigNumber(10).pow(token.decimals); + +const makeTokenBalance = ( + token: Token, + overrides: Partial = {}, +): OptionalTokenBalance => ({ + token, + ...overrides, +}); + +const baseTokenBalances: OptionalTokenBalance[] = [ + makeTokenBalance(xvs, { balanceMantissa: getTokenBalanceMantissa(xvs) }), + makeTokenBalance(bnb, { balanceMantissa: getTokenBalanceMantissa(bnb) }), + makeTokenBalance(usdt, { balanceMantissa: getTokenBalanceMantissa(usdt) }), + makeTokenBalance(busd), + makeTokenBalance(usdc, { balanceMantissa: new BigNumber(0) }), + makeTokenBalance(vai), +]; + +const renderTokenListWrapper = ({ + tokenBalances = baseTokenBalances, + selectedToken = xvs, + displayCommonTokenButtons = true, +}: { + tokenBalances?: OptionalTokenBalance[]; + selectedToken?: Token; + displayCommonTokenButtons?: boolean; +} = {}) => + renderComponent( + +
Trigger
+
, + ); + +describe('TokenListWrapper', () => { + beforeEach(() => { + (useUserChainSettings as Mock).mockReturnValue([ + defaultUserChainSettings, + mockSetUserChainSettings, + ]); + }); + + it('sorts tokens with a positive balance ahead of tokens with zero or no balance', () => { + const { container } = renderTokenListWrapper({ + tokenBalances: [ + makeTokenBalance(busd), + makeTokenBalance(xvs, { balanceMantissa: getTokenBalanceMantissa(xvs) }), + makeTokenBalance(usdc, { balanceMantissa: new BigNumber(0) }), + makeTokenBalance(bnb, { balanceMantissa: getTokenBalanceMantissa(bnb) }), + ], + }); + + const tokenItems = Array.from( + container.querySelectorAll(`[data-testid^="${testId}-token-select-button-"]`), + ); + + expect(tokenItems[0]).toHaveTextContent(xvs.symbol); + expect(tokenItems[1]).toHaveTextContent(bnb.symbol); + expect(tokenItems[2]).toHaveTextContent(busd.symbol); + expect(tokenItems[3]).toHaveTextContent(usdc.symbol); + }); + + it('filters tokens by symbol and address', () => { + renderTokenListWrapper({ + displayCommonTokenButtons: false, + }); + + const searchInput = screen.getByPlaceholderText( + en.selectTokenTextField.searchInput.placeholder, + ); + + fireEvent.change(searchInput, { + target: { + value: 'xv', + }, + }); + + expect( + screen.getByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: xvs.address, + }), + ), + ).toBeInTheDocument(); + expect( + screen.queryByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: busd.address, + }), + ), + ).not.toBeInTheDocument(); + + fireEvent.change(searchInput, { + target: { + value: busd.address.toLowerCase().slice(2, 10), + }, + }); + + expect( + screen.getByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: busd.address, + }), + ), + ).toBeInTheDocument(); + expect( + screen.queryByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: xvs.address, + }), + ), + ).not.toBeInTheDocument(); + }); + + it('selects an allowed token, clears the search input and closes the list', async () => { + renderTokenListWrapper({ + displayCommonTokenButtons: false, + }); + + const searchInput = screen.getByPlaceholderText( + en.selectTokenTextField.searchInput.placeholder, + ) as HTMLInputElement; + + fireEvent.change(searchInput, { + target: { + value: 'busd', + }, + }); + + fireEvent.click( + screen.getByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: busd.address, + }), + ), + ); + + await waitFor(() => expect(mockOnTokenClick).toHaveBeenCalledWith(busd)); + + expect(mockOnClose).toHaveBeenCalledTimes(1); + expect(searchInput.value).toBe(''); + }); + + it('shows the gated asset notice instead of selecting a gated token when the notice is not acknowledged', async () => { + renderTokenListWrapper({ + tokenBalances: [makeTokenBalance(busd, { isGated: true })], + }); + + fireEvent.click( + screen.getByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: busd.address, + }), + ), + ); + + expect(mockOnTokenClick).not.toHaveBeenCalled(); + expect(mockOnClose).not.toHaveBeenCalled(); + expect(await screen.findByText(en.gatedAssetAcknowledgementModal.title)).toBeInTheDocument(); + }); + + it('selects a gated token after the user accepts the notice', async () => { + renderTokenListWrapper({ + tokenBalances: [makeTokenBalance(busd, { isGated: true })], + }); + + fireEvent.click( + screen.getByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: busd.address, + }), + ), + ); + + fireEvent.click( + await screen.findByRole('button', { + name: en.gatedAssetAcknowledgementModal.acceptButtonLabel, + }), + ); + + expect(mockSetUserChainSettings).toHaveBeenCalledWith({ + doNotShowGatedAssetModal: true, + }); + }); + + it('clears the pending selection when the user rejects the gated notice', async () => { + renderTokenListWrapper({ + tokenBalances: [makeTokenBalance(busd, { isGated: true })], + }); + + fireEvent.click( + screen.getByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: busd.address, + }), + ), + ); + + fireEvent.click( + await screen.findByRole('button', { + name: en.gatedAssetAcknowledgementModal.rejectButtonLabel, + }), + ); + + await waitFor(() => + expect(screen.queryByText(en.gatedAssetAcknowledgementModal.title)).not.toBeInTheDocument(), + ); + + expect(mockSetUserChainSettings).toHaveBeenCalledWith({ + doNotShowGatedAssetModal: false, + }); + expect(mockOnTokenClick).not.toHaveBeenCalled(); + expect(mockOnClose).not.toHaveBeenCalled(); + }); + + it('selects a gated token immediately when the gated asset notice has already been acknowledged', async () => { + (useUserChainSettings as Mock).mockReturnValue([ + { + ...defaultUserChainSettings, + doNotShowGatedAssetModal: true, + }, + mockSetUserChainSettings, + ]); + + renderTokenListWrapper({ + tokenBalances: [makeTokenBalance(busd, { isGated: true })], + }); + + fireEvent.click( + screen.getByTestId( + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: busd.address, + }), + ), + ); + + await waitFor(() => expect(mockOnTokenClick).toHaveBeenCalledWith(busd)); + expect(mockOnClose).toHaveBeenCalledTimes(1); + expect(screen.queryByText(en.gatedAssetAcknowledgementModal.title)).not.toBeInTheDocument(); + }); +}); diff --git a/apps/evm/src/components/TokenListWrapper/testIdGetters.ts b/apps/evm/src/containers/TokenListWrapper/getTokenListItemTestId/index.ts similarity index 100% rename from apps/evm/src/components/TokenListWrapper/testIdGetters.ts rename to apps/evm/src/containers/TokenListWrapper/getTokenListItemTestId/index.ts diff --git a/apps/evm/src/containers/TokenListWrapper/index.tsx b/apps/evm/src/containers/TokenListWrapper/index.tsx new file mode 100644 index 0000000000..608675b167 --- /dev/null +++ b/apps/evm/src/containers/TokenListWrapper/index.tsx @@ -0,0 +1,225 @@ +import { Typography } from '@mui/material'; +import { type InputHTMLAttributes, useEffect, useMemo, useRef, useState } from 'react'; + +import { SenaryButton, cn } from '@venusprotocol/ui'; +import { Icon, TextField } from 'components'; +import { TokenIconWithSymbol } from 'components/TokenIconWithSymbol'; +import { GatedAssetAcknowledgementModal } from 'containers/GatedAssetAcknowledgementModal'; +import { useUserChainSettings } from 'hooks/useUserChainSettings'; +import { useTranslation } from 'libs/translations'; +import type { Token, TokenBalance } from 'types'; +import { areTokensEqual, convertMantissaToTokens } from 'utilities'; +import { getTokenListItemTestId } from './getTokenListItemTestId'; + +export interface OptionalTokenBalance extends Omit { + isDeemed?: boolean; + isGated?: boolean; + balanceMantissa?: BigNumber; +} + +export interface TokenListWrapperProps { + children: React.ReactNode; + tokenBalances: OptionalTokenBalance[]; + onTokenClick: (token: Token) => void; + onClose: () => void; + isListShown: boolean; + selectedToken: Token; + className?: string; + displayCommonTokenButtons?: boolean; + 'data-testid'?: string; +} + +const commonTokenSymbols = ['XVS', 'BNB', 'USDT', 'BTCB']; + +export const TokenListWrapper: React.FC = ({ + children, + tokenBalances, + onTokenClick, + onClose, + isListShown, + selectedToken, + displayCommonTokenButtons = true, + className, + 'data-testid': testId, +}) => { + const { t } = useTranslation(); + const [userChainSettings] = useUserChainSettings(); + + const [pendingSelectedToken, setPendingSelectedToken] = useState(); + + const clearPendingSelectTokenRef = useRef(() => setPendingSelectedToken(undefined)); + + const pendingSelectedTokenBalance = useMemo( + () => + pendingSelectedToken && + tokenBalances.find(tokenBalance => areTokensEqual(tokenBalance.token, pendingSelectedToken)), + [pendingSelectedToken, tokenBalances], + ); + + const commonTokenBalances = displayCommonTokenButtons + ? tokenBalances.filter(tokenBalance => commonTokenSymbols.includes(tokenBalance.token.symbol)) + : []; + + const [searchValue, onSearchValueChange] = useState(''); + + const handleSearchInputChange: InputHTMLAttributes['onChange'] = event => + onSearchValueChange(event.currentTarget.value); + + // Sort tokens by balance (if it exists) + const sortedTokenBalances = useMemo( + () => + [...tokenBalances].sort((a, b) => { + const aIsNonNegative = !!a.balanceMantissa?.isGreaterThan(0); + const bIsNonNegative = !!b.balanceMantissa?.isGreaterThan(0); + + // Both are non-negative + if (aIsNonNegative === bIsNonNegative) { + return 0; + } + + // If a is non-negative and b is negative, a comes first + if (aIsNonNegative) { + return -1; + } + + // If b is non-negative and a is negative, b comes first + return 1; + }) as OptionalTokenBalance[], + [tokenBalances], + ); + + // Filter tokens based on search + const filteredTokenBalances = useMemo(() => { + if (!searchValue) { + return sortedTokenBalances; + } + + const formattedSearchValue = searchValue.toLowerCase(); + + // Enable user to search by symbol or address + return sortedTokenBalances.filter( + tokenBalance => + tokenBalance.token.symbol.toLowerCase().includes(formattedSearchValue) || + tokenBalance.token.address.toLowerCase().includes(formattedSearchValue), + ); + }, [sortedTokenBalances, searchValue]); + + const handleTokenClick = (token: Token) => + // Queue selected token update + setPendingSelectedToken(token); + + useEffect(() => { + // Handle pending selected token update. If the user has acknowledged the gated assets notice, + // or if the asset isn't gated, we can proceed with the update + if ( + pendingSelectedTokenBalance && + (!pendingSelectedTokenBalance?.isGated || userChainSettings.doNotShowGatedAssetModal) + ) { + onTokenClick(pendingSelectedTokenBalance.token); + + // Clear pending selected token update + clearPendingSelectTokenRef.current(); + + // Reset search value + onSearchValueChange(''); + + // Close list + onClose(); + } + }, [ + pendingSelectedTokenBalance, + userChainSettings.doNotShowGatedAssetModal, + onClose, + onTokenClick, + ]); + + return ( + <> +
+ {children} + + {isListShown && ( +
+
+ {tokenBalances.length > 5 && ( +
+ + + {commonTokenBalances.length > 2 && ( +
+ {commonTokenBalances.map(commonTokenBalance => ( + handleTokenClick(commonTokenBalance.token)} + className="shrink-0" + key={`select-token-text-field-common-token-${commonTokenBalance.token.symbol}`} + > + + + ))} +
+ )} +
+ )} + +
+ {filteredTokenBalances.map(tokenBalance => ( +
handleTokenClick(tokenBalance.token)} + key={`select-token-text-field-item-${tokenBalance.token.address}`} + data-testid={ + !!testId && + getTokenListItemTestId({ + parentTestId: testId, + tokenAddress: tokenBalance.token.address, + }) + } + > + + + {tokenBalance.balanceMantissa && ( + + {convertMantissaToTokens({ + value: tokenBalance.balanceMantissa, + token: tokenBalance.token, + returnInReadableFormat: true, + addSymbol: false, + })} + + )} + + {!tokenBalance.balanceMantissa && + areTokensEqual(tokenBalance.token, selectedToken) && ( + + )} +
+ ))} +
+
+
+ )} + +
+
+ + {pendingSelectedTokenBalance?.isGated && ( + + )} + + ); +}; diff --git a/apps/evm/src/containers/VaultCard/VenusVaultModal/WithdrawTab/WithdrawFromVestingVaultForm/RequestWithdrawalForm/index.tsx b/apps/evm/src/containers/VaultCard/VenusVaultModal/WithdrawTab/WithdrawFromVestingVaultForm/RequestWithdrawalForm/index.tsx index 1c432ce1d3..0c1438d472 100644 --- a/apps/evm/src/containers/VaultCard/VenusVaultModal/WithdrawTab/WithdrawFromVestingVaultForm/RequestWithdrawalForm/index.tsx +++ b/apps/evm/src/containers/VaultCard/VenusVaultModal/WithdrawTab/WithdrawFromVestingVaultForm/RequestWithdrawalForm/index.tsx @@ -176,7 +176,7 @@ export const RequestWithdrawalForm: React.FC = ({ }; return ( -
+
{isInitialLoading ? ( ) : ( diff --git a/apps/evm/src/hooks/useGetProfitableImports/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/hooks/useGetProfitableImports/__tests__/__snapshots__/index.spec.tsx.snap index dc1030c4be..0d6c9e07fd 100644 --- a/apps/evm/src/hooks/useGetProfitableImports/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/hooks/useGetProfitableImports/__tests__/__snapshots__/index.spec.tsx.snap @@ -69,7 +69,9 @@ exports[`useGetProfitableImports > returns profitable importable positions 1`] = "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -224,7 +226,9 @@ exports[`useGetProfitableImports > returns profitable importable positions 1`] = "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", diff --git a/apps/evm/src/hooks/useGetSwapTokenUserBalances/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/hooks/useGetSwapTokenUserBalances/__tests__/__snapshots__/index.spec.tsx.snap deleted file mode 100644 index 63c5fd0a52..0000000000 --- a/apps/evm/src/hooks/useGetSwapTokenUserBalances/__tests__/__snapshots__/index.spec.tsx.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`useGetSwapTokenUserBalances > filters out paused assets and returns mapped token balances 1`] = ` -[ - { - "balanceMantissa": "100000000000000000000", - "token": { - "address": "0xB9e0E753630434d7863528cc73CB7AC638a7c8ff", - "chainId": 97, - "decimals": 18, - "iconSrc": "fake-xvs-asset", - "symbol": "XVS", - }, - }, - { - "balanceMantissa": "0", - "token": { - "address": "0x16227D60f7a0e586C66B005219dfc887D13C9531", - "chainId": 97, - "decimals": 6, - "iconSrc": "fake-usdc-asset", - "symbol": "USDC", - }, - }, -] -`; diff --git a/apps/evm/src/hooks/useGetSwapTokenUserBalances/__tests__/index.spec.tsx b/apps/evm/src/hooks/useGetSwapTokenUserBalances/__tests__/index.spec.tsx index fecde1be9c..e6435b5039 100644 --- a/apps/evm/src/hooks/useGetSwapTokenUserBalances/__tests__/index.spec.tsx +++ b/apps/evm/src/hooks/useGetSwapTokenUserBalances/__tests__/index.spec.tsx @@ -43,6 +43,39 @@ describe('useGetSwapTokenUserBalances', () => { }), ); - expect(result.current.data).toMatchSnapshot(); + expect(result.current.data).toEqual([ + expect.objectContaining({ + token: assetData[0].vToken.underlyingToken, + isGated: false, + }), + expect.objectContaining({ + token: assetData[1].vToken.underlyingToken, + isGated: false, + }), + ]); + }); + + it('filters out restricted assets and preserves gated asset metadata', () => { + (useGetPool as Mock).mockImplementation(() => ({ + data: { + pool: { + assets: [assetData[2], assetData[3]], + }, + }, + })); + + const { result } = renderHook(() => + useGetSwapTokenUserBalances({ + poolComptrollerContractAddress: fakePoolComptrollerContractAddress, + accountAddress: fakeAccountAddress, + }), + ); + + expect(result.current.data).toEqual([ + expect.objectContaining({ + token: assetData[2].vToken.underlyingToken, + isGated: true, + }), + ]); }); }); diff --git a/apps/evm/src/hooks/useGetSwapTokenUserBalances/index.ts b/apps/evm/src/hooks/useGetSwapTokenUserBalances/index.ts index 9e5bf1f325..dd03da5be8 100644 --- a/apps/evm/src/hooks/useGetSwapTokenUserBalances/index.ts +++ b/apps/evm/src/hooks/useGetSwapTokenUserBalances/index.ts @@ -1,8 +1,8 @@ -import { useGetPool } from 'clients/api'; -import type { TokenBalance } from 'types'; +import type { Address } from 'viem'; +import { useGetPool } from 'clients/api'; +import type { OptionalTokenBalance } from 'containers/TokenListWrapper'; import { convertTokensToMantissa } from 'utilities'; -import type { Address } from 'viem'; export const useGetSwapTokenUserBalances = ({ poolComptrollerContractAddress, @@ -17,13 +17,19 @@ export const useGetSwapTokenUserBalances = ({ }); const data = getPoolData?.pool.assets - ? getPoolData?.pool.assets.reduce((acc, poolAsset) => { - const tokenBalance: TokenBalance = { + ? getPoolData?.pool.assets.reduce((acc, poolAsset) => { + // Filter out restricted assets + if (poolAsset.isRestricted) { + return acc; + } + + const tokenBalance: OptionalTokenBalance = { token: poolAsset.vToken.underlyingToken, balanceMantissa: convertTokensToMantissa({ token: poolAsset.vToken.underlyingToken, value: poolAsset.userWalletBalanceTokens, }), + isGated: poolAsset.isGated, }; return [...acc, tokenBalance]; diff --git a/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap index 7f3f4c4465..29453aca57 100644 --- a/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/hooks/useGetTradePositions/__tests__/__snapshots__/index.spec.ts.snap @@ -44,7 +44,9 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -172,7 +174,9 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -289,7 +293,9 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -415,7 +421,9 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -557,7 +565,9 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -691,7 +701,9 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -1056,7 +1068,9 @@ exports[`useGetTradePositions > enriches positions with wallet balances from the "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -1204,7 +1218,9 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -1332,7 +1348,9 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -1449,7 +1467,9 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -1575,7 +1595,9 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -1717,7 +1739,9 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -1851,7 +1875,9 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -2216,7 +2242,9 @@ exports[`useGetTradePositions > falls back to the raw Trade position balances wh "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -2364,7 +2392,9 @@ exports[`useGetTradePositions > passes the expected query params when the wallet "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -2492,7 +2522,9 @@ exports[`useGetTradePositions > passes the expected query params when the wallet "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -2609,7 +2641,9 @@ exports[`useGetTradePositions > passes the expected query params when the wallet "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -2735,7 +2769,9 @@ exports[`useGetTradePositions > passes the expected query params when the wallet "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -2877,7 +2913,9 @@ exports[`useGetTradePositions > passes the expected query params when the wallet "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -3011,7 +3049,9 @@ exports[`useGetTradePositions > passes the expected query params when the wallet "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -3376,7 +3416,9 @@ exports[`useGetTradePositions > passes the expected query params when the wallet "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", diff --git a/apps/evm/src/hooks/useGetTradePositions/index.ts b/apps/evm/src/hooks/useGetTradePositions/index.ts index d91a98db26..e0b602f8f7 100644 --- a/apps/evm/src/hooks/useGetTradePositions/index.ts +++ b/apps/evm/src/hooks/useGetTradePositions/index.ts @@ -1,3 +1,4 @@ +import BigNumber from 'bignumber.js'; import type { Address } from 'viem'; import { useGetPools, useGetRawTradePositions } from 'clients/api'; @@ -30,21 +31,20 @@ export const useGetTradePositions = ({ accountAddress }: { accountAddress?: Addr }), ); - // Enrich raw Trade positions with user wallet balances + // Enrich raw Trade positions with user data const positions: TradePosition[] = rawTradePositions.map(rawTradePosition => { const pool: Pool = { ...rawTradePosition.pool, - assets: rawTradePosition.pool.assets.map(asset => { - const sanitizedVTokenAddress = asset.vToken.address.toLowerCase() as Address; + assets: rawTradePosition.pool.assets.map(positionAccountAsset => { + const sanitizedVTokenAddress = positionAccountAsset.vToken.address.toLowerCase() as Address; + const asset = assetMapping[sanitizedVTokenAddress]; return { - ...asset, - userWalletBalanceTokens: - assetMapping[sanitizedVTokenAddress]?.userWalletBalanceTokens || - asset.userWalletBalanceTokens, - userWalletBalanceCents: - assetMapping[sanitizedVTokenAddress]?.userWalletBalanceCents || - asset.userWalletBalanceCents, + ...positionAccountAsset, + isRestricted: asset?.isRestricted || false, + isGated: asset?.isGated || false, + userWalletBalanceTokens: asset?.userWalletBalanceTokens || new BigNumber(0), + userWalletBalanceCents: asset?.userWalletBalanceCents || new BigNumber(0), }; }), }; diff --git a/apps/evm/src/hooks/useSimulateBalanceMutations/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/hooks/useSimulateBalanceMutations/__tests__/__snapshots__/index.spec.tsx.snap index 308788a708..15642929c5 100644 --- a/apps/evm/src/hooks/useSimulateBalanceMutations/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/hooks/useSimulateBalanceMutations/__tests__/__snapshots__/index.spec.tsx.snap @@ -42,7 +42,9 @@ exports[`useSimulateBalanceMutations > calls the right hooks and returns the rig "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -168,7 +170,9 @@ exports[`useSimulateBalanceMutations > calls the right hooks and returns the rig "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -310,7 +314,9 @@ exports[`useSimulateBalanceMutations > calls the right hooks and returns the rig "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -444,7 +450,9 @@ exports[`useSimulateBalanceMutations > calls the right hooks and returns the rig "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", diff --git a/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap b/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap index 6c91734457..c968a92f2e 100644 --- a/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap +++ b/apps/evm/src/hooks/useUserChainSettings/__tests__/__snapshots__/index.spec.tsx.snap @@ -4,6 +4,7 @@ exports[`useUserChainSettings > returns correct settings from the store 1`] = ` { "doNotExpandGuide": false, "doNotShowFixedRateVaultsAdBanner": false, + "doNotShowGatedAssetModal": false, "doNotShowImportPositionsModal": false, "doNotShowUserBalances": false, "gaslessTransactions": true, diff --git a/apps/evm/src/hooks/useUserChainSettings/index.tsx b/apps/evm/src/hooks/useUserChainSettings/index.tsx index 4d5bb28478..975475285d 100644 --- a/apps/evm/src/hooks/useUserChainSettings/index.tsx +++ b/apps/evm/src/hooks/useUserChainSettings/index.tsx @@ -11,6 +11,7 @@ export const defaultUserChainSettings: UserChainSettings = { doNotShowUserBalances: false, doNotExpandGuide: false, doNotShowFixedRateVaultsAdBanner: false, + doNotShowGatedAssetModal: false, }; export const useUserChainSettings = () => { diff --git a/apps/evm/src/libs/translations/translations/en.json b/apps/evm/src/libs/translations/translations/en.json index 2ab3b6abaf..1bc78f2847 100644 --- a/apps/evm/src/libs/translations/translations/en.json +++ b/apps/evm/src/libs/translations/translations/en.json @@ -273,6 +273,7 @@ } }, "assetAccessor": { + "assetNotAvailable": "This asset is not available in your country", "disabledActionNotice": { "boost": "Boosting has been disabled for this market", "borrow": "Borrowing has been disabled for this market", @@ -596,6 +597,12 @@ "enabled": "Transactions on this network are gas-free, with no fees required. More info here" } }, + "gatedAssetAcknowledgementModal": { + "acceptButtonLabel": "I understand and confirm", + "description": "bStocks are tokenized securities issued by BTECH Holdings Ltd (ADGM). The information presented here is for informational purposes only and does not constitute an offer, solicitation, or investment advice. bStocks are not intended for distribution in the United States, to any US Person, or in any other restricted jurisdiction. Investing involves risk — the value of your investment may go down and you may not get back the amount invested. For full risk disclosures, legal documentation, and a list of restricted jurisdictions, please visit {{ url }}.By proceeding, you confirm you are not a Restricted Person and that accessing this product is lawful in your jurisdiction.", + "rejectButtonLabel": "I reject & leave", + "title": "Important notice" + }, "healthFactor": { "label": { "healthy": "Healthy", diff --git a/apps/evm/src/libs/translations/translations/ja.json b/apps/evm/src/libs/translations/translations/ja.json index 975aaac114..317303220e 100644 --- a/apps/evm/src/libs/translations/translations/ja.json +++ b/apps/evm/src/libs/translations/translations/ja.json @@ -273,6 +273,7 @@ } }, "assetAccessor": { + "assetNotAvailable": "この資産はお住まいの国では利用できません", "disabledActionNotice": { "boost": "このマーケットではブーストが無効化されています", "borrow": "このマーケットでは借入が無効化されています", @@ -596,6 +597,12 @@ "enabled": "このネットワークの取引はガスレスで手数料不要です。詳しくはこちら" } }, + "gatedAssetAcknowledgementModal": { + "acceptButtonLabel": "理解し、確認しました", + "description": "bStocksは、BTECH Holdings Ltd (ADGM) が発行するトークン化有価証券です。ここに記載された情報は情報提供のみを目的としており、募集、勧誘、または投資助言を構成するものではありません。bStocksは、米国、米国人、またはその他の制限された法域において配布されることを意図していません。投資にはリスクが伴います。投資価値が下落し、投資元本を回収できない場合があります。完全なリスク開示、法的文書、および制限された法域の一覧については、bstocks.finance/prospectusをご覧ください。続行することにより、あなたは自らが制限対象者ではなく、この商品へのアクセスがあなたの法域において適法であることを確認したものとみなされます。", + "rejectButtonLabel": "拒否して離れる", + "title": "重要なお知らせ" + }, "healthFactor": { "label": { "healthy": "健全", diff --git a/apps/evm/src/libs/translations/translations/th.json b/apps/evm/src/libs/translations/translations/th.json index 7451af7cb3..7fe6124b77 100644 --- a/apps/evm/src/libs/translations/translations/th.json +++ b/apps/evm/src/libs/translations/translations/th.json @@ -273,6 +273,7 @@ } }, "assetAccessor": { + "assetNotAvailable": "สินทรัพย์นี้ไม่สามารถใช้งานได้ในประเทศของคุณ", "disabledActionNotice": { "boost": "การบูสต์ถูกปิดใช้งานสำหรับตลาดนี้", "borrow": "การยืมถูกปิดใช้งานสำหรับตลาดนี้", @@ -596,6 +597,12 @@ "enabled": "ธุรกรรมบนเครือข่ายนี้ไม่ต้องจ่ายแก๊ส ไม่มีค่าธรรมเนียม ข้อมูลเพิ่มเติม ที่นี่" } }, + "gatedAssetAcknowledgementModal": { + "acceptButtonLabel": "ฉันเข้าใจและยืนยัน", + "description": "bStocks คือหลักทรัพย์โทเคนไนซ์ที่ออกโดย BTECH Holdings Ltd (ADGM) ข้อมูลที่แสดงไว้ที่นี่มีไว้เพื่อให้ข้อมูลเท่านั้น และไม่ถือเป็นข้อเสนอ การชักชวน หรือคำแนะนำด้านการลงทุน bStocks ไม่ได้มีไว้เพื่อเผยแพร่ในสหรัฐอเมริกา แก่บุคคลสหรัฐฯ หรือในเขตอำนาจศาลอื่นใดที่มีข้อจำกัด การลงทุนมีความเสี่ยง มูลค่าการลงทุนของคุณอาจลดลง และคุณอาจไม่ได้รับเงินลงทุนคืนครบถ้วน สำหรับการเปิดเผยความเสี่ยงทั้งหมด เอกสารทางกฎหมาย และรายชื่อเขตอำนาจศาลที่มีข้อจำกัด โปรดไปที่ bstocks.finance/prospectusเมื่อดำเนินการต่อ คุณยืนยันว่าคุณไม่ใช่บุคคลที่ถูกจำกัด และการเข้าถึงผลิตภัณฑ์นี้เป็นสิ่งที่ชอบด้วยกฎหมายในเขตอำนาจศาลของคุณ", + "rejectButtonLabel": "ฉันปฏิเสธและออก", + "title": "ประกาศสำคัญ" + }, "healthFactor": { "label": { "healthy": "ปลอดภัย", diff --git a/apps/evm/src/libs/translations/translations/tr.json b/apps/evm/src/libs/translations/translations/tr.json index 9c84ac9224..7b8a43e24e 100644 --- a/apps/evm/src/libs/translations/translations/tr.json +++ b/apps/evm/src/libs/translations/translations/tr.json @@ -273,6 +273,7 @@ } }, "assetAccessor": { + "assetNotAvailable": "Bu varlık ülkenizde kullanılamıyor", "disabledActionNotice": { "boost": "Bu piyasada boost devre dışı", "borrow": "Bu piyasada borç alma devre dışı", @@ -596,6 +597,12 @@ "enabled": "Bu ağdaki işlemler gas'sizdir, ücret gerekmez. Daha fazla bilgi burada" } }, + "gatedAssetAcknowledgementModal": { + "acceptButtonLabel": "Anladım ve onaylıyorum", + "description": "bStocks, BTECH Holdings Ltd (ADGM) tarafından ihraç edilen tokenize menkul kıymetlerdir. Burada sunulan bilgiler yalnızca bilgilendirme amaçlıdır ve herhangi bir teklif, talep veya yatırım tavsiyesi teşkil etmez. bStocks, Amerika Birleşik Devletleri'nde, herhangi bir ABD Kişisi'ne veya diğer kısıtlı yargı bölgelerinde dağıtım amacıyla sunulmamaktadır. Yatırım risk içerir; yatırımınızın değeri düşebilir ve yatırdığınız tutarın tamamını geri alamayabilirsiniz. Risklere ilişkin tam açıklamalar, hukuki belgeler ve kısıtlı yargı bölgelerinin listesi için lütfen bstocks.finance/prospectus adresini ziyaret edin.Devam ederek, Kısıtlı Kişi olmadığınızı ve bu ürüne erişimin bulunduğunuz yargı bölgesinde yasal olduğunu teyit edersiniz.", + "rejectButtonLabel": "Reddedip çık", + "title": "Önemli uyarı" + }, "healthFactor": { "label": { "healthy": "Sağlıklı", diff --git a/apps/evm/src/libs/translations/translations/vi.json b/apps/evm/src/libs/translations/translations/vi.json index f554b595e0..345cab1232 100644 --- a/apps/evm/src/libs/translations/translations/vi.json +++ b/apps/evm/src/libs/translations/translations/vi.json @@ -273,6 +273,7 @@ } }, "assetAccessor": { + "assetNotAvailable": "Tài sản này không khả dụng tại quốc gia của bạn", "disabledActionNotice": { "boost": "Tăng cường đã bị vô hiệu hóa cho thị trường này", "borrow": "Vay đã bị vô hiệu hóa cho thị trường này", @@ -596,6 +597,12 @@ "enabled": "Giao dịch trên mạng này không cần gas, không mất phí. Thêm thông tin tại đây" } }, + "gatedAssetAcknowledgementModal": { + "acceptButtonLabel": "Tôi hiểu và xác nhận", + "description": "bStocks là chứng khoán được token hóa do BTECH Holdings Ltd (ADGM) phát hành. Thông tin được trình bày tại đây chỉ nhằm mục đích cung cấp thông tin và không cấu thành đề nghị, lời mời chào hoặc lời khuyên đầu tư. bStocks không được phân phối tại Hoa Kỳ, cho bất kỳ US Person nào, hoặc tại bất kỳ khu vực tài phán hạn chế nào khác. Đầu tư luôn đi kèm rủi ro; giá trị khoản đầu tư của bạn có thể giảm và bạn có thể không thu hồi được số tiền đã đầu tư. Để xem đầy đủ các công bố rủi ro, tài liệu pháp lý và danh sách các khu vực tài phán hạn chế, vui lòng truy cập bstocks.finance/prospectus.Khi tiếp tục, bạn xác nhận rằng mình không phải là Restricted Person và việc truy cập sản phẩm này là hợp pháp trong khu vực tài phán của bạn.", + "rejectButtonLabel": "Tôi từ chối và rời đi", + "title": "Thông báo quan trọng" + }, "healthFactor": { "label": { "healthy": "An toàn", diff --git a/apps/evm/src/libs/translations/translations/zh-Hans.json b/apps/evm/src/libs/translations/translations/zh-Hans.json index 230d9ce1df..e5e1d52092 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hans.json +++ b/apps/evm/src/libs/translations/translations/zh-Hans.json @@ -273,6 +273,7 @@ } }, "assetAccessor": { + "assetNotAvailable": "此资产在您所在的国家/地区不可用", "disabledActionNotice": { "boost": "该市场已禁用 Boost", "borrow": "该市场已禁用借款", @@ -596,6 +597,12 @@ "enabled": "该网络上的交易免 gas 且无需费用。更多信息 这里" } }, + "gatedAssetAcknowledgementModal": { + "acceptButtonLabel": "我理解并确认", + "description": "bStocks 是由 BTECH Holdings Ltd (ADGM) 发行的代币化证券。此处展示的信息仅供参考,不构成任何要约、招揽或投资建议。bStocks 不面向美国、任何美国人士或任何其他受限制司法管辖区进行分发。投资存在风险,您的投资价值可能下跌,且您可能无法收回全部投资金额。有关完整的风险披露、法律文件以及受限制司法管辖区名单,请访问 bstocks.finance/prospectus。继续操作即表示您确认自己不是受限制人士,并且在您所在的司法管辖区访问此产品是合法的。", + "rejectButtonLabel": "我拒绝并离开", + "title": "重要提示" + }, "healthFactor": { "label": { "healthy": "健康", diff --git a/apps/evm/src/libs/translations/translations/zh-Hant.json b/apps/evm/src/libs/translations/translations/zh-Hant.json index dcf119bfcf..437ec1f251 100644 --- a/apps/evm/src/libs/translations/translations/zh-Hant.json +++ b/apps/evm/src/libs/translations/translations/zh-Hant.json @@ -273,6 +273,7 @@ } }, "assetAccessor": { + "assetNotAvailable": "此資產在您所在的國家/地區不可用", "disabledActionNotice": { "boost": "該市場已禁用 Boost", "borrow": "該市場已禁用借款", @@ -596,6 +597,12 @@ "enabled": "該網絡上的交易免 gas 且無需費用。更多信息 這裡" } }, + "gatedAssetAcknowledgementModal": { + "acceptButtonLabel": "我理解並確認", + "description": "bStocks 是由 BTECH Holdings Ltd (ADGM) 發行的代幣化證券。此處展示的資訊僅供參考,不構成任何要約、招攬或投資建議。bStocks 並不擬在美國、向任何美國人士或任何其他受限制司法管轄區分發。投資涉及風險,您的投資價值可能下跌,且您可能無法收回全部投資金額。關於完整的風險披露、法律文件以及受限制司法管轄區名單,請造訪 bstocks.finance/prospectus。繼續操作即表示您確認自己並非受限制人士,且在您所在的司法管轄區存取此產品屬合法行為。", + "rejectButtonLabel": "我拒絕並離開", + "title": "重要提示" + }, "healthFactor": { "label": { "healthy": "健康", diff --git a/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx index 147726e70d..b1fef6de3a 100644 --- a/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/BoostForm/index.tsx @@ -9,8 +9,6 @@ import { Icon, LabeledInlineContent, LabeledSlider, - type OptionalTokenBalance, - TokenListWrapper, TokenTextField, } from 'components'; import { NULL_ADDRESS } from 'constants/address'; @@ -21,6 +19,7 @@ import { } from 'constants/swap'; import { ConnectWallet } from 'containers/ConnectWallet'; import { SwapDetails } from 'containers/SwapDetails'; +import { type OptionalTokenBalance, TokenListWrapper } from 'containers/TokenListWrapper'; import useDebounceValue from 'hooks/useDebounceValue'; import { useGetContractAddress } from 'hooks/useGetContractAddress'; import { useGetUserSlippageTolerance } from 'hooks/useGetUserSlippageTolerance'; @@ -77,14 +76,16 @@ const BoostForm: React.FC = ({ asset: borrowedAsset, pool }) => asset.vToken.symbol === 'vBNB' || // Skip tokens that have reached their supply cap asset.supplyBalanceTokens.isGreaterThanOrEqualTo(asset.supplyCapTokens) || - // Skip paused tokens - asset.disabledTokenActions.includes('supply') + // Skip paused and restricted tokens + asset.disabledTokenActions.includes('supply') || + asset.isRestricted ) { return acc; } const tokenBalance: OptionalTokenBalance = { token: asset.vToken.underlyingToken, + isGated: asset.isGated, }; return [...acc, tokenBalance]; diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx index 7fbfb365d3..32b7f1096d 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/__tests__/index.spec.tsx @@ -23,6 +23,7 @@ import { MAXIMUM_PRICE_IMPACT_THRESHOLD_PERCENTAGE, } from 'constants/swap'; import { useSimulateBalanceMutations } from 'hooks/useSimulateBalanceMutations'; +import { defaultUserChainSettings, useUserChainSettings } from 'hooks/useUserChainSettings'; import { VError } from 'libs/errors'; import { en } from 'libs/translations'; import { @@ -83,6 +84,14 @@ const getLastUseGetSwapQuoteCallArgs = () => describe('RepayWithCollateralForm', () => { beforeEach(() => { + (useUserChainSettings as Mock).mockReturnValue([ + { + ...defaultUserChainSettings, + doNotShowGatedAssetModal: true, + }, + vi.fn(), + ]); + (useRepayWithCollateral as Mock).mockImplementation(() => ({ mutateAsync: mockRepayWithCollateral, })); diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx index 12f0c2dba1..3bdc34c9a0 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithCollateralForm/index.tsx @@ -12,7 +12,6 @@ import { AcknowledgementToggle, Icon, LabeledInlineContent, - type OptionalTokenBalance, SelectTokenTextField, TokenTextField, } from 'components'; @@ -24,6 +23,7 @@ import { } from 'constants/swap'; import { ConnectWallet } from 'containers/ConnectWallet'; import { SwapDetails } from 'containers/SwapDetails'; +import type { OptionalTokenBalance } from 'containers/TokenListWrapper'; import useDebounceValue from 'hooks/useDebounceValue'; import { useGetContractAddress } from 'hooks/useGetContractAddress'; import { useGetUserSlippageTolerance } from 'hooks/useGetUserSlippageTolerance'; @@ -78,7 +78,9 @@ export const RepayWithCollateralForm: React.FC = ( // Skip vBNB asset.vToken.symbol === 'vBNB' || // Skip tokens for which user has no supply - asset.userSupplyBalanceCents.isEqualTo(0) + asset.userSupplyBalanceCents.isEqualTo(0) || + // Skip restricted assets + asset.isRestricted ) { return acc; } @@ -89,6 +91,7 @@ export const RepayWithCollateralForm: React.FC = ( value: asset.userSupplyBalanceTokens, token: asset.vToken.underlyingToken, }), + isGated: asset.isGated, }; return [...acc, tokenBalance]; diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/__tests__/indexIntegratedSwap.spec.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/__tests__/indexIntegratedSwap.spec.tsx index 94d0d5fd7a..4ad5a68579 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/__tests__/indexIntegratedSwap.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/__tests__/indexIntegratedSwap.spec.tsx @@ -54,6 +54,8 @@ const makeIntegratedSwapPool = ( fakeAsset, { ...fakeBusdAsset, + isRestricted: false, + isGated: false, userWalletBalanceTokens: busdBalanceTokens, }, ]); diff --git a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/index.tsx index 8601277825..356757d11a 100644 --- a/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/Repay/RepayWithWalletBalanceForm/index.tsx @@ -277,7 +277,7 @@ const RepayWithWalletBalanceForm: React.FC = ({ ); fromTokenUserWalletBalanceTokens = - fromTokenUserWalletBalanceMantissa && + fromTokenUserWalletBalanceMantissa?.balanceMantissa && convertMantissaToTokens({ value: fromTokenUserWalletBalanceMantissa.balanceMantissa, token: fromTokenUserWalletBalanceMantissa.token, diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx b/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx index 9f12e7c680..618834bbb1 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/__tests__/indexIntegratedSwap.spec.tsx @@ -78,6 +78,8 @@ const makeIntegratedSwapPool = ( fakeAsset, { ...fakeBusdAsset, + isRestricted: false, + isGated: false, userWalletBalanceTokens: busdBalanceTokens, disabledTokenActions: [], }, @@ -246,6 +248,8 @@ describe('SupplyForm - Feature flag enabled: integratedSwap', () => { ...customFakePool.assets.find(poolAsset => areTokensEqual(poolAsset.vToken.underlyingToken, busd), )!, + isRestricted: false, + isGated: false, userWalletBalanceTokens: new BigNumber(FAKE_BUSD_BALANCE_TOKENS), disabledTokenActions: [], }, diff --git a/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx b/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx index cda73178b7..a8ab4724f1 100644 --- a/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx +++ b/apps/evm/src/pages/Market/OperationForm/SupplyForm/index.tsx @@ -237,7 +237,7 @@ const SupplyForm: React.FC = ({ asset, pool }) => { ); return ( - tokenBalance && + tokenBalance?.balanceMantissa && convertMantissaToTokens({ value: tokenBalance.balanceMantissa, token: tokenBalance.token, diff --git a/apps/evm/src/pages/Market/OperationForm/useGetOperationFormTokenBalances/__tests__/index.spec.ts b/apps/evm/src/pages/Market/OperationForm/useGetOperationFormTokenBalances/__tests__/index.spec.ts new file mode 100644 index 0000000000..ab2025988a --- /dev/null +++ b/apps/evm/src/pages/Market/OperationForm/useGetOperationFormTokenBalances/__tests__/index.spec.ts @@ -0,0 +1,77 @@ +import BigNumber from 'bignumber.js'; +import type { Mock } from 'vitest'; + +import fakeAccountAddress from '__mocks__/models/address'; +import { assetData } from '__mocks__/models/asset'; +import { useGetPool } from 'clients/api'; +import { renderHook } from 'testUtils/render'; +import { useGetOperationFormTokenBalances } from '..'; + +vi.mock('clients/api', () => ({ + useGetPool: vi.fn(), +})); + +describe('useGetOperationFormTokenBalances', () => { + beforeEach(() => { + (useGetPool as Mock).mockReturnValue({ + data: { + pool: { + assets: [], + }, + }, + }); + }); + + it('filters out restricted assets and preserves gated asset metadata', () => { + const allowedAsset = { + ...assetData[0], + disabledTokenActions: [], + isRestricted: false, + isGated: false, + }; + const gatedAsset = { + ...assetData[2], + disabledTokenActions: [], + isRestricted: false, + isGated: true, + }; + const restrictedAsset = { + ...assetData[3], + disabledTokenActions: [], + isRestricted: true, + isGated: false, + }; + + (useGetPool as Mock).mockReturnValue({ + data: { + pool: { + assets: [allowedAsset, gatedAsset, restrictedAsset], + }, + }, + }); + + const { result } = renderHook(() => + useGetOperationFormTokenBalances({ + poolComptrollerContractAddress: fakeAccountAddress, + accountAddress: fakeAccountAddress, + underlyingToken: allowedAsset.vToken.underlyingToken, + isIntegratedSwapFeatureEnabled: true, + canWrapNativeToken: false, + action: 'supply', + }), + ); + + expect(result.current.tokenBalances).toEqual([ + expect.objectContaining({ + token: allowedAsset.vToken.underlyingToken, + isGated: false, + balanceMantissa: expect.any(BigNumber), + }), + expect.objectContaining({ + token: gatedAsset.vToken.underlyingToken, + isGated: true, + balanceMantissa: expect.any(BigNumber), + }), + ]); + }); +}); diff --git a/apps/evm/src/pages/Market/OperationForm/useGetOperationFormTokenBalances/index.ts b/apps/evm/src/pages/Market/OperationForm/useGetOperationFormTokenBalances/index.ts index bc22bdbd96..5518e29630 100644 --- a/apps/evm/src/pages/Market/OperationForm/useGetOperationFormTokenBalances/index.ts +++ b/apps/evm/src/pages/Market/OperationForm/useGetOperationFormTokenBalances/index.ts @@ -1,7 +1,8 @@ import type BigNumber from 'bignumber.js'; import { useGetPool } from 'clients/api'; +import type { OptionalTokenBalance } from 'containers/TokenListWrapper'; -import type { Token, TokenAction, TokenBalance } from 'types'; +import type { Token, TokenAction } from 'types'; import { areTokensEqual, convertTokensToMantissa } from 'utilities'; import type { Address } from 'viem'; @@ -15,7 +16,7 @@ export interface UseGetOperationFormTokenBalancesInput { } export interface UseGetOperationFormTokenBalancesOutput { - tokenBalances: TokenBalance[]; + tokenBalances: OptionalTokenBalance[]; userWalletNativeTokenBalanceTokens?: BigNumber; } @@ -37,7 +38,7 @@ export const useGetOperationFormTokenBalances = ({ const wrappedUnderlyingToken = underlyingToken.tokenWrapped; let userWalletNativeTokenBalanceTokens: BigNumber | undefined; - const tokenBalances: TokenBalance[] = []; + const tokenBalances: OptionalTokenBalance[] = []; assets.forEach(asset => { const isWrappedToken = !!asset.vToken.underlyingToken.tokenWrapped; @@ -52,13 +53,14 @@ export const useGetOperationFormTokenBalances = ({ const isPaused = asset.disabledTokenActions.includes(action); - if (shouldIncludeTokenBalance && !isPaused) { + if (shouldIncludeTokenBalance && !isPaused && !asset.isRestricted) { tokenBalances.push({ token: asset.vToken.underlyingToken, balanceMantissa: convertTokensToMantissa({ token: asset.vToken.underlyingToken, value: asset.userWalletBalanceTokens, }), + isGated: asset.isGated, }); } diff --git a/apps/evm/src/pages/Market/__tests__/index.spec.tsx b/apps/evm/src/pages/Market/__tests__/index.spec.tsx index 9227a913fb..1aa3f5178c 100644 --- a/apps/evm/src/pages/Market/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Market/__tests__/index.spec.tsx @@ -1,11 +1,16 @@ -import { waitFor } from '@testing-library/react'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; import type { Mock } from 'vitest'; import { poolData } from '__mocks__/models/pools'; import { useGetAsset, useGetPool } from 'clients/api'; +import { routes } from 'constants/routing'; +import { defaultUserChainSettings, useUserChainSettings } from 'hooks/useUserChainSettings'; +import { en } from 'libs/translations'; import { renderComponent } from 'testUtils/render'; import Market from '..'; +const mockNavigate = vi.fn(); + vi.mock('react-router', async importOriginal => { const actual: Record = await importOriginal(); @@ -18,8 +23,16 @@ vi.mock('react-router', async importOriginal => { }; }); +vi.mock('hooks/useNavigate', () => ({ + useNavigate: () => ({ + navigate: mockNavigate, + }), +})); + describe('Market', () => { beforeEach(() => { + (useUserChainSettings as Mock).mockReturnValue([defaultUserChainSettings, vi.fn()]); + (useGetPool as Mock).mockImplementation(() => ({ isLoading: false, data: { @@ -42,4 +55,34 @@ describe('Market', () => { expect(container.textContent).toMatchSnapshot(); }); + + it('does not display the acknowledgement modal for non-gated assets', async () => { + renderComponent(); + + await waitFor(() => + expect(screen.queryByText(en.gatedAssetAcknowledgementModal.title)).toBeNull(), + ); + }); + + it('displays the acknowledgement modal for gated assets and navigates away on reject', async () => { + (useGetAsset as Mock).mockImplementation(() => ({ + isLoading: false, + data: { + asset: { + ...poolData[0].assets[0], + isGated: true, + }, + }, + })); + + renderComponent(); + + fireEvent.click( + await screen.findByRole('button', { + name: en.gatedAssetAcknowledgementModal.rejectButtonLabel, + }), + ); + + expect(mockNavigate).toHaveBeenCalledWith(routes.landing.path); + }); }); diff --git a/apps/evm/src/pages/Market/index.tsx b/apps/evm/src/pages/Market/index.tsx index 27ceb3e375..2316e17cd7 100644 --- a/apps/evm/src/pages/Market/index.tsx +++ b/apps/evm/src/pages/Market/index.tsx @@ -2,7 +2,10 @@ import { Page as PageComp } from 'components'; import { useParams } from 'react-router'; import type { Address } from 'viem'; +import { routes } from 'constants/routing'; +import { GatedAssetAcknowledgementModal } from 'containers/GatedAssetAcknowledgementModal'; import MarketLoader from 'containers/MarketLoader'; +import { useNavigate } from 'hooks/useNavigate'; import AssetWarning from './AssetWarning'; import { EModeInfo } from './EModeInfo'; import { InterestRateChart } from './InterestRateChart'; @@ -16,6 +19,8 @@ const Page: React.FC = () => { poolComptrollerAddress: Address; }>(); + const { navigate } = useNavigate(); + return ( @@ -55,6 +60,10 @@ const Page: React.FC = () => {
+ + {asset.isGated && ( + navigate(routes.landing.path)} /> + )} )} diff --git a/apps/evm/src/pages/Swap/index.tsx b/apps/evm/src/pages/Swap/index.tsx index 23eb053cb7..a53fa0de0c 100644 --- a/apps/evm/src/pages/Swap/index.tsx +++ b/apps/evm/src/pages/Swap/index.tsx @@ -20,11 +20,12 @@ import { VError, handleError } from 'libs/errors'; import { useGetToken, useGetTokens } from 'libs/tokens'; import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; -import type { Swap, SwapError, TokenBalance } from 'types'; +import type { Swap, SwapError } from 'types'; import { areTokensEqual, convertMantissaToTokens } from 'utilities'; import { SwapDetails } from './SwapDetails'; import { NULL_ADDRESS } from 'constants/address'; +import type { OptionalTokenBalance } from 'containers/TokenListWrapper'; import { useGetContractAddress } from 'hooks/useGetContractAddress'; import Notice from './Notice'; import SubmitSection, { type SubmitSectionProps } from './SubmitSection'; @@ -45,7 +46,7 @@ export interface SwapPageUiProps setFormValues: (setter: (currentFormValues: FormValues) => FormValues) => void; onSubmit: (swap: Swap) => Promise; isSubmitting: boolean; - tokenBalances: TokenBalance[]; + tokenBalances: OptionalTokenBalance[]; isSwapLoading: boolean; revokeFromTokenWalletSpendingLimit: () => Promise; isRevokeFromTokenWalletSpendingLimitLoading: boolean; diff --git a/apps/evm/src/pages/Trade/OperationForm/OpenForm/__tests__/index.spec.tsx b/apps/evm/src/pages/Trade/OperationForm/OpenForm/__tests__/index.spec.tsx index 3980202b4e..8f5300c9ec 100644 --- a/apps/evm/src/pages/Trade/OperationForm/OpenForm/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Trade/OperationForm/OpenForm/__tests__/index.spec.tsx @@ -23,10 +23,21 @@ import { OpenForm } from '..'; const longAsset = poolData[0].assets[2]; const shortAsset = poolData[0].assets[3]; const dsaAsset = poolData[0].assets[0]; +const pool = { + ...poolData[0], + assets: poolData[0].assets.map(asset => + asset.vToken.address === shortAsset.vToken.address + ? { + ...asset, + isRestricted: false, + } + : asset, + ), +}; const createProtectedPricePool = (): Pool => ({ - ...poolData[0], - assets: poolData[0].assets.map(asset => { + ...pool, + assets: pool.assets.map(asset => { if (asset.vToken.address === dsaAsset.vToken.address) { return { ...asset, @@ -137,7 +148,7 @@ describe('OpenForm', () => { } = {}) => { mockUseGetPool.mockImplementation(() => ({ data: { - pool: poolData[0], + pool, }, isLoading: false, })); diff --git a/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap index 705d2b0138..52b6472b69 100644 --- a/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/pages/Trade/OperationForm/OpenForm/useGetNewTradePosition/__tests__/__snapshots__/index.spec.ts.snap @@ -41,7 +41,9 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -169,7 +171,9 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -286,7 +290,9 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -412,7 +418,9 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -554,7 +562,9 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -688,7 +698,9 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -1054,7 +1066,9 @@ exports[`useGetNewTradePosition > returns a new base position built from the cor "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", diff --git a/apps/evm/src/pages/Trade/OperationForm/index.tsx b/apps/evm/src/pages/Trade/OperationForm/index.tsx index d77c108aaf..ee535edaf8 100644 --- a/apps/evm/src/pages/Trade/OperationForm/index.tsx +++ b/apps/evm/src/pages/Trade/OperationForm/index.tsx @@ -10,7 +10,7 @@ export const OperationForm: React.FC = () => { const isLoading = isGetSelectedPositionLoading; if (isLoading) { - return ; + return ; } if (selectedPosition) { diff --git a/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx b/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx index 67c0e9a2cb..0166ee7644 100644 --- a/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx +++ b/apps/evm/src/pages/Trade/PairInfo/TokenSelect/index.tsx @@ -1,6 +1,7 @@ import { SelectButton, cn } from '@venusprotocol/ui'; -import { Icon, type OptionalTokenBalance, TokenIconWithSymbol, TokenListWrapper } from 'components'; +import { Icon, TokenIconWithSymbol } from 'components'; +import { type OptionalTokenBalance, TokenListWrapper } from 'containers/TokenListWrapper'; import { useTranslation } from 'libs/translations'; import { useState } from 'react'; import type { Token } from 'types'; diff --git a/apps/evm/src/pages/Trade/PairInfo/__tests__/index.spec.tsx b/apps/evm/src/pages/Trade/PairInfo/__tests__/index.spec.tsx index 9bcb377802..ed4323dd4a 100644 --- a/apps/evm/src/pages/Trade/PairInfo/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Trade/PairInfo/__tests__/index.spec.tsx @@ -3,7 +3,8 @@ import { useSearchParams } from 'react-router'; import type { Mock } from 'vitest'; import { poolData } from '__mocks__/models/pools'; -import { getTokenListItemTestId } from 'components/TokenListWrapper/testIdGetters'; +import { getTokenListItemTestId } from 'containers/TokenListWrapper/getTokenListItemTestId'; +import { defaultUserChainSettings, useUserChainSettings } from 'hooks/useUserChainSettings'; import { t } from 'libs/translations'; import { renderComponent } from 'testUtils/render'; import type { Asset, Token } from 'types'; @@ -37,11 +38,17 @@ const defaultLongAsset = poolData[0].assets[2]; const defaultShortAsset = poolData[0].assets[3]; const alternativeLongAsset = poolData[0].assets[1]; const alternativeShortAsset = poolData[0].assets[0]; +const nonGatedAssets = poolData[0].assets.map(asset => ({ + ...asset, + isGated: false, + isRestricted: false, +})); const defaultLongToken = defaultLongAsset.vToken.underlyingToken; const defaultShortToken = defaultShortAsset.vToken.underlyingToken; const mockSetSearchParams = vi.fn(); +const mockSetUserChainSettings = vi.fn(); const setSearchParamsState = ({ searchParams = new URLSearchParams(), @@ -55,8 +62,8 @@ const setSearchParamsState = ({ const setComponentState = ({ longToken = defaultLongToken, shortToken = defaultShortToken, - supplyAssets = poolData[0].assets, - borrowAssets = poolData[0].assets, + supplyAssets = nonGatedAssets, + borrowAssets = nonGatedAssets, searchParams = new URLSearchParams({ foo: 'bar', [LONG_TOKEN_ADDRESS_PARAM_KEY]: longToken.address, @@ -142,6 +149,14 @@ const expectUpdatedSearchParams = async ( describe('PairInfo', () => { beforeEach(() => { + mockSetSearchParams.mockReset(); + mockSetUserChainSettings.mockReset(); + + (useUserChainSettings as Mock).mockReturnValue([ + defaultUserChainSettings, + mockSetUserChainSettings, + ]); + setComponentState(); }); @@ -221,4 +236,27 @@ describe('PairInfo', () => { [SHORT_TOKEN_ADDRESS_PARAM_KEY]: defaultLongToken.address, }); }); + + it('opens the gated acknowledgement modal before updating search params for a gated token', async () => { + setComponentState({ + supplyAssets: poolData[0].assets.map(asset => + asset.vToken.address === alternativeLongAsset.vToken.address + ? { + ...asset, + isGated: true, + } + : asset, + ), + }); + + renderPairInfo(); + + selectPairInfoToken({ + type: 'long', + token: alternativeLongAsset.vToken.underlyingToken, + }); + + expect(await screen.findByText(t('gatedAssetAcknowledgementModal.title'))).toBeInTheDocument(); + expect(mockSetSearchParams).not.toHaveBeenCalled(); + }); }); diff --git a/apps/evm/src/pages/Trade/PairInfo/index.tsx b/apps/evm/src/pages/Trade/PairInfo/index.tsx index 92222a0085..a6afda8e80 100644 --- a/apps/evm/src/pages/Trade/PairInfo/index.tsx +++ b/apps/evm/src/pages/Trade/PairInfo/index.tsx @@ -2,8 +2,9 @@ import { cn } from '@venusprotocol/ui'; import BigNumber from 'bignumber.js'; import { useSearchParams } from 'react-router'; -import { Apy, CellGroup, type CellProps, Icon, type OptionalTokenBalance } from 'components'; +import { Apy, CellGroup, type CellProps, Icon } from 'components'; import { PLACEHOLDER_KEY } from 'constants/placeholders'; +import type { OptionalTokenBalance } from 'containers/TokenListWrapper'; import { useTranslation } from 'libs/translations'; import type { Asset, Token } from 'types'; import { @@ -75,6 +76,7 @@ export const PairInfo: React.FC = ({ changePercentage, priceCents const tokenBalance: OptionalTokenBalance = { token: asset.vToken.underlyingToken, isDeemed: asset.disabledTokenActions.includes('supply'), + isGated: asset.isGated, }; return { @@ -100,6 +102,7 @@ export const PairInfo: React.FC = ({ changePercentage, priceCents const tokenBalance: OptionalTokenBalance = { token: asset.vToken.underlyingToken, isDeemed: asset.disabledTokenActions.includes('borrow') || !asset.isBorrowable, + isGated: asset.isGated, }; return { diff --git a/apps/evm/src/pages/Trade/PositionForm/Form/SelectDsaTokenTextField/index.tsx b/apps/evm/src/pages/Trade/PositionForm/Form/SelectDsaTokenTextField/index.tsx index 78c6dea72b..9b52e8d26c 100644 --- a/apps/evm/src/pages/Trade/PositionForm/Form/SelectDsaTokenTextField/index.tsx +++ b/apps/evm/src/pages/Trade/PositionForm/Form/SelectDsaTokenTextField/index.tsx @@ -1,11 +1,7 @@ import BigNumber from 'bignumber.js'; -import { - Button, - type OptionalTokenBalance, - SelectTokenTextField, - type SelectTokenTextFieldProps, -} from 'components'; +import { Button, SelectTokenTextField, type SelectTokenTextFieldProps } from 'components'; +import type { OptionalTokenBalance } from 'containers/TokenListWrapper'; import { useTranslation } from 'libs/translations'; import { useAccountAddress } from 'libs/wallet'; import { useGetTradeAssets } from 'pages/Trade/useGetTradeAssets'; @@ -52,13 +48,23 @@ export const SelectDsaTokenTextField: React.FC = ( accountAddress, }); - const tokenBalances = dsaAssets.map(asset => ({ - token: asset.vToken.underlyingToken, - balanceMantissa: convertTokensToMantissa({ - value: asset.userWalletBalanceTokens, + const tokenBalances = dsaAssets.reduce((acc, asset) => { + // Filter out restricted assets + if (asset.isRestricted) { + return acc; + } + + const tokenBalance: OptionalTokenBalance = { token: asset.vToken.underlyingToken, - }), - })); + balanceMantissa: convertTokensToMantissa({ + value: asset.userWalletBalanceTokens, + token: asset.vToken.underlyingToken, + }), + isGated: asset.isGated, + }; + + return [...acc, tokenBalance]; + }, []); return ( <> diff --git a/apps/evm/src/pages/Trade/__tests__/index.spec.tsx b/apps/evm/src/pages/Trade/__tests__/index.spec.tsx index fe97c2d0a9..d241c68483 100644 --- a/apps/evm/src/pages/Trade/__tests__/index.spec.tsx +++ b/apps/evm/src/pages/Trade/__tests__/index.spec.tsx @@ -69,6 +69,10 @@ vi.mock('../ClosePositionModal', () => ({ ClosePositionModal: () =>
, })); +vi.mock('containers/GatedAssetAcknowledgementModal', () => ({ + GatedAssetAcknowledgementModal: () =>
, +})); + const longAsset = poolData[0].assets[2]; const shortAsset = poolData[0].assets[3]; const otherAsset = poolData[0].assets[1]; diff --git a/apps/evm/src/pages/Trade/index.tsx b/apps/evm/src/pages/Trade/index.tsx index 0b55d7a513..c10a4f8bb1 100644 --- a/apps/evm/src/pages/Trade/index.tsx +++ b/apps/evm/src/pages/Trade/index.tsx @@ -2,7 +2,10 @@ import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router'; import { Card, KLineChart, Page, Spinner } from 'components'; +import { routes } from 'constants/routing'; import { ONE_DAY_MS } from 'constants/time'; +import { GatedAssetAcknowledgementModal } from 'containers/GatedAssetAcknowledgementModal'; +import { useNavigate } from 'hooks/useNavigate'; import { ApiOhlcInterval } from 'types'; import { areAddressesEqual } from 'utilities'; import { Banner } from './Banner'; @@ -18,6 +21,7 @@ import { useTokenPair } from './useTokenPair'; const Trade: React.FC = () => { const [searchParams, setSearchParams] = useSearchParams(); + const { navigate } = useNavigate(); const { shortToken, longToken, defaultLongToken, defaultShortToken } = useTokenPair(); const shortTokenAddressParam = searchParams.get(SHORT_TOKEN_ADDRESS_PARAM_KEY); @@ -60,7 +64,7 @@ const Trade: React.FC = () => { const firstNonZeroIndex = priceCentsRatio ?.toFixed() .split('.')[1] - .match(/[1-9]\d*/)?.index; + ?.match(/[1-9]\d*/)?.index; pricePrecision = firstNonZeroIndex && firstNonZeroIndex > 1 ? firstNonZeroIndex + 5 : pricePrecision; @@ -131,7 +135,7 @@ const Trade: React.FC = () => {
{!doNotShowBanner && } - + @@ -141,6 +145,10 @@ const Trade: React.FC = () => {
)} + + {(shortAsset?.isGated || longAsset?.isGated) && ( + navigate(routes.trade.path)} /> + )} ); }; diff --git a/apps/evm/src/pages/Trade/useGetTradeAssets/__tests__/index.spec.ts b/apps/evm/src/pages/Trade/useGetTradeAssets/__tests__/index.spec.ts index b7c8c58453..c2824855e1 100644 --- a/apps/evm/src/pages/Trade/useGetTradeAssets/__tests__/index.spec.ts +++ b/apps/evm/src/pages/Trade/useGetTradeAssets/__tests__/index.spec.ts @@ -167,4 +167,36 @@ describe('useGetTradeAssets', () => { dsaAssets: [], }); }); + + it('filters restricted assets out of borrow, supply, and DSA lists', () => { + const restrictedAsset = { + ...poolData[0].assets[0], + isRestricted: true, + }; + const unrestrictedAsset = poolData[0].assets[2]; + const poolWithRestrictedAsset = { + ...poolData[0], + assets: [restrictedAsset, unrestrictedAsset], + }; + + (useGetPool as Mock).mockImplementation(() => ({ + isLoading: false, + data: { + pool: poolWithRestrictedAsset, + }, + })); + + (useGetDsaVTokens as Mock).mockImplementation(() => ({ + isLoading: false, + data: { + dsaVTokenAddresses: poolWithRestrictedAsset.assets.map(asset => asset.vToken.address), + }, + })); + + const { result } = renderHook(() => useGetTradeAssets()); + + expect(result.current.data.borrowAssets).toEqual([unrestrictedAsset]); + expect(result.current.data.supplyAssets).toEqual([unrestrictedAsset]); + expect(result.current.data.dsaAssets).toEqual([unrestrictedAsset]); + }); }); diff --git a/apps/evm/src/pages/Trade/useGetTradeAssets/index.ts b/apps/evm/src/pages/Trade/useGetTradeAssets/index.ts index ac2b34436d..fa587b99ef 100644 --- a/apps/evm/src/pages/Trade/useGetTradeAssets/index.ts +++ b/apps/evm/src/pages/Trade/useGetTradeAssets/index.ts @@ -26,7 +26,7 @@ export const useGetTradeAssets = (input?: { accountAddress?: Address }) => { !isGetPoolLoading && !isGetDsaVTokensLoading && !!corePool && !!dsaVTokenAddresses; const assets = isDataReady - ? corePool.assets.filter(asset => !asset.vToken.underlyingToken.isNative) + ? corePool.assets.filter(asset => !asset.vToken.underlyingToken.isNative && !asset.isRestricted) : []; const dsaAssets = isDataReady diff --git a/apps/evm/src/store/index.ts b/apps/evm/src/store/index.ts index 927c0cdd95..6d3ee1fc19 100644 --- a/apps/evm/src/store/index.ts +++ b/apps/evm/src/store/index.ts @@ -19,6 +19,7 @@ export interface UserChainSettings { doNotShowUserBalances: boolean; doNotExpandGuide: boolean; doNotShowFixedRateVaultsAdBanner: boolean; + doNotShowGatedAssetModal: boolean; } type UserSettings = Partial>>; diff --git a/apps/evm/src/types/index.ts b/apps/evm/src/types/index.ts index 23d1f913b7..60b9f6a8c4 100644 --- a/apps/evm/src/types/index.ts +++ b/apps/evm/src/types/index.ts @@ -151,6 +151,8 @@ export interface Asset { disabledTokenActions: TokenAction[]; borrowCapTokens: BigNumber; supplyCapTokens: BigNumber; + isRestricted: boolean; + isGated: boolean; // User-specific props // TODO: make these optional so they can be set to undefined when no wallet is // connected @@ -786,3 +788,15 @@ export enum ApiOhlcInterval { '4h' = '4h', '1d' = '1d', } + +export interface ApiTokenPrice { + tokenWrappedAddress: Address | null; + priceMantissa: string; + priceSource: 'oracle' | 'merkl' | 'coingecko'; + priceOracleAddress: Address | null; + isPriceInvalid: boolean; + hasErrorFetchingPrice: boolean; + isPriceProtected: boolean; + supplyPriceMantissa: string | null; + borrowPriceMantissa: string | null; +} diff --git a/apps/evm/src/utilities/addUserBorrowLimitShares/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/utilities/addUserBorrowLimitShares/__tests__/__snapshots__/index.spec.ts.snap index 1ff93143ea..50d0ca5b2a 100644 --- a/apps/evm/src/utilities/addUserBorrowLimitShares/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/utilities/addUserBorrowLimitShares/__tests__/__snapshots__/index.spec.ts.snap @@ -41,7 +41,9 @@ exports[`addUserBorrowLimitShares > adds userBorrowLimitSharePercentage property "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -167,7 +169,9 @@ exports[`addUserBorrowLimitShares > adds userBorrowLimitSharePercentage property "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -309,7 +313,9 @@ exports[`addUserBorrowLimitShares > adds userBorrowLimitSharePercentage property "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -443,7 +449,9 @@ exports[`addUserBorrowLimitShares > adds userBorrowLimitSharePercentage property "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", diff --git a/apps/evm/src/utilities/formatToTradePosition/__tests__/__snapshots__/index.spec.ts.snap b/apps/evm/src/utilities/formatToTradePosition/__tests__/__snapshots__/index.spec.ts.snap index ee9341508b..8c8b88265d 100644 --- a/apps/evm/src/utilities/formatToTradePosition/__tests__/__snapshots__/index.spec.ts.snap +++ b/apps/evm/src/utilities/formatToTradePosition/__tests__/__snapshots__/index.spec.ts.snap @@ -65,7 +65,9 @@ exports[`formatToTradePosition > deducts the DSA balance from the long balance a "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -219,7 +221,9 @@ exports[`formatToTradePosition > deducts the DSA balance from the long balance a "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -348,7 +352,9 @@ exports[`formatToTradePosition > deducts the DSA balance from the long balance a "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -474,7 +480,9 @@ exports[`formatToTradePosition > deducts the DSA balance from the long balance a "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -616,7 +624,9 @@ exports[`formatToTradePosition > deducts the DSA balance from the long balance a "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -750,7 +760,9 @@ exports[`formatToTradePosition > deducts the DSA balance from the long balance a "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -1111,7 +1123,9 @@ exports[`formatToTradePosition > deducts the DSA balance from the long balance a "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -1232,7 +1246,9 @@ exports[`formatToTradePosition > returns the position in the expected format 1`] "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -1360,7 +1376,9 @@ exports[`formatToTradePosition > returns the position in the expected format 1`] "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -1477,7 +1495,9 @@ exports[`formatToTradePosition > returns the position in the expected format 1`] "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -1603,7 +1623,9 @@ exports[`formatToTradePosition > returns the position in the expected format 1`] "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -1745,7 +1767,9 @@ exports[`formatToTradePosition > returns the position in the expected format 1`] "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -1879,7 +1903,9 @@ exports[`formatToTradePosition > returns the position in the expected format 1`] "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -2244,7 +2270,9 @@ exports[`formatToTradePosition > returns the position in the expected format 1`] "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -2408,7 +2436,9 @@ exports[`formatToTradePosition > returns zero for derived values when the positi "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -2569,7 +2599,9 @@ exports[`formatToTradePosition > returns zero for derived values when the positi "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -2706,7 +2738,9 @@ exports[`formatToTradePosition > returns zero for derived values when the positi "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -2832,7 +2866,9 @@ exports[`formatToTradePosition > returns zero for derived values when the positi "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -2974,7 +3010,9 @@ exports[`formatToTradePosition > returns zero for derived values when the positi "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -3108,7 +3146,9 @@ exports[`formatToTradePosition > returns zero for derived values when the positi "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -3469,7 +3509,9 @@ exports[`formatToTradePosition > returns zero for derived values when the positi "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -3614,7 +3656,9 @@ exports[`formatToTradePosition > uses the combined discounted DSA and long balan "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -3768,7 +3812,9 @@ exports[`formatToTradePosition > uses the combined discounted DSA and long balan "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -3897,7 +3943,9 @@ exports[`formatToTradePosition > uses the combined discounted DSA and long balan "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 50, "liquidityCents": "8036465875", @@ -4023,7 +4071,9 @@ exports[`formatToTradePosition > uses the combined discounted DSA and long balan "isBorrowable": false, "isBorrowableByUser": false, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "1702951959", @@ -4165,7 +4215,9 @@ exports[`formatToTradePosition > uses the combined discounted DSA and long balan "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", @@ -4299,7 +4351,9 @@ exports[`formatToTradePosition > uses the combined discounted DSA and long balan "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": false, + "isGated": false, "isProtectionModeEnabled": false, + "isRestricted": true, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "3654492935", @@ -4660,7 +4714,9 @@ exports[`formatToTradePosition > uses the combined discounted DSA and long balan "isBorrowable": true, "isBorrowableByUser": true, "isCollateralOfUser": true, + "isGated": true, "isProtectionModeEnabled": false, + "isRestricted": false, "liquidationPenaltyPercentage": 4, "liquidationThresholdPercentage": 80, "liquidityCents": "5534102886", diff --git a/packages/chains/src/images/tokens/nvdab.svg b/packages/chains/src/images/tokens/nvdab.svg new file mode 100644 index 0000000000..09ad2ab824 --- /dev/null +++ b/packages/chains/src/images/tokens/nvdab.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/chains/src/images/tokens/spcxb.svg b/packages/chains/src/images/tokens/spcxb.svg new file mode 100644 index 0000000000..f98b889416 --- /dev/null +++ b/packages/chains/src/images/tokens/spcxb.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/chains/src/images/tokens/tslab.svg b/packages/chains/src/images/tokens/tslab.svg new file mode 100644 index 0000000000..32c66eef08 --- /dev/null +++ b/packages/chains/src/images/tokens/tslab.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/chains/src/tokens/underlyingTokens/bscTestnet.ts b/packages/chains/src/tokens/underlyingTokens/bscTestnet.ts index 45e6dc83f0..1fbae4ce2e 100644 --- a/packages/chains/src/tokens/underlyingTokens/bscTestnet.ts +++ b/packages/chains/src/tokens/underlyingTokens/bscTestnet.ts @@ -467,4 +467,25 @@ export const bscTestnet: Token[] = [ symbol: 'MOCK_WBTC', iconSrc: iconSrcs.wbtc, }, + { + chainId: ChainId.BSC_TESTNET, + address: '0x10d63B1203E5A0719AbbE927C8BFc87135b2F129', + decimals: 18, + symbol: 'TSLAB', + iconSrc: iconSrcs.tslab, + }, + { + chainId: ChainId.BSC_TESTNET, + address: '0x8A7d8589A597619A7842d3BC284b9a5a276FaE56', + decimals: 18, + symbol: 'NVDAB', + iconSrc: iconSrcs.nvdab, + }, + { + chainId: ChainId.BSC_TESTNET, + address: '0x6D9e91cB766259af42619c14c994E694E57e6E85', + decimals: 18, + symbol: 'SPCXB', + iconSrc: iconSrcs.spcxb, + }, ];