diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongForm/OpenLongForm.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongForm/OpenLongForm.tsx index 55b655aa9..5f6c136c6 100644 --- a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongForm/OpenLongForm.tsx +++ b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongForm/OpenLongForm.tsx @@ -469,7 +469,7 @@ export function OpenLongForm({ onOpenLong?.(e); }} > - Open Long + Buy Fixed ); })()} diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongPreview/OpenLongStats.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongPreview/OpenLongStats.tsx index 6e55333db..1e6729d6a 100644 --- a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongPreview/OpenLongStats.tsx +++ b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongPreview/OpenLongStats.tsx @@ -16,6 +16,7 @@ import { convertSharesToBase } from "src/hyperdrive/convertSharesToBase"; import { getDepositAssets } from "src/hyperdrive/getDepositAssets"; import { useAppConfigForConnectedChain } from "src/ui/appconfig/useAppConfigForConnectedChain"; import { PrimaryStat } from "src/ui/base/components/PrimaryStat"; +import { Tooltip } from "src/ui/base/components/Tooltip/Tooltip"; import { formatBalance } from "src/ui/base/formatting/formatBalance"; import { formatDate } from "src/ui/base/formatting/formatDate"; import { useFixedRate } from "src/ui/hyperdrive/longs/hooks/useFixedRate"; @@ -153,7 +154,15 @@ export function OpenLongStats({ "text-base-content/80": !amountPaid, })} > - + + + {`${formatBalance({ balance: amountPaidInBase + yieldAtMaturity, decimals: baseToken.decimals, @@ -162,7 +171,6 @@ export function OpenLongStats({ ) } - valueUnit={`${baseToken.symbol}`} subValue={ // Defillama fetches the token price via {chain}:{tokenAddress}. Since the token address differs on testnet, term length is displayed instead. diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortForm/OpenShortForm.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortForm/OpenShortForm.tsx index 412a299c9..14c5f176b 100644 --- a/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortForm/OpenShortForm.tsx +++ b/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortForm/OpenShortForm.tsx @@ -1,8 +1,12 @@ import { fixed } from "@delvtech/fixed-point-wasm"; -import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; +import { + getYieldSource, + HyperdriveConfig, +} from "@delvtech/hyperdrive-appconfig"; import { adjustAmountByPercentage } from "@delvtech/hyperdrive-js"; import { InformationCircleIcon } from "@heroicons/react/24/outline"; -import { MouseEvent, ReactElement, useState } from "react"; +import classNames from "classnames"; +import { MouseEvent, ReactElement } from "react"; import { MAX_UINT256 } from "src/base/constants"; import { formatRate } from "src/base/formatRate"; import { isTestnetChain } from "src/chains/isTestnetChain"; @@ -32,6 +36,7 @@ import { OpenShortPreview } from "src/ui/hyperdrive/shorts/OpenShortPreview/Open import { useMaxShort } from "src/ui/hyperdrive/shorts/hooks/useMaxShort"; import { useOpenShort } from "src/ui/hyperdrive/shorts/hooks/useOpenShort"; import { usePreviewOpenShort } from "src/ui/hyperdrive/shorts/hooks/usePreviewOpenShort"; +import { useShortRate } from "src/ui/hyperdrive/shorts/hooks/useShortRate"; import { PositionPicker } from "src/ui/markets/PositionPicker"; import { RewardsTooltip } from "src/ui/rewards/RewardsTooltip/RewardsTooltip"; import { useOpenShortRewards } from "src/ui/rewards/hooks/useOpenShortRewards"; @@ -48,8 +53,6 @@ import { useYieldSourceRate } from "src/ui/vaults/useYieldSourceRate"; import { formatUnits } from "viem"; import { useAccount, useChainId } from "wagmi"; -(window as any).fixed = fixed; - interface OpenShortPositionFormProps { hyperdrive: HyperdriveConfig; onOpenShort?: (e: MouseEvent) => void; @@ -68,6 +71,12 @@ export function OpenShortForm({ chainId: hyperdrive.chainId, }); const appConfig = useAppConfigForConnectedChain(); + const yieldSource = getYieldSource({ + hyperdriveChainId: hyperdrive.chainId, + hyperdriveAddress: hyperdrive.address, + appConfig, + }); + const { baseToken, baseTokenDepositEnabled, tokenOptions } = useTokenDepositOptions({ hyperdrive, @@ -122,9 +131,6 @@ export function OpenShortForm({ tokenChainId: activeToken.chainId, }); - // TODO: Implement the two way input switch once getMaxShort is fixed on the sdk - const [activeInput, setActiveInput] = useState<"bonds" | "budget">("bonds"); - const { amount: amountOfBondsToShort, amountAsBigInt: amountOfBondsToShortAsBigInt, @@ -132,13 +138,6 @@ export function OpenShortForm({ } = useNumericInput({ decimals: hyperdrive.decimals, }); - const { - amount: amountToPay, - amountAsBigInt: amountToPayAsBigInt, - setAmount: setPaymentAmount, - } = useNumericInput({ - decimals: hyperdrive.decimals, - }); const { traderDeposit, @@ -187,17 +186,23 @@ export function OpenShortForm({ budget: MAX_UINT256, }); - const { maxBondsOut: maxBondsOutFromPayment } = useMaxShort({ - chainId: hyperdrive.chainId, - hyperdriveAddress: hyperdrive.address, - budget: amountToPayAsBigInt || 0n, - }); - const hasEnoughLiquidity = getIsValidTradeSize({ tradeAmount: amountOfBondsToShortAsBigInt, maxTradeSize: maxBondsOut, }); + const defaultBondAmount = + hyperdrive.decimals > 6 ? BigInt(1e15) : BigInt(1e6); + const { shortApr, shortRateStatus } = useShortRate({ + chainId: hyperdrive.chainId, + // show the market short rate (aka bond amount of 1) if the user hasn't + // already entered a short size + bondAmount: amountOfBondsToShortAsBigInt || defaultBondAmount, + hyperdriveAddress: hyperdrive.address, + timestamp: BigInt(Math.floor(Date.now() / 1000)), + variableApy: vaultRate?.netVaultRate, + }); + const { setSlippage, slippage, @@ -232,23 +237,9 @@ export function OpenShortForm({ }, onExecuted: () => { setShortAmount(""); - setPaymentAmount(""); }, }); - // TODO: Implement the two way input switch once getMaxShort is fixed on the sdk - // Max button is wired up to the user's balance, or the pool's max long. - // Whichever is smallest. - // let maxButtonValue = "0"; - // if (activeTokenBalance && maxBondsOut) { - // maxButtonValue = formatUnits( - // activeTokenBalance.value > maxBondsOut - // ? maxBondsOut - // : activeTokenBalance?.value, - // activeToken.decimals, - // ); - // } - const exposureMultiplier = amountOfBondsToShortAsBigInt && traderDeposit ? fixed(amountOfBondsToShortAsBigInt, activeToken.decimals) @@ -293,19 +284,10 @@ export function OpenShortForm({ }, }); setActiveToken(tokenAddress); - - // TODO: Determine if there is a bug here. - setPaymentAmount("0"); }} /> } - value={ - activeInput === "bonds" - ? amountOfBondsToShort || "" - : maxBondsOutFromPayment - ? formatUnits(maxBondsOutFromPayment, baseToken.decimals) - : "" - } + value={amountOfBondsToShort || ""} settings={
@@ -342,7 +324,6 @@ export function OpenShortForm({ }, }); setShortAmount(newAmount); - setActiveInput("bonds"); }} bottomLeftElement={ // Defillama fetches the token price via {chain}:{tokenAddress}. @@ -366,6 +347,7 @@ export function OpenShortForm({ } /> { setActiveToken(tokenAddress); - setPaymentAmount("0"); }} /> } inputLabel="You pay" - value={ - activeInput === "budget" - ? amountToPay || "0" - : formatUnits(traderDeposit || 0n, activeToken.decimals) - } - // This input is disabled until the getMaxShort is fixed on the sdk. - disabled - // maxValue={maxButtonValue} - // onChange={(newAmount) => { - // setActiveInput("budget"); - // setPaymentAmount(newAmount); - // }} + value={formatUnits(traderDeposit || 0n, activeToken.decimals)} bottomLeftElement={ // Defillama fetches the token price via {chain}:{tokenAddress}. Since the token address differs on testnet, price display is disabled there. !isTestnetChain(hyperdrive.chainId) ? ( @@ -425,59 +395,85 @@ export function OpenShortForm({
} primaryStats={ -
- - {isNewPool ? ( - "✨New✨" - ) : rewards?.length ? ( - - {formatRate({ rate: vaultRate?.netVaultRate ?? 0n })}⚡ - - ) : ( - formatRate({ rate: vaultRate?.netVaultRate ?? 0n }) - )} - - } - valueContainerClassName="flex items-end" - unitClassName="text-h3 font-bold" - subValue={ - - {`${exposureMultiplier}x`}{" "} - capital exposure - - } - valueLoading={longPriceStatus === "loading"} - /> -
- - {formatRate({ rate: fixedRatePaid || fixedApr?.apr || 0n })} - - } - unitClassName="mb-1 font-bold" - subValue={ - - What am I paying for?{" "} - - - } - /> + // TOOD: Make this it's own component, OpenShortStats +
+
+ + {isNewPool ? ( + "✨New✨" + ) : rewards?.length ? ( + + {formatRate({ rate: shortApr?.apr ?? 0n })}⚡ + + ) : ( + `${formatRate({ rate: shortApr?.apr ?? 0n })}` + )} + + } + valueContainerClassName="flex items-end" + unitClassName="text-h3 font-bold" + subValue={ + + + ≈ {`${exposureMultiplier}x`} + {" "} + capital exposure + + } + valueLoading={longPriceStatus === "loading"} + /> +
+ + {formatRate({ rate: fixedRatePaid || fixedApr?.apr || 0n })} + + } + unitClassName="mb-1 font-bold" + subValue={ + + What am I paying for?{" "} + + + } + /> +
+ {!!amountOfBondsToShortAsBigInt && hasEnoughLiquidity ? ( +
+

+ Earn{" "} + + {formatRate({ rate: shortApr?.apr ?? 0n })} + {" "} + if {yieldSource.shortName}{" "} + remains constant at{" "} + + {vaultRate?.netVaultRate + ? formatRate({ rate: vaultRate.netVaultRate }) + : null} + +

+
+ ) : null}
} transactionPreview={ diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx index 0f4a0d61d..2598f7a63 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx @@ -1,19 +1,15 @@ -import { fixed } from "@delvtech/fixed-point-wasm"; import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; -import { Link } from "@tanstack/react-router"; import { ReactElement } from "react"; import { formatRate } from "src/base/formatRate"; import { useFixedRate } from "src/ui/hyperdrive/longs/hooks/useFixedRate"; import { PercentLabel } from "src/ui/markets/PoolRow/PercentLabel"; import { PoolStat } from "src/ui/markets/PoolRow/PoolStat"; -import { useAccount } from "wagmi"; interface FixedAprCtaProps { hyperdrive: HyperdriveConfig; } export function FixedAprCta({ hyperdrive }: FixedAprCtaProps): ReactElement { - const { address: account } = useAccount(); const { fixedApr, fixedRateStatus } = useFixedRate({ chainId: hyperdrive.chainId, hyperdriveAddress: hyperdrive.address, @@ -38,32 +34,6 @@ export function FixedAprCta({ hyperdrive }: FixedAprCtaProps): ReactElement { ) } isLoading={fixedRateStatus === "loading"} - action={ - { - e.stopPropagation(); - window.plausible("positionCtaClick", { - props: { - chainId: hyperdrive.chainId, - poolAddress: hyperdrive.address, - positionType: "long", - statName: label, - statValue: fixedApr ? fixed(fixedApr.apr, 18).toString() : "", - connectedWallet: account, - }, - }); - }} - > - Open Long - - } /> ); } diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/LpApyCta.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/LpApyCta.tsx index 2afaa85f1..cb809ec73 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/LpApyCta.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/LpApyCta.tsx @@ -1,19 +1,15 @@ -import { fixed } from "@delvtech/fixed-point-wasm"; import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; -import { Link } from "@tanstack/react-router"; import { ReactElement } from "react"; import { useLpApy } from "src/ui/hyperdrive/lp/hooks/useLpApy"; import { LpApyStat } from "src/ui/markets/PoolRow/LpApyStat"; import { PoolStat } from "src/ui/markets/PoolRow/PoolStat"; import { RewardsTooltipContent } from "src/ui/rewards/RewardsTooltip/RewardsTooltipContent"; -import { useAccount } from "wagmi"; interface LpApyCtaProps { hyperdrive: HyperdriveConfig; } export function LpApyCta({ hyperdrive }: LpApyCtaProps): ReactElement { - const { address: account } = useAccount(); const { lpApy, lpApyStatus } = useLpApy({ hyperdriveAddress: hyperdrive.address, chainId: hyperdrive.chainId, @@ -41,34 +37,6 @@ export function LpApyCta({ hyperdrive }: LpApyCtaProps): ReactElement { hyperdriveAddress={hyperdrive.address} /> } - action={ - { - e.stopPropagation(); - window.plausible("positionCtaClick", { - props: { - chainId: hyperdrive.chainId, - poolAddress: hyperdrive.address, - positionType: "lp", - statName: label, - statValue: lpApy?.netLpApy - ? fixed(lpApy.netLpApy).toString() - : "", - connectedWallet: account, - }, - }); - }} - > - Add Liquidity - - } /> ); } diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolRow.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolRow.tsx index a41af0f4f..af6e092fa 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolRow.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolRow.tsx @@ -174,14 +174,14 @@ export function PoolRow({ hyperdrive }: PoolRowProps): ReactElement {
{/* Right side */} -
-
+
+
-
+
-
+
diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolStat.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolStat.tsx index 902b7f7f0..69e253bd0 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolStat.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolStat.tsx @@ -14,7 +14,6 @@ export interface PoolStatProps { isLoading?: boolean; isNew?: boolean; variant?: "default" | "gradient"; - action?: ReactNode; } export function PoolStat({ @@ -24,7 +23,6 @@ export function PoolStat({ isNew = false, variant = "default", isLoading = false, - action, }: PoolStatProps): ReactElement { let displayValue; if (isLoading) { @@ -45,7 +43,6 @@ export function PoolStat({ > {displayValue}
- {action}
); diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyCta.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyCta.tsx index 5dca73ed2..2cf8e302b 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyCta.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyCta.tsx @@ -1,14 +1,10 @@ import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; -import { Link } from "@tanstack/react-router"; import { ReactElement } from "react"; -import { calculateMarketYieldMultiplier } from "src/hyperdrive/calculateMarketYieldMultiplier"; -import { useCurrentLongPrice } from "src/ui/hyperdrive/longs/hooks/useCurrentLongPrice"; import { PoolStat } from "src/ui/markets/PoolRow/PoolStat"; import { VariableApyStat } from "src/ui/markets/PoolRow/VariableApyStat"; import { useOpenShortRewards } from "src/ui/rewards/hooks/useOpenShortRewards"; import { RewardsTooltipContent } from "src/ui/rewards/RewardsTooltip/RewardsTooltipContent"; import { useYieldSourceRate } from "src/ui/vaults/useYieldSourceRate"; -import { useAccount } from "wagmi"; interface YieldMultiplierCtaProps { hyperdrive: HyperdriveConfig; @@ -17,25 +13,15 @@ interface YieldMultiplierCtaProps { export function VariableApyCta({ hyperdrive, }: YieldMultiplierCtaProps): ReactElement { - const { address: account } = useAccount(); - const { vaultRate: yieldSourceRate } = useYieldSourceRate({ chainId: hyperdrive.chainId, hyperdriveAddress: hyperdrive.address, }); - const { longPrice, longPriceStatus } = useCurrentLongPrice({ - chainId: hyperdrive.chainId, - hyperdriveAddress: hyperdrive.address, - }); const { rewards } = useOpenShortRewards({ hyperdriveConfig: hyperdrive }); const label = yieldSourceRate ? `Variable APY (${yieldSourceRate.ratePeriodDays}d)` : "Variable APY"; - const multiplier = - longPriceStatus === "success" && longPrice - ? calculateMarketYieldMultiplier(longPrice) - : undefined; return ( } - action={ - { - e.stopPropagation(); - window.plausible("positionCtaClick", { - props: { - chainId: hyperdrive.chainId, - poolAddress: hyperdrive.address, - positionType: "short", - statName: label, - statValue: multiplier ? multiplier.toString() : "", - connectedWallet: account, - }, - }); - }} - > - Open Short - - } /> ); } diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx index d3aa98cbe..865a5e028 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx @@ -1,11 +1,12 @@ +import { parseFixed } from "@delvtech/fixed-point-wasm"; import { getHyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; import { ReactNode } from "react"; import Skeleton from "react-loading-skeleton"; import { formatRate } from "src/base/formatRate"; import { calculateMarketYieldMultiplier } from "src/hyperdrive/calculateMarketYieldMultiplier"; import { useAppConfigForConnectedChain } from "src/ui/appconfig/useAppConfigForConnectedChain"; -import { GradientBadge } from "src/ui/base/components/GradientBadge"; import { useCurrentLongPrice } from "src/ui/hyperdrive/longs/hooks/useCurrentLongPrice"; +import { useShortRate } from "src/ui/hyperdrive/shorts/hooks/useShortRate"; import { PercentLabel } from "src/ui/markets/PoolRow/PercentLabel"; import { useOpenShortRewards } from "src/ui/rewards/hooks/useOpenShortRewards"; import { useYieldSourceRate } from "src/ui/vaults/useYieldSourceRate"; @@ -34,40 +35,36 @@ export function VariableApyStat({ hyperdriveAddress: hyperdrive.address, }); + const { shortApr, shortRateStatus } = useShortRate({ + chainId: hyperdrive.chainId, + // show the market short rate (aka bond amount of 1) + bondAmount: parseFixed("1", hyperdrive.decimals).bigint, + hyperdriveAddress: hyperdrive.address, + timestamp: BigInt(Math.floor(Date.now() / 1000)), + variableApy: yieldSourceRate?.netVaultRate, + }); + const multiplierLabel = longPriceStatus === "success" && longPrice - ? `${calculateMarketYieldMultiplier(longPrice).format({ decimals: 1 })}x` + ? `${calculateMarketYieldMultiplier(longPrice).format({ decimals: 0 })}x` : undefined; - if (yieldSourceRateStatus !== "success") { + if (yieldSourceRateStatus !== "success" || shortRateStatus !== "success") { return ; } - if (!rewards?.length && multiplierLabel && yieldSourceRate) { - return ( -
+ + return ( +
+
- {multiplierLabel} + {rewards?.length ? : null}
- ); - } - - return ( -
- - {multiplierLabel} - + {`Up to ${multiplierLabel} exposure`}
); } diff --git a/apps/hyperdrive-trading/src/ui/markets/PositionPicker.tsx b/apps/hyperdrive-trading/src/ui/markets/PositionPicker.tsx index 7f2b25544..8c9b5b4b6 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PositionPicker.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PositionPicker.tsx @@ -67,7 +67,7 @@ export function PositionPicker({ }); }} > - Long + Fixed -

Open a Long and hold it to maturity.

- When you open a Long in Hyperdrive, you're buying tokens at a - discount. These tokens are redeemable at maturity for their full - value, giving you a fixed return. + When you open a fixed rate position in Hyperdrive, you're buying + tokens at a discount. These tokens are redeemable at maturity for + their full value, giving you a fixed return.

- Opening Longs has an immediate impact on the market. If more people - open Longs, the fixed rate goes down. If they close Longs, the fixed - rate goes up. + Opening a fixed rate position has an immediate impact on the market. + If more people buy the fixed rate, the fixed rate goes down. If they + close their position, the fixed rate goes up.

- Users can also take speculative Long positions where they bet on the - short-term movement of rates. Read our docs to learn more about{" "} + Users can also take speculative fixed rate positions where they bet on + the short-term movement of rates. Read our docs to learn more about{" "} , - label: "Long", + label: "Fixed Rates", onClick: () => { navigate({ search: (prev) => ({ ...prev, position: "longs" }), diff --git a/apps/hyperdrive-trading/src/ui/portfolio/longs/OpenLongsTable/OpenLongsTableDesktop.tsx b/apps/hyperdrive-trading/src/ui/portfolio/longs/OpenLongsTable/OpenLongsTableDesktop.tsx index 175f41532..5954a206a 100644 --- a/apps/hyperdrive-trading/src/ui/portfolio/longs/OpenLongsTable/OpenLongsTableDesktop.tsx +++ b/apps/hyperdrive-trading/src/ui/portfolio/longs/OpenLongsTable/OpenLongsTableDesktop.tsx @@ -332,15 +332,15 @@ function getColumns({ ); }, }), - columnHelper.accessor("assetId", { + columnHelper.display({ id: "go-to-market", - cell: ({ row, getValue }) => { + cell: ({ row }) => { return ( ); },