diff --git a/apps/hub/src/app/pools/create/CreatePageContent.tsx b/apps/hub/src/app/pools/create/CreatePageContent.tsx index b913d5363..6b03652d8 100755 --- a/apps/hub/src/app/pools/create/CreatePageContent.tsx +++ b/apps/hub/src/app/pools/create/CreatePageContent.tsx @@ -4,7 +4,12 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { type Token } from "@bera/berajs"; import { balancerPoolCreationHelper, balancerVaultAddress } from "@bera/config"; -import { ActionButton, ApproveButton, useAnalytics } from "@bera/shared-ui"; +import { + ActionButton, + ApproveButton, + SwapFeeInput, + useAnalytics, +} from "@bera/shared-ui"; import { cn } from "@bera/ui"; import { Alert, AlertDescription, AlertTitle } from "@bera/ui/alert"; import { Button } from "@bera/ui/button"; @@ -40,10 +45,11 @@ export default function CreatePageContent() { const [errorMessage, setErrorMessage] = useState(null); const [enableLiquidityInput, setEnableLiquidityInput] = useState(false); + const [swapFee, setSwapFee] = useState(0.01); // handle max/min tokens per https://docs.balancer.fi/concepts/pools/more/configuration.html - const minTokensLength = poolType === PoolType.Weighted ? 3 : 2; // i.e. for stable/composable stable it's 2 - const maxTokensLength = poolType === PoolType.Weighted ? 8 : 4; // i.e. for stable/composable stable it's 4 w/ BPT as a token + const minTokensLength = poolType === PoolType.Weighted ? 3 : 2; // i.e. for meta/stable it's 2 + const maxTokensLength = poolType === PoolType.Weighted ? 8 : 5; // i.e. for meta/stable it's 5 // check for token approvals const { needsApproval: tokensNeedApproval, refresh: refreshAllowances } = @@ -126,6 +132,7 @@ export default function CreatePageContent() { tokens, weights: weights, poolType, + swapFee, onSuccess: () => { track("create_pool_success"); router.push("/pools"); @@ -183,7 +190,7 @@ export default function CreatePageContent() { setPoolType(PoolType.ComposableStable)} className={cn( - "flex w-full cursor-pointer flex-col gap-0 border-2 p-4", + "flex w-full cursor-pointer flex-col gap-0 border p-4", poolType === PoolType.ComposableStable && "border-sky-600", )} > @@ -199,7 +206,7 @@ export default function CreatePageContent() { setPoolType(PoolType.Weighted)} className={cn( - "flex w-full cursor-pointer flex-col gap-0 border-2 p-4", + "flex w-full cursor-pointer flex-col gap-0 border p-4", poolType === PoolType.Weighted && "border-sky-600", )} > @@ -214,7 +221,7 @@ export default function CreatePageContent() { setPoolType(PoolType.MetaStable)} className={cn( - "flex w-full cursor-pointer flex-col gap-0 border-2 p-4", + "flex w-full cursor-pointer flex-col gap-0 border p-4", poolType === PoolType.MetaStable && "border-sky-600", )} > @@ -242,13 +249,20 @@ export default function CreatePageContent() { } /> {tokens.length > minTokensLength && ( - + )} ))} {tokens.length < maxTokensLength && (
- +
)} @@ -349,22 +363,20 @@ export default function CreatePageContent() {

Set Swap Fee

- - Swap Fee - - Fee charged on each swap - - - Fee:{" "} - 0.01% - + + { + setSwapFee(fee); + }} + />
- + - + 0 && (() => { + // NOTE: we might avoid doing this if we can return TokenInput amount in the ApprovalToken[] const approvalTokenIndex = tokens.findIndex( (t) => t.address === tokensNeedApproval[0]?.address, ); diff --git a/apps/hub/src/hooks/useCreatePool.ts b/apps/hub/src/hooks/useCreatePool.ts index f876d5768..d117a5901 100755 --- a/apps/hub/src/hooks/useCreatePool.ts +++ b/apps/hub/src/hooks/useCreatePool.ts @@ -19,6 +19,7 @@ interface UseCreatePoolProps { tokens: TokenInput[]; weights: number[]; // NOTE: weights are an array of percentages summing to 100 poolType: PoolType; + swapFee: number; onSuccess?: (hash: string) => void; onError?: (e?: Error) => void; } @@ -40,6 +41,7 @@ export const useCreatePool = ({ tokens, weights, poolType, + swapFee, onSuccess, onError, }: UseCreatePoolProps): UseCreatePoolReturn => { @@ -156,10 +158,13 @@ export const useCreatePool = ({ const tokensAddresses = tokens.map((token) => token.address); const rateProviders = tokens.map(() => ADDRESS_ZERO); - const swapFeePercentage = BigInt(parseFixed("0.01", 16).toString()); + const swapFeePercentage = parseUnits(swapFee.toString(), 16); + + // TODO: the below 3 inputs will be connected as a part of stable pool create PR const tokenRateCacheDurations = tokens.map(() => BigInt(100)); const exemptFromYieldProtocolFeeFlag = tokens.map(() => false); const amplificationParameter = BigInt(62); + const amountsIn = tokens.map((token) => parseUnits(token.amount || "0", token.decimals ?? 18), ); diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index 433235228..698d6ea9a 100755 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -64,3 +64,4 @@ export * from "./hooks"; export * from "./utils"; export { BgtStationBanner } from "./bgt-station-banner"; export { Combobox } from "./combo-box"; +export * from "./swap-fee"; diff --git a/packages/shared-ui/src/swap-fee.tsx b/packages/shared-ui/src/swap-fee.tsx new file mode 100644 index 000000000..59be49ad1 --- /dev/null +++ b/packages/shared-ui/src/swap-fee.tsx @@ -0,0 +1,87 @@ +"use client"; + +import React, { useState } from "react"; +import { cn } from "@bera/ui"; + +interface SwapFeeInputProps { + initialFee?: number; + onFeeChange?: (fee: number) => void; +} + +export function SwapFeeInput({ + initialFee = 0, + onFeeChange, +}: SwapFeeInputProps) { + const [fee, setFee] = useState(initialFee); + const [isInvalid, setIsInvalid] = useState(false); + + const predefinedFees = [0.1, 0.2, 0.3]; + + const handleFeeChange = (value: string) => { + const parsedValue = parseFloat(value); + setFee(parsedValue); + + // Validate the fee range and update the invalid state + if (parsedValue >= 0.00001 && parsedValue <= 10) { + setIsInvalid(false); + onFeeChange?.(parsedValue); // Pass valid fee to parent, if callback provided + } else { + setIsInvalid(true); + } + }; + + const handlePredefinedFeeClick = (value: number) => { + setFee(value); + setIsInvalid(false); + onFeeChange?.(value); + }; + + return ( +
+

Set Swap Fee

+ +
+
+ handleFeeChange(e.target.value)} + placeholder="Enter swap fee" + className={cn( + "w-full rounded-md border-2 p-2 pr-10 text-2xl", + isInvalid ? "border-red-500" : "border-gray-300", + )} + aria-label="Swap Fee Input" + /> + + % + +
+ +
+ {predefinedFees.map((preset) => ( + + ))} +
+ + {isInvalid && ( +
+ ⚠️ + Invalid fee. Ensure the entered fee is between 0.00001% and 10%. +
+ )} +
+
+ ); +}