-
Notifications
You must be signed in to change notification settings - Fork 4
Update Remove LP form -> New Design #1557
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
Changes from all commits
68714f4
e78fcce
b9cb66b
7e76fc5
d576b87
7cd65fd
1dfb893
43ba662
39dbd48
1cf45e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,26 +6,34 @@ import { | |
| HyperdriveConfig, | ||
| } from "@delvtech/hyperdrive-appconfig"; | ||
| import { adjustAmountByPercentage } from "@delvtech/hyperdrive-viem"; | ||
| import { ConnectButton } from "@rainbow-me/rainbowkit"; | ||
| import { MouseEvent, ReactElement } from "react"; | ||
| import Skeleton from "react-loading-skeleton"; | ||
| import { calculateRatio } from "src/base/calculateRatio"; | ||
| import { calculateValueFromPrice } from "src/base/calculateValueFromPrice"; | ||
| import { isTestnetChain } from "src/chains/isTestnetChain"; | ||
| import { getHasEnoughBalance } from "src/token/getHasEnoughBalance"; | ||
| import { LabelValue } from "src/ui/base/components/LabelValue"; | ||
| import { ConnectWalletButton } from "src/ui/base/components/ConnectWallet"; | ||
| import { LoadingButton } from "src/ui/base/components/LoadingButton"; | ||
| import { PrimaryStat } from "src/ui/base/components/PrimaryStat"; | ||
| import { formatBalance } from "src/ui/base/formatting/formatBalance"; | ||
| import { useActiveItem } from "src/ui/base/hooks/useActiveItem"; | ||
| import { useNumericInput } from "src/ui/base/hooks/useNumericInput"; | ||
| import { SwitchNetworksButton } from "src/ui/chains/SwitchChainButton/SwitchChainButton"; | ||
| import { usePoolInfo } from "src/ui/hyperdrive/hooks/usePoolInfo"; | ||
| import { InvalidTransactionButton } from "src/ui/hyperdrive/InvalidTransactionButton"; | ||
| import { useLpSharesTotalSupply } from "src/ui/hyperdrive/lp/hooks/useLpSharesTotalSupply"; | ||
| import { usePreviewRemoveLiquidity } from "src/ui/hyperdrive/lp/hooks/usePreviewRemoveLiquidity"; | ||
| import { useRemoveLiquidity } from "src/ui/hyperdrive/lp/hooks/useRemoveLiquidity"; | ||
| import { TransactionViewOld } from "src/ui/hyperdrive/TransactionView"; | ||
| import { TransactionView } from "src/ui/hyperdrive/TransactionView"; | ||
| import { useSlippageSettings } from "src/ui/token/hooks/useSlippageSettings"; | ||
| import { useTokenBalance } from "src/ui/token/hooks/useTokenBalance"; | ||
| import { SlippageSettings } from "src/ui/token/SlippageSettings"; | ||
| import { TokenInput } from "src/ui/token/TokenInput"; | ||
| import { TokenChoice, TokenPicker } from "src/ui/token/TokenPicker"; | ||
| import { useTokenFiatPrice } from "src/ui/token/hooks/useTokenFiatPrice"; | ||
| import { SlippageSettingsTwo } from "src/ui/token/SlippageSettingsTwo"; | ||
| import { TokenInputTwo } from "src/ui/token/TokenInputTwo"; | ||
| import { TokenChoice } from "src/ui/token/TokenPicker"; | ||
| import { TokenPickerTwo } from "src/ui/token/TokenPickerTwo"; | ||
| import { formatUnits } from "viem"; | ||
| import { useAccount } from "wagmi"; | ||
| import { useAccount, useChainId } from "wagmi"; | ||
| interface RemoveLiquidityFormProps { | ||
| hyperdrive: HyperdriveConfig; | ||
| lpShares: bigint; | ||
|
|
@@ -38,7 +46,7 @@ export function RemoveLiquidityForm({ | |
| onRemoveLiquidity, | ||
| }: RemoveLiquidityFormProps): ReactElement { | ||
| const { address: account } = useAccount(); | ||
|
|
||
| const connectedChainId = useChainId(); | ||
| const baseToken = findBaseToken({ | ||
| hyperdriveChainId: hyperdrive.chainId, | ||
| hyperdriveAddress: hyperdrive.address, | ||
|
|
@@ -63,9 +71,6 @@ export function RemoveLiquidityForm({ | |
| decimals: hyperdrive.decimals, | ||
| }); | ||
|
|
||
| const baseTokenDepositEnabled = | ||
| hyperdrive.depositOptions.isBaseTokenDepositEnabled; | ||
|
|
||
| const tokens: TokenChoice[] = []; | ||
| if (sharesToken && hyperdrive.withdrawOptions.isShareTokenWithdrawalEnabled) { | ||
| tokens.push({ | ||
|
|
@@ -95,6 +100,11 @@ export function RemoveLiquidityForm({ | |
| chainId: hyperdrive.chainId, | ||
| }); | ||
|
|
||
| const { fiatPrice: activeWithdrawTokenPrice } = useTokenFiatPrice({ | ||
| tokenAddress: activeWithdrawToken.address, | ||
| chainId: activeWithdrawToken.chainId, | ||
| }); | ||
|
|
||
| // Let users type in an amount of lp shares they want to remove | ||
| const { | ||
| amount, | ||
|
|
@@ -106,7 +116,6 @@ export function RemoveLiquidityForm({ | |
|
|
||
| // Then we preview that trade to show users the split between the actual base | ||
| // and withdrawal shares they'll receive | ||
|
|
||
| // if withdrawingin shares, we need to also convert the minLpSharePrice to be | ||
| // priced in terms of shares | ||
| const isBaseActiveToken = activeWithdrawToken.address === baseToken.address; | ||
|
|
@@ -131,6 +140,12 @@ export function RemoveLiquidityForm({ | |
| direction: "down", | ||
| }); | ||
|
|
||
| const { lpSharesTotalSupply, lpSharesTotalSupplyStatus } = | ||
| useLpSharesTotalSupply({ | ||
| hyperdriveAddress: hyperdrive.address, | ||
| chainId: hyperdrive.chainId, | ||
| }); | ||
|
|
||
| const { | ||
| proceeds: actualValueOut, | ||
| previewRemoveLiquidityStatus, | ||
|
|
@@ -145,6 +160,7 @@ export function RemoveLiquidityForm({ | |
| hyperdrive.withdrawOptions.isBaseTokenWithdrawalEnabled && | ||
| activeWithdrawToken.address === baseToken.address, | ||
| }); | ||
|
|
||
| const { removeLiquidity, removeLiquidityStatus } = useRemoveLiquidity({ | ||
| chainId: hyperdrive.chainId, | ||
| hyperdriveAddress: hyperdrive.address, | ||
|
|
@@ -156,8 +172,9 @@ export function RemoveLiquidityForm({ | |
| hyperdrive.withdrawOptions.isBaseTokenWithdrawalEnabled && | ||
| activeWithdrawToken.address === baseToken.address, | ||
| onSubmitted: () => { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| (window as any)["withdrawalLpModal"]?.close(); | ||
| ( | ||
| document.getElementById("withdrawalLpModal") as HTMLDialogElement | ||
| )?.close(); | ||
| }, | ||
| onExecuted: () => { | ||
| setAmount(""); | ||
|
|
@@ -196,124 +213,147 @@ export function RemoveLiquidityForm({ | |
| }); | ||
| } | ||
|
|
||
| const poolShareRemaining = | ||
| lpSharesTotalSupplyStatus === "success" | ||
| ? (() => { | ||
| // Calculate the ratio of remaining shares to the total supply | ||
| const ratio = calculateRatio({ | ||
| a: lpShares - (lpSharesIn || 0n) - (withdrawalShares || 0n), // Remaining LP Shares in pool after removing input value lpShares and withdrawalShares received | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick question: If a user has withdrawalShares, does that count as a part of the pool percentage? I'm including those in the ratio calculation here because i'm assuming they are removed from the pool along with the LP shares and treated differently.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, your assumption is correct 👍 They are accounted for separately and don't contribute to the total pools liquidity. |
||
| b: lpSharesTotalSupply || 0n, | ||
| decimals: hyperdrive?.decimals, | ||
| }); | ||
| // Ensure the ratio is not negative | ||
| return ratio < 0n ? 0n : ratio; | ||
| })() | ||
| : 0n; | ||
|
|
||
| return ( | ||
| <TransactionViewOld | ||
| setting={ | ||
| <TokenPicker | ||
| label={ | ||
| baseTokenDepositEnabled | ||
| ? "Choose asset for withdrawal" | ||
| : "Asset for withdrawal" | ||
| } | ||
| activeTokenAddress={activeWithdrawToken?.address} | ||
| onChange={(tokenAddress) => setActiveWithdrawToken(tokenAddress)} | ||
| tokens={withdrawTokenChoices} | ||
| /> | ||
| } | ||
| <TransactionView | ||
| tokenInput={ | ||
| <TokenInput | ||
| name="Input LP shares" | ||
| token={ | ||
| <div className="daisy-join-item flex h-12 shrink-0 items-center gap-1.5 border border-neutral-content/30 bg-base-100 px-4"> | ||
| <img src={baseToken.iconUrl} className="h-5 rounded-full" />{" "} | ||
| <span className="text-sm font-semibold"> | ||
| {baseToken.symbol}-LP | ||
| </span> | ||
| </div> | ||
| } | ||
| settings={ | ||
| <SlippageSettings | ||
| onSlippageChange={setSlippage} | ||
| slippage={slippage} | ||
| activeOption={activeSlippageOption} | ||
| onActiveOptionChange={setActiveSlippageOption} | ||
| tooltip="Your transaction will revert if the price changes unfavorably by more than this percentage." | ||
| /> | ||
| } | ||
| value={amount ?? ""} | ||
| maxValue={formatUnits(lpShares, baseToken.decimals)} | ||
| stat={ | ||
| <div className="flex flex-col gap-1 text-xs text-neutral-content"> | ||
| <span> | ||
| {lpShares && !!poolInfo | ||
| ? `Withdrawable: ${formatBalance({ | ||
| balance: lpShares, | ||
| decimals: hyperdrive.decimals, | ||
| places: baseToken.places, | ||
| })} ${baseToken.symbol}-LP` | ||
| : undefined} | ||
| <div className="flex flex-col gap-3"> | ||
| <TokenInputTwo | ||
| name="Input LP shares" | ||
| token={`${baseToken.symbol}-LP`} | ||
| settings={ | ||
| <SlippageSettingsTwo | ||
| onSlippageChange={setSlippage} | ||
| slippage={slippage} | ||
| activeOption={activeSlippageOption} | ||
| onActiveOptionChange={setActiveSlippageOption} | ||
| tooltip="Your transaction will revert if the price changes unfavorably by more than this percentage." | ||
| /> | ||
| } | ||
| value={amount ?? ""} | ||
| maxValue={formatUnits(lpShares, baseToken.decimals)} | ||
| bottomRightElement={ | ||
| <span className="text-xs text-neutral-content"> | ||
| {`Withdrawable: ${formatBalance({ | ||
| balance: lpShares, | ||
| decimals: baseToken.decimals, | ||
| places: baseToken.places, | ||
| })}`} | ||
| </span> | ||
| <span>{`Slippage: ${slippage || "0.5"}%`}</span> | ||
| </div> | ||
| } | ||
| onChange={(newAmount) => setAmount(newAmount)} | ||
| /> | ||
| } | ||
| transactionPreview={ | ||
| <div className="flex flex-col gap-3 px-2 pb-2"> | ||
| <LabelValue | ||
| label="Amount to withdraw" | ||
| value={`${ | ||
| actualValueOut | ||
| ? `${formatBalance({ | ||
| balance: lpShares || 0n, | ||
| decimals: hyperdrive.decimals, | ||
| places: baseToken.places, | ||
| })}` | ||
| : "0" | ||
| } ${baseToken.symbol}-LP`} | ||
| } | ||
| onChange={(newAmount) => setAmount(newAmount)} | ||
| /> | ||
| <LabelValue | ||
| label="Total you receive now" | ||
| <TokenInputTwo | ||
| name={baseToken.symbol} | ||
| inputLabel="You receive" | ||
| token={ | ||
| <TokenPickerTwo | ||
| tokens={withdrawTokenChoices} | ||
| activeTokenAddress={activeWithdrawToken.address} | ||
| onChange={(tokenAddress) => | ||
| setActiveWithdrawToken(tokenAddress) | ||
| } | ||
| /> | ||
| } | ||
| value={ | ||
| <span className="font-bold"> | ||
| {actualValueOut | ||
| ? `${formatBalance({ | ||
| balance: actualValueOut, | ||
| decimals: activeWithdrawToken.decimals, | ||
| places: activeWithdrawToken.places, | ||
| })}` | ||
| : "0"}{" "} | ||
| {activeWithdrawToken.symbol} | ||
| </span> | ||
| actualValueOut | ||
| ? fixed(actualValueOut, hyperdrive.decimals).toString() | ||
| : "0" | ||
| } | ||
| maxValue={ | ||
| actualValueOut | ||
| ? fixed(actualValueOut, hyperdrive.decimals).toString() | ||
| : "" | ||
| } | ||
| disabled | ||
| bottomLeftElement={ | ||
| // Defillama fetches the token price via {chain}:{tokenAddress}. Since the token address differs on testnet, price display is disabled there. | ||
| !isTestnetChain(hyperdrive.chainId) ? ( | ||
| <label className="text-sm text-neutral-content"> | ||
| {`$${formatBalance({ | ||
| balance: | ||
| activeWithdrawTokenPrice && actualValueOut | ||
| ? fixed( | ||
| actualValueOut, | ||
| activeWithdrawToken.decimals, | ||
| ).mul( | ||
| activeWithdrawTokenPrice, | ||
| activeWithdrawToken.decimals, | ||
| ).bigint | ||
| : 0n, | ||
| decimals: activeWithdrawToken.decimals, | ||
| places: 2, | ||
| })}`} | ||
| </label> | ||
| ) : null | ||
| } | ||
| onChange={(newAmount) => setAmount(newAmount)} | ||
| /> | ||
| <LabelValue | ||
| </div> | ||
| } | ||
| primaryStats={ | ||
| <div className="flex flex-row justify-between px-4 py-8"> | ||
| <PrimaryStat | ||
| label="Queued for delayed withdrawal" | ||
| value={ | ||
| <span className="font-bold"> | ||
| <div className="text-h3 font-bold"> | ||
| {formattedWithdrawalSharesOut || 0} {baseToken.symbol} | ||
| </span> | ||
| </div> | ||
| } | ||
| /> | ||
| <div className="daisy-divider daisy-divider-horizontal mx-0" /> | ||
| <PrimaryStat | ||
| label="Pool share remaining" | ||
| value={ | ||
| <div className="text-h3 font-bold"> | ||
| {poolShareRemaining !== null ? ( | ||
| `${fixed(poolShareRemaining).format({ decimals: 4 })}%` | ||
| ) : ( | ||
| <Skeleton /> | ||
| )} | ||
| </div> | ||
| } | ||
| /> | ||
| </div> | ||
| } | ||
| disclaimer={ | ||
| <> | ||
| {lpSharesIn && !hasEnoughBalance ? ( | ||
| <p className="mb-2 text-center text-sm text-error"> | ||
| Insufficient balance | ||
| </p> | ||
| ) : null} | ||
|
|
||
| <p className="text-center text-sm text-neutral-content"> | ||
| You can withdraw liquidity at any time. The utilized portion may be | ||
| queued for delayed withdrawal. | ||
| </p> | ||
| </> | ||
| } | ||
| actionButton={(() => { | ||
| if (!account) { | ||
| return <ConnectButton />; | ||
| return <ConnectWalletButton wide />; | ||
| } | ||
| if (connectedChainId !== hyperdrive.chainId) { | ||
| return ( | ||
| <SwitchNetworksButton | ||
| targetChainId={hyperdrive.chainId} | ||
| targetChainName={appConfig.chains[hyperdrive.chainId].name} | ||
| /> | ||
| ); | ||
| } | ||
| if (!!lpSharesIn && !hasEnoughBalance) { | ||
| return ( | ||
| <InvalidTransactionButton wide> | ||
| Insufficient balance | ||
| </InvalidTransactionButton> | ||
| ); | ||
| } | ||
|
|
||
| if (removeLiquidityStatus === "loading") { | ||
| return <LoadingButton label="Removing liquidity" variant="primary" />; | ||
| return <LoadingButton label="Removing Liquidity" variant="primary" />; | ||
| } | ||
|
|
||
| return ( | ||
| <button | ||
| className="daisy-btn daisy-btn-circle daisy-btn-primary w-full" | ||
| className="daisy-btn daisy-btn-circle daisy-btn-primary w-full disabled:bg-primary disabled:text-base-100 disabled:opacity-30" | ||
| disabled={!hasEnoughBalance || !removeLiquidity} | ||
| onClick={(e) => { | ||
| // prevent closing the modal until the user approves the transaction | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was unused