Skip to content

Commit a03a479

Browse files
authored
Migrate steth -> lidoShares conversion from sdk to frontend (#1176)
* Remove conversions from ReadStethHyperdrive and ReadWriteStethHyperdrive * Create usePrepareSharesIn and usePrepareSharesOut * minor cleanup in aisle useAccruedYield * Update usePreviewOpenLong to prepare shares in * Update usePreviewCloseLong to prepare shares out * Update useMaxLong to prepare shares out * Update useCloseLong to prepare shares in * Update useOpenLong to prepare shares in * Update useAddLiquidity to prepare shares in * Update usePreviewAddLiquidity to prepare shares in * Update usePreviewRedeemWithdrawalShares to convert shares out * Update useRedeemWithdrawalShares to prepare shares in * Update useRemoveLiquidity to prepare shares in * Update usePreviewRemoveLiquidity to prepare shares in and out * Update useMaxShort to prepare shares out * Update useOpenShort to prepare shares in * Update usePreviewOpenShort to prepare shares out * Update useCloseShort to prepare shares in * Update usePreviewCloseShort to prepare shares out * Fix sign in prepareSharesOut * Remove unnecessary extra conversions that caused loss of precision * early return
1 parent d805c19 commit a03a479

26 files changed

+731
-840
lines changed

apps/hyperdrive-trading/src/ui/hyperdrive/hooks/useAccruedYield.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { HyperdriveConfig } from "@hyperdrive/appconfig";
22
import { useQuery } from "@tanstack/react-query";
33
import { makeQueryKey } from "src/base/makeQueryKey";
4+
import { QueryStatusWithIdle, getStatus } from "src/base/queryStatus";
45
import { useReadHyperdrive } from "src/ui/hyperdrive/hooks/useReadHyperdrive";
56

67
export function useAccruedYield({
@@ -13,20 +14,28 @@ export function useAccruedYield({
1314
bondAmount: bigint;
1415
}): {
1516
accruedYield: bigint | undefined;
17+
status: QueryStatusWithIdle;
1618
} {
1719
const readHyperdrive = useReadHyperdrive(hyperdrive.address);
1820
const queryEnabled = !!readHyperdrive;
19-
const { data: accruedYield } = useQuery({
21+
const { data, status, fetchStatus } = useQuery({
2022
queryKey: makeQueryKey("accruedYield", {
2123
hyperdriveAddress: hyperdrive.address,
2224
checkpointId: checkpointTime.toString(),
2325
bondAmount: bondAmount.toString(),
2426
}),
2527
queryFn: queryEnabled
2628
? () =>
27-
readHyperdrive.getShortAccruedYield({ checkpointTime, bondAmount })
29+
readHyperdrive.getShortAccruedYield({
30+
checkpointTime,
31+
bondAmount,
32+
})
2833
: undefined,
2934
enabled: queryEnabled,
3035
});
31-
return { accruedYield };
36+
37+
return {
38+
accruedYield: data,
39+
status: getStatus(status, fetchStatus),
40+
};
3241
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { ReadHyperdrive } from "@delvtech/hyperdrive-viem";
2+
import {
3+
AppConfig,
4+
findHyperdriveConfig,
5+
findYieldSourceToken,
6+
} from "@hyperdrive/appconfig";
7+
import { useQuery } from "@tanstack/react-query";
8+
import { makeQueryKey } from "src/base/makeQueryKey";
9+
import { QueryStatusWithIdle, getStatus } from "src/base/queryStatus";
10+
import { useAppConfig } from "src/ui/appconfig/useAppConfig";
11+
import { useReadHyperdrive } from "src/ui/hyperdrive/hooks/useReadHyperdrive";
12+
import { Address } from "viem";
13+
14+
export function usePrepareSharesIn({
15+
sharesAmount,
16+
hyperdriveAddress,
17+
enabled,
18+
}: {
19+
sharesAmount: bigint | undefined;
20+
hyperdriveAddress: Address;
21+
enabled: boolean;
22+
}): {
23+
amount: bigint | undefined;
24+
status: QueryStatusWithIdle;
25+
} {
26+
const appConfig = useAppConfig();
27+
const readHyperdrive = useReadHyperdrive(hyperdriveAddress);
28+
29+
const queryEnabled =
30+
!!readHyperdrive && sharesAmount !== undefined && enabled;
31+
const { data, status, fetchStatus } = useQuery({
32+
queryKey: makeQueryKey("prepare-shares-amount-in", {
33+
hyperdrive: hyperdriveAddress,
34+
amount: sharesAmount?.toString(),
35+
}),
36+
enabled: queryEnabled,
37+
queryFn: queryEnabled
38+
? () =>
39+
prepareSharesIn({
40+
appConfig,
41+
hyperdriveAddress,
42+
sharesAmount,
43+
readHyperdrive,
44+
})
45+
: undefined,
46+
});
47+
48+
return {
49+
amount: data,
50+
status: getStatus(status, fetchStatus),
51+
};
52+
}
53+
54+
export async function prepareSharesIn({
55+
appConfig,
56+
hyperdriveAddress,
57+
sharesAmount,
58+
readHyperdrive,
59+
}: {
60+
appConfig: AppConfig;
61+
hyperdriveAddress: Address;
62+
sharesAmount: bigint;
63+
readHyperdrive: ReadHyperdrive;
64+
}): Promise<bigint> {
65+
const hyperdriveConfig = findHyperdriveConfig({
66+
hyperdrives: appConfig.hyperdrives,
67+
hyperdriveAddress: hyperdriveAddress,
68+
});
69+
70+
const sharesToken = findYieldSourceToken({
71+
yieldSourceTokenAddress: hyperdriveConfig.sharesToken,
72+
tokens: appConfig.tokens,
73+
});
74+
75+
// If the shares token is pegged to its base token (e.g., stETH to ETH), then
76+
// we need to treat the amount as if it were base. To get the actual shares
77+
// amount then, we convert to the shares used by the pool (eg: lidoShares).
78+
if (sharesToken.extensions.isSharesPeggedToBase) {
79+
return readHyperdrive.convertToShares({
80+
baseAmount: sharesAmount,
81+
});
82+
}
83+
84+
// If the shares token is already in the format required by the hyperdrive
85+
// pool, return the original shares amount without any conversion.
86+
return sharesAmount;
87+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { ReadHyperdrive } from "@delvtech/hyperdrive-viem";
2+
import {
3+
AppConfig,
4+
findHyperdriveConfig,
5+
findYieldSourceToken,
6+
} from "@hyperdrive/appconfig";
7+
import { useQuery } from "@tanstack/react-query";
8+
import { makeQueryKey } from "src/base/makeQueryKey";
9+
import { QueryStatusWithIdle, getStatus } from "src/base/queryStatus";
10+
import { useAppConfig } from "src/ui/appconfig/useAppConfig";
11+
import { useReadHyperdrive } from "src/ui/hyperdrive/hooks/useReadHyperdrive";
12+
import { Address } from "viem";
13+
14+
export function usePrepareSharesOut({
15+
sharesAmount,
16+
hyperdriveAddress,
17+
enabled = true,
18+
}: {
19+
sharesAmount: bigint | undefined;
20+
hyperdriveAddress: Address;
21+
enabled?: boolean;
22+
}): {
23+
amount: bigint | undefined;
24+
status: QueryStatusWithIdle;
25+
} {
26+
const appConfig = useAppConfig();
27+
const readHyperdrive = useReadHyperdrive(hyperdriveAddress);
28+
29+
const queryEnabled =
30+
!!readHyperdrive && sharesAmount !== undefined && enabled;
31+
const { data, status, fetchStatus } = useQuery({
32+
queryKey: makeQueryKey("prepare-shares-amount-out", {
33+
hyperdrive: hyperdriveAddress,
34+
amount: sharesAmount?.toString(),
35+
}),
36+
enabled: queryEnabled,
37+
queryFn: queryEnabled
38+
? () =>
39+
prepareSharesOut({
40+
appConfig,
41+
hyperdriveAddress,
42+
sharesAmount,
43+
readHyperdrive,
44+
})
45+
: undefined,
46+
});
47+
48+
return {
49+
amount: data,
50+
status: getStatus(status, fetchStatus),
51+
};
52+
}
53+
54+
export async function prepareSharesOut({
55+
appConfig,
56+
hyperdriveAddress,
57+
sharesAmount,
58+
readHyperdrive,
59+
}: {
60+
appConfig: AppConfig;
61+
hyperdriveAddress: Address;
62+
sharesAmount: bigint;
63+
readHyperdrive: ReadHyperdrive;
64+
}): Promise<bigint> {
65+
if (sharesAmount === 0n) {
66+
return sharesAmount;
67+
}
68+
69+
const hyperdriveConfig = findHyperdriveConfig({
70+
hyperdrives: appConfig.hyperdrives,
71+
hyperdriveAddress: hyperdriveAddress,
72+
});
73+
74+
const sharesToken = findYieldSourceToken({
75+
yieldSourceTokenAddress: hyperdriveConfig.sharesToken,
76+
tokens: appConfig.tokens,
77+
});
78+
79+
// If the shares token is pegged to its base token (e.g., stETH to ETH), then
80+
// we need to treat the shares amount out as base. To get the actual shares
81+
// amount then, we convert to base. For example, when preparing lido shares
82+
// received back from a steth hyperdrive, this will convert lido shares to
83+
// eth, and since 1 eth = 1 steth we return this as the shares value.
84+
if (sharesToken.extensions.isSharesPeggedToBase) {
85+
return readHyperdrive.convertToBase({
86+
sharesAmount,
87+
});
88+
}
89+
90+
// If the shares token from the hyperdrive pool is already in the format
91+
// required by ui, return the original shares amount without any conversion.
92+
return sharesAmount;
93+
}

apps/hyperdrive-trading/src/ui/hyperdrive/longs/OpenLongsTable/CurrentValueCell.tsx

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { HyperdriveConfig, findBaseToken } from "@hyperdrive/appconfig";
44
import classNames from "classnames";
55
import { ReactElement } from "react";
66
import Skeleton from "react-loading-skeleton";
7-
import { convertSharesToBase } from "src/hyperdrive/convertSharesToBase";
87
import { useAppConfig } from "src/ui/appconfig/useAppConfig";
98
import { formatBalance } from "src/ui/base/formatting/formatBalance";
109
import { useIsTailwindSmallScreen } from "src/ui/base/mediaBreakpoints";
@@ -27,40 +26,29 @@ export function CurrentValueCell({
2726
const { poolInfo } = usePoolInfo({ hyperdriveAddress: hyperdrive.address });
2827

2928
const {
30-
amountOut: sharesAmountOut,
29+
amountOut: baseAmountOut,
3130
previewCloseLongStatus,
3231
previewCloseLongError,
3332
} = usePreviewCloseLong({
3433
hyperdriveAddress: hyperdrive.address,
3534
maturityTime: row.maturity,
3635
bondAmountIn: row.bondAmount,
37-
// Not all hyperdrives can close to base, but they can all close to
38-
// shares! To make this component easy, we'll always preview to shares
39-
// then do the conversion to base ourselves.
40-
asBase: false,
41-
});
42-
43-
// To get the base value of the shares, just do a simple conversion
44-
const amountOutInBase = convertSharesToBase({
45-
decimals: hyperdrive.decimals,
46-
sharesAmount: sharesAmountOut,
47-
vaultSharePrice: poolInfo?.vaultSharePrice,
4836
});
4937

5038
const currentValueLabel = formatBalance({
51-
balance: amountOutInBase || 0n,
39+
balance: baseAmountOut || 0n,
5240
decimals: baseToken.decimals,
5341
places: baseToken.places,
5442
});
5543

5644
const profitLoss = formatBalance({
57-
balance: amountOutInBase - row.baseAmountPaid,
45+
balance: (baseAmountOut || 0n) - row.baseAmountPaid,
5846
decimals: baseToken.decimals,
5947
places: baseToken.places,
6048
});
6149

6250
const isPositiveChangeInValue =
63-
amountOutInBase && amountOutInBase > row.baseAmountPaid;
51+
baseAmountOut && baseAmountOut > row.baseAmountPaid;
6452

6553
const cellClassName = classNames("daisy-stat flex flex-row p-0 xl:flex-col", {
6654
"flex w-32 flex-col items-end": !isTailwindSmallScreen,
@@ -103,7 +91,7 @@ export function CurrentValueCell({
10391
)}
10492
>
10593
<span>{isPositiveChangeInValue ? "+" : ""}</span>
106-
{amountOutInBase
94+
{baseAmountOut
10795
? `${profitLoss === "-0" ? "0" : profitLoss} ${baseToken.symbol}`
10896
: undefined}
10997
</div>

apps/hyperdrive-trading/src/ui/hyperdrive/longs/hooks/useCloseLong.tsx

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import {
66
} from "@tanstack/react-query";
77
import toast from "react-hot-toast";
88
import { parseError } from "src/network/parseError";
9+
import { useAppConfig } from "src/ui/appconfig/useAppConfig";
910
import TransactionToast from "src/ui/base/components/Toaster/TransactionToast";
1011
import { SUCCESS_TOAST_DURATION } from "src/ui/base/toasts";
12+
import { prepareSharesIn } from "src/ui/hyperdrive/hooks/usePrepareSharesIn";
1113
import { useReadWriteHyperdrive } from "src/ui/hyperdrive/hooks/useReadWriteHyperdrive";
1214
import { toastWarpcast } from "src/ui/social/WarpcastToast";
1315
import { Address, Hash } from "viem";
@@ -45,6 +47,7 @@ export function useCloseLong({
4547
const publicClient = usePublicClient();
4648
const queryClient = useQueryClient();
4749
const addTransaction = useAddRecentTransaction();
50+
const appConfig = useAppConfig();
4851

4952
const mutationEnabled =
5053
!!maturityTime &&
@@ -57,37 +60,50 @@ export function useCloseLong({
5760

5861
const { mutate: closeLong, status } = useMutation({
5962
mutationFn: async () => {
60-
if (mutationEnabled) {
61-
const hash = await readWriteHyperdrive.closeLong({
62-
args: {
63-
bondAmountIn,
64-
minAmountOut,
65-
destination,
66-
maturityTime,
67-
asBase,
68-
},
69-
onTransactionCompleted: (txHash: Hash) => {
70-
queryClient.invalidateQueries();
71-
toast.success(
72-
<TransactionToast message="Long closed" txHash={hash} />,
73-
{ id: hash, duration: SUCCESS_TOAST_DURATION },
74-
);
75-
toastWarpcast();
76-
onExecuted?.(txHash);
77-
},
78-
});
63+
if (!mutationEnabled) {
64+
return;
65+
}
7966

80-
toast.loading(
81-
<TransactionToast message="Closing Long..." txHash={hash} />,
82-
{ id: hash },
83-
);
84-
onSubmitted?.(hash);
67+
// if closing to shares, make sure the minimum shares out gets
68+
// prepared before going into the sdk
69+
const finalMinAmountOut = asBase
70+
? minAmountOut
71+
: await prepareSharesIn({
72+
appConfig,
73+
hyperdriveAddress,
74+
sharesAmount: minAmountOut,
75+
readHyperdrive: readWriteHyperdrive,
76+
});
8577

86-
addTransaction({
87-
hash,
88-
description: "Close Long",
89-
});
90-
}
78+
const hash = await readWriteHyperdrive.closeLong({
79+
args: {
80+
bondAmountIn,
81+
minAmountOut: finalMinAmountOut,
82+
destination,
83+
maturityTime,
84+
asBase,
85+
},
86+
onTransactionCompleted: (txHash: Hash) => {
87+
queryClient.invalidateQueries();
88+
toast.success(
89+
<TransactionToast message="Long closed" txHash={hash} />,
90+
{ id: hash, duration: SUCCESS_TOAST_DURATION },
91+
);
92+
toastWarpcast();
93+
onExecuted?.(txHash);
94+
},
95+
});
96+
97+
toast.loading(
98+
<TransactionToast message="Closing Long..." txHash={hash} />,
99+
{ id: hash },
100+
);
101+
onSubmitted?.(hash);
102+
103+
addTransaction({
104+
hash,
105+
description: "Close Long",
106+
});
91107
},
92108
onError(error) {
93109
const message = parseError({

0 commit comments

Comments
 (0)