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 @@ -33,7 +33,8 @@ export function getRemainingTimeLabel({
const suffix = showLeftSuffix ? " left" : "";

if (days > 0) {
return `${days} days${suffix}`;
const dayLabel = days === 1 ? "day" : "days";
return `${days} ${dayLabel}${suffix}`;
}

// Condensed only needed when showing both hours and minutes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export function CloseLongForm({
chainId={hyperdrive.chainId}
maturity={long.maturity}
statusCellClassName="mb-0 text-h3 w-full text-gray-50 font-bold"
showLeftSuffix={false}
/>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ export function StatusCell({
chainId,
maturity,
statusCellClassName,
showLeftSuffix,
}: {
chainId: number;
maturity: bigint;
statusCellClassName?: string;
showLeftSuffix?: boolean;
}): ReactElement {
const { data: currentBlock } = useBlock({ chainId });
const isTermComplete = maturity < (currentBlock?.timestamp || 0n);

const remainingTime = getRemainingTimeLabel({
maturitySeconds: Number(maturity),
condensed: true,
showLeftSuffix,
});

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { fixed } from "@delvtech/fixed-point-wasm";
import {
appConfig,
findBaseToken,
findToken,
HyperdriveConfig,
} from "@delvtech/hyperdrive-appconfig";
import { adjustAmountByPercentage, OpenShort } from "@delvtech/hyperdrive-viem";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { MouseEvent, ReactElement } from "react";
import { LabelValue } from "src/ui/base/components/LabelValue";
import { isTestnetChain } from "src/chains/isTestnetChain";
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 { InvalidTransactionButton } from "src/ui/hyperdrive/InvalidTransactionButton";
import { StatusCell } from "src/ui/hyperdrive/longs/OpenLongsTable/StatusCell";
import { useCloseShort } from "src/ui/hyperdrive/shorts/hooks/useCloseShort";
import { usePreviewCloseShort } from "src/ui/hyperdrive/shorts/hooks/usePreviewCloseShort";
import { TransactionViewOld } from "src/ui/hyperdrive/TransactionView";
import { TransactionView } from "src/ui/hyperdrive/TransactionView";
import { useTokenBalance } from "src/ui/token/hooks/useTokenBalance";
import { TokenInput } from "src/ui/token/TokenInput";
import { TokenChoice, TokenPicker } from "src/ui/token/TokenPicker";
import { useTokenFiatPrice } from "src/ui/token/hooks/useTokenFiatPrice";
import { TokenInputTwo } from "src/ui/token/TokenInputTwo";
import { TokenChoice } from "src/ui/token/TokenPicker";
import { TokenPickerTwo } from "src/ui/token/TokenPickerTwo";
import { formatUnits, parseUnits } from "viem";
import { useAccount } from "wagmi";
import { useAccount, useChainId } from "wagmi";

interface CloseShortFormProps {
hyperdrive: HyperdriveConfig;
Expand All @@ -33,6 +40,7 @@ export function CloseShortForm({
short,
}: CloseShortFormProps): ReactElement {
const { address: account } = useAccount();
const connectedChainId = useChainId();
const defaultItems = [];
const baseToken = findBaseToken({
hyperdriveChainId: hyperdrive.chainId,
Expand Down Expand Up @@ -72,6 +80,11 @@ export function CloseShortForm({
decimals: hyperdrive.decimals,
});

const { fiatPrice: activeWithdrawTokenPrice } = useTokenFiatPrice({
tokenAddress: activeWithdrawToken.address,
chainId: activeWithdrawToken.chainId,
});

// You can't close an amount that's larger than the position size
const isAmountLargerThanPositionSize = !!(
amountAsBigInt && amountAsBigInt > short.bondAmount
Expand Down Expand Up @@ -130,60 +143,92 @@ export function CloseShortForm({
}

return (
<TransactionViewOld
<TransactionView
tokenInput={
<TokenInput
name="shorts"
inputLabel="Amount to redeem"
token={`hy${baseToken?.symbol}`}
value={amount ?? ""}
maxValue={
short ? formatUnits(short.bondAmount, hyperdrive.decimals) : ""
}
stat={
short
? `Balance: ${formatBalance({
balance: short.bondAmount,
decimals: hyperdrive.decimals,
places: baseToken?.places,
})}`
: undefined
}
onChange={(newAmount) => setAmount(newAmount)}
/>
}
setting={
withdrawTokenChoices.length > 1 ? (
<TokenPicker
tokens={withdrawTokenChoices}
activeTokenAddress={activeWithdrawToken.address}
onChange={(tokenAddress) => setActiveWithdrawToken(tokenAddress)}
label="Choose withdrawal asset"
/>
) : undefined
}
transactionPreview={
<div className="flex flex-col gap-3 px-2 pb-2">
<LabelValue
label="You receive"
value={
<p className="font-bold">
{amountOut
? `${formatBalance({
balance: amountOut,
<div className="flex flex-col gap-3">
<TokenInputTwo
name={baseToken.symbol}
inputLabel="Amount to redeem"
token={`hy${baseToken.symbol}`}
value={amount ?? ""}
maxValue={
short ? formatUnits(short.bondAmount, hyperdrive.decimals) : ""
}
onChange={(newAmount) => setAmount(newAmount)}
bottomRightElement={
<div className="flex flex-col text-xs text-neutral-content">
{short
? `Balance: ${formatBalance({
balance: short.bondAmount,
decimals: hyperdrive.decimals,
places: baseToken?.places,
})}`
: "0"}{" "}
{activeWithdrawToken.symbol}
</p>
: undefined}
</div>
}
/>

<LabelValue
<TokenInputTwo
name={baseToken.symbol}
inputLabel="You receive"
token={
<TokenPickerTwo
tokens={withdrawTokenChoices}
activeTokenAddress={activeWithdrawToken.address}
onChange={(tokenAddress) =>
setActiveWithdrawToken(tokenAddress)
}
/>
}
value={
amountOut ? fixed(amountOut, hyperdrive.decimals).toString() : "0"
}
maxValue={
short ? formatUnits(short.bondAmount, hyperdrive.decimals) : ""
}
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 && amountOut
? fixed(
amountOut ?? 0n,
activeWithdrawToken.decimals,
).mul(
activeWithdrawTokenPrice,
activeWithdrawToken.decimals,
).bigint
: 0n,
decimals: activeWithdrawToken.decimals,
places: 2,
})}`}
</label>
) : null
}
onChange={(newAmount) => setAmount(newAmount)}
/>
</div>
}
primaryStats={
<div className="flex flex-row justify-between px-4 py-8">
<PrimaryStat
label="Time remaining"
value={
<StatusCell
chainId={hyperdrive.chainId}
maturity={short.maturity}
statusCellClassName="mb-0 text-h3 w-full text-gray-50 font-bold"
showLeftSuffix={false}
/>
}
/>
<div className="daisy-divider daisy-divider-horizontal mx-0" />
<PrimaryStat
label="Pool fee"
value={
<p>
<div className="text-h3 font-bold">
{flatPlusCurveFee
? `${formatBalance({
balance: flatPlusCurveFee,
Expand All @@ -192,20 +237,31 @@ export function CloseShortForm({
places: 6,
})}`
: "0"}{" "}
{activeWithdrawToken.symbol}
</p>
</div>
}
valueUnit={activeWithdrawToken.symbol}
valueContainerClassName="flex flex-row gap-2 items-end"
/>
</div>
}
disclaimer={
!!amountAsBigInt && isAmountLargerThanPositionSize ? (
<p className="text-center text-error">Insufficient balance</p>
) : undefined
}
actionButton={(() => {
if (!account) {
return <ConnectButton />;
return <ConnectWalletButton wide />;
}
if (connectedChainId !== hyperdrive.chainId) {
return (
<SwitchNetworksButton
targetChainId={hyperdrive.chainId}
targetChainName={appConfig.chains[hyperdrive.chainId].name}
/>
);
}
if (!!amountAsBigInt && isAmountLargerThanPositionSize) {
return (
<InvalidTransactionButton wide>
Insufficient balance
</InvalidTransactionButton>
);
}
if (closeShortStatus === "loading") {
return <LoadingButton label="Closing Short" variant="primary" />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ import {
findToken,
} from "@delvtech/hyperdrive-appconfig";
import { OpenShort } from "@delvtech/hyperdrive-viem";
import { CheckIcon, XMarkIcon } from "@heroicons/react/24/solid";
import classNames from "classnames";
import { ReactElement } from "react";
import { Modal } from "src/ui/base/components/Modal/Modal";
import { ModalHeader } from "src/ui/base/components/Modal/ModalHeader";
import { Stat } from "src/ui/base/components/Stat";
import { formatDate } from "src/ui/base/formatting/formatDate";
import { getRemainingTimeLabel } from "src/ui/hyperdrive/getRemainingTimeLabel";
import { CloseShortForm } from "src/ui/hyperdrive/shorts/CloseShortForm/CloseShortForm";

export interface CloseShortModalButtonProps {
Expand All @@ -40,71 +35,22 @@ export function CloseShortModalButton({
? getSubHeadingLabel(baseToken, hyperdrive, sharesToken)
: "";

const maturityMilliseconds = Number(short.maturity * 1000n);
const isMature = Date.now() > maturityMilliseconds;
function closeModal() {
(document.getElementById(modalId) as HTMLDialogElement)?.close();
}

return (
<Modal
modalHeader={
<ModalHeader heading="Close Short" subHeading={subHeading}>
<div className="mt-5 flex w-full flex-wrap justify-between gap-4">
<div
className={classNames("daisy-badge daisy-badge-lg", {
"daisy-badge-neutral text-success": isMature,
})}
>
<Stat
horizontal
size="small"
label={isMature ? undefined : "Term:"}
value={
<span
className={classNames("flex items-center", {
"font-normal": isMature,
})}
>
{isMature ? <CheckIcon className="mr-2 h-4" /> : undefined}
{getRemainingTimeLabel({
maturitySeconds: Number(short.maturity),
condensed: true,
})}
</span>
}
/>
</div>
<div className="daisy-badge daisy-badge-lg">
<Stat
horizontal
size="small"
label="Maturity Date:"
value={formatDate(maturityMilliseconds)}
/>
</div>
</div>
</ModalHeader>
<ModalHeader heading="Close Short" subHeading={subHeading} />
}
modalId={modalId}
modalContent={
<div>
<button
className="daisy-btn daisy-btn-circle daisy-btn-ghost daisy-btn-sm absolute right-4 top-4"
onClick={closeModal}
>
<XMarkIcon className="w-6" title="Close position" />
</button>
<CloseShortForm
hyperdrive={hyperdrive}
short={short}
onCloseShort={(e) => {
// preventDefault since we don't want to close the modal while the
// tx is temporarily pending the user's signature in their wallet.
e.preventDefault();
}}
/>
</div>
<CloseShortForm
hyperdrive={hyperdrive}
short={short}
onCloseShort={(e) => {
// preventDefault since we don't want to close the modal while the
// tx is temporarily pending the user's signature in their wallet.
e.preventDefault();
}}
/>
}
/>
);
Expand Down