diff --git a/apps/hyperdrive-trading/src/ui/app/Navbar/DevtoolsMenu.tsx b/apps/hyperdrive-trading/src/ui/app/Navbar/DevtoolsMenu.tsx index 6339560a4..40ed2e279 100644 --- a/apps/hyperdrive-trading/src/ui/app/Navbar/DevtoolsMenu.tsx +++ b/apps/hyperdrive-trading/src/ui/app/Navbar/DevtoolsMenu.tsx @@ -20,6 +20,9 @@ export function DevtoolsMenu(): ReactElement { Show paused pools + + New design + { window.localStorage.clear(); 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 bb6f2f315..68c520593 100644 --- a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongForm/OpenLongForm.tsx +++ b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongForm/OpenLongForm.tsx @@ -49,6 +49,7 @@ export function OpenLongForm({ onOpenLong, }: OpenLongFormProps): ReactElement { const { address: account } = useAccount(); + const { isFlagEnabled: isNewDesign } = useFeatureFlag("new-design"); const connectedChainId = useChainId(); const { marketState } = useMarketState({ hyperdriveAddress: hyperdrive.address, @@ -450,7 +451,7 @@ export function OpenLongForm({ onOpenLong?.(e); }} > - Buy Fixed + {isNewDesign ? "Buy Fixed" : "Open Long"} ); })()} diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx index 3c1041f99..fa24ec8a1 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/FixedAprCta.tsx @@ -3,6 +3,7 @@ import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; import { Link } from "@tanstack/react-router"; import { ReactElement } from "react"; import { formatRate } from "src/base/formatRate"; +import { useFeatureFlag } from "src/ui/base/featureFlags/featureFlags"; 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"; @@ -14,6 +15,7 @@ interface FixedAprCtaProps { export function FixedAprCta({ hyperdrive }: FixedAprCtaProps): ReactElement { const { address: account } = useAccount(); + const { isFlagEnabled: isNewDesign } = useFeatureFlag("new-design"); const { fixedApr, fixedRateStatus } = useFixedRate({ chainId: hyperdrive.chainId, hyperdriveAddress: hyperdrive.address, @@ -59,6 +61,36 @@ export function FixedAprCta({ hyperdrive }: FixedAprCtaProps): ReactElement { ) } isLoading={fixedRateStatus === "loading"} + action={ + isNewDesign ? null : ( + { + 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 5a4204169..64a360010 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/LpApyCta.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/LpApyCta.tsx @@ -2,6 +2,7 @@ import { fixed } from "@delvtech/fixed-point-wasm"; import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; import { Link } from "@tanstack/react-router"; import { ReactElement } from "react"; +import { useFeatureFlag } from "src/ui/base/featureFlags/featureFlags"; 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"; @@ -14,6 +15,7 @@ interface LpApyCtaProps { export function LpApyCta({ hyperdrive }: LpApyCtaProps): ReactElement { const { address: account } = useAccount(); + const { isFlagEnabled: isNewDesign } = useFeatureFlag("new-design"); const { lpApy, lpApyStatus } = useLpApy({ hyperdriveAddress: hyperdrive.address, chainId: hyperdrive.chainId, @@ -62,6 +64,36 @@ export function LpApyCta({ hyperdrive }: LpApyCtaProps): ReactElement { hyperdriveAddress={hyperdrive.address} /> } + action={ + isNewDesign ? null : ( + { + 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 af6e092fa..7a0dbaa70 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolRow.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolRow.tsx @@ -12,6 +12,7 @@ import { isTestnetChain } from "src/chains/isTestnetChain"; import { getDepositAssets } from "src/hyperdrive/getDepositAssets"; import { useAppConfigForConnectedChain } from "src/ui/appconfig/useAppConfigForConnectedChain"; import { Well } from "src/ui/base/components/Well/Well"; +import { useFeatureFlag } from "src/ui/base/featureFlags/featureFlags"; import { formatCompact } from "src/ui/base/formatting/formatCompact"; import { usePresentValue } from "src/ui/hyperdrive/hooks/usePresentValue"; import { AssetStack } from "src/ui/markets/AssetStack"; @@ -37,6 +38,7 @@ const pinnedBorderClassNames: Record = { export function PoolRow({ hyperdrive }: PoolRowProps): ReactElement { const navigate = useNavigate(); const appConfig = useAppConfigForConnectedChain(); + const { isFlagEnabled: isNewDesign } = useFeatureFlag("new-design"); const { chains } = appConfig; const chainInfo = chains[hyperdrive.chainId]; @@ -175,13 +177,29 @@ 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 69e253bd0..f3aa1943c 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolStat.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/PoolStat.tsx @@ -12,6 +12,11 @@ export interface PoolStatProps { overlay?: ReactNode; value: ReactNode; isLoading?: boolean; + + /** + * @deprecated + */ + action?: ReactNode; isNew?: boolean; variant?: "default" | "gradient"; } @@ -22,6 +27,7 @@ export function PoolStat({ overlay, isNew = false, variant = "default", + action, isLoading = false, }: PoolStatProps): ReactElement { let displayValue; @@ -43,6 +49,7 @@ 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 7cd53ef4b..1d1122461 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyCta.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyCta.tsx @@ -2,6 +2,9 @@ import { fixed } from "@delvtech/fixed-point-wasm"; import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; import { Link } from "@tanstack/react-router"; import { ReactElement } from "react"; +import { calculateMarketYieldMultiplier } from "src/hyperdrive/calculateMarketYieldMultiplier"; +import { useFeatureFlag } from "src/ui/base/featureFlags/featureFlags"; +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"; @@ -17,12 +20,23 @@ export function VariableApyCta({ hyperdrive, }: YieldMultiplierCtaProps): ReactElement { const { address: account } = useAccount(); + const { isFlagEnabled: isNewDesign } = useFeatureFlag("new-design"); const { vaultRate: yieldSourceRate } = useYieldSourceRate({ chainId: hyperdrive.chainId, hyperdriveAddress: hyperdrive.address, }); + const { rewards } = useOpenShortRewards({ hyperdriveConfig: hyperdrive }); + const { longPrice, longPriceStatus } = useCurrentLongPrice({ + chainId: hyperdrive.chainId, + hyperdriveAddress: hyperdrive.address, + }); + const multiplier = + longPriceStatus === "success" && longPrice + ? calculateMarketYieldMultiplier(longPrice) + : undefined; + const label = yieldSourceRate ? `Variable APY (${yieldSourceRate.ratePeriodDays}d)` : "Variable APY"; @@ -70,6 +84,37 @@ export function VariableApyCta({ chainId={hyperdrive.chainId} /> } + action={ +
+ {isNewDesign ? null : ( + { + e.stopPropagation(); + window.plausible("positionCtaClick", { + props: { + chainId: hyperdrive.chainId, + poolAddress: hyperdrive.address, + positionType: "short", + statName: label, + statValue: multiplier ? multiplier.toString() : "", + connectedWallet: account, + }, + }); + }} + > + Open Short + + )} + {`${multiplier?.format({ decimals: 0 })}x yield exposure`} +
+ } /> ); diff --git a/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx b/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx index 865a5e028..88373a057 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PoolRow/VariableApyStat.tsx @@ -3,9 +3,7 @@ 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 { 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"; @@ -30,10 +28,6 @@ export function VariableApyStat({ chainId, hyperdriveAddress, }); - const { longPrice, longPriceStatus } = useCurrentLongPrice({ - chainId: hyperdrive.chainId, - hyperdriveAddress: hyperdrive.address, - }); const { shortApr, shortRateStatus } = useShortRate({ chainId: hyperdrive.chainId, @@ -44,11 +38,6 @@ export function VariableApyStat({ variableApy: yieldSourceRate?.netVaultRate, }); - const multiplierLabel = - longPriceStatus === "success" && longPrice - ? `${calculateMarketYieldMultiplier(longPrice).format({ decimals: 0 })}x` - : undefined; - if (yieldSourceRateStatus !== "success" || shortRateStatus !== "success") { return ; } @@ -64,7 +53,6 @@ export function VariableApyStat({ /> {rewards?.length ? : null}
- {`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 8c9b5b4b6..51c2de4e5 100644 --- a/apps/hyperdrive-trading/src/ui/markets/PositionPicker.tsx +++ b/apps/hyperdrive-trading/src/ui/markets/PositionPicker.tsx @@ -2,6 +2,7 @@ import { HyperdriveConfig } from "@delvtech/hyperdrive-appconfig"; import { Link, useSearch } from "@tanstack/react-router"; import classNames from "classnames"; import { ReactElement } from "react"; +import { useFeatureFlag } from "src/ui/base/featureFlags/featureFlags"; import { formatTermLength2 } from "src/ui/markets/formatTermLength"; import { MARKET_DETAILS_ROUTE } from "src/ui/markets/routes"; import { useAccount } from "wagmi"; @@ -12,6 +13,7 @@ export function PositionPicker({ hyperdrive: HyperdriveConfig; }): ReactElement { const { address: account } = useAccount(); + const { isFlagEnabled: isNewDesign } = useFeatureFlag("new-design"); const { position: activePosition = "long" } = useSearch({ from: MARKET_DETAILS_ROUTE, }); @@ -67,7 +69,7 @@ export function PositionPicker({ }); }} > - Fixed + {isNewDesign ? "Fixed" : "Long"} +

Open a Long and hold it to maturity.

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

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

- 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{" "} + Users can also take speculative Long positions where they bet on the + short-term movement of rates. Read our docs to learn more about{" "} , - label: "Fixed Rates", + label: isNewDesign ? "Fixed Rates" : "Long", onClick: () => { navigate({ search: (prev) => ({ ...prev, position: "longs" }), diff --git a/packages/hyperdrive-js/package.json b/packages/hyperdrive-js/package.json index dba2ebf49..db5503577 100644 --- a/packages/hyperdrive-js/package.json +++ b/packages/hyperdrive-js/package.json @@ -32,6 +32,7 @@ "abitype": "^1.0.5", "dotenv": "^16.4.5", "prettier": "3.3.3", + "p-retry": "^6.2.1", "prettier-plugin-organize-imports": "4.0.0", "sinon": "^18.0.0", "tsconfig-paths": "^4.2.0", diff --git a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts index d51aa0256..6ab224d30 100644 --- a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts +++ b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts @@ -10,6 +10,7 @@ import { ReadContract, } from "@delvtech/drift"; import { fixed } from "@delvtech/fixed-point-wasm"; +import retry from "p-retry"; import { HyperdriveSdkError } from "src/HyperdriveSdkError"; import { assertNever } from "src/base/assertNever"; import { calculateAprFromPrice } from "src/base/calculateAprFromPrice"; @@ -188,13 +189,34 @@ export class ReadHyperdrive extends ReadClient { fromBlock?: BlockTag | bigint; toBlock?: BlockTag | bigint; }): Promise { - const events = await this.contract.getEvents("Initialize", options); + const events = await retry( + async () => { + // On Gnosischain, the Initialize event request sometimes comes back as + // an empty array due to Alchemy blips. We throw an error in this case + // so that we trigger a retry. + const events = await this.contract.getEvents("Initialize", options); + if (!events.length || events[0].blockNumber === undefined) { + // We need to clear the cache otherwise we'll get the same empty array + // back from the cache. + this.contract.cache.clear(); + throw new HyperdriveSdkError( + `Pool has not been initialized, no block found. ${this.address}`, + ); + } + return events; + }, + { + retries: 10, + onFailedAttempt: async (error) => { + // TODO: Remove retry logic all together once we stop seeing retry + // logs in the console all the time. + console.log( + `getInitializationBlock failed on Hyperdrive ${this.address}, chainId: ${await this.drift.getChainId()}. There are ${error.retriesLeft} retries left.`, + ); + }, + }, + ); - if (!events.length || events[0].blockNumber === undefined) { - throw new HyperdriveSdkError( - `Pool has not been initialized, no block found. ${this.address}`, - ); - } const blockNumber = events[0].blockNumber; return getBlockOrThrow(this.drift, { blockNumber });