From 601d2ebe8173bab901b1c46305c24418ed7a3d76 Mon Sep 17 00:00:00 2001 From: Matt Pereira Date: Mon, 8 Sep 2025 17:00:09 -0700 Subject: [PATCH 1/6] move token amount inputs back to before creation starts --- .../PoolConfiguration/ChooseInfo/index.tsx | 2 + .../PoolCreation/ChooseTokenAmounts/index.tsx | 67 +++++++------------ .../app/v3/_components/PoolCreation/index.tsx | 27 +------- .../nextjs/app/v3/_components/PoolDetails.tsx | 21 +----- .../nextjs/hooks/v3/usePoolCreationStore.ts | 4 -- .../hooks/v3/useValidateCreationInputs.ts | 8 ++- 6 files changed, 35 insertions(+), 94 deletions(-) diff --git a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx index d3d38f5f..be0b0200 100644 --- a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx +++ b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from "react"; +import { ChooseTokenAmounts } from "../../PoolCreation/ChooseTokenAmounts"; import { PoolType } from "@balancer/sdk"; import { sonic } from "viem/chains"; import { TextField } from "~~/components/common"; @@ -66,6 +67,7 @@ export const ChooseInfo = () => { + ); }; diff --git a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx index 4773abed..e1e2e045 100644 --- a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx +++ b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx @@ -1,54 +1,37 @@ import React from "react"; import { ChooseTokenAmount } from "./ChooseTokenAmount"; import { PoolType } from "@balancer/sdk"; -import { XMarkIcon } from "@heroicons/react/24/outline"; -import { Alert, TransactionButton } from "~~/components/common"; -import { usePoolCreationStore, useUserDataStore, useValidateInitializationInputs } from "~~/hooks/v3"; +import { Alert } from "~~/components/common"; +import { usePoolCreationStore, useUserDataStore } from "~~/hooks/v3"; export function ChooseTokenAmounts() { - const { tokenConfigs, poolType, updatePool, step, setIsChooseTokenAmountsModalOpen } = usePoolCreationStore(); + const { tokenConfigs, poolType } = usePoolCreationStore(); const { updateUserData, hasAgreedToWarning } = useUserDataStore(); - const { isInitializePoolInputsValid } = useValidateInitializationInputs(); return ( -
-
- -
Choose Token Amounts
-
- {tokenConfigs.map((tokenConfig, index) => ( - - ))} -
- - {poolType === PoolType.Weighted && ( - - - - )} - - { - updatePool({ step: step + 1 }); - setIsChooseTokenAmountsModalOpen(false); - }} - title="Confirm Amounts" - isDisabled={!isInitializePoolInputsValid} - isPending={false} - /> +
+
Choose Token Amounts:
+
+ {tokenConfigs.map((tokenConfig, index) => ( + + ))}
+ + {poolType === PoolType.Weighted && ( + + + + )}
); } diff --git a/packages/nextjs/app/v3/_components/PoolCreation/index.tsx b/packages/nextjs/app/v3/_components/PoolCreation/index.tsx index 723c7caa..a55522c2 100644 --- a/packages/nextjs/app/v3/_components/PoolCreation/index.tsx +++ b/packages/nextjs/app/v3/_components/PoolCreation/index.tsx @@ -1,7 +1,6 @@ import { PoolDetails } from "../PoolDetails"; import { SupportAndResetModals } from "../SupportAndResetModals"; import { ApproveOnTokenManager } from "./ApproveOnTokenManager"; -import { ChooseTokenAmounts } from "./ChooseTokenAmounts"; import { PoolCreatedView } from "./PoolCreatedView"; import { Alert, PoolStepsDisplay, TransactionButton } from "~~/components/common"; import { useIsHyperEvm, useIsUsingBigBlocks, useToggleBlockSize } from "~~/hooks/hyperliquid"; @@ -21,17 +20,7 @@ import { getBlockExplorerTxLink } from "~~/utils/scaffold-eth"; * Manages the pool creation process using a modal that cannot be closed after execution of the first step */ export function PoolCreation({ setIsModalOpen }: { setIsModalOpen: (isOpen: boolean) => void }) { - const { - step, - tokenConfigs, - createPoolTx, - swapToBoostedTx, - initPoolTx, - chain, - poolAddress, - isChooseTokenAmountsModalOpen, - setIsChooseTokenAmountsModalOpen, - } = usePoolCreationStore(); + const { step, tokenConfigs, createPoolTx, swapToBoostedTx, initPoolTx, chain, poolAddress } = usePoolCreationStore(); const { data: boostableWhitelist } = useBoostableWhitelist(); const { mutate: createPool, isPending: isCreatePoolPending, error: createPoolError } = useCreatePool(); @@ -55,18 +44,6 @@ export function PoolCreation({ setIsModalOpen }: { setIsModalOpen: (isOpen: bool error: createPoolError || fetchPoolAddressError, }); - const chooseAmountsStep = { - label: "Choose Amounts", - component: ( - setIsChooseTokenAmountsModalOpen(true)} - title="Choose Token Amounts" - isDisabled={false} - isPending={false} - /> - ), - }; - const approveOnTokenSteps = tokenConfigs.map((token, idx) => { const { address, amount, tokenInfo } = token; const { decimals, symbol } = tokenInfo || {}; @@ -169,7 +146,6 @@ export function PoolCreation({ setIsModalOpen }: { setIsModalOpen: (isOpen: bool ...(showUseBigBlocksStep ? [useToggleBlockSizeStep] : []), deployStep, ...(showUseSmallBlocksStep ? [useToggleBlockSizeStep] : []), - chooseAmountsStep, ...approveOnTokenSteps, ...swapToBoostedStep, ...approveOnBoostedVariantSteps, @@ -217,7 +193,6 @@ export function PoolCreation({ setIsModalOpen }: { setIsModalOpen: (isOpen: bool
- {isChooseTokenAmountsModalOpen && } ); } diff --git a/packages/nextjs/app/v3/_components/PoolDetails.tsx b/packages/nextjs/app/v3/_components/PoolDetails.tsx index 06eac9a2..6a20d8d7 100644 --- a/packages/nextjs/app/v3/_components/PoolDetails.tsx +++ b/packages/nextjs/app/v3/_components/PoolDetails.tsx @@ -3,12 +3,7 @@ import { EclpChartDisplay } from "./PoolConfiguration/ChooseParameters/EclpParams"; import { PoolType } from "@balancer/sdk"; import { zeroAddress } from "viem"; -import { - ArrowTopRightOnSquareIcon, - CheckCircleIcon, - PencilSquareIcon, - QuestionMarkCircleIcon, -} from "@heroicons/react/24/outline"; +import { ArrowTopRightOnSquareIcon, CheckCircleIcon, QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; import { TokenImage, TokenToolTip } from "~~/components/common"; import { useTargetNetwork } from "~~/hooks/scaffold-eth"; import { @@ -41,8 +36,6 @@ export function PoolDetails({ isPreview }: { isPreview?: boolean }) { reClammParams, isDelegatingPauseManagement, isDelegatingSwapFeeManagement, - step, - setIsChooseTokenAmountsModalOpen, selectedTab, chain, } = usePoolCreationStore(); @@ -81,17 +74,7 @@ export function PoolDetails({ isPreview }: { isPreview?: boolean }) { isValid={isTokensValid} isEmpty={tokenConfigs.every(token => token.address === zeroAddress)} content={ -
- {(step === 2 || step === 3) && ( -
-
setIsChooseTokenAmountsModalOpen(true)} - > - -
-
- )} +
{tokenConfigs.map((token, index) => ( ))} diff --git a/packages/nextjs/hooks/v3/usePoolCreationStore.ts b/packages/nextjs/hooks/v3/usePoolCreationStore.ts index 76ccd020..fee49d5d 100644 --- a/packages/nextjs/hooks/v3/usePoolCreationStore.ts +++ b/packages/nextjs/hooks/v3/usePoolCreationStore.ts @@ -70,8 +70,6 @@ export interface PoolCreationStore { swapToBoostedTx: TransactionDetails; eclpParams: EclpParams; reClammParams: ReClammParams; - isChooseTokenAmountsModalOpen: boolean; - setIsChooseTokenAmountsModalOpen: (isOpen: boolean) => void; updatePool: (updates: Partial) => void; updateTokenConfig: (index: number, updates: Partial) => void; updateEclpParam: (updates: Partial) => void; @@ -149,8 +147,6 @@ export const usePoolCreationStore = create( set => ({ ...initialPoolCreationState, updatePool: (updates: Partial) => set(state => ({ ...state, ...updates })), - setIsChooseTokenAmountsModalOpen: (isOpen: boolean) => - set(state => ({ ...state, isChooseTokenAmountsModalOpen: isOpen })), updateTokenConfig: (index: number, updates: Partial) => set(state => { const newTokenConfigs = [...state.tokenConfigs]; diff --git a/packages/nextjs/hooks/v3/useValidateCreationInputs.ts b/packages/nextjs/hooks/v3/useValidateCreationInputs.ts index b8f314ed..653aac0c 100644 --- a/packages/nextjs/hooks/v3/useValidateCreationInputs.ts +++ b/packages/nextjs/hooks/v3/useValidateCreationInputs.ts @@ -2,7 +2,7 @@ import { PoolType, STABLE_POOL_CONSTRAINTS, TokenType } from "@balancer/sdk"; import { useQueryClient } from "@tanstack/react-query"; import { isAddress } from "viem"; import { useEclpParamValidations } from "~~/hooks/gyro"; -import { usePoolCreationStore, useValidateHooksContract } from "~~/hooks/v3"; +import { usePoolCreationStore, useValidateHooksContract, useValidateInitializationInputs } from "~~/hooks/v3"; import { MAX_POOL_NAME_LENGTH, MAX_POOL_SYMBOL_LENGTH } from "~~/utils/constants"; export function useValidateCreationInputs() { @@ -26,6 +26,8 @@ export function useValidateCreationInputs() { const { baseParamsError, derivedParamsError } = useEclpParamValidations(eclpParams); + const { isInitializePoolInputsValid } = useValidateInitializationInputs(); + const isTypeValid = poolType !== undefined; const isValidTokenWeights = @@ -47,7 +49,6 @@ export function useValidateCreationInputs() { } return true; }) && isValidTokenWeights; - // Check tanstack query cache for pool hooks contract validity const { isValidPoolHooksContract } = useValidateHooksContract(poolHooksContract); @@ -79,7 +80,8 @@ export function useValidateCreationInputs() { symbol.length <= MAX_POOL_SYMBOL_LENGTH && isValidTokenWeights; - const isPoolCreationInputValid = isTypeValid && isTokensValid && isParametersValid && isInfoValid; + const isPoolCreationInputValid = + isTypeValid && isTokensValid && isParametersValid && isInfoValid && isInitializePoolInputsValid; return { isParametersValid, isTypeValid, isInfoValid, isTokensValid, isPoolCreationInputValid, isValidTokenWeights }; } From b505679316699017f489f5c323de114c247782b3 Mon Sep 17 00:00:00 2001 From: Matt Pereira Date: Wed, 10 Sep 2025 16:58:16 -0700 Subject: [PATCH 2/6] use eclp init amounts ratio to proportionally fill other token --- .../PoolConfiguration/ChooseInfo/index.tsx | 6 +-- .../ChooseParameters/EclpParams.tsx | 44 +++---------------- .../ChooseTokens/ChooseToken.tsx | 1 + .../ChooseTokenAmounts/ChooseTokenAmount.tsx | 41 +++++++++++------ .../PoolCreation/ChooseTokenAmounts/index.tsx | 27 ++++++++++-- packages/nextjs/hooks/gyro/index.ts | 2 + .../hooks/gyro/useEclpInitAmountsRatio.ts | 40 +++++++++++++++++ .../nextjs/hooks/gyro/useInvertEclpParams.ts | 36 +++++++++++++++ 8 files changed, 139 insertions(+), 58 deletions(-) create mode 100644 packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts create mode 100644 packages/nextjs/hooks/gyro/useInvertEclpParams.ts diff --git a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx index be0b0200..0261bd62 100644 --- a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx +++ b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseInfo/index.tsx @@ -39,8 +39,8 @@ export const ChooseInfo = () => {
Choose pool information:
-
-
+
+
{ />
-
+
{ - const D18 = 10n ** 18n; - - const { alpha, beta, peakPrice, c, s, lambda, usdPerTokenInput0, usdPerTokenInput1 } = eclpParams; - - // take reciprocal and flip alpha to beta - const invertedAlpha = Number(formatUnits((D18 * D18) / parseUnits(beta, 18), 18)); - // take reciprocal and flip beta to alpha - const invertedBeta = Number(formatUnits((D18 * D18) / parseUnits(alpha, 18), 18)); - // take reciprocal of peakPrice - const invertedPeakPrice = Number(formatUnits((D18 * D18) / parseUnits(peakPrice, 18), 18)); - - const invertedParams = { - alpha: formatEclpParamValues(invertedAlpha), - beta: formatEclpParamValues(invertedBeta), - peakPrice: formatEclpParamValues(invertedPeakPrice), - c: s, // flip c and s - s: c, // flip s and c - usdPerTokenInput0: usdPerTokenInput1, - usdPerTokenInput1: usdPerTokenInput0, - lambda, // stays the same - }; - - updateEclpParam(invertedParams); - updatePool({ tokenConfigs: [...tokenConfigs].reverse() }); - }; + + const { invertEclpParams } = useInvertEclpParams(); return (
@@ -89,7 +57,7 @@ export function EclpChartDisplay({ size }: { size: "full" | "mini" }) { {size === "full" && (
diff --git a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseTokens/ChooseToken.tsx b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseTokens/ChooseToken.tsx index ff188a41..8dca2c20 100644 --- a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseTokens/ChooseToken.tsx +++ b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseTokens/ChooseToken.tsx @@ -67,6 +67,7 @@ export function ChooseToken({ index }: { index: number }) { tokenInfo: { ...tokenInfo }, useBoostedVariant: false, paysYieldFees: false, + amount: "", }); // Gross pattern but works for triggering new autofill of pool type specific params? diff --git a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx index 1b997bfa..76b4e8e2 100644 --- a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx +++ b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx @@ -4,8 +4,9 @@ import { PoolType } from "@balancer/sdk"; import { useQueryClient } from "@tanstack/react-query"; import { erc20Abi, formatUnits } from "viem"; import { useAccount, useReadContract } from "wagmi"; +import { useEclpInitAmountsRatio } from "~~/hooks/gyro"; import { useTokenUsdValue } from "~~/hooks/token"; -import { type TokenConfig, usePoolCreationStore, useUserDataStore } from "~~/hooks/v3"; +import { type TokenConfig, useFetchTokenRate, usePoolCreationStore, useUserDataStore } from "~~/hooks/v3"; export function ChooseTokenAmount({ index, tokenConfig }: { index: number; tokenConfig: TokenConfig }) { const { updateUserData, userTokenBalances } = useUserDataStore(); @@ -44,22 +45,36 @@ export function ChooseTokenAmount({ index, tokenConfig }: { index: number; token return basePrice * Number(formatUnits(rate, 18)); }; - const handleAmountChange = (e: React.ChangeEvent) => { - const inputValue = e.target.value.trim(); - if (Number(inputValue) >= 0) { - if (poolType === PoolType.GyroE) { - const otherIndex = index === 0 ? 1 : 0; + const { data: rateTokenA } = useFetchTokenRate(tokenConfigs[0].rateProvider); + const { data: rateTokenB } = useFetchTokenRate(tokenConfigs[1].rateProvider); - const referenceTokenPrice = getRateAdjustedUsdPrice(index); - const otherTokenPrice = getRateAdjustedUsdPrice(otherIndex); + const { alpha, beta, c, s, lambda } = eclpParams; - const calculatedAmount = (Number(inputValue) * referenceTokenPrice) / otherTokenPrice; + const initAmountsRatio = useEclpInitAmountsRatio({ + alpha: Number(alpha), + beta: Number(beta), + c: Number(c), + s: Number(s), + lambda: Number(lambda), + rateA: rateTokenA ? +formatUnits(rateTokenA, 18) : 1, + rateB: rateTokenB ? +formatUnits(rateTokenB, 18) : 1, + }); - updateTokenConfig(index, { amount: inputValue }); - updateTokenConfig(otherIndex, { amount: calculatedAmount.toString() }); // update other token input to be proportional - } else { - updateTokenConfig(index, { amount: inputValue }); + const handleAmountChange = (e: React.ChangeEvent) => { + const referenceAmount = Number(e.target.value.trim()); + if (referenceAmount >= 0) { + if (poolType === PoolType.GyroE && initAmountsRatio) { + // app forces tokens to be sorted before offering init amounts inputs + const isReferenceAmountForTokenA = index === 0; + const otherIndex = isReferenceAmountForTokenA ? 1 : 0; + + const otherTokenAmount = isReferenceAmountForTokenA + ? referenceAmount / initAmountsRatio // If entering tokenA, divide to get tokenB amount + : referenceAmount * initAmountsRatio; // If entering tokenB, multiply to get tokenA amount + + updateTokenConfig(otherIndex, { amount: Math.abs(otherTokenAmount).toString() }); } + updateTokenConfig(index, { amount: referenceAmount.toString() }); } else { updateTokenConfig(index, { amount: "" }); } diff --git a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx index e1e2e045..aadaeff2 100644 --- a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx +++ b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx @@ -1,17 +1,36 @@ -import React from "react"; +import React, { useEffect, useRef } from "react"; import { ChooseTokenAmount } from "./ChooseTokenAmount"; import { PoolType } from "@balancer/sdk"; import { Alert } from "~~/components/common"; +import { useInvertEclpParams } from "~~/hooks/gyro"; import { usePoolCreationStore, useUserDataStore } from "~~/hooks/v3"; export function ChooseTokenAmounts() { const { tokenConfigs, poolType } = usePoolCreationStore(); const { updateUserData, hasAgreedToWarning } = useUserDataStore(); + const isTokenConfigsSorted = tokenConfigs.every((token, index) => { + if (index === 0) return true; + return token.address.toLowerCase() >= tokenConfigs[index - 1].address.toLowerCase(); + }); + + const shouldInvertEclpParams = !isTokenConfigsSorted && poolType === PoolType.GyroE; + + const { invertEclpParams } = useInvertEclpParams(); + const hasInvertedRef = useRef(false); + + // force token configs to be sorted in order before user enters amounts + useEffect(() => { + if (shouldInvertEclpParams && !hasInvertedRef.current) { + invertEclpParams(); + hasInvertedRef.current = true; + } + }, [shouldInvertEclpParams, invertEclpParams]); + return ( -
-
Choose Token Amounts:
-
+
+
Choose initialization amounts:
+
{tokenConfigs.map((tokenConfig, index) => ( ))} diff --git a/packages/nextjs/hooks/gyro/index.ts b/packages/nextjs/hooks/gyro/index.ts index 4854d9c9..2c57dd6a 100644 --- a/packages/nextjs/hooks/gyro/index.ts +++ b/packages/nextjs/hooks/gyro/index.ts @@ -4,3 +4,5 @@ export * from "./drawLiquidityECLP"; export * from "./useValidateEclpParams"; export * from "./useAutofillStarterParams"; export * from "./useEclpSpotPrice"; +export * from "./useEclpInitAmountsRatio"; +export * from "./useInvertEclpParams"; diff --git a/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts b/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts new file mode 100644 index 00000000..f364faeb --- /dev/null +++ b/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts @@ -0,0 +1,40 @@ +import { useEclpSpotPrice } from "./useEclpSpotPrice"; + +type UseEclpInitAmountsRatio = { + alpha: number; + beta: number; + c: number; + s: number; + lambda: number; + rateA: number; + rateB: number; +}; + +/** + * helper function for calculation of proper token amounts for ECLP initialization + * logic provided by @joaobrunoah + */ +export function useEclpInitAmountsRatio({ alpha, beta, c, s, lambda, rateA, rateB }: UseEclpInitAmountsRatio) { + const { poolSpotPrice: spotPriceWithoutRate } = useEclpSpotPrice(); + if (!spotPriceWithoutRate) return undefined; + + const rHint = 1000; + const tauAlpha = getTau(alpha, c, s, lambda); + const tauBeta = getTau(beta, c, s, lambda); + const tauSpotPrice = getTau(spotPriceWithoutRate, c, s, lambda); + + const amountTokenA = + rateA * rHint * (c * lambda * tauBeta[0] + s * tauBeta[1]) - (c * lambda * tauSpotPrice[0] + s * tauSpotPrice[1]); + const amountTokenB = + rateB * rHint * (-s * lambda * tauAlpha[0] + c * tauAlpha[1]) - + (-s * lambda * tauSpotPrice[0] + c * tauSpotPrice[1]); + const ratio = amountTokenA / amountTokenB; + + console.log({ alpha, beta, c, s, lambda, rateA, rateB, spotPriceWithoutRate, amountTokenA, amountTokenB, ratio }); + + return ratio; +} + +function getTau(price: number, c: number, s: number, lambda: number) { + return [(price * c) / (1 - s), (c + s * price) / lambda]; +} diff --git a/packages/nextjs/hooks/gyro/useInvertEclpParams.ts b/packages/nextjs/hooks/gyro/useInvertEclpParams.ts new file mode 100644 index 00000000..488d351b --- /dev/null +++ b/packages/nextjs/hooks/gyro/useInvertEclpParams.ts @@ -0,0 +1,36 @@ +import { useCallback } from "react"; +import { usePoolCreationStore } from "../v3"; +import { formatUnits, parseUnits } from "viem"; +import { formatEclpParamValues } from "~~/utils/gryo"; + +const D18 = 10n ** 18n; + +export function useInvertEclpParams() { + const { eclpParams, updateEclpParam, updatePool, tokenConfigs } = usePoolCreationStore(); + + const invertEclpParams = useCallback(() => { + const { alpha, beta, peakPrice, c, s, usdPerTokenInput0, usdPerTokenInput1 } = eclpParams; + + const invertedAlpha = Number(formatUnits((D18 * D18) / parseUnits(alpha, 18), 18)); + const invertedBeta = Number(formatUnits((D18 * D18) / parseUnits(beta, 18), 18)); + const invertedPeakPrice = Number(formatUnits((D18 * D18) / parseUnits(peakPrice, 18), 18)); + + const invertedParams = { + alpha: formatEclpParamValues(invertedBeta), // flip alpha to inverted beta + beta: formatEclpParamValues(invertedAlpha), // flip beta to inverted alpha + peakPrice: formatEclpParamValues(invertedPeakPrice), + c: s, // flip c to s + s: c, // flip s to c + usdPerTokenInput0: usdPerTokenInput1, + usdPerTokenInput1: usdPerTokenInput0, + }; + + // Use a timeout to ensure this update happens after other effects + setTimeout(() => { + updateEclpParam(invertedParams); + updatePool({ tokenConfigs: [...tokenConfigs].reverse() }); + }, 0); + }, [eclpParams, updateEclpParam, updatePool, tokenConfigs]); + + return { invertEclpParams }; +} From d1a91c065b854a8980feee56d4d0f9bd00d47931 Mon Sep 17 00:00:00 2001 From: Matt Pereira Date: Thu, 11 Sep 2025 10:45:12 -0700 Subject: [PATCH 3/6] update getTau computation --- packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts b/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts index f364faeb..dee740c2 100644 --- a/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts +++ b/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts @@ -36,5 +36,5 @@ export function useEclpInitAmountsRatio({ alpha, beta, c, s, lambda, rateA, rate } function getTau(price: number, c: number, s: number, lambda: number) { - return [(price * c) / (1 - s), (c + s * price) / lambda]; + return [price * c - s, (c + s * price) / lambda]; } From bd5ba2be4a5e6ec4b2efc2457fcd241817848aa7 Mon Sep 17 00:00:00 2001 From: Matt Pereira Date: Thu, 11 Sep 2025 10:56:10 -0700 Subject: [PATCH 4/6] clean up UX --- .../ChooseTokenAmounts/ChooseTokenAmount.tsx | 1 - packages/nextjs/app/v3/_components/PoolDetails.tsx | 10 ---------- packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts | 4 +--- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx index 76b4e8e2..d73701bd 100644 --- a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx +++ b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx @@ -17,7 +17,6 @@ export function ChooseTokenAmount({ index, tokenConfig }: { index: number; token const [usdValue, setUsdValue] = useState(null); const queryClient = useQueryClient(); - const usdPerToken0 = Number(usdPerTokenInput0); const usdPerToken1 = Number(usdPerTokenInput1); diff --git a/packages/nextjs/app/v3/_components/PoolDetails.tsx b/packages/nextjs/app/v3/_components/PoolDetails.tsx index 6a20d8d7..5811dea8 100644 --- a/packages/nextjs/app/v3/_components/PoolDetails.tsx +++ b/packages/nextjs/app/v3/_components/PoolDetails.tsx @@ -1,6 +1,5 @@ "use client"; -import { EclpChartDisplay } from "./PoolConfiguration/ChooseParameters/EclpParams"; import { PoolType } from "@balancer/sdk"; import { zeroAddress } from "viem"; import { ArrowTopRightOnSquareIcon, CheckCircleIcon, QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; @@ -36,7 +35,6 @@ export function PoolDetails({ isPreview }: { isPreview?: boolean }) { reClammParams, isDelegatingPauseManagement, isDelegatingSwapFeeManagement, - selectedTab, chain, } = usePoolCreationStore(); const { poolHooksWhitelist } = usePoolHooksWhitelist(chain?.id); @@ -49,12 +47,9 @@ export function PoolDetails({ isPreview }: { isPreview?: boolean }) { const poolDeploymentUrl = poolAddress ? getBlockExplorerAddressLink(targetNetwork, poolAddress) : undefined; - const isGyroEclp = poolType === PoolType.GyroE; const isStablePool = poolType === PoolType.Stable || poolType === PoolType.StableSurge; const isReClamm = poolType === PoolType.ReClamm; - const showMiniEclpChart = isGyroEclp && selectedTab === "Information"; - const poolHooksName = poolHooksWhitelist.find( hook => hook.value.toLowerCase() === poolHooksContract.toLowerCase(), )?.label; @@ -89,11 +84,6 @@ export function PoolDetails({ isPreview }: { isPreview?: boolean }) { isEmpty={false} content={
- {showMiniEclpChart && ( -
- -
- )} {isStablePool && (
Amplification Parameter
diff --git a/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts b/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts index dee740c2..1f96ed5b 100644 --- a/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts +++ b/packages/nextjs/hooks/gyro/useEclpInitAmountsRatio.ts @@ -16,7 +16,7 @@ type UseEclpInitAmountsRatio = { */ export function useEclpInitAmountsRatio({ alpha, beta, c, s, lambda, rateA, rateB }: UseEclpInitAmountsRatio) { const { poolSpotPrice: spotPriceWithoutRate } = useEclpSpotPrice(); - if (!spotPriceWithoutRate) return undefined; + if (!spotPriceWithoutRate || !alpha || !beta || !c || !s || !lambda || !rateA || !rateB) return undefined; const rHint = 1000; const tauAlpha = getTau(alpha, c, s, lambda); @@ -30,8 +30,6 @@ export function useEclpInitAmountsRatio({ alpha, beta, c, s, lambda, rateA, rate (-s * lambda * tauSpotPrice[0] + c * tauSpotPrice[1]); const ratio = amountTokenA / amountTokenB; - console.log({ alpha, beta, c, s, lambda, rateA, rateB, spotPriceWithoutRate, amountTokenA, amountTokenB, ratio }); - return ratio; } From 1e45b4d17a58fd72e23b552aa21df910a9ce8262 Mon Sep 17 00:00:00 2001 From: Matt Pereira Date: Thu, 11 Sep 2025 11:01:05 -0700 Subject: [PATCH 5/6] clean up UX bugs --- .../ChooseParameters/LiquidityManagement.tsx | 6 +++--- .../ChooseParameters/PoolHooks.tsx | 4 ++-- .../PoolConfiguration/ChooseType/index.tsx | 14 ++++++-------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseParameters/LiquidityManagement.tsx b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseParameters/LiquidityManagement.tsx index 0f5cdcf0..879d9d3f 100644 --- a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseParameters/LiquidityManagement.tsx +++ b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseParameters/LiquidityManagement.tsx @@ -26,9 +26,9 @@ export function LiquidityManagement() { {poolType === PoolType.StableSurge ? (
-
- Stable Surge pools must set disable unbalanced liquidity to false - +
+ + Stable Surge pools must allow unbalanced liquidity operations
-
- Stable surge pools must use Balancer's stable surge hook +
+ Stable surge pools must use Balancer's stable surge hook
) : ( diff --git a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseType/index.tsx b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseType/index.tsx index b174ee0e..b45102f9 100644 --- a/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseType/index.tsx +++ b/packages/nextjs/app/v3/_components/PoolConfiguration/ChooseType/index.tsx @@ -7,14 +7,12 @@ export function ChooseType() { return ( <> -
-
-
Choose a pool type:
-
- {poolTypes.map((type: SupportedPoolTypes) => ( - - ))} -
+
+
Choose a pool type:
+
+ {poolTypes.map((type: SupportedPoolTypes) => ( + + ))}
From 99b445e43db99d8dd1355ebc78f7e8b536eb95c5 Mon Sep 17 00:00:00 2001 From: Matt Pereira Date: Thu, 11 Sep 2025 11:25:28 -0700 Subject: [PATCH 6/6] allow user to toggle init amount autofill --- .../ChooseTokenAmounts/ChooseTokenAmount.tsx | 12 ++++++-- .../PoolCreation/ChooseTokenAmounts/index.tsx | 30 +++++++++++++++---- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx index d73701bd..180f5410 100644 --- a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx +++ b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/ChooseTokenAmount.tsx @@ -8,7 +8,15 @@ import { useEclpInitAmountsRatio } from "~~/hooks/gyro"; import { useTokenUsdValue } from "~~/hooks/token"; import { type TokenConfig, useFetchTokenRate, usePoolCreationStore, useUserDataStore } from "~~/hooks/v3"; -export function ChooseTokenAmount({ index, tokenConfig }: { index: number; tokenConfig: TokenConfig }) { +export function ChooseTokenAmount({ + index, + tokenConfig, + useSuggestedAmounts, +}: { + index: number; + tokenConfig: TokenConfig; + useSuggestedAmounts: boolean; +}) { const { updateUserData, userTokenBalances } = useUserDataStore(); const { poolType, updateTokenConfig, eclpParams, tokenConfigs } = usePoolCreationStore(); const { tokenInfo, amount, address, weight } = tokenConfig; @@ -62,7 +70,7 @@ export function ChooseTokenAmount({ index, tokenConfig }: { index: number; token const handleAmountChange = (e: React.ChangeEvent) => { const referenceAmount = Number(e.target.value.trim()); if (referenceAmount >= 0) { - if (poolType === PoolType.GyroE && initAmountsRatio) { + if (poolType === PoolType.GyroE && initAmountsRatio && useSuggestedAmounts) { // app forces tokens to be sorted before offering init amounts inputs const isReferenceAmountForTokenA = index === 0; const otherIndex = isReferenceAmountForTokenA ? 1 : 0; diff --git a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx index aadaeff2..cb6c1ad5 100644 --- a/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx +++ b/packages/nextjs/app/v3/_components/PoolCreation/ChooseTokenAmounts/index.tsx @@ -1,14 +1,19 @@ -import React, { useEffect, useRef } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { ChooseTokenAmount } from "./ChooseTokenAmount"; import { PoolType } from "@balancer/sdk"; import { Alert } from "~~/components/common"; +import { Checkbox } from "~~/components/common"; import { useInvertEclpParams } from "~~/hooks/gyro"; import { usePoolCreationStore, useUserDataStore } from "~~/hooks/v3"; export function ChooseTokenAmounts() { + const [useSuggestedAmounts, setUseSuggestedAmounts] = useState(false); const { tokenConfigs, poolType } = usePoolCreationStore(); const { updateUserData, hasAgreedToWarning } = useUserDataStore(); + const isGyroEclp = poolType === PoolType.GyroE; + const isWeightedPool = poolType === PoolType.Weighted; + const isTokenConfigsSorted = tokenConfigs.every((token, index) => { if (index === 0) return true; return token.address.toLowerCase() >= tokenConfigs[index - 1].address.toLowerCase(); @@ -28,15 +33,30 @@ export function ChooseTokenAmounts() { }, [shouldInvertEclpParams, invertEclpParams]); return ( -
-
Choose initialization amounts:
+
+
Choose initialization amounts:
+
+ {isGyroEclp && ( + { + setUseSuggestedAmounts(!useSuggestedAmounts); + }} + /> + )} {tokenConfigs.map((tokenConfig, index) => ( - + ))}
- {poolType === PoolType.Weighted && ( + {isWeightedPool && (