diff --git a/apps/hyperdrive-trading/src/base/calculateAnnualizedPercentageChange.ts b/apps/hyperdrive-trading/src/base/calculateAnnualizedPercentageChange.ts
new file mode 100644
index 000000000..de7392f0f
--- /dev/null
+++ b/apps/hyperdrive-trading/src/base/calculateAnnualizedPercentageChange.ts
@@ -0,0 +1,25 @@
+/**
+ * Calculates the annualized profit or loss percentage.
+ * @param amountBefore The amount paid.
+ * @param amountAfter The amount received.
+ * @param days The number of days the investment was held.
+ * @returns The calculated annualized profit or loss percentage.
+ */
+export function calculateAnnualizedPercentageChange({
+ amountBefore,
+ amountAfter,
+ days,
+}: {
+ amountBefore: bigint;
+ amountAfter: bigint;
+ days: number;
+}): string {
+ if (amountBefore === 0n) {
+ return "0"; // Prevent division by zero
+ }
+
+ const profitOrLoss =
+ Number(amountAfter - amountBefore) / Number(amountBefore);
+ const annualizedProfitOrLoss = ((1 + profitOrLoss) ** (365 / days) - 1) * 100;
+ return annualizedProfitOrLoss.toFixed(2);
+}
diff --git a/apps/hyperdrive-trading/src/ui/app/Navbar/Navbar.tsx b/apps/hyperdrive-trading/src/ui/app/Navbar/Navbar.tsx
index 6e7b72ef0..8d788faf8 100644
--- a/apps/hyperdrive-trading/src/ui/app/Navbar/Navbar.tsx
+++ b/apps/hyperdrive-trading/src/ui/app/Navbar/Navbar.tsx
@@ -7,9 +7,9 @@ export function Navbar(): ReactElement {
Hyperdrive
diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/CloseLongModalButton/CloseLongModalButton.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/CloseLongModalButton/CloseLongModalButton.tsx
index 43ca22fc7..c012a64e7 100644
--- a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/CloseLongModalButton/CloseLongModalButton.tsx
+++ b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/CloseLongModalButton/CloseLongModalButton.tsx
@@ -19,9 +19,6 @@ export function CloseLongModalButton({
(window as any)[modalId].close();
}
- const maturity = new Date(Number(long.maturity * 1000n));
- const hasMatured = maturity < new Date();
-
return (
-
+
Close position
@@ -51,18 +45,6 @@ export function CloseLongModalButton({
/>
}
- >
- {({ showModal }) => (
-
- {hasMatured ? "Redeem" : "Close"}
-
- )}
-
+ />
);
}
diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongModalButton/OpenLongModalButton.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongModalButton/OpenLongModalButton.tsx
index f3d0c0bc2..5b67fbc3d 100644
--- a/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongModalButton/OpenLongModalButton.tsx
+++ b/apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongModalButton/OpenLongModalButton.tsx
@@ -28,7 +28,7 @@ export function OpenLongModalButton({
onClick={() => {
setSearchParams({
...searchParams,
- position: "Longs",
+ position: "Buys",
openOrClosed: "Open",
});
showModal();
@@ -36,15 +36,17 @@ export function OpenLongModalButton({
>
-
Buy hy{hyperdrive.baseToken.symbol}
+
+ Buy hy{hyperdrive.baseToken.symbol}
+
Earn {fixedAPR?.formatted || "-"}% APR on{" "}
{hyperdrive.baseToken.symbol}
- 1 hy{hyperdrive.baseToken.symbol} is always worth one{" "}
- {hyperdrive.baseToken.symbol} at maturity. It{"'"}s a predictable
- fixed rate.
+ 1 hy{hyperdrive.baseToken.symbol} is worth 1{" "}
+ {hyperdrive.baseToken.symbol} at maturity, giving you a
+ predictable fixed rate yield.
diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/lp/AddLiquidityModalButton/AddLiquidityModalButton.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/lp/AddLiquidityModalButton/AddLiquidityModalButton.tsx
index 731ee9e66..fd3f74fab 100644
--- a/apps/hyperdrive-trading/src/ui/hyperdrive/lp/AddLiquidityModalButton/AddLiquidityModalButton.tsx
+++ b/apps/hyperdrive-trading/src/ui/hyperdrive/lp/AddLiquidityModalButton/AddLiquidityModalButton.tsx
@@ -34,7 +34,7 @@ export function AddLiquidityModalButton({
>
-
Add Liquidity
+
Add Liquidity
Earn trading fees and interest
@@ -47,10 +47,10 @@ export function AddLiquidityModalButton({
Single-sided deposit with {hyperdrive.baseToken.symbol}
- Idle capital earns yield source rate
+ Idle liquidity earns yield source rate
- No terms or rollovers, easy-to-use
+ No terms or manual LP rollovers
{/* Using a div styled as a button here just as a visual cue. Don't
diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/CloseShortModalButton/CloseShortModalButton.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/CloseShortModalButton/CloseShortModalButton.tsx
index 0ba537aae..17a70e7d6 100644
--- a/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/CloseShortModalButton/CloseShortModalButton.tsx
+++ b/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/CloseShortModalButton/CloseShortModalButton.tsx
@@ -28,10 +28,7 @@ export function CloseShortModalButton({
className="daisy-btn-ghost daisy-btn-sm daisy-btn-circle daisy-btn absolute right-4 top-4"
onClick={closeModal}
>
-
+
Close position
diff --git a/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortModalButton/OpenShortModalButton.tsx b/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortModalButton/OpenShortModalButton.tsx
index c3c396e08..33a3d9715 100644
--- a/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortModalButton/OpenShortModalButton.tsx
+++ b/apps/hyperdrive-trading/src/ui/hyperdrive/shorts/OpenShortModalButton/OpenShortModalButton.tsx
@@ -38,16 +38,18 @@ export function OpenShortModalButton({
>
-
Short hy{hyperdrive.baseToken.symbol}
+
+ Short hy{hyperdrive.baseToken.symbol}
+
Earn {vaultRate?.formatted}% APY on{" "}
{hyperdrive.baseToken.symbol}
- Profit when the price of hy{hyperdrive.baseToken.symbol} drops,
- and also earn the yield source rate.
+ Profit when hy{hyperdrive.baseToken.symbol} price drops, while
+ maximizing exposure to the yield source.
-
+
Fixed rate up, hy{hyperdrive.baseToken.symbol} price down
diff --git a/apps/hyperdrive-trading/src/ui/portfolio/OpenLongsTable/OpenLongsTable.tsx b/apps/hyperdrive-trading/src/ui/portfolio/OpenLongsTable/OpenLongsTable.tsx
index a3938d659..577b80f6b 100644
--- a/apps/hyperdrive-trading/src/ui/portfolio/OpenLongsTable/OpenLongsTable.tsx
+++ b/apps/hyperdrive-trading/src/ui/portfolio/OpenLongsTable/OpenLongsTable.tsx
@@ -1,69 +1,221 @@
/* eslint-disable react/jsx-key */
+import { Long } from "@hyperdrive/sdk";
+import { useQuery } from "@tanstack/react-query";
+import {
+ Row,
+ createColumnHelper,
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+import classNames from "classnames";
import { ReactElement } from "react";
import { Hyperdrive } from "src/appconfig/types";
-import {
- CellWithTooltip,
- SortableGridTable,
-} from "src/ui/base/components/tables/SortableGridTable";
-import { useOpenLongRows } from "src/ui/portfolio/OpenLongsTable/useOpenLongRows";
+import { calculateAnnualizedPercentageChange } from "src/base/calculateAnnualizedPercentageChange";
+import { convertMillisecondsToDays } from "src/base/convertMillisecondsToDays";
+import { makeQueryKey } from "src/base/makeQueryKey";
+import { formatBalance } from "src/ui/base/formatting/formatBalance";
+import { useReadHyperdrive } from "src/ui/hyperdrive/hooks/useReadHyperdrive";
+import { CloseLongModalButton } from "src/ui/hyperdrive/longs/CloseLongModalButton/CloseLongModalButton";
+import { usePreviewCloseLong } from "src/ui/hyperdrive/longs/hooks/usePreviewCloseLong";
+import { parseUnits } from "viem";
import { useAccount } from "wagmi";
-interface OpenOrdersTableProps {
+interface OpenLongsTableProps {
hyperdrive: Hyperdrive;
}
+const columnHelper = createColumnHelper
();
+
+function getColumns(hyperdrive: Hyperdrive) {
+ return [
+ columnHelper.display({
+ header: `ID`,
+ cell: ({ row }) => {Number(row.original.maturity)} ,
+ }),
+ columnHelper.display({
+ header: `Matures on`,
+ cell: ({ row }) => {
+ const maturity = new Date(Number(row.original.maturity * 1000n));
+ return {maturity.toLocaleDateString()} ;
+ },
+ }),
+ columnHelper.display({
+ id: "size",
+ header: `Size (hy${hyperdrive.baseToken.symbol})`,
+ cell: ({ row }) => {
+ return (
+
+ {formatBalance({
+ balance: row.original.bondAmount,
+ decimals: hyperdrive.baseToken.decimals,
+ places: 2,
+ })}
+
+ );
+ },
+ }),
+ columnHelper.accessor("baseAmountPaid", {
+ id: "amountPaid",
+ header: `Amount paid (${hyperdrive.baseToken.symbol})`,
+ cell: (baseAmountPaid) => {
+ const amountPaid = baseAmountPaid.getValue();
+ return formatBalance({
+ balance: amountPaid,
+ decimals: hyperdrive.baseToken.decimals,
+ places: 2,
+ });
+ },
+ }),
+ columnHelper.display({
+ id: "fixedRate",
+ header: `Fixed rate (APR)`,
+ cell: ({ row }) => {
+ return `${calculateAnnualizedPercentageChange({
+ amountBefore: row.original.baseAmountPaid,
+ amountAfter: row.original.bondAmount,
+ days: convertMillisecondsToDays(hyperdrive.termLengthMS),
+ })}%`;
+ },
+ }),
+ columnHelper.display({
+ id: "value",
+ header: `Market value (${hyperdrive.baseToken.symbol})`,
+ cell: ({ row }) => {
+ return ;
+ },
+ }),
+ ];
+}
export function OpenLongsTable({
hyperdrive,
-}: OpenOrdersTableProps): ReactElement {
+}: OpenLongsTableProps): ReactElement {
const { address: account } = useAccount();
- const { openLongRows = [], openLongRowsStatus } = useOpenLongRows({
- account,
- hyperdrive,
+ const readHyperdrive = useReadHyperdrive(hyperdrive.address);
+ const queryEnabled = !!readHyperdrive && !!account;
+ const { data: longs } = useQuery({
+ queryKey: makeQueryKey("longPositions", { account }),
+ queryFn: queryEnabled
+ ? () => readHyperdrive?.getOpenLongs({ account })
+ : undefined,
+ enabled: queryEnabled,
});
+ const tableInstance = useReactTable({
+ columns: getColumns(hyperdrive),
+ data: longs || [],
+ getCoreRowModel: getCoreRowModel(),
+ });
+
+ return (
+
+ {/* Modal needs to be rendered outside of the table so that dialog can be used. Otherwise react throws a dom nesting error */}
+ {tableInstance.getRowModel().rows.map((row) => {
+ const modalId = `${row.original.assetId}`;
+ return (
+
+ );
+ })}
+
+
+ {tableInstance.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ ))}
+
+ ))}
+
+
+ {tableInstance.getRowModel().rows.map((row) => {
+ return (
+ {
+ const modalId = `${row.original.assetId}`;
+ (window as any)[modalId].showModal();
+ }}
+ >
+ <>
+ {row.getVisibleCells().map((cell) => {
+ return (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+ );
+ })}
+ >
+
+ );
+ })}
+
+
+
+ );
+}
+
+function CurrentValueCell({
+ row,
+ hyperdrive,
+}: {
+ row: Row;
+ hyperdrive: Hyperdrive;
+}) {
+ const { address: account } = useAccount();
+ const { baseAmountOut } = usePreviewCloseLong({
+ hyperdriveAddress: hyperdrive.address,
+ maturityTime: row.original.maturity,
+ bondAmountIn: row.original.bondAmount,
+ minBaseAmountOut: parseUnits("0", hyperdrive.baseToken.decimals),
+ destination: account,
+ });
+ const currentValue =
+ baseAmountOut &&
+ formatBalance({
+ balance: baseAmountOut,
+ decimals: hyperdrive.baseToken.decimals,
+ places: 4,
+ });
+
+ const isPositiveChangeInValue =
+ baseAmountOut && baseAmountOut > row.original.baseAmountPaid;
return (
-
- ),
- },
- {
- cell: (
-
- ),
- },
- {
- cell: (
-
- ),
- },
- {
- cell: (
-
- ),
- },
- ]}
- // cols={["Position", "Bonds", "Amount paid", "Value", "Matures on", ""]}
- rows={openLongRows}
- showSkeleton={openLongRowsStatus === "loading"}
- />
+
+
{currentValue?.toString()}
+
+ {isPositiveChangeInValue ? "+" : ""}
+ {baseAmountOut
+ ? `${formatBalance({
+ balance: baseAmountOut - row.original.baseAmountPaid,
+ decimals: hyperdrive.baseToken.decimals,
+ places: 4,
+ })} ${hyperdrive.baseToken.symbol}`
+ : undefined}
+
+
);
}
diff --git a/apps/hyperdrive-trading/src/ui/portfolio/OpenShortsTable/useOpenShortRows.tsx b/apps/hyperdrive-trading/src/ui/portfolio/OpenShortsTable/useOpenShortRows.tsx
deleted file mode 100644
index 9ec65f8a4..000000000
--- a/apps/hyperdrive-trading/src/ui/portfolio/OpenShortsTable/useOpenShortRows.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import { OpenShort } from "@hyperdrive/sdk";
-import { Hyperdrive } from "src/appconfig/types";
-import { Row } from "src/ui/base/components/tables/SortableGridTable";
-import { formatBalance } from "src/ui/base/formatting/formatBalance";
-import {
- getProfitLossText,
- getStyleClassForProfitLoss,
-} from "src/ui/hyperdrive/shorts/CloseShortForm/getProfitLossText";
-import { CloseShortModalButton } from "src/ui/hyperdrive/shorts/CloseShortModalButton/CloseShortModalButton";
-import { useOpenShorts } from "src/ui/hyperdrive/shorts/hooks/useOpenShorts";
-import { usePreviewCloseShort } from "src/ui/hyperdrive/shorts/hooks/usePreviewCloseShort";
-import { Address, parseUnits } from "viem";
-import { useAccount } from "wagmi";
-
-interface UseOpenShortRowsOptions {
- account: Address | undefined;
- hyperdrive: Hyperdrive | undefined;
-}
-
-export function useOpenShortRows({
- account,
- hyperdrive,
-}: UseOpenShortRowsOptions): {
- openShortRows: Row[] | undefined;
- openShortRowsStatus: "error" | "success" | "loading";
-} {
- const { openShorts = [], openShortsStatus: openShortRowsStatus } =
- useOpenShorts({
- account,
- hyperdriveAddress: hyperdrive?.address,
- });
-
- const openShortRows = hyperdrive
- ? openShorts.map((short) =>
- createOpenShortRow({
- hyperdrive,
- short,
- }),
- )
- : [];
-
- return {
- openShortRows,
- openShortRowsStatus,
- };
-}
-
-function createOpenShortRow({
- short,
- hyperdrive,
-}: {
- hyperdrive: Hyperdrive;
- short: OpenShort;
-}): Row {
- const {
- baseToken: { decimals: baseDecimals, symbol: baseSymbol },
- } = hyperdrive;
-
- const modalId = `${short.assetId}`;
-
- return {
- cells: [
-
- Short
- ,
-
- {formatBalance({
- balance: short.bondAmount,
- decimals: baseDecimals,
- places: 4,
- })}
- ,
- ,
-
- {new Date(Number(short.maturity * 1000n)).toLocaleDateString()}
- ,
-
-
- ,
- ],
- };
-}
-
-function ProfitLossCell({
- baseDecimals,
- hyperdriveAddress,
- baseSymbol,
- short,
- hyperdrive,
-}: {
- short: OpenShort;
- hyperdriveAddress: Address;
- baseDecimals: number;
- baseSymbol: string;
- hyperdrive: Hyperdrive;
-}) {
- const { address: account } = useAccount();
- const { baseAmountOut } = usePreviewCloseShort({
- hyperdriveAddress,
- maturityTime: short.maturity,
- shortAmountIn: short.bondAmount,
- minBaseAmountOut: parseUnits("0", 18),
- destination: account,
- });
-
- const profitLossClass = baseAmountOut
- ? getStyleClassForProfitLoss(baseAmountOut, short.baseAmountPaid)
- : "";
-
- return (
-
- {baseAmountOut && short.bondAmount !== 0n
- ? `${getProfitLossText({
- baseAmountOut,
- amountInput: short.baseAmountPaid,
- baseDecimals,
- baseSymbol,
- showPercentage: false,
- })}`
- : ""}
-
- );
-}
diff --git a/apps/hyperdrive-trading/src/ui/portfolio/PositionTabs/PositionTabs.tsx b/apps/hyperdrive-trading/src/ui/portfolio/PositionTabs/PositionTabs.tsx
index df63d4a62..75c3ca0c8 100644
--- a/apps/hyperdrive-trading/src/ui/portfolio/PositionTabs/PositionTabs.tsx
+++ b/apps/hyperdrive-trading/src/ui/portfolio/PositionTabs/PositionTabs.tsx
@@ -1,7 +1,7 @@
import classNames from "classnames";
import { ReactElement } from "react";
-export type PositionTab = "Longs" | "Shorts" | "LP";
+export type PositionTab = "Buys" | "Shorts" | "LP";
export function PositionTabs({
onTabClick,
@@ -13,15 +13,15 @@ export function PositionTabs({
return (
onTabClick("Longs")}
+ onClick={() => onTabClick("Buys")}
className={classNames(
"daisy-tab-lifted daisy-tab daisy-tab-sm border-b-base-100 md:daisy-tab-lg",
{
- "daisy-tab-active": activePositionTab === "Longs",
+ "daisy-tab-active": activePositionTab === "Buys",
},
)}
>
- Longs
+ Buys
onTabClick("Shorts")}
diff --git a/apps/hyperdrive-trading/src/ui/portfolio/PositionsSection/PositionsSection.tsx b/apps/hyperdrive-trading/src/ui/portfolio/PositionsSection/PositionsSection.tsx
index 0b9f85088..db37d2653 100644
--- a/apps/hyperdrive-trading/src/ui/portfolio/PositionsSection/PositionsSection.tsx
+++ b/apps/hyperdrive-trading/src/ui/portfolio/PositionsSection/PositionsSection.tsx
@@ -24,7 +24,7 @@ export function PositionsSection({
const [searchParams, setSearchParams] = useSearchParams();
const activePositionTab =
- (searchParams.get("position") as PositionTab) || "Longs";
+ (searchParams.get("position") as PositionTab) || "Buys";
const activeOpenOrClosedTab =
(searchParams.get("openOrClosed") as OpenOrClosedTab) || "Open";
@@ -85,7 +85,7 @@ export function PositionsSection({
{(() => {
switch (activePositionTab) {
- case "Longs": {
+ case "Buys": {
if (activeOpenOrClosedTab === "Open") {
return
;
}
diff --git a/apps/hyperdrive-trading/src/ui/portfolio/YourBalanceWell/YourBalanceWell.tsx b/apps/hyperdrive-trading/src/ui/portfolio/YourBalanceWell/YourBalanceWell.tsx
index 2c9a210bf..6a5d4de3e 100644
--- a/apps/hyperdrive-trading/src/ui/portfolio/YourBalanceWell/YourBalanceWell.tsx
+++ b/apps/hyperdrive-trading/src/ui/portfolio/YourBalanceWell/YourBalanceWell.tsx
@@ -26,7 +26,7 @@ export function YourBalanceWell({ token }: { token: Token }): ReactElement {
+
{formatBalance({
balance: balance?.value || 0n,
decimals: token.decimals,
diff --git a/apps/hyperdrive-trading/tailwind.config.js b/apps/hyperdrive-trading/tailwind.config.js
index 5a474f2a0..86b0b9950 100644
--- a/apps/hyperdrive-trading/tailwind.config.js
+++ b/apps/hyperdrive-trading/tailwind.config.js
@@ -61,9 +61,15 @@ module.exports = {
night: {
// eslint-disable-next-line @typescript-eslint/no-var-requires
...require("daisyui/src/theming/themes")["[data-theme=lofi]"],
- primary: "#007eed",
- secondary: "#522dae",
- accent: "#f40000",
+ // Tone down the pure black
+ neutral: "#333333",
+ primary: "#333333",
+ secondary: "#333333",
+ accent: "#333333",
+
+ error: "#f40000",
+ success: "#019d60",
+
"--tab-radius": "0.4rem",
"--rounded-box": "0.4rem", // border radius rounded-box utility class, used in card and other large boxes
"--rounded-btn": "0.4rem",