Skip to content

Commit

Permalink
New token input feature flagged (#1270)
Browse files Browse the repository at this point in the history
* input almost there

* token input finished with placeholders

* token picker fixed

* slippage fully added

* primary stats created

* max button wired up

* feature flag primary stats

* cleanup globals

* final cleanup

* fix feature flagged modal header

* updates per comments

* update coding style guide

* fix icons

* rename openlongprimarystats

* rename file
  • Loading branch information
jackburrus authored Jul 12, 2024
1 parent 0e34ad7 commit e38b87e
Show file tree
Hide file tree
Showing 10 changed files with 605 additions and 44 deletions.
3 changes: 3 additions & 0 deletions apps/hyperdrive-trading/src/ui/app/Navbar/DevtoolsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export function DevtoolsMenu(): ReactElement {
<FeatureFlagMenuItem flagName="bridge">
Bridge Assets
</FeatureFlagMenuItem>
<FeatureFlagMenuItem flagName="new-open-long-form">
New Open Long Form
</FeatureFlagMenuItem>
<MenuItem
onClick={() => {
throw new Error(
Expand Down
26 changes: 26 additions & 0 deletions apps/hyperdrive-trading/src/ui/base/components/PrimaryStat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ReactNode } from "react";

export function PrimaryStat({
label,
value,
valueUnit,
subValue,
valueClassName,
}: {
label: string;
value: ReactNode;
valueUnit: string;
subValue?: ReactNode;
valueClassName?: string;
}): JSX.Element {
return (
<div className="flex flex-col gap-1">
<p className="text-sm text-neutral-content">{label}</p>
<div className={valueClassName}>
<p className="text-h3 font-bold">{value}</p>
<p className="ml-1">{valueUnit}</p>
</div>
{subValue && <p className="text-sm text-neutral-content">{subValue}</p>}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ interface TransactionViewProps {
tokenInput: ReactNode;

setting?: ReactNode;
primaryStats?: ReactNode;
transactionPreview: ReactNode;
disclaimer?: ReactNode;

Expand All @@ -15,6 +16,7 @@ export function TransactionView({
heading,
tokenInput,
setting,
primaryStats,
transactionPreview,
disclaimer,
actionButton,
Expand All @@ -27,11 +29,11 @@ export function TransactionView({
{setting}
</div>
<div className="flex flex-col gap-8">
{primaryStats}
<div>
<h6 className="mb-4">Preview transaction</h6>
{transactionPreview}
</div>

<div className="text-center">{actionButton}</div>
{disclaimer ? <div>{disclaimer}</div> : null}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ import { useMaxLong } from "src/ui/hyperdrive/longs/hooks/useMaxLong";
import { useOpenLong } from "src/ui/hyperdrive/longs/hooks/useOpenLong";
import { usePreviewOpenLong } from "src/ui/hyperdrive/longs/hooks/usePreviewOpenLong";
import { OpenLongPreview } from "src/ui/hyperdrive/longs/OpenLongPreview/OpenLongPreview";
import { OpenLongStats } from "src/ui/hyperdrive/longs/OpenLongPreview/OpenLongStats";
import { TransactionView } from "src/ui/hyperdrive/TransactionView";
import { ApproveTokenChoices } from "src/ui/token/ApproveTokenChoices";
import { useActiveToken } from "src/ui/token/hooks/useActiveToken";
import { useSlippageSettings } from "src/ui/token/hooks/useSlippageSettings";
import { useTokenAllowance } from "src/ui/token/hooks/useTokenAllowance";
import { useTokenBalance } from "src/ui/token/hooks/useTokenBalance";
import { SlippageSettings } from "src/ui/token/SlippageSettings";
import { SlippageSettingsTwo } from "src/ui/token/SlippageSettingsTwo";
import { TokenInput } from "src/ui/token/TokenInput";
import { TokenInputTwo } from "src/ui/token/TokenInputTwo";
import { TokenPicker } from "src/ui/token/TokenPicker";
import { TokenPickerTwo } from "src/ui/token/TokenPickerTwo";
import { formatUnits } from "viem";
import { useAccount } from "wagmi";
interface OpenLongFormProps {
Expand All @@ -45,6 +49,8 @@ export function OpenLongForm({
}: OpenLongFormProps): ReactElement {
const { address: account } = useAccount();
const { isFlagEnabled: isBridgingEnabled } = useFeatureFlag("bridge");
const { isFlagEnabled: isNewOpenLongFormEnabled } =
useFeatureFlag("new-open-long-form");
const appConfig = useAppConfig();
const { poolInfo } = usePoolInfo({ hyperdriveAddress: hyperdrive.address });

Expand Down Expand Up @@ -210,54 +216,109 @@ export function OpenLongForm({
return (
<TransactionView
tokenInput={
<TokenInput
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."
/>
}
name={activeToken.symbol}
token={
<TokenPicker
// TODO: Remove check for Sepolia chain after testnet period.
tokens={tokenOptions}
activeTokenAddress={activeToken.address}
onChange={(tokenAddress) => {
setActiveToken(tokenAddress);
setAmount("0");
}}
joined={true}
/>
}
value={depositAmount ?? ""}
maxValue={maxButtonValue}
inputLabel="Amount to spend"
stat={
<div className="flex flex-col gap-1 text-xs text-neutral-content">
<span>
{activeTokenBalance
? `Balance: ${formatBalance({
balance: activeTokenBalance?.value,
decimals: activeToken.decimals,
places: activeToken.places,
})} ${activeToken.symbol}`
: undefined}
</span>
<span>{`Slippage: ${slippage || "0.5"}%`}</span>
</div>
}
onChange={(newAmount) => setAmount(newAmount)}
/>
isNewOpenLongFormEnabled ? (
<TokenInputTwo
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."
/>
}
name={activeToken.symbol}
token={
<TokenPickerTwo
tokens={tokenOptions}
activeTokenAddress={activeToken.address}
onChange={(tokenAddress) => {
setActiveToken(tokenAddress);
setAmount("0");
}}
/>
}
value={depositAmount ?? ""}
maxValue={maxButtonValue}
inputLabel="You spend"
stat={
<div className="flex flex-col gap-1 text-xs text-neutral-content">
<span>
{activeTokenBalance
? `Balance: ${formatBalance({
balance: activeTokenBalance?.value,
decimals: activeToken.decimals,
places: activeToken.places,
})}`
: undefined}
</span>
</div>
}
onChange={(newAmount) => setAmount(newAmount)}
/>
) : (
<TokenInput
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."
/>
}
name={activeToken.symbol}
token={
<TokenPicker
// TODO: Remove check for Sepolia chain after testnet period.
tokens={tokenOptions}
activeTokenAddress={activeToken.address}
onChange={(tokenAddress) => {
setActiveToken(tokenAddress);
setAmount("0");
}}
joined={true}
/>
}
value={depositAmount ?? ""}
maxValue={maxButtonValue}
inputLabel="Amount to spend"
stat={
<div className="flex flex-col gap-1 text-xs text-neutral-content">
<span>
{activeTokenBalance
? `Balance: ${formatBalance({
balance: activeTokenBalance?.value,
decimals: activeToken.decimals,
places: activeToken.places,
})} ${activeToken.symbol}`
: undefined}
</span>
<span>{`Slippage: ${slippage || "0.5"}%`}</span>
</div>
}
onChange={(newAmount) => setAmount(newAmount)}
/>
)
}
setting={
isBridgingEnabled && hasBridgeableBalance
? switchToBridgeUIButton
: null
}
primaryStats={
isNewOpenLongFormEnabled ? (
<OpenLongStats
hyperdrive={hyperdrive}
activeToken={activeToken}
amountPaid={depositAmountAsBigInt || 0n}
bondAmount={bondsReceived || 0n}
openLongPreviewStatus={openLongPreviewStatus}
asBase={activeToken.address === baseToken.address}
vaultSharePrice={poolInfo?.vaultSharePrice}
/>
) : null
}
transactionPreview={
<OpenLongPreview
hyperdrive={hyperdrive}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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 { useFeatureFlag } from "src/ui/base/featureFlags/featureFlags";
import { formatDate } from "src/ui/base/formatting/formatDate";
import { OpenLongForm } from "src/ui/hyperdrive/longs/OpenLongForm/OpenLongForm";

Expand Down Expand Up @@ -58,6 +59,16 @@ export function OpenLongModalHeader({
numDays,
termLengthMS,
}: OpenLongModalHeaderProps): ReactElement {
const { isFlagEnabled: isNewOpenLongFormEnabled } =
useFeatureFlag("new-open-long-form");
if (isNewOpenLongFormEnabled) {
return (
<ModalHeader
heading="Open a Long"
subHeading="Lock in a fixed rate and know your exact yield upfront"
/>
);
}
return (
<ModalHeader
heading="Open a Long"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { calculateAprFromPrice } from "@delvtech/hyperdrive-viem";
import {
findBaseToken,
findYieldSourceToken,
HyperdriveConfig,
TokenConfig,
} from "@hyperdrive/appconfig";
import classNames from "classnames";
import Skeleton from "react-loading-skeleton";
import { convertMillisecondsToDays } from "src/base/convertMillisecondsToDays";
import { formatRate } from "src/base/formatRate";
import { QueryStatusWithIdle } from "src/base/queryStatus";
import { convertSharesToBase } from "src/hyperdrive/convertSharesToBase";
import { useAppConfig } from "src/ui/appconfig/useAppConfig";
import { PrimaryStat } from "src/ui/base/components/PrimaryStat";
import { formatBalance } from "src/ui/base/formatting/formatBalance";
import { useFixedRate } from "src/ui/hyperdrive/longs/hooks/useFixedRate";
interface OpenLongStatsProps {
hyperdrive: HyperdriveConfig;
bondAmount: bigint;
amountPaid: bigint;
openLongPreviewStatus: QueryStatusWithIdle;
activeToken: TokenConfig<any>;
asBase: boolean;
vaultSharePrice: bigint | undefined;
}
export function OpenLongStats({
hyperdrive,
openLongPreviewStatus,
amountPaid,
bondAmount,
activeToken,
asBase,
vaultSharePrice,
}: OpenLongStatsProps): JSX.Element {
const appConfig = useAppConfig();
const baseToken = findBaseToken({
baseTokenAddress: hyperdrive.baseToken,
tokens: appConfig.tokens,
});
const sharesToken = findYieldSourceToken({
yieldSourceTokenAddress: hyperdrive.sharesToken,
tokens: appConfig.tokens,
});
const { fixedApr } = useFixedRate(hyperdrive.address);

const isBaseAmount = asBase || sharesToken.extensions.isSharesPeggedToBase;
const amountPaidInBase = isBaseAmount
? amountPaid
: convertSharesToBase({
sharesAmount: amountPaid,
vaultSharePrice: vaultSharePrice,
decimals: baseToken.decimals,
});
const yieldAtMaturity = bondAmount - amountPaidInBase;
const termLengthMS = Number(hyperdrive.poolConfig.positionDuration * 1000n);
const numDays = convertMillisecondsToDays(termLengthMS);
return (
<div className="flex flex-row justify-between px-4">
<PrimaryStat
label="Your Fixed Rate"
value={
openLongPreviewStatus === "loading" ? (
<Skeleton width={100} />
) : (
<>
{bondAmount > 0
? `${formatRate(
calculateAprFromPrice({
positionDuration:
hyperdrive.poolConfig.positionDuration || 0n,
baseAmount: amountPaidInBase,
bondAmount: bondAmount,
}),
baseToken.decimals,
)}%`
: `${fixedApr?.formatted}%`}
</>
)
}
valueUnit="APR"
subValue={
openLongPreviewStatus === "loading" ? (
<Skeleton width={100} />
) : (
<>{`${formatBalance({
balance: bondAmount,
decimals: baseToken.decimals,
places: baseToken.places,
})} hy${baseToken.symbol}`}</>
)
}
valueClassName="bg-gradient-to-r from-accent to-primary bg-clip-text text-transparent flex items-end"
/>
<div className="daisy-divider daisy-divider-horizontal mx-0" />
<PrimaryStat
label="Value at Maturity"
value={
openLongPreviewStatus === "loading" ? (
<Skeleton width={100} />
) : (
<span
className={classNames("flex", {
"text-base-content/80": !amountPaid,
})}
>
<img
src={activeToken.iconUrl}
className="mr-1 h-9 rounded-full p-1"
/>
{`${formatBalance({
balance: amountPaidInBase + yieldAtMaturity,
decimals: baseToken.decimals,
places: baseToken.places,
})} `}
</span>
)
}
valueUnit={`${activeToken.symbol}`}
valueClassName="text-base-content flex items-end"
subValue={`Term: ${numDays} days`}
/>
</div>
);
}
Loading

0 comments on commit e38b87e

Please sign in to comment.