Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/lib/modules/pool/actions/create/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export const MAX_POOL_NAME_LENGTH = 32
export const MAX_POOL_SYMBOL_LENGTH = 26
export const MAX_SWAP_FEE_PERCENTAGE = 10
export const REQUIRED_TOTAL_WEIGHT = 100
export const AMPLIFICATION_PARAMETER_OPTIONS = ['100', '1000']
export const MIN_AMPLIFICATION_PARAMETER = Number(STABLE_POOL_CONSTRAINTS.MIN_AMP)
export const MAX_AMPLIFICATION_PARAMETER = Number(STABLE_POOL_CONSTRAINTS.MAX_AMP)
export const MAX_LAMBDA = 100000000
Expand Down Expand Up @@ -160,7 +159,7 @@ export const INITIAL_POOL_CREATION_FORM: PoolCreationForm = {
pauseManager: zeroAddress,
poolCreator: zeroAddress,
swapFeePercentage: getSwapFeePercentageOptions(PoolType.Stable)[0].value,
amplificationParameter: AMPLIFICATION_PARAMETER_OPTIONS[0],
amplificationParameter: '100',
poolHooksContract: zeroAddress,
enableDonation: false,
disableUnbalancedLiquidity: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export function LiquidityManagement() {
isStableSurgePool(poolType) || hookFlags?.enableHookAdjustedAmounts
const isDonationToggleDisabled = isReClammPool(poolType)

const donationsToolTip = isDonationToggleDisabled
? 'The selected pool type does not allow donations to be enabled'
: 'Option to add liquidity to a pool without minting additional LP tokens. Most pools should NOT allow donations. Only recommended for advanced users.'

return (
<VStack align="start" spacing="md" w="full">
<HStack>
Expand All @@ -59,11 +63,7 @@ export function LiquidityManagement() {
onChange={e => poolCreationForm.setValue('disableUnbalancedLiquidity', !e.target.checked)}
/>
</TooltipWithTouch>
<TooltipWithTouch
isHidden={!isDonationToggleDisabled}
label="The reClamm pool factory does not allow donations"
placement="right"
>
<TooltipWithTouch label={donationsToolTip} placement="right">
<PoolCreationCheckbox
isChecked={enableDonation}
isDisabled={isDonationToggleDisabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ import { usePoolCreationForm } from '../../PoolCreationFormProvider'
import { validatePoolDetails } from '../../validatePoolCreationForm'
import { useWatch } from 'react-hook-form'
import { useEffect, useRef } from 'react'
import { PoolCreationToken, SupportedPoolTypes } from '../../types'
import { isWeightedPool } from '../../helpers'
import { MAX_POOL_NAME_LENGTH, MAX_POOL_SYMBOL_LENGTH } from '../../constants'
import { PoolType } from '@balancer/sdk'
import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig'

export function PoolDetails() {
const { poolCreationForm } = usePoolCreationForm()
const poolTokens = useWatch({ control: poolCreationForm.control, name: 'poolTokens' })

const tokenSymbols = poolTokens.map(token => {
const { data, weight } = token
if (!data) return ''
if (!weight) return data.symbol
return weight + '% ' + data.symbol
const [poolTokens, poolType] = useWatch({
control: poolCreationForm.control,
name: ['poolTokens', 'poolType'],
})

const suggestedPoolName = tokenSymbols.join(' / ')
const suggestedPoolSymbol = tokenSymbols.join('-').replace(/% /g, '-')
const { suggestedPoolName, suggestedPoolSymbol } = getSuggestions(poolTokens, poolType)

const hasInitialized = useRef(false)

useEffect(() => {
if (hasInitialized.current) return
if (!suggestedPoolName || suggestedPoolName === ' / ') return
if (!suggestedPoolName || suggestedPoolName === '-') return

const currentName = poolCreationForm.getValues('name')
const currentSymbol = poolCreationForm.getValues('symbol')
Expand Down Expand Up @@ -76,3 +76,33 @@ export function PoolDetails() {
</VStack>
)
}

function getSuggestions(poolTokens: PoolCreationToken[], poolType: SupportedPoolTypes) {
const poolTypePrefixMap: Partial<Record<SupportedPoolTypes, string>> = {
[PoolType.StableSurge]: 'surge',
[PoolType.ReClamm]: 'reCLAMM',
}

const poolTypePrefix = poolTypePrefixMap[poolType] ?? ''

const tokenSymbols = poolTokens
.map(({ data, weight }) => {
if (!data?.symbol) return ''
if (!isWeightedPool(poolType) || !weight) return data.symbol
return weight + data.symbol
})
.join('-')

const poolSymbol = poolTypePrefix ? `${poolTypePrefix}-${tokenSymbols}` : tokenSymbols
const suggestedPoolSymbol =
poolSymbol.length <= MAX_POOL_SYMBOL_LENGTH ? poolSymbol : tokenSymbols
Copy link
Copy Markdown
Collaborator

@groninge01 groninge01 Apr 7, 2026

Choose a reason for hiding this comment

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

what if tokenSymbols.length or poolName.length (on ln 97) is bigger than MAX_POOL_SYMBOL_LENGTH?

Copy link
Copy Markdown
Member Author

@MattPereira MattPereira Apr 7, 2026

Choose a reason for hiding this comment

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

any ideas on how best to handle?

arbitrarily slicing the whole string to fit doesnt make sense imo

slicing each token symbol some to fit also seems messy

my initial thought was to just let user decide 👉 #2311 (comment)


const { projectName } = PROJECT_CONFIG

const poolName = poolTypePrefix ? `${poolTypePrefix} ${tokenSymbols}` : tokenSymbols
const poolNameWithProject = `${projectName} ${poolName}`
const suggestedPoolName =
poolNameWithProject.length <= MAX_POOL_NAME_LENGTH ? poolNameWithProject : poolName

return { suggestedPoolName, suggestedPoolSymbol }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { usePoolCreationForm } from '../../PoolCreationFormProvider'
import { PoolSettingsRadioGroup } from './PoolSettingsRadioGroup'
import { LiquidityManagement } from './LiquidityManagement'
import { BlockExplorerLink } from '@repo/lib/shared/components/BlockExplorerLink'
import { AMPLIFICATION_PARAMETER_OPTIONS } from '../../constants'
import { getSwapFeePercentageOptions } from '../../helpers'
import { validatePoolSettings } from '../../validatePoolCreationForm'
import { usePoolHooksWhitelist } from './usePoolHooksWhitelist'
Expand All @@ -16,6 +15,12 @@ import { useUserAccount } from '@repo/lib/modules/web3/UserAccountProvider'
import { PROJECT_CONFIG } from '@repo/lib/config/getProjectConfig'
import { isStablePool, isStableSurgePool, isPoolCreatorEnabled } from '../../helpers'
import { useWatch } from 'react-hook-form'
import { ConfigOptionsGroup } from './ReClammConfiguration'
import {
SteepCurve,
FlatCurve,
VeryFlatCurve,
} from '@repo/lib/shared/components/imgs/AmplificationParameterSvgs'

export type PoolSettingsOption = {
label: string
Expand Down Expand Up @@ -65,10 +70,6 @@ export function PoolSettings() {
...(filteredPoolHooksOptions || []),
]

const amplificationParameterOptions: PoolSettingsOption[] = AMPLIFICATION_PARAMETER_OPTIONS.map(
value => ({ label: value, value })
)

useEffect(() => {
if (isStableSurgePool(poolType) && poolHooksWhitelist) {
const stableSurgeHookMetadata = poolHooksWhitelist.find(hook => hook.label === 'StableSurge')
Expand Down Expand Up @@ -109,6 +110,45 @@ export function PoolSettings() {

return (
<VStack align="start" spacing="lg" w="full">
{showAmplificationParameter && (
<>
<Heading color="font.maxContrast" size="md">
Stable Pool Configuration
</Heading>
<ConfigOptionsGroup
control={poolCreationForm.control}
customInputLabel="Custom amplification parameter"
label="Amplification parameter"
name="amplificationParameter"
options={[
{
label: 'Steep curve',
displayValue: '100',
rawValue: '100',
svg: SteepCurve,
},
{
label: 'Flat curve',
displayValue: '1,000',
rawValue: '1000',
svg: FlatCurve,
},
{
label: 'Very flat curve',
displayValue: '10,000',
rawValue: '10000',
svg: VeryFlatCurve,
},
]}
tooltip="Controls the 'flatness' of the invariant curve. Higher values = lower slippage and assumes prices are near parity. Lower values = closer to the constant product curve (e.g., more like a weighted pool). This has higher slippage and accommodates greater price volatility."
updateFn={(value: string) => {
poolCreationForm.setValue('amplificationParameter', value, { shouldValidate: true })
}}
validateFn={(value: string) => validatePoolSettings.amplificationParameter(value)}
/>
</>
)}

<Heading color="font.maxContrast" size="md">
Pool settings
</Heading>
Expand Down Expand Up @@ -156,18 +196,6 @@ export function PoolSettings() {
validate={value => validatePoolSettings.swapFeePercentage(value, poolType)}
/>

{showAmplificationParameter && (
<PoolSettingsRadioGroup
customInputLabel="Custom amplification parameter"
customInputType="number"
name="amplificationParameter"
options={amplificationParameterOptions}
title="Amplification parameter"
tooltip='Controls the "flatness" of the invariant curve. Higher values = lower slippage and assumes prices are near parity. Lower values = closer to the constant product curve (e.g., more like a weighted pool). This has higher slippage and accommodates greater price volatility.'
validate={validatePoolSettings.amplificationParameter}
/>
)}

{showPoolHooks && (
<PoolSettingsRadioGroup
customInputLabel="Custom pool hooks address"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { Heading, VStack, Text, HStack, Radio, SimpleGrid, useRadioGroup } from '@chakra-ui/react'
import { InfoIconPopover } from '../../InfoIconPopover'
import {
useReClammConfigurationOptions,
ReClammConfigOptionsGroup,
} from './useReClammConfigurationOptions'
import { useReClammConfigurationOptions } from './useReClammConfigurationOptions'
import { usePoolCreationForm } from '../../PoolCreationFormProvider'
import { NumberInput } from '@repo/lib/shared/components/inputs/NumberInput'
import { bn } from '@repo/lib/shared/utils/numbers'
import { getPercentFromPrice } from '../../helpers'
import { formatNumber } from '../../helpers'
import { RadioCard } from '@repo/lib/shared/components/inputs/RadioCardGroup'
import { useWatch } from 'react-hook-form'
import { useWatch, Control } from 'react-hook-form'
import { useState, SVGProps } from 'react'

export function ReClammConfiguration() {
const reClammConfigurationOptions = useReClammConfigurationOptions()
Expand All @@ -22,6 +20,7 @@ export function ReClammConfiguration() {
</Heading>
{reClammConfigurationOptions.map(option => (
<ConfigOptionsGroup
control={option.control}
customInputLabel={option.customInputLabel}
key={option.label}
label={option.label}
Expand All @@ -36,25 +35,43 @@ export function ReClammConfiguration() {
)
}

function ConfigOptionsGroup({
export type ConfigOptionsGroupProps = {
label: string
options: {
label: string
displayValue: string
rawValue: string
svg?: React.ComponentType<SVGProps<SVGSVGElement>>
}[]
updateFn: (rawValue: string) => void
validateFn: (value: string) => string | boolean
name: string
control: Control<any>
customInputLabel: string
tooltip: string
}

export function ConfigOptionsGroup({
label,
options,
updateFn,
validateFn,
name,
control,
customInputLabel,
tooltip,
}: ReClammConfigOptionsGroup) {
}: ConfigOptionsGroupProps) {
const { reClammConfigForm } = usePoolCreationForm()
const [initialMinPrice, initialTargetPrice, initialMaxPrice] = useWatch({
control: reClammConfigForm.control,
name: ['initialMinPrice', 'initialTargetPrice', 'initialMaxPrice'],
})
const formValue = useWatch({ control: reClammConfigForm.control, name })
const [forceCustom, setForceCustom] = useState(false)
const formValue = useWatch({ control, name })
const normalizedFormValue = formValue?.toString?.() ?? ''
const matchedOption = options.find(option => {
if (option.rawValue === normalizedFormValue) return true
if (option.rawValue === '' || normalizedFormValue === '') return false
if (normalizedFormValue === '') return false

const optionNumber = Number(option.rawValue)
const formValueNumber = Number(normalizedFormValue)
Expand All @@ -64,18 +81,20 @@ function ConfigOptionsGroup({
return optionNumber === formValueNumber
})

const isCustom = matchedOption ? matchedOption.rawValue === '' : normalizedFormValue !== ''
const isCustom = forceCustom || (!matchedOption && normalizedFormValue !== '')
const selectedValue = isCustom ? '' : (matchedOption?.rawValue ?? '')
const isCustomTargetPrice = isCustom && name === 'initialTargetPrice'
const ispriceRangePercentage = name === 'priceRangePercentage'
const isCustomPriceRange = isCustom && ispriceRangePercentage
const isPercentage = name === 'centerednessMargin' || name === 'priceShiftDailyRate'
const cardOptions = options.filter(option => option.rawValue !== '')
const customOption = options.find(option => option.rawValue === '')
const cardOptions = options
const { getRootProps, getRadioProps } = useRadioGroup({
name,
value: selectedValue,
onChange: (value: string) => updateFn(value),
onChange: (value: string) => {
setForceCustom(false)
updateFn(value)
},
})
const radioGroupProps = getRootProps()
const cardContainerProps = {
Expand Down Expand Up @@ -109,7 +128,7 @@ function ConfigOptionsGroup({
return (
<VStack align="start" spacing="md" w="full">
<HStack>
<Text textAlign="start" w="full">
<Text fontWeight="bold" textAlign="start" w="full">
{label}
</Text>
<InfoIconPopover message={tooltip} />
Expand All @@ -136,19 +155,18 @@ function ConfigOptionsGroup({
)
})}
</SimpleGrid>
{customOption ? (
<Radio
isChecked={selectedValue === customOption.rawValue}
mt="2"
name={name}
onChange={() => updateFn(customOption.rawValue)}
value={customOption.rawValue}
>
<Text color="font.secondary" fontSize="sm">
{customOption.label}
</Text>
</Radio>
) : null}
<Radio
isChecked={isCustom}
mt="2"
name={name}
onChange={() => {
setForceCustom(true)
updateFn('')
}}
value=""
>
<Text color="font.secondary">Or choose custom</Text>
</Radio>
{isCustomPriceRange ? (
<VStack align="start" spacing="md" w="full">
<NumberInput
Expand Down Expand Up @@ -196,7 +214,7 @@ function ConfigOptionsGroup({
</VStack>
) : isCustom ? (
<NumberInput
control={reClammConfigForm.control}
control={control}
isPercentage={isPercentage}
label={customInputLabel}
name={name}
Expand Down
Loading
Loading