Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function PrimaryStat({
return (
<div className="flex flex-col justify-between gap-1">
<div className="flex gap-1">
<p className="text-sm text-neutral-content">{label}</p>
<p className="max-w-40 text-sm text-neutral-content">{label}</p>
{tooltipContent && (
<div
className={classNames(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -63,9 +71,6 @@ export function RemoveLiquidityForm({
decimals: hyperdrive.decimals,
});

const baseTokenDepositEnabled =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was unused

hyperdrive.depositOptions.isBaseTokenDepositEnabled;

const tokens: TokenChoice[] = [];
if (sharesToken && hyperdrive.withdrawOptions.isShareTokenWithdrawalEnabled) {
tokens.push({
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -131,6 +140,12 @@ export function RemoveLiquidityForm({
direction: "down",
});

const { lpSharesTotalSupply, lpSharesTotalSupplyStatus } =
useLpSharesTotalSupply({
hyperdriveAddress: hyperdrive.address,
chainId: hyperdrive.chainId,
});

const {
proceeds: actualValueOut,
previewRemoveLiquidityStatus,
Expand All @@ -145,6 +160,7 @@ export function RemoveLiquidityForm({
hyperdrive.withdrawOptions.isBaseTokenWithdrawalEnabled &&
activeWithdrawToken.address === baseToken.address,
});

const { removeLiquidity, removeLiquidityStatus } = useRemoveLiquidity({
chainId: hyperdrive.chainId,
hyperdriveAddress: hyperdrive.address,
Expand All @@ -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("");
Expand Down Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function SlippageSettingsTwo({
setIsDropdownOpen((prevState) => !prevState);
}}
className={
"daisy-btn daisy-btn-md h-9 min-h-9 rounded-full text-xs font-normal text-neutral-content"
"daisy-btn daisy-btn-md mb-1 h-9 min-h-9 rounded-full text-xs font-normal text-neutral-content"
}
>
{`${slippage || DEFAULT_SLIPPAGE_AMOUNT}%`} slippage
Expand Down
Loading
Loading