Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Create Pool Flow with Presets #246

Open
wants to merge 63 commits into
base: v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
1938842
Add pool summary
thevolcanomanishere Dec 9, 2024
14b2cbf
Add process steps
thevolcanomanishere Dec 9, 2024
5678b57
Steps progress
thevolcanomanishere Dec 9, 2024
93b38a4
Change flex direction
thevolcanomanishere Dec 9, 2024
39a69cf
Basic layout
thevolcanomanishere Dec 10, 2024
c597aa9
Swap logic
thevolcanomanishere Dec 11, 2024
a15e33c
Merge branch 'v2' into am/bex-create-pool-polish
PaulMcInnis Dec 11, 2024
631df8b
feature(bex): working pool create flow with preset amplification para…
PaulMcInnis Dec 11, 2024
ca9fd26
chore(bex): cleanup
PaulMcInnis Dec 11, 2024
7313de5
fix(bex): finalize the amplification parameter tooltip copy
PaulMcInnis Dec 11, 2024
92579d6
Merge branch 'v2' into am/bex-create-pool-polish
PaulMcInnis Dec 12, 2024
604490d
feat(bex): working responsive mobile view for pool create flow
PaulMcInnis Dec 12, 2024
0d0a1cf
fix(bex): styling fixes
PaulMcInnis Dec 12, 2024
d01cbb1
Merge branch 'v2' into am/bex-create-pool-polish
PaulMcInnis Dec 12, 2024
2d286e8
fix(bex): more styling fixes & cleanup
PaulMcInnis Dec 12, 2024
74af018
chore(bex): cleaning up some more by adding jsdocs strings for the de…
PaulMcInnis Dec 12, 2024
39c474e
chore(bex): cleanup some strings inside the process steps & pool crea…
PaulMcInnis Dec 12, 2024
469fd1d
feature(bex): only show Pool Summary for steps we have completed + ma…
PaulMcInnis Dec 12, 2024
46572e2
fix(bex): fix an issue in the sanitization of custom swap fees
PaulMcInnis Dec 13, 2024
76a6977
Merge branch 'v2' into am/bex-create-pool-polish
PaulMcInnis Dec 16, 2024
c5143bb
fix(bex): fix issues with weighted input wrapping on xl size screens …
PaulMcInnis Dec 17, 2024
c520df4
fix(bex): create pool button colour back to white
PaulMcInnis Dec 17, 2024
457f427
fix(bex): wrap fee ownership section as columns when viewing on xl-si…
PaulMcInnis Dec 17, 2024
3ee58bc
fix(bex): display (x) if a step is failing validation and don't let u…
PaulMcInnis Dec 17, 2024
d7f5a0a
feat(bex): if there are validation errors for one or more steps, show…
PaulMcInnis Dec 17, 2024
34e615a
chore(bex): simplify the weights input without impacting underlying n…
PaulMcInnis Dec 17, 2024
aa50adc
feat(bex): add a warning for if you select non-stablecoin tokens when…
PaulMcInnis Dec 17, 2024
b5f970b
feat(bex): add a tooltip explaining how swap fees are distributed
PaulMcInnis Dec 18, 2024
46dfd91
chore(bex): ownership address is uppercase
PaulMcInnis Dec 18, 2024
44ec51a
chore(bex): uppercase ownership string + default to the lowest fee
PaulMcInnis Dec 18, 2024
f2b1a17
feat(bex): support for rate providers (aka oracles) in the pool input…
PaulMcInnis Dec 18, 2024
5de4d66
fix(bex): don't show oracles if we change pool type to weighted
PaulMcInnis Dec 19, 2024
02e77e1
Merge branch 'v2' into am/bex-create-pool-polish
PaulMcInnis Dec 19, 2024
85ec6c8
chore(bex): Rename Oracle Address -> Rate Provider Address
PaulMcInnis Dec 19, 2024
0cbfc5f
feat(bex): add support for pulling token prices from the bex API
PaulMcInnis Dec 19, 2024
84a7391
feat(bex): dump the existing defaultTokenList based hook to check for…
PaulMcInnis Dec 19, 2024
ee31311
fix(bex): fix some bugs in the calculation of losses given liquidity …
PaulMcInnis Dec 20, 2024
880250a
fix(bex): limit weighted pools to 2 tokens max
PaulMcInnis Dec 20, 2024
39d62fc
chore(bex): switch to non-upper-case ownership titles in the summarie…
PaulMcInnis Dec 20, 2024
8ada882
feat(bex): improved swap fee input validation (you can type in an inv…
PaulMcInnis Dec 20, 2024
80efabc
chore(bex): Improve styling for swap-fee alerts
PaulMcInnis Dec 20, 2024
f422618
feat(bex): improved handling for amplification parameter like with sw…
PaulMcInnis Dec 20, 2024
ff08506
fix(bex): limit weighted pools to 2 and stable to 5
PaulMcInnis Dec 20, 2024
6046e75
chore(bex): proper styling for invalid/valid amplification parameters
PaulMcInnis Dec 20, 2024
befe3a1
fix(bex): always show a generic price/initial liquidity warning if th…
PaulMcInnis Dec 20, 2024
4b7b764
fix(bex): don't display price deviation error for weighted pool durin…
PaulMcInnis Dec 20, 2024
b780593
fix(bex): fix default swap fee validity
PaulMcInnis Dec 20, 2024
4755c2e
fix(bex): move swap-fee component into hub so we can pull PoolType.
PaulMcInnis Dec 20, 2024
0ac9128
fix(bex): check that we have completed steps 1 or 2 to show the liqui…
PaulMcInnis Dec 20, 2024
c81f118
fix(bex): properly update the default swap fee if we change the pool …
PaulMcInnis Dec 20, 2024
a78c1d9
Merge branch 'v2' into am/bex-create-pool-polish
PaulMcInnis Dec 23, 2024
6327b8a
Merge branch 'v2' into am/bex-create-pool-polish
PaulMcInnis Dec 23, 2024
b53f07d
fix(bex): display liquidity warning messages if set, and let the step…
PaulMcInnis Dec 23, 2024
3f6e21d
fix(bex): only show liquidity warnings as late as step 2 (liquidity i…
PaulMcInnis Dec 23, 2024
3acb91c
fix(bex): styling updates
bearpong Dec 23, 2024
e2e2bbe
fix(bex): Fix issue with subgraph pull for price data and dump the di…
PaulMcInnis Dec 23, 2024
12888ba
fix(bex): switch to existing isAddress in Oracle init validation
PaulMcInnis Dec 23, 2024
e58f590
chore(bex): Move constants / inits / defaults out of CreatePoolConten…
PaulMcInnis Dec 23, 2024
87ec584
chore(bex): move getStepVerification into it's own file
PaulMcInnis Dec 23, 2024
46700b2
fix(bex): switch to a non-custom colours
PaulMcInnis Dec 23, 2024
04debb7
chore(bex): switch to a more cohesive enum-based way of tracking pool…
PaulMcInnis Dec 23, 2024
9dadf0d
fix(bex): fix an issue where if you changed the selected Amplificatio…
PaulMcInnis Dec 23, 2024
1894953
chore(bex): uppercase the ownership types so that they fit the UI loo…
PaulMcInnis Dec 24, 2024
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
109 changes: 109 additions & 0 deletions apps/hub/src/app/pools/components/amplification-parameter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"use client";

import React, { useState } from "react";
import { cn } from "@bera/ui";
import { Alert, AlertDescription, AlertTitle } from "@bera/ui/alert";
import { InputWithLabel } from "@bera/ui/input";

import BeraTooltip from "~/components/bera-tooltip";

interface AmplificationInputProps {
initialAmplification: number;
onAmplificationChange: (value: number) => void;
onInvalidAmplification: (isInvalid: boolean) => void;
minAmplification?: number;
maxAmplification?: number;
}

/**
* A component that allows the user to input an amplification parameter within a valid range.
*/
export const AmplificationInput: React.FC<AmplificationInputProps> = ({
initialAmplification,
onAmplificationChange,
onInvalidAmplification,
minAmplification = 1,
maxAmplification = 5000,
}) => {
const [rawAmplification, setRawAmplification] = useState<string>(
`${initialAmplification}`,
);
const [isInvalid, setIsInvalid] = useState<boolean>(false); // Invalid state

const validateAmplification = (
value: string,
): { valid: boolean; parsedValue?: number } => {
const parsedValue = Number(value);
if (
!Number.isNaN(parsedValue) &&
parsedValue >= minAmplification &&
parsedValue <= maxAmplification
) {
return { valid: true, parsedValue };
}
return { valid: false };
};

const handleAmplificationChange = (value: string) => {
setRawAmplification(value); // Always update raw input
const { valid, parsedValue } = validateAmplification(value);

if (valid && parsedValue !== undefined) {
onAmplificationChange(parsedValue);
onInvalidAmplification(false);
setIsInvalid(false);
} else {
setIsInvalid(true);
onInvalidAmplification(true);
}
};

const handleBlur = () => {
const { valid, parsedValue } = validateAmplification(rawAmplification);
if (!valid) {
onInvalidAmplification(true);
} else {
setRawAmplification(`${parsedValue}`);
}
};

return (
<>
<div className="flex flex-col gap-4">
<InputWithLabel
label="Amplification Factor"
id="amplification-factor"
className={cn({
"border-destructive-foreground text-destructive-foreground":
isInvalid,
})}
variant="black"
value={rawAmplification}
onChange={(e) => handleAmplificationChange(e.target.value)}
onBlur={handleBlur}
maxLength={5}
tooltip={
<BeraTooltip
size="lg"
wrap
text={`
The Amplification Factor (A) controls how aggressively the pool reacts to price fluctuations. Higher A values
maintain tighter spreads for small imbalances but increase slippage during large deviations like depegs.
Conversely, lower A values permit price changes for smaller deviations, but lower efficiency. `}
/>
}
/>
</div>

{isInvalid && (
<Alert variant="destructive" className="mt-2">
<AlertTitle>Error</AlertTitle>
<AlertDescription>
Invalid amplification factor. Ensure it is between{" "}
{minAmplification} and {maxAmplification}.
</AlertDescription>
</Alert>
)}
</>
);
};
61 changes: 54 additions & 7 deletions apps/hub/src/app/pools/components/create-pool-input.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import React, { useEffect, useState } from "react";
import { type Token } from "@bera/berajs";
import { Oracle, OracleMode, truncateDecimal, type Token } from "@bera/berajs";
import {
beraTokenAddress,
bgtTokenAddress,
nativeTokenAddress,
} from "@bera/config";
import { SelectToken } from "@bera/shared-ui";
import { Button } from "@bera/ui/button";
import { Icons } from "@bera/ui/icons";
import { InputWithLabel } from "@bera/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@bera/ui/select";
import { PoolType } from "@berachain-foundation/berancer-sdk";
import { formatUnits, parseUnits } from "viem";

type Props = {
Expand All @@ -19,10 +28,13 @@ type Props = {
locked: boolean;
index: number;
selectable?: boolean;
poolType: PoolType;
oracle: Oracle;
onTokenSelection: (token: Token | undefined) => void;
onWeightChange: (index: number, newWeight: bigint) => void;
onLockToggle: (index: number) => void;
onRemoveToken: (index: number) => void;
onOracleChange: (index: number, updates: Partial<Oracle>) => void;
};

export default function CreatePoolInput({
Expand All @@ -34,10 +46,13 @@ export default function CreatePoolInput({
locked,
index,
selectable = true,
poolType,
oracle,
onTokenSelection,
onWeightChange,
onLockToggle,
onRemoveToken,
onOracleChange,
}: Props) {
const [rawInput, setRawInput] = useState(
weight ? formatUnits(weight < 0n ? 0n : weight, 16) : "0",
Expand Down Expand Up @@ -85,26 +100,27 @@ export default function CreatePoolInput({
onTokenSelection(selectedToken)
}
selectedTokens={selectedTokens}
btnClassName="border-none"
btnClassName="border-none overflow-clip"
/>

{/* Weight Input */}
<div className="ml-auto flex items-center">
<div className="ml-auto flex items-center gap-2">
{displayWeight && (
<div className="ml-auto flex items-center gap-1">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-400">%</span>
<InputWithLabel
variant="black"
type="text"
value={rawInput}
title={rawInput}
value={truncateDecimal(rawInput, 3)}
onChange={handleWeightChange}
className="w-52 rounded-md border bg-transparent text-center text-white"
className="w-24 rounded-md border bg-transparent text-center text-white"
/>

<button
type="button"
onClick={() => onLockToggle(index)}
className="ml-2 mr-6"
className="ml-2"
>
{locked ? (
<Icons.lock className="h-4 w-4" />
Expand All @@ -114,6 +130,37 @@ export default function CreatePoolInput({
</button>
</div>
)}
{poolType === PoolType.ComposableStable && oracle && (
<div className="w-full max-w-xs">
<Select
value={oracle.mode}
onValueChange={(value) =>
onOracleChange(index, {
mode: value as OracleMode,
tokenAddress: token?.address,
})
}
>
<SelectTrigger className="w-full cursor-pointer border-border bg-background text-base font-medium text-secondary-foreground">
<SelectValue placeholder="Select mode" />
</SelectTrigger>
<SelectContent>
<SelectItem
className="cursor-pointer border-border text-base font-medium hover:bg-muted"
value={OracleMode.None}
>
Standard
</SelectItem>
<SelectItem
className="cursor-pointer border-border text-base font-medium hover:bg-muted"
value={OracleMode.Custom}
>
Oracle
</SelectItem>
</SelectContent>
</Select>
</div>
)}

{/* Remove Button */}
{displayRemove && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
balancerVaultAbi,
formatUsd,
getSafeNumber,
truncateHash,
useBeraJs,
wrapNativeToken,
type Token,
Expand Down Expand Up @@ -37,7 +38,7 @@ import { formatUnits, parseUnits } from "viem";

import { usePollPoolCreationRelayerApproval } from "~/hooks/usePollPoolCreationRelayerApproval";
import { getPoolUrl } from "../fetchPools";
import { OwnershipType } from "./ownership-input";
import { OwnershipType } from "./parameters-input";

type Props = {
open: boolean;
Expand Down Expand Up @@ -141,18 +142,16 @@ export default function DynamicPoolCreationPreview({
}
}, [isRelayerApprovalError]);

const formattedOwnerAddress = `${ownerAddress.slice(
0,
6,
)}...${ownerAddress.slice(-4)} (${ownershipType})`;
const formattedOwnerAddress = `${ownershipType} (${
ownerAddress && truncateHash(ownerAddress, undefined, undefined, true)
})`;

const poolDetails = [
{ label: "Pool Name", value: poolName },
{ label: "Pool Symbol", value: poolSymbol },
{ label: "Pool Type", value: poolType },
{ label: "Swap Fee", value: `${swapFee}%` },
{ label: "Owner Address", value: formattedOwnerAddress },
// TODO (#): we will want to display rate providers here
];

if (
Expand Down
97 changes: 97 additions & 0 deletions apps/hub/src/app/pools/components/oracle-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useState } from "react";
import { ADDRESS_ZERO, Oracle, Token } from "@bera/berajs";
import { Alert, AlertDescription, AlertTitle } from "@bera/ui/alert";
import { InputWithLabel } from "@bera/ui/input";
import { ZERO_ADDRESS } from "@berachain-foundation/berancer-sdk";

import BeraTooltip from "~/components/bera-tooltip";

interface OracleInputProps {
oracle: Oracle;
token: Token;
onOracleChange: (index: number, updates: Partial<Oracle>) => void;
index: number;
}

const MAXIMUM_CACHE_DURATION = 100000000;

const OracleInput: React.FC<OracleInputProps> = ({
oracle,
token,
onOracleChange,
index,
}) => {
const [rawAddress, setRawAddress] = useState(
oracle.address === ZERO_ADDRESS ? "" : oracle.address,
); // NOTE: this works well since the default is 0x0, but if you set the value to 0x0 (a strange choice for custom) it will appear empty
const [addressError, setAddressError] = useState<string | null>(
oracle.address === ZERO_ADDRESS
? "Please define a rate provider address."
: null,
);

const validateAddress = (value: string): boolean =>
/^0x[a-fA-F0-9]{40}$/.test(value);
PaulMcInnis marked this conversation as resolved.
Show resolved Hide resolved

const handleAddressChange = (value: string) => {
setRawAddress(value);

if (validateAddress(value)) {
setAddressError(null);
onOracleChange(index, { address: value });
} else {
setAddressError("Invalid address");
}
};

return (
<div className="flex flex-col gap-4 p-4">
<h3 className="font-bold">{token.symbol}</h3>

<InputWithLabel
label="Rate Provider Address"
variant="black"
value={rawAddress}
onChange={(e) => handleAddressChange(e.target.value)}
className="bg-transparent"
tooltip={
<BeraTooltip
size="md"
wrap={true}
// FIXME: this is an info-dump, do we want a full modal/Dialog for this?
text={`Address must point to a rate provider deployment implementing the function getRate() which returns an
exchange rate. You will want to use rateProviders for all assets in your pool when each asset has its own
price that is independent of all the other assets' prices.\nIf we have tokens A, B, and C and only have price
feeds with respect to USD, then we would want all assets to have price feeds. When internally calculating
relative prices, the USD would cancel out, giving us prices for A:B, A:C, B:C, and their inverses.\nIf we have
tokens A and B and a rate provider that gives the price of A with respect to B,
then the rateProvider corresponding to token A would get the A:B price feed, and the rateProvider
corresponding to token B would be the zero address.`}
/>
}
/>
{addressError && (
<Alert variant="destructive">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{addressError}</AlertDescription>
</Alert>
)}

<InputWithLabel
label="Cache Duration (seconds)"
variant="black"
type="number"
value={oracle.cacheDuration.toString()}
onChange={(e) => {
const duration = parseInt(e.target.value, 10);
if (duration >= 1 && duration <= MAXIMUM_CACHE_DURATION) {
onOracleChange(index, { cacheDuration: duration });
}
}}
className="bg-transparent"
/>
</div>
);
};

export default OracleInput;
Loading
Loading