From 0b67ecd663ef39f03f5b17550aa3a393991f3b80 Mon Sep 17 00:00:00 2001 From: JustJousting Date: Wed, 3 Dec 2025 17:33:35 +0200 Subject: [PATCH 01/10] feat: add erc4626 check and warning to token selector --- .../PageCreatePool/Summary/OracleSummary.tsx | 17 +-- .../Summary/TokensInPoolSummary.tsx | 11 +- .../TokensInPool/SelectToken.tsx | 49 ++++---- .../PageCreatePool/TokensInPool/SetOracle.tsx | 48 +++---- .../PageCreatePool/TokensInPool/index.tsx | 43 ++++--- .../components/PageCreatePool/constants.ts | 8 ++ .../hooks/useAutoDetectErc4626.ts | 96 ++++++++++++++ .../PageCreatePool/hooks/useIsErc4626.ts | 42 +++++++ .../dex/components/PageCreatePool/types.ts | 6 + .../dex/components/PageCreatePool/utils.ts | 6 +- .../src/dex/store/createCreatePoolSlice.ts | 118 ++++++++++-------- 11 files changed, 306 insertions(+), 138 deletions(-) create mode 100644 apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts create mode 100644 apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts diff --git a/apps/main/src/dex/components/PageCreatePool/Summary/OracleSummary.tsx b/apps/main/src/dex/components/PageCreatePool/Summary/OracleSummary.tsx index 28a90a71f6..4a07fc2719 100644 --- a/apps/main/src/dex/components/PageCreatePool/Summary/OracleSummary.tsx +++ b/apps/main/src/dex/components/PageCreatePool/Summary/OracleSummary.tsx @@ -1,5 +1,6 @@ import { styled } from 'styled-components' import { isAddress } from 'viem' +import { NG_ASSET_TYPE } from '@/dex/components/PageCreatePool/constants' import { CategoryDataRow, SummaryDataTitle, @@ -39,28 +40,28 @@ const OracleSummary = ({ chainId }: Props) => { return ( - {tokenA.ngAssetType === 1 && tokenA.address !== '' && ( + {tokenA.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenA.address !== '' && ( )} - {tokenB.ngAssetType === 1 && tokenB.address !== '' && ( + {tokenB.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenB.address !== '' && ( )} - {tokenC.ngAssetType === 1 && tokenC.address !== '' && ( + {tokenC.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenC.address !== '' && ( )} - {tokenD.ngAssetType === 1 && tokenD.address !== '' && ( + {tokenD.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenD.address !== '' && ( )} - {tokenD.ngAssetType === 1 && tokenE.address !== '' && ( + {tokenD.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenE.address !== '' && ( )} - {tokenD.ngAssetType === 1 && tokenF.address !== '' && ( + {tokenD.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenF.address !== '' && ( )} - {tokenD.ngAssetType === 1 && tokenG.address !== '' && ( + {tokenD.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenG.address !== '' && ( )} - {tokenD.ngAssetType === 1 && tokenH.address !== '' && ( + {tokenD.ngAssetType === NG_ASSET_TYPE.ORACLE && tokenH.address !== '' && ( )} diff --git a/apps/main/src/dex/components/PageCreatePool/Summary/TokensInPoolSummary.tsx b/apps/main/src/dex/components/PageCreatePool/Summary/TokensInPoolSummary.tsx index 59aa85ba12..114953af3a 100644 --- a/apps/main/src/dex/components/PageCreatePool/Summary/TokensInPoolSummary.tsx +++ b/apps/main/src/dex/components/PageCreatePool/Summary/TokensInPoolSummary.tsx @@ -1,6 +1,6 @@ import { styled } from 'styled-components' -import { STABLESWAP } from '@/dex/components/PageCreatePool/constants' import { + STABLESWAP, TOKEN_A, TOKEN_B, TOKEN_C, @@ -9,6 +9,7 @@ import { TOKEN_F, TOKEN_G, TOKEN_H, + NG_ASSET_TYPE, } from '@/dex/components/PageCreatePool/constants' import OracleSummary from '@/dex/components/PageCreatePool/Summary/OracleSummary' import { @@ -208,10 +209,10 @@ const TokenSummary = ({ blockchainId, token, chainId, swapType }: TokenSummary) {swapType === STABLESWAP && network.stableswapFactory && ( - {token.ngAssetType === 0 && t`Standard`} - {token.ngAssetType === 1 && t`Oracle`} - {token.ngAssetType === 2 && t`Rebasing`} - {token.ngAssetType === 3 && t`ERC4626`} + {token.ngAssetType === NG_ASSET_TYPE.STANDARD && t`Standard`} + {token.ngAssetType === NG_ASSET_TYPE.ORACLE && t`Oracle`} + {token.ngAssetType === NG_ASSET_TYPE.REBASING && t`Rebasing`} + {token.ngAssetType === NG_ASSET_TYPE.ERC4626 && t`ERC4626`} )} diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx index cc77c1a1c9..8f85c8c1f5 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx @@ -11,6 +11,7 @@ import { TOKEN_F, TOKEN_G, TOKEN_H, + NG_ASSET_TYPE, } from '@/dex/components/PageCreatePool/constants' import { CreateToken, @@ -27,6 +28,7 @@ import Button from '@ui/Button' import Checkbox from '@ui/Checkbox' import Icon from '@ui/Icon' import { t } from '@ui-kit/lib/i18n' +import WarningBox from '../components/WarningBox' import SelectTokenButton from './SelectTokenButton' type Props = { @@ -116,28 +118,31 @@ const SelectToken = ({ onSelectionChange={(value: Key) => handleInpChange(tokenId, value as string, tokensInPool)} /> {swapType === STABLESWAP && (network.stableswapFactory || network.stableswapFactoryOld) && !token.basePool && ( - - updateNgAssetType(tokenId, 0)} - isDisabled={false} - >{t`Standard`} - updateNgAssetType(tokenId, 1)} - >{t`Oracle`} - updateNgAssetType(tokenId, 2)} - isDisabled={false} - >{t`Rebasing`} - updateNgAssetType(tokenId, 3)} - >{t`ERC4626`} - + <> + + updateNgAssetType(tokenId, NG_ASSET_TYPE.STANDARD)} + >{t`Standard`} + updateNgAssetType(tokenId, NG_ASSET_TYPE.ORACLE)} + >{t`Oracle`} + updateNgAssetType(tokenId, NG_ASSET_TYPE.REBASING)} + >{t`Rebasing`} + updateNgAssetType(tokenId, NG_ASSET_TYPE.ERC4626)} + >{t`ERC4626`} + + {token.erc4626.isErc4626 && token.ngAssetType !== NG_ASSET_TYPE.ERC4626 && ( + + )} + )} ) diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SetOracle.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SetOracle.tsx index 6d3412d014..777339dd87 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SetOracle.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SetOracle.tsx @@ -12,6 +12,7 @@ import { TOKEN_F, TOKEN_G, TOKEN_H, + NG_ASSET_TYPE, } from '@/dex/components/PageCreatePool/constants' import type { TokenState, TokenId } from '@/dex/components/PageCreatePool/types' import { validateOracleFunction } from '@/dex/components/PageCreatePool/utils' @@ -26,41 +27,24 @@ type OracleInputProps = { } const SetOracle = () => { - const tokenA = useStore((state) => state.createPool.tokensInPool.tokenA) - const tokenB = useStore((state) => state.createPool.tokensInPool.tokenB) - const tokenC = useStore((state) => state.createPool.tokensInPool.tokenC) - const tokenD = useStore((state) => state.createPool.tokensInPool.tokenD) - const tokenE = useStore((state) => state.createPool.tokensInPool.tokenE) - const tokenF = useStore((state) => state.createPool.tokensInPool.tokenF) - const tokenG = useStore((state) => state.createPool.tokensInPool.tokenG) - const tokenH = useStore((state) => state.createPool.tokensInPool.tokenH) + const tokens = useStore((state) => state.createPool.tokensInPool) + + const oracleTokens: { token: TokenState; tokenId: TokenId; title: string }[] = [ + { token: tokens.tokenA, tokenId: TOKEN_A as TokenId, title: t`Token A` }, + { token: tokens.tokenB, tokenId: TOKEN_B as TokenId, title: t`Token B` }, + { token: tokens.tokenC, tokenId: TOKEN_C as TokenId, title: t`Token C` }, + { token: tokens.tokenD, tokenId: TOKEN_D as TokenId, title: t`Token D` }, + { token: tokens.tokenE, tokenId: TOKEN_E as TokenId, title: t`Token E` }, + { token: tokens.tokenF, tokenId: TOKEN_F as TokenId, title: t`Token F` }, + { token: tokens.tokenG, tokenId: TOKEN_G as TokenId, title: t`Token G` }, + { token: tokens.tokenH, tokenId: TOKEN_H as TokenId, title: t`Token H` }, + ].filter(({ token }) => token.ngAssetType === NG_ASSET_TYPE.ORACLE && token.address !== '') return ( - {tokenA.ngAssetType === 1 && tokenA.address !== '' && ( - - )} - {tokenB.ngAssetType === 1 && tokenB.address !== '' && ( - - )} - {tokenC.ngAssetType === 1 && tokenC.address !== '' && ( - - )} - {tokenD.ngAssetType === 1 && tokenD.address !== '' && ( - - )} - {tokenD.ngAssetType === 1 && tokenD.address !== '' && ( - - )} - {tokenD.ngAssetType === 1 && tokenD.address !== '' && ( - - )} - {tokenD.ngAssetType === 1 && tokenD.address !== '' && ( - - )} - {tokenD.ngAssetType === 1 && tokenD.address !== '' && ( - - )} + {oracleTokens.map(({ token, tokenId, title }) => ( + + ))} ) } diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx index 54cbc6ce8f..a421490227 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx @@ -15,7 +15,9 @@ import { TOKEN_G, TOKEN_H, FXSWAP, + NG_ASSET_TYPE, } from '@/dex/components/PageCreatePool/constants' +import { useAutoDetectErc4626 } from '@/dex/components/PageCreatePool/hooks/useAutoDetectErc4626' import SelectToken from '@/dex/components/PageCreatePool/TokensInPool/SelectToken' import SetOracle from '@/dex/components/PageCreatePool/TokensInPool/SetOracle' import { CreateToken, TokenId, TokensInPoolState } from '@/dex/components/PageCreatePool/types' @@ -47,11 +49,12 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { const updateTokensInPool = useStore((state) => state.createPool.updateTokensInPool) const updateTokenAmount = useStore((state) => state.createPool.updateTokenAmount) const updateSwapType = useStore((state) => state.createPool.updateSwapType) + const updateNgAssetType = useStore((state) => state.createPool.updateNgAssetType) const basePools = useStore((state) => state.pools.basePools[chainId]) ?? DEFAULT_POOLS const userBalances = useStore((state) => state.userBalances.userBalancesMapper) const { tokensMapper } = useTokensMapper(chainId) const nativeToken = curve.getNetworkConstants().NATIVE_TOKEN - + const { scheduleCheck: scheduleErc4626Check } = useAutoDetectErc4626({ tokensInPool }) const { data: { createDisabledTokens, stableswapFactory, tricryptoFactory, twocryptoFactory }, } = useNetworkByChain({ chainId }) @@ -99,7 +102,15 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { const handleInpChange = useCallback( (name: TokenId, value: string, tokensInPoolState: TokensInPoolState) => { - if (!value.startsWith('0x')) return + const normalizedValue = value.toLowerCase() + + if (!normalizedValue.startsWith('0x')) return + const previousAddress = tokensInPoolState[name].address.toLowerCase() + + if (normalizedValue === previousAddress) return + + // reset the ngAssetType to Standard by default + updateNgAssetType(name, NG_ASSET_TYPE.STANDARD) const basePoolCoins: string[] = getBasepoolCoins( value, @@ -144,7 +155,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, basePool: true, }, @@ -163,7 +174,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, symbol: findSymbol(value), address: value, basePool: true, @@ -217,7 +228,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_B]: { ...updatedFormValues[TOKEN_B], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -237,7 +248,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_B]: { ...updatedFormValues[TOKEN_B], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -291,7 +302,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -327,7 +338,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -363,7 +374,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -399,7 +410,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -435,7 +446,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -471,7 +482,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...updatedFormValues, [TOKEN_A]: { ...updatedFormValues[TOKEN_A], - ngAssetType: 0, + ngAssetType: NG_ASSET_TYPE.STANDARD, address: value, symbol: findSymbol(value), basePool: true, @@ -511,8 +522,12 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { updatedFormValues[TOKEN_G], updatedFormValues[TOKEN_H], ) + + if (updatedFormValues[name].address.toLowerCase() === normalizedValue) { + scheduleErc4626Check(name, value) + } }, - [tokensInPool, updateTokensInPool, curve, findSymbol, swapType, basePools], + [tokensInPool, updateTokensInPool, curve, findSymbol, swapType, basePools, updateNgAssetType, scheduleErc4626Check], ) const addToken = useCallback(() => { @@ -538,7 +553,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ...tokensInPoolState[token], address: tokenId === token ? '' : tokensInPoolState[token].address, symbol: tokenId === token ? '' : tokensInPoolState[token].symbol, - ngAssetType: tokenId === token ? 0 : tokensInPoolState[token].ngAssetType, + ngAssetType: tokenId === token ? NG_ASSET_TYPE.STANDARD : tokensInPoolState[token].ngAssetType, oracleAddress: tokenId === token ? '' : tokensInPoolState[token].oracleAddress, oracleFunction: tokenId === token ? '' : tokensInPoolState[token].oracleFunction, }) diff --git a/apps/main/src/dex/components/PageCreatePool/constants.ts b/apps/main/src/dex/components/PageCreatePool/constants.ts index 7637435d36..c1f4d31794 100644 --- a/apps/main/src/dex/components/PageCreatePool/constants.ts +++ b/apps/main/src/dex/components/PageCreatePool/constants.ts @@ -1,5 +1,6 @@ import BigNumber from 'bignumber.js' import { t } from '@ui-kit/lib/i18n' +import type { NgAssetType } from './types' export const CRYPTOSWAP = 'Cryptoswap' export const STABLESWAP = 'Stableswap' @@ -35,6 +36,13 @@ export type PRESETS = { } } +export const NG_ASSET_TYPE: Record = { + STANDARD: 0, + ORACLE: 1, + REBASING: 2, + ERC4626: 3, +} + const fillerParams = { stableSwapFee: '', stableA: '', diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts new file mode 100644 index 0000000000..b4ec040d1b --- /dev/null +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts @@ -0,0 +1,96 @@ +import { useCallback, useEffect, useState } from 'react' +import { NG_ASSET_TYPE } from '@/dex/components/PageCreatePool/constants' +import { useIsErc4626 } from '@/dex/components/PageCreatePool/hooks/useIsErc4626' +import { TokenId, TokenState, TokensInPoolState } from '@/dex/components/PageCreatePool/types' +import { DEFAULT_ERC4626_STATUS } from '@/dex/store/createCreatePoolSlice' +import useStore from '@/dex/store/useStore' + +type Candidate = { + tokenId: TokenId + address: string +} + +type UseAutoDetectErc4626Params = { + tokensInPool: TokensInPoolState +} + +/** + * This hook is used to automatically detect if a token is an ERC4626 token and set the asset type to ERC4626 + * @param tokensInPool - The tokens in the pool + * @returns A function to schedule a check for an ERC4626 token + */ +export const useAutoDetectErc4626 = ({ tokensInPool }: UseAutoDetectErc4626Params) => { + const updateNgAssetType = useStore((state) => state.createPool.updateNgAssetType) + const updateTokenErc4626Status = useStore((state) => state.createPool.updateTokenErc4626Status) + const [candidate, setCandidate] = useState(null) + const { isErc4626, isLoading, isError, isSuccess } = useIsErc4626({ address: candidate?.address }) + + const setStatus = useCallback( + (tokenId: TokenId, status: TokenState['erc4626']) => { + updateTokenErc4626Status(tokenId, status) + }, + [updateTokenErc4626Status], + ) + + useEffect(() => { + if (!candidate) return + + const currentToken = tokensInPool[candidate.tokenId] + if (!currentToken) { + setCandidate(null) + return + } + + const nextStatus: TokenState['erc4626'] = { + isErc4626: Boolean(isErc4626 && isSuccess), + isLoading, + isError, + isSuccess, + } + + const currentStatus = currentToken.erc4626 + const statusChanged = + currentStatus.isErc4626 !== nextStatus.isErc4626 || + currentStatus.isLoading !== nextStatus.isLoading || + currentStatus.isError !== nextStatus.isError || + currentStatus.isSuccess !== nextStatus.isSuccess + + if (statusChanged) { + setStatus(candidate.tokenId, nextStatus) + } + + if (isLoading || (!isSuccess && !isError)) return + + const currentAddress = tokensInPool[candidate.tokenId].address + const addressesMatch = currentAddress !== '' && currentAddress.toLowerCase() === candidate.address.toLowerCase() + + if (!addressesMatch) { + if (currentStatus.isErc4626 || currentStatus.isLoading || currentStatus.isError || currentStatus.isSuccess) { + setStatus(candidate.tokenId, { ...DEFAULT_ERC4626_STATUS }) + } + setCandidate(null) + return + } + + if (isErc4626) { + updateNgAssetType(candidate.tokenId, NG_ASSET_TYPE.ERC4626) + } + + setCandidate(null) + }, [candidate, isErc4626, isError, isLoading, isSuccess, setStatus, tokensInPool, updateNgAssetType]) + + const scheduleCheck = useCallback( + (tokenId: TokenId, address: string) => { + setCandidate({ tokenId, address }) + setStatus(tokenId, { + ...DEFAULT_ERC4626_STATUS, + isLoading: true, + }) + }, + [setStatus], + ) + + return { + scheduleCheck, + } +} diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts new file mode 100644 index 0000000000..09b5e1b6a4 --- /dev/null +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts @@ -0,0 +1,42 @@ +import { erc4626Abi, isAddress } from 'viem' +import { useReadContract } from 'wagmi' + +type UseIsErc4626Params = { + address?: string +} + +export function useIsErc4626({ address }: UseIsErc4626Params) { + const isValidAddress = address !== undefined && isAddress(address) + + const { + data: assetAddress, + error, + refetch, + } = useReadContract({ + abi: erc4626Abi, + address: isValidAddress ? address : undefined, + functionName: 'asset', + query: { + enabled: isValidAddress, + retry: false, + }, + }) + + const hasData = assetAddress !== undefined + const hasError = Boolean(error) + + const isSuccess = isValidAddress && hasData + const isError = isValidAddress && !hasData && hasError + const isLoading = isValidAddress && !hasData && !hasError + + const isErc4626 = isSuccess ? true : isError ? false : undefined + + return { + isErc4626, + assetAddress, + isLoading, + isError, + isSuccess, + refetch, + } +} diff --git a/apps/main/src/dex/components/PageCreatePool/types.ts b/apps/main/src/dex/components/PageCreatePool/types.ts index b84d1ca521..82db1f0efe 100644 --- a/apps/main/src/dex/components/PageCreatePool/types.ts +++ b/apps/main/src/dex/components/PageCreatePool/types.ts @@ -31,6 +31,12 @@ export type TokenState = { oracleAddress: string oracleFunction: string basePool: boolean + erc4626: { + isErc4626: boolean | undefined + isLoading: boolean + isError: boolean + isSuccess: boolean + } } export type TokensInPoolState = CreatePoolSlice['createPool']['tokensInPool'] diff --git a/apps/main/src/dex/components/PageCreatePool/utils.ts b/apps/main/src/dex/components/PageCreatePool/utils.ts index 9241917c4c..9ef95bc6d8 100644 --- a/apps/main/src/dex/components/PageCreatePool/utils.ts +++ b/apps/main/src/dex/components/PageCreatePool/utils.ts @@ -1,5 +1,5 @@ import { isAddress } from 'viem' -import { STABLESWAP, CRYPTOSWAP, FXSWAP } from '@/dex/components/PageCreatePool/constants' +import { STABLESWAP, CRYPTOSWAP, FXSWAP, NG_ASSET_TYPE } from '@/dex/components/PageCreatePool/constants' import type { SwapType, TokenState } from '@/dex/components/PageCreatePool/types' import { BasePool } from '@/dex/types/main.types' export const checkSwapType = (swapType: SwapType) => swapType !== '' @@ -82,14 +82,14 @@ export const checkOracle = isAddress export const validateOracleFunction = (functionName: string) => functionName.endsWith('()') export const oraclesReady = (tokens: TokenState[]) => { - const oracleTokens = tokens.filter((token) => token.ngAssetType === 1) + const oracleTokens = tokens.filter((token) => token.ngAssetType === NG_ASSET_TYPE.ORACLE) const allValid = oracleTokens.every((token) => checkOracle(token.oracleAddress)) const functionsValid = oracleTokens.every((token) => validateOracleFunction(token.oracleFunction)) return allValid && functionsValid } export const containsOracle = (tokens: TokenState[]) => - tokens.some((token) => token.ngAssetType === 1 && token.address !== '') + tokens.some((token) => token.ngAssetType === NG_ASSET_TYPE.ORACLE && token.address !== '') export const checkFormReady = ( poolTypeValid: boolean, diff --git a/apps/main/src/dex/store/createCreatePoolSlice.ts b/apps/main/src/dex/store/createCreatePoolSlice.ts index d182cf8f09..97d45e9eee 100644 --- a/apps/main/src/dex/store/createCreatePoolSlice.ts +++ b/apps/main/src/dex/store/createCreatePoolSlice.ts @@ -16,6 +16,7 @@ import { TOKEN_F, TOKEN_G, TOKEN_H, + NG_ASSET_TYPE, } from '@/dex/components/PageCreatePool/constants' import { CreateToken, NgAssetType, SwapType, TokenId, TokenState } from '@/dex/components/PageCreatePool/types' import { isTricrypto } from '@/dex/components/PageCreatePool/utils' @@ -114,6 +115,7 @@ export type CreatePoolSlice = { ) => void clearToken: (tokenId: TokenId) => void updateNgAssetType: (tokenId: TokenId, value: NgAssetType) => void + updateTokenErc4626Status: (tokenId: TokenId, status: TokenState['erc4626']) => void updateOracleAddress: (tokenId: TokenId, oracleAddress: string) => void updateOracleFunction: (tokenId: TokenId, oracleFunction: string) => void updateUserAddedTokens: (address: string, symbol: string, haveSameTokenName: boolean, basePool: boolean) => void @@ -143,6 +145,23 @@ export type CreatePoolSlice = { } } +export const DEFAULT_ERC4626_STATUS: TokenState['erc4626'] = { + isErc4626: false, + isLoading: false, + isError: false, + isSuccess: false, +} + +const DEFAULT_TOKEN_STATE: TokenState = { + address: '', + symbol: '', + ngAssetType: NG_ASSET_TYPE.STANDARD, + basePool: false, + oracleAddress: '', + oracleFunction: '', + erc4626: { ...DEFAULT_ERC4626_STATUS }, +} + export const DEFAULT_CREATE_POOL_STATE = { navigationIndex: 0, advanced: false, @@ -152,68 +171,28 @@ export const DEFAULT_CREATE_POOL_STATE = { tokenAmount: 2, metaPoolToken: false, [TOKEN_A]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, [TOKEN_B]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, [TOKEN_C]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, [TOKEN_D]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, [TOKEN_E]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, [TOKEN_F]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, [TOKEN_G]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, [TOKEN_H]: { - address: '', - symbol: '', - ngAssetType: 0, - basePool: false, - oracleAddress: '', - oracleFunction: '', + ...DEFAULT_TOKEN_STATE, }, }, initialPrice: { @@ -427,12 +406,30 @@ const createCreatePoolSlice = ( tokensInPoolUpdates.tokenA = { ...tokenA, basePool: tokenA.basePool, - ngAssetType: tokenA.basePool ? 0 : get().createPool.tokensInPool[TOKEN_A].ngAssetType, + ngAssetType: tokenA.basePool ? NG_ASSET_TYPE.STANDARD : get().createPool.tokensInPool[TOKEN_A].ngAssetType, } tokensInPoolUpdates.tokenB = { ...tokenB, basePool: tokenB.basePool, - ngAssetType: tokenB.basePool ? 0 : get().createPool.tokensInPool[TOKEN_B].ngAssetType, + ngAssetType: tokenB.basePool ? NG_ASSET_TYPE.STANDARD : get().createPool.tokensInPool[TOKEN_B].ngAssetType, + } + + const syncErc4626Statuses = () => { + const latestTokens = get().createPool.tokensInPool + + const cloneWithStatus = (tokenId: TokenId, tokenValue: TokenState) => ({ + ...tokenValue, + erc4626: { ...latestTokens[tokenId].erc4626 }, + }) + + tokensInPoolUpdates.tokenA = cloneWithStatus(TOKEN_A, tokensInPoolUpdates.tokenA) + tokensInPoolUpdates.tokenB = cloneWithStatus(TOKEN_B, tokensInPoolUpdates.tokenB) + tokensInPoolUpdates.tokenC = cloneWithStatus(TOKEN_C, tokensInPoolUpdates.tokenC) + tokensInPoolUpdates.tokenD = cloneWithStatus(TOKEN_D, tokensInPoolUpdates.tokenD) + tokensInPoolUpdates.tokenE = cloneWithStatus(TOKEN_E, tokensInPoolUpdates.tokenE) + tokensInPoolUpdates.tokenF = cloneWithStatus(TOKEN_F, tokensInPoolUpdates.tokenF) + tokensInPoolUpdates.tokenG = cloneWithStatus(TOKEN_G, tokensInPoolUpdates.tokenG) + tokensInPoolUpdates.tokenH = cloneWithStatus(TOKEN_H, tokensInPoolUpdates.tokenH) } const initialPriceUpdates = { @@ -480,6 +477,8 @@ const createCreatePoolSlice = ( calculateInitialPrice(initialPriceUpdates.tokenA, initialPriceUpdates.tokenC), ] + syncErc4626Statuses() + set( produce((state) => { state.createPool.tokensInPool = tokensInPoolUpdates @@ -504,6 +503,13 @@ const createCreatePoolSlice = ( }), ) }, + updateTokenErc4626Status: (tokenId: TokenId, status: TokenState['erc4626']) => { + set( + produce((state) => { + state.createPool.tokensInPool[tokenId].erc4626 = { ...status } + }), + ) + }, updateOracleAddress: (tokenId: TokenId, oracleAddress: string) => { set( produce((state) => { @@ -1047,8 +1053,8 @@ const createCreatePoolSlice = ( if (networks[chainId].stableswapFactory) { // STABLE NG META try { - const oracleAddress = coin.ngAssetType === 1 ? coin.oracleAddress : zeroAddress - const oracleFunction = coin.ngAssetType === 1 ? coin.oracleFunction : '0x00000000' + const oracleAddress = coin.ngAssetType === NG_ASSET_TYPE.ORACLE ? coin.oracleAddress : zeroAddress + const oracleFunction = coin.ngAssetType === NG_ASSET_TYPE.ORACLE ? coin.oracleFunction : '0x00000000' const maExpTimeFormatted = Math.round(+maExpTime / 0.693) const deployPoolTx = await curve.stableNgFactory.deployMetaPool( @@ -1136,8 +1142,12 @@ const createCreatePoolSlice = ( try { const coinAddresses = coins.map((coin) => coin.address) const assetTypes = coins.map((coin) => coin.ngAssetType) - const oracleAddresses = coins.map((coin) => (coin.ngAssetType === 1 ? coin.oracleAddress : zeroAddress)) - const oracleFunctions = coins.map((coin) => (coin.ngAssetType === 1 ? coin.oracleFunction : '0x00000000')) + const oracleAddresses = coins.map((coin) => + coin.ngAssetType === NG_ASSET_TYPE.ORACLE ? coin.oracleAddress : zeroAddress, + ) + const oracleFunctions = coins.map((coin) => + coin.ngAssetType === NG_ASSET_TYPE.ORACLE ? coin.oracleFunction : '0x00000000', + ) const maExpTimeFormatted = Math.round(+maExpTime / 0.693) const deployPoolTx = await curve.stableNgFactory.deployPlainPool( From da0687b1612502cddab33f9dc4bf75ec681b28d0 Mon Sep 17 00:00:00 2001 From: JustJousting Date: Wed, 3 Dec 2025 17:40:04 +0200 Subject: [PATCH 02/10] fix: reset erc4626 state too when removing token --- .../src/dex/components/PageCreatePool/TokensInPool/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx index a421490227..56dfdf30d3 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx @@ -24,7 +24,7 @@ import { CreateToken, TokenId, TokensInPoolState } from '@/dex/components/PageCr import { checkMetaPool, containsOracle, getBasepoolCoins } from '@/dex/components/PageCreatePool/utils' import { useNetworkByChain } from '@/dex/entities/networks' import useTokensMapper from '@/dex/hooks/useTokensMapper' -import { DEFAULT_CREATE_POOL_STATE } from '@/dex/store/createCreatePoolSlice' +import { DEFAULT_CREATE_POOL_STATE, DEFAULT_ERC4626_STATUS } from '@/dex/store/createCreatePoolSlice' import useStore from '@/dex/store/useStore' import { CurveApi, ChainId, BasePool } from '@/dex/types/main.types' import Box from '@ui/Box' @@ -556,6 +556,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ngAssetType: tokenId === token ? NG_ASSET_TYPE.STANDARD : tokensInPoolState[token].ngAssetType, oracleAddress: tokenId === token ? '' : tokensInPoolState[token].oracleAddress, oracleFunction: tokenId === token ? '' : tokensInPoolState[token].oracleFunction, + erc4626: tokenId === token ? { ...DEFAULT_ERC4626_STATUS } : tokensInPoolState[token].erc4626, }) updateTokensInPool( From fc2f16a0905e272b856a87b0fb98612274cad53e Mon Sep 17 00:00:00 2001 From: JustJousting Date: Tue, 9 Dec 2025 03:30:35 +0200 Subject: [PATCH 03/10] refactor: improve erc4626 status preservation when rearranging tokens in pool --- .../src/dex/store/createCreatePoolSlice.ts | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/apps/main/src/dex/store/createCreatePoolSlice.ts b/apps/main/src/dex/store/createCreatePoolSlice.ts index 97d45e9eee..b7ea1d6cdd 100644 --- a/apps/main/src/dex/store/createCreatePoolSlice.ts +++ b/apps/main/src/dex/store/createCreatePoolSlice.ts @@ -385,8 +385,9 @@ const createCreatePoolSlice = ( tokenG: TokenState, tokenH: TokenState, ) => { + const currentTokens = get().createPool.tokensInPool const tokensInPoolUpdates = { - ...get().createPool.tokensInPool, + ...currentTokens, tokenA, tokenB, tokenC, @@ -414,22 +415,27 @@ const createCreatePoolSlice = ( ngAssetType: tokenB.basePool ? NG_ASSET_TYPE.STANDARD : get().createPool.tokensInPool[TOKEN_B].ngAssetType, } + // Preserve erc4626 statuses when tokens are rearranged. Status follows the address, const syncErc4626Statuses = () => { - const latestTokens = get().createPool.tokensInPool - - const cloneWithStatus = (tokenId: TokenId, tokenValue: TokenState) => ({ - ...tokenValue, - erc4626: { ...latestTokens[tokenId].erc4626 }, - }) - - tokensInPoolUpdates.tokenA = cloneWithStatus(TOKEN_A, tokensInPoolUpdates.tokenA) - tokensInPoolUpdates.tokenB = cloneWithStatus(TOKEN_B, tokensInPoolUpdates.tokenB) - tokensInPoolUpdates.tokenC = cloneWithStatus(TOKEN_C, tokensInPoolUpdates.tokenC) - tokensInPoolUpdates.tokenD = cloneWithStatus(TOKEN_D, tokensInPoolUpdates.tokenD) - tokensInPoolUpdates.tokenE = cloneWithStatus(TOKEN_E, tokensInPoolUpdates.tokenE) - tokensInPoolUpdates.tokenF = cloneWithStatus(TOKEN_F, tokensInPoolUpdates.tokenF) - tokensInPoolUpdates.tokenG = cloneWithStatus(TOKEN_G, tokensInPoolUpdates.tokenG) - tokensInPoolUpdates.tokenH = cloneWithStatus(TOKEN_H, tokensInPoolUpdates.tokenH) + const tokenIds = [TOKEN_A, TOKEN_B, TOKEN_C, TOKEN_D, TOKEN_E, TOKEN_F, TOKEN_G, TOKEN_H] as const + + const statusByAddress = new Map() + for (const id of tokenIds) { + const address = currentTokens[id].address?.toLowerCase() + if (address) statusByAddress.set(address, currentTokens[id].erc4626) + } + + for (const id of tokenIds) { + const token = tokensInPoolUpdates[id] + const address = token.address?.toLowerCase() + tokensInPoolUpdates[id] = { + ...token, + erc4626: + address && statusByAddress.has(address) + ? { ...statusByAddress.get(address)! } + : { ...DEFAULT_ERC4626_STATUS }, + } + } } const initialPriceUpdates = { From 6fc0bb8457ae4f8f577a120d3ecd6b29227f8372 Mon Sep 17 00:00:00 2001 From: JustJousting Date: Tue, 9 Dec 2025 18:55:20 +0200 Subject: [PATCH 04/10] refactor: review comments --- .../hooks/useAutoDetectErc4626.ts | 93 +++++++++---------- .../PageCreatePool/hooks/useIsErc4626.ts | 5 +- .../dex/components/PageCreatePool/types.ts | 3 +- .../src/dex/store/createCreatePoolSlice.ts | 35 ++++--- 4 files changed, 68 insertions(+), 68 deletions(-) diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts index b4ec040d1b..585c4f63bb 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useState } from 'react' import { NG_ASSET_TYPE } from '@/dex/components/PageCreatePool/constants' import { useIsErc4626 } from '@/dex/components/PageCreatePool/hooks/useIsErc4626' import { TokenId, TokenState, TokensInPoolState } from '@/dex/components/PageCreatePool/types' @@ -23,7 +23,7 @@ export const useAutoDetectErc4626 = ({ tokensInPool }: UseAutoDetectErc4626Param const updateNgAssetType = useStore((state) => state.createPool.updateNgAssetType) const updateTokenErc4626Status = useStore((state) => state.createPool.updateTokenErc4626Status) const [candidate, setCandidate] = useState(null) - const { isErc4626, isLoading, isError, isSuccess } = useIsErc4626({ address: candidate?.address }) + const { isErc4626, isLoading, error, isSuccess } = useIsErc4626({ address: candidate?.address }) const setStatus = useCallback( (tokenId: TokenId, status: TokenState['erc4626']) => { @@ -32,53 +32,6 @@ export const useAutoDetectErc4626 = ({ tokensInPool }: UseAutoDetectErc4626Param [updateTokenErc4626Status], ) - useEffect(() => { - if (!candidate) return - - const currentToken = tokensInPool[candidate.tokenId] - if (!currentToken) { - setCandidate(null) - return - } - - const nextStatus: TokenState['erc4626'] = { - isErc4626: Boolean(isErc4626 && isSuccess), - isLoading, - isError, - isSuccess, - } - - const currentStatus = currentToken.erc4626 - const statusChanged = - currentStatus.isErc4626 !== nextStatus.isErc4626 || - currentStatus.isLoading !== nextStatus.isLoading || - currentStatus.isError !== nextStatus.isError || - currentStatus.isSuccess !== nextStatus.isSuccess - - if (statusChanged) { - setStatus(candidate.tokenId, nextStatus) - } - - if (isLoading || (!isSuccess && !isError)) return - - const currentAddress = tokensInPool[candidate.tokenId].address - const addressesMatch = currentAddress !== '' && currentAddress.toLowerCase() === candidate.address.toLowerCase() - - if (!addressesMatch) { - if (currentStatus.isErc4626 || currentStatus.isLoading || currentStatus.isError || currentStatus.isSuccess) { - setStatus(candidate.tokenId, { ...DEFAULT_ERC4626_STATUS }) - } - setCandidate(null) - return - } - - if (isErc4626) { - updateNgAssetType(candidate.tokenId, NG_ASSET_TYPE.ERC4626) - } - - setCandidate(null) - }, [candidate, isErc4626, isError, isLoading, isSuccess, setStatus, tokensInPool, updateNgAssetType]) - const scheduleCheck = useCallback( (tokenId: TokenId, address: string) => { setCandidate({ tokenId, address }) @@ -90,6 +43,48 @@ export const useAutoDetectErc4626 = ({ tokensInPool }: UseAutoDetectErc4626Param [setStatus], ) + if (candidate) { + const currentToken = tokensInPool[candidate.tokenId] + if (!currentToken) { + setCandidate(null) + } else { + const nextStatus: TokenState['erc4626'] = { + isErc4626: Boolean(isErc4626 && isSuccess), + isLoading, + error, + isSuccess, + } + + const currentStatus = currentToken.erc4626 + const statusChanged = + currentStatus.isErc4626 !== nextStatus.isErc4626 || + currentStatus.isLoading !== nextStatus.isLoading || + currentStatus.error !== nextStatus.error || + currentStatus.isSuccess !== nextStatus.isSuccess + + if (statusChanged) { + setStatus(candidate.tokenId, nextStatus) + } + + if (!isLoading) { + const currentAddress = tokensInPool[candidate.tokenId].address + const addressesMatch = currentAddress !== '' && currentAddress.toLowerCase() === candidate.address.toLowerCase() + + if (!addressesMatch) { + if (currentStatus.isErc4626 || currentStatus.isLoading || currentStatus.error || currentStatus.isSuccess) { + setStatus(candidate.tokenId, { ...DEFAULT_ERC4626_STATUS }) + } + setCandidate(null) + } else { + if (isErc4626) { + updateNgAssetType(candidate.tokenId, NG_ASSET_TYPE.ERC4626) + } + setCandidate(null) + } + } + } + } + return { scheduleCheck, } diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts index 09b5e1b6a4..53685bc61a 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts @@ -26,16 +26,15 @@ export function useIsErc4626({ address }: UseIsErc4626Params) { const hasError = Boolean(error) const isSuccess = isValidAddress && hasData - const isError = isValidAddress && !hasData && hasError const isLoading = isValidAddress && !hasData && !hasError - const isErc4626 = isSuccess ? true : isError ? false : undefined + const isErc4626 = assetAddress && true return { isErc4626, assetAddress, isLoading, - isError, + error, isSuccess, refetch, } diff --git a/apps/main/src/dex/components/PageCreatePool/types.ts b/apps/main/src/dex/components/PageCreatePool/types.ts index 82db1f0efe..972950673a 100644 --- a/apps/main/src/dex/components/PageCreatePool/types.ts +++ b/apps/main/src/dex/components/PageCreatePool/types.ts @@ -1,3 +1,4 @@ +import type { ReadContractErrorType } from 'viem' import { CRYPTOSWAP, STABLESWAP, @@ -34,7 +35,7 @@ export type TokenState = { erc4626: { isErc4626: boolean | undefined isLoading: boolean - isError: boolean + error: ReadContractErrorType | null isSuccess: boolean } } diff --git a/apps/main/src/dex/store/createCreatePoolSlice.ts b/apps/main/src/dex/store/createCreatePoolSlice.ts index b7ea1d6cdd..b621e4f9b9 100644 --- a/apps/main/src/dex/store/createCreatePoolSlice.ts +++ b/apps/main/src/dex/store/createCreatePoolSlice.ts @@ -23,6 +23,7 @@ import { isTricrypto } from '@/dex/components/PageCreatePool/utils' import type { State } from '@/dex/store/useStore' import { ChainId, CurveApi } from '@/dex/types/main.types' import { TwoCryptoImplementation } from '@curvefi/api/lib/constants/twoCryptoImplementations' +import { notFalsy } from '@curvefi/prices-api/objects.util' import { scanTxPath } from '@ui/utils' import { notify } from '@ui-kit/features/connect-wallet' import { t } from '@ui-kit/lib/i18n' @@ -148,7 +149,7 @@ export type CreatePoolSlice = { export const DEFAULT_ERC4626_STATUS: TokenState['erc4626'] = { isErc4626: false, isLoading: false, - isError: false, + error: null, isSuccess: false, } @@ -407,33 +408,37 @@ const createCreatePoolSlice = ( tokensInPoolUpdates.tokenA = { ...tokenA, basePool: tokenA.basePool, - ngAssetType: tokenA.basePool ? NG_ASSET_TYPE.STANDARD : get().createPool.tokensInPool[TOKEN_A].ngAssetType, } tokensInPoolUpdates.tokenB = { ...tokenB, basePool: tokenB.basePool, - ngAssetType: tokenB.basePool ? NG_ASSET_TYPE.STANDARD : get().createPool.tokensInPool[TOKEN_B].ngAssetType, } - // Preserve erc4626 statuses when tokens are rearranged. Status follows the address, - const syncErc4626Statuses = () => { + // Preserve erc4626 statuses and ngAssetType when tokens are rearranged. Status follows the address. + const syncTokenStatuses = () => { const tokenIds = [TOKEN_A, TOKEN_B, TOKEN_C, TOKEN_D, TOKEN_E, TOKEN_F, TOKEN_G, TOKEN_H] as const - const statusByAddress = new Map() - for (const id of tokenIds) { - const address = currentTokens[id].address?.toLowerCase() - if (address) statusByAddress.set(address, currentTokens[id].erc4626) - } + const statusByAddress = new Map( + notFalsy( + ...tokenIds.map( + (id) => + currentTokens[id].address && + ([ + currentTokens[id].address.toLowerCase(), + { erc4626: currentTokens[id].erc4626, ngAssetType: currentTokens[id].ngAssetType }, + ] as const), + ), + ), + ) for (const id of tokenIds) { const token = tokensInPoolUpdates[id] const address = token.address?.toLowerCase() + const status = address ? statusByAddress.get(address) : undefined tokensInPoolUpdates[id] = { ...token, - erc4626: - address && statusByAddress.has(address) - ? { ...statusByAddress.get(address)! } - : { ...DEFAULT_ERC4626_STATUS }, + erc4626: status ? { ...status.erc4626 } : { ...DEFAULT_ERC4626_STATUS }, + ngAssetType: token.basePool ? NG_ASSET_TYPE.STANDARD : (status?.ngAssetType ?? token.ngAssetType), } } } @@ -483,7 +488,7 @@ const createCreatePoolSlice = ( calculateInitialPrice(initialPriceUpdates.tokenA, initialPriceUpdates.tokenC), ] - syncErc4626Statuses() + syncTokenStatuses() set( produce((state) => { From 826e4964f51109038d974735700d674e836b366e Mon Sep 17 00:00:00 2001 From: JustJousting Date: Tue, 9 Dec 2025 19:02:48 +0200 Subject: [PATCH 05/10] refactor: add constant for oracle function null value --- apps/main/src/dex/store/createCreatePoolSlice.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/main/src/dex/store/createCreatePoolSlice.ts b/apps/main/src/dex/store/createCreatePoolSlice.ts index b621e4f9b9..b3b4eb8a2c 100644 --- a/apps/main/src/dex/store/createCreatePoolSlice.ts +++ b/apps/main/src/dex/store/createCreatePoolSlice.ts @@ -146,6 +146,8 @@ export type CreatePoolSlice = { } } +const ORACLE_FUNCTION_NULL_VALUE = '0x00000000' + export const DEFAULT_ERC4626_STATUS: TokenState['erc4626'] = { isErc4626: false, isLoading: false, @@ -1065,7 +1067,8 @@ const createCreatePoolSlice = ( // STABLE NG META try { const oracleAddress = coin.ngAssetType === NG_ASSET_TYPE.ORACLE ? coin.oracleAddress : zeroAddress - const oracleFunction = coin.ngAssetType === NG_ASSET_TYPE.ORACLE ? coin.oracleFunction : '0x00000000' + const oracleFunction = + coin.ngAssetType === NG_ASSET_TYPE.ORACLE ? coin.oracleFunction : ORACLE_FUNCTION_NULL_VALUE const maExpTimeFormatted = Math.round(+maExpTime / 0.693) const deployPoolTx = await curve.stableNgFactory.deployMetaPool( From 809fd02bcbd4306da3d311d73bacff52f50a1839 Mon Sep 17 00:00:00 2001 From: Alunara Date: Tue, 9 Dec 2025 18:04:52 +0100 Subject: [PATCH 06/10] refactor: simplify useIsErc4626 --- .../PageCreatePool/TokensInPool/index.tsx | 3 +- .../hooks/useAutoDetectErc4626.ts | 7 +++-- .../PageCreatePool/hooks/useIsErc4626.ts | 28 ++++++------------- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx index 56dfdf30d3..8acd886885 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx @@ -1,6 +1,7 @@ import lodash from 'lodash' import { useMemo, useCallback } from 'react' import { styled } from 'styled-components' +import type { Address } from 'viem' import SwitchTokensButton from '@/dex/components/PageCreatePool/components/SwitchTokensButton' import WarningBox from '@/dex/components/PageCreatePool/components/WarningBox' import { @@ -524,7 +525,7 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { ) if (updatedFormValues[name].address.toLowerCase() === normalizedValue) { - scheduleErc4626Check(name, value) + scheduleErc4626Check(name, value as Address) } }, [tokensInPool, updateTokensInPool, curve, findSymbol, swapType, basePools, updateNgAssetType, scheduleErc4626Check], diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts index 585c4f63bb..df3b0c7f74 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react' +import { isAddress, isAddressEqual, type Address } from 'viem' import { NG_ASSET_TYPE } from '@/dex/components/PageCreatePool/constants' import { useIsErc4626 } from '@/dex/components/PageCreatePool/hooks/useIsErc4626' import { TokenId, TokenState, TokensInPoolState } from '@/dex/components/PageCreatePool/types' @@ -7,7 +8,7 @@ import useStore from '@/dex/store/useStore' type Candidate = { tokenId: TokenId - address: string + address: Address } type UseAutoDetectErc4626Params = { @@ -33,7 +34,7 @@ export const useAutoDetectErc4626 = ({ tokensInPool }: UseAutoDetectErc4626Param ) const scheduleCheck = useCallback( - (tokenId: TokenId, address: string) => { + (tokenId: TokenId, address: Address) => { setCandidate({ tokenId, address }) setStatus(tokenId, { ...DEFAULT_ERC4626_STATUS, @@ -68,7 +69,7 @@ export const useAutoDetectErc4626 = ({ tokensInPool }: UseAutoDetectErc4626Param if (!isLoading) { const currentAddress = tokensInPool[candidate.tokenId].address - const addressesMatch = currentAddress !== '' && currentAddress.toLowerCase() === candidate.address.toLowerCase() + const addressesMatch = isAddress(currentAddress) && isAddressEqual(currentAddress, candidate.address) if (!addressesMatch) { if (currentStatus.isErc4626 || currentStatus.isLoading || currentStatus.error || currentStatus.isSuccess) { diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts index 53685bc61a..a4fdec85c9 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useIsErc4626.ts @@ -1,41 +1,29 @@ -import { erc4626Abi, isAddress } from 'viem' +import { erc4626Abi, type Address } from 'viem' import { useReadContract } from 'wagmi' -type UseIsErc4626Params = { - address?: string -} - -export function useIsErc4626({ address }: UseIsErc4626Params) { - const isValidAddress = address !== undefined && isAddress(address) - +export function useIsErc4626({ address }: { address?: Address }) { const { data: assetAddress, + isFetching: isLoading, + isSuccess, error, refetch, } = useReadContract({ abi: erc4626Abi, - address: isValidAddress ? address : undefined, + address, functionName: 'asset', query: { - enabled: isValidAddress, + enabled: !!address, retry: false, }, }) - const hasData = assetAddress !== undefined - const hasError = Boolean(error) - - const isSuccess = isValidAddress && hasData - const isLoading = isValidAddress && !hasData && !hasError - - const isErc4626 = assetAddress && true - return { - isErc4626, + isErc4626: assetAddress && true, assetAddress, isLoading, - error, isSuccess, + error, refetch, } } From 29dd2dbd11ea2b3b38e47b038adc7f0b17bd1006 Mon Sep 17 00:00:00 2001 From: JustJousting Date: Fri, 12 Dec 2025 13:56:03 +0200 Subject: [PATCH 07/10] refactor: simplify useAutoDetectErc4626 --- .../TokensInPool/SelectToken.tsx | 6 ++ .../PageCreatePool/TokensInPool/index.tsx | 9 +- .../hooks/useAutoDetectErc4626.ts | 91 +++---------------- 3 files changed, 21 insertions(+), 85 deletions(-) diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx index 8f85c8c1f5..06183f62ca 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/SelectToken.tsx @@ -1,5 +1,6 @@ import { Key } from 'react' import { styled } from 'styled-components' +import { type Address } from 'viem' import { STABLESWAP, CRYPTOSWAP, @@ -13,6 +14,7 @@ import { TOKEN_H, NG_ASSET_TYPE, } from '@/dex/components/PageCreatePool/constants' +import { useAutoDetectErc4626 } from '@/dex/components/PageCreatePool/hooks/useAutoDetectErc4626' import { CreateToken, TokenState, @@ -61,6 +63,10 @@ const SelectToken = ({ const clearToken = useStore((state) => state.createPool.clearToken) const tokensInPool = useStore((state) => state.createPool.tokensInPool) const { data: network } = useNetworkByChain({ chainId }) + void useAutoDetectErc4626({ + tokenId, + address: token.address as Address, + }) const getTokenName = (tokenId: TokenId) => { if (tokenId === TOKEN_D) return t`Token D` diff --git a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx index 8acd886885..3a62067405 100644 --- a/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx +++ b/apps/main/src/dex/components/PageCreatePool/TokensInPool/index.tsx @@ -1,7 +1,6 @@ import lodash from 'lodash' import { useMemo, useCallback } from 'react' import { styled } from 'styled-components' -import type { Address } from 'viem' import SwitchTokensButton from '@/dex/components/PageCreatePool/components/SwitchTokensButton' import WarningBox from '@/dex/components/PageCreatePool/components/WarningBox' import { @@ -18,7 +17,6 @@ import { FXSWAP, NG_ASSET_TYPE, } from '@/dex/components/PageCreatePool/constants' -import { useAutoDetectErc4626 } from '@/dex/components/PageCreatePool/hooks/useAutoDetectErc4626' import SelectToken from '@/dex/components/PageCreatePool/TokensInPool/SelectToken' import SetOracle from '@/dex/components/PageCreatePool/TokensInPool/SetOracle' import { CreateToken, TokenId, TokensInPoolState } from '@/dex/components/PageCreatePool/types' @@ -55,7 +53,6 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { const userBalances = useStore((state) => state.userBalances.userBalancesMapper) const { tokensMapper } = useTokensMapper(chainId) const nativeToken = curve.getNetworkConstants().NATIVE_TOKEN - const { scheduleCheck: scheduleErc4626Check } = useAutoDetectErc4626({ tokensInPool }) const { data: { createDisabledTokens, stableswapFactory, tricryptoFactory, twocryptoFactory }, } = useNetworkByChain({ chainId }) @@ -523,12 +520,8 @@ const TokensInPool = ({ curve, chainId, haveSigner }: Props) => { updatedFormValues[TOKEN_G], updatedFormValues[TOKEN_H], ) - - if (updatedFormValues[name].address.toLowerCase() === normalizedValue) { - scheduleErc4626Check(name, value as Address) - } }, - [tokensInPool, updateTokensInPool, curve, findSymbol, swapType, basePools, updateNgAssetType, scheduleErc4626Check], + [tokensInPool, updateTokensInPool, curve, findSymbol, swapType, basePools, updateNgAssetType], ) const addToken = useCallback(() => { diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts index df3b0c7f74..175bc3174b 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts @@ -1,92 +1,29 @@ -import { useCallback, useState } from 'react' -import { isAddress, isAddressEqual, type Address } from 'viem' +import { useMemo } from 'react' +import { type Address } from 'viem' import { NG_ASSET_TYPE } from '@/dex/components/PageCreatePool/constants' import { useIsErc4626 } from '@/dex/components/PageCreatePool/hooks/useIsErc4626' -import { TokenId, TokenState, TokensInPoolState } from '@/dex/components/PageCreatePool/types' -import { DEFAULT_ERC4626_STATUS } from '@/dex/store/createCreatePoolSlice' +import { TokenId } from '@/dex/components/PageCreatePool/types' import useStore from '@/dex/store/useStore' -type Candidate = { +type UseAutoDetectErc4626Params = { tokenId: TokenId address: Address } -type UseAutoDetectErc4626Params = { - tokensInPool: TokensInPoolState -} - /** * This hook is used to automatically detect if a token is an ERC4626 token and set the asset type to ERC4626 - * @param tokensInPool - The tokens in the pool - * @returns A function to schedule a check for an ERC4626 token */ -export const useAutoDetectErc4626 = ({ tokensInPool }: UseAutoDetectErc4626Params) => { +export const useAutoDetectErc4626 = ({ tokenId, address }: UseAutoDetectErc4626Params) => { const updateNgAssetType = useStore((state) => state.createPool.updateNgAssetType) const updateTokenErc4626Status = useStore((state) => state.createPool.updateTokenErc4626Status) - const [candidate, setCandidate] = useState(null) - const { isErc4626, isLoading, error, isSuccess } = useIsErc4626({ address: candidate?.address }) - - const setStatus = useCallback( - (tokenId: TokenId, status: TokenState['erc4626']) => { - updateTokenErc4626Status(tokenId, status) - }, - [updateTokenErc4626Status], - ) - - const scheduleCheck = useCallback( - (tokenId: TokenId, address: Address) => { - setCandidate({ tokenId, address }) - setStatus(tokenId, { - ...DEFAULT_ERC4626_STATUS, - isLoading: true, - }) - }, - [setStatus], - ) - - if (candidate) { - const currentToken = tokensInPool[candidate.tokenId] - if (!currentToken) { - setCandidate(null) - } else { - const nextStatus: TokenState['erc4626'] = { - isErc4626: Boolean(isErc4626 && isSuccess), - isLoading, - error, - isSuccess, - } - - const currentStatus = currentToken.erc4626 - const statusChanged = - currentStatus.isErc4626 !== nextStatus.isErc4626 || - currentStatus.isLoading !== nextStatus.isLoading || - currentStatus.error !== nextStatus.error || - currentStatus.isSuccess !== nextStatus.isSuccess - - if (statusChanged) { - setStatus(candidate.tokenId, nextStatus) - } - - if (!isLoading) { - const currentAddress = tokensInPool[candidate.tokenId].address - const addressesMatch = isAddress(currentAddress) && isAddressEqual(currentAddress, candidate.address) - - if (!addressesMatch) { - if (currentStatus.isErc4626 || currentStatus.isLoading || currentStatus.error || currentStatus.isSuccess) { - setStatus(candidate.tokenId, { ...DEFAULT_ERC4626_STATUS }) - } - setCandidate(null) - } else { - if (isErc4626) { - updateNgAssetType(candidate.tokenId, NG_ASSET_TYPE.ERC4626) - } - setCandidate(null) - } - } + // check if the status is already set to avoid overriding the user's choice when switching the order of tokens + const statusAlreadySet = useStore((state) => state.createPool.tokensInPool[tokenId].erc4626.isSuccess) + const { isErc4626, isLoading, error, isSuccess } = useIsErc4626({ address }) + + useMemo(() => { + if (isErc4626 && isSuccess && !statusAlreadySet) { + updateNgAssetType(tokenId, NG_ASSET_TYPE.ERC4626) + updateTokenErc4626Status(tokenId, { isErc4626, isLoading, error, isSuccess }) } - } - - return { - scheduleCheck, - } + }, [tokenId, isErc4626, isSuccess, isLoading, error, updateNgAssetType, updateTokenErc4626Status, statusAlreadySet]) } From 42db0b919ed10dfa1fb76c5e7930d38162ee6595 Mon Sep 17 00:00:00 2001 From: JustJousting Date: Fri, 12 Dec 2025 16:14:50 +0200 Subject: [PATCH 08/10] feat: ensure loading and error states are updated in the store --- .../PageCreatePool/hooks/useAutoDetectErc4626.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts index 175bc3174b..53a578eed0 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts @@ -21,6 +21,14 @@ export const useAutoDetectErc4626 = ({ tokenId, address }: UseAutoDetectErc4626P const { isErc4626, isLoading, error, isSuccess } = useIsErc4626({ address }) useMemo(() => { + if (isLoading) { + updateTokenErc4626Status(tokenId, { isErc4626, isLoading, error, isSuccess }) + } + + if (error) { + updateTokenErc4626Status(tokenId, { isErc4626, isLoading, error, isSuccess }) + } + if (isErc4626 && isSuccess && !statusAlreadySet) { updateNgAssetType(tokenId, NG_ASSET_TYPE.ERC4626) updateTokenErc4626Status(tokenId, { isErc4626, isLoading, error, isSuccess }) From 9e9713127dfc6731be9d489eac76426b4cb85219 Mon Sep 17 00:00:00 2001 From: JustJousting Date: Fri, 12 Dec 2025 16:24:26 +0200 Subject: [PATCH 09/10] fix: clean up state update logic --- .../PageCreatePool/hooks/useAutoDetectErc4626.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts index 53a578eed0..f809c2f56b 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts @@ -21,17 +21,13 @@ export const useAutoDetectErc4626 = ({ tokenId, address }: UseAutoDetectErc4626P const { isErc4626, isLoading, error, isSuccess } = useIsErc4626({ address }) useMemo(() => { - if (isLoading) { - updateTokenErc4626Status(tokenId, { isErc4626, isLoading, error, isSuccess }) - } - - if (error) { + if (isLoading || error || isSuccess) { updateTokenErc4626Status(tokenId, { isErc4626, isLoading, error, isSuccess }) } + // Only auto-set ngAssetType on first successful detection (don't override user's choice) if (isErc4626 && isSuccess && !statusAlreadySet) { updateNgAssetType(tokenId, NG_ASSET_TYPE.ERC4626) - updateTokenErc4626Status(tokenId, { isErc4626, isLoading, error, isSuccess }) } }, [tokenId, isErc4626, isSuccess, isLoading, error, updateNgAssetType, updateTokenErc4626Status, statusAlreadySet]) } From 4e913c9c88e3a82f6216f84890fdbd6ca6237d80 Mon Sep 17 00:00:00 2001 From: JustJousting Date: Mon, 15 Dec 2025 06:13:05 +0200 Subject: [PATCH 10/10] fix: remove extra isSuccess check --- .../dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts index f809c2f56b..859419c168 100644 --- a/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts +++ b/apps/main/src/dex/components/PageCreatePool/hooks/useAutoDetectErc4626.ts @@ -26,7 +26,7 @@ export const useAutoDetectErc4626 = ({ tokenId, address }: UseAutoDetectErc4626P } // Only auto-set ngAssetType on first successful detection (don't override user's choice) - if (isErc4626 && isSuccess && !statusAlreadySet) { + if (isErc4626 && !statusAlreadySet) { updateNgAssetType(tokenId, NG_ASSET_TYPE.ERC4626) } }, [tokenId, isErc4626, isSuccess, isLoading, error, updateNgAssetType, updateTokenErc4626Status, statusAlreadySet])