Skip to content

Commit

Permalink
Add pointsMultiplier reward strategy to app config (#1690)
Browse files Browse the repository at this point in the history
* Refactor type field for rewards

* Handle pointMultiplier reward type

* Update RewardsTooltip to show multiplier based rewards
  • Loading branch information
DannyDelott authored Dec 17, 2024
1 parent b3f87b0 commit 7c4515b
Show file tree
Hide file tree
Showing 15 changed files with 443 additions and 389 deletions.
9 changes: 9 additions & 0 deletions apps/hyperdrive-trading/src/base/assertNever.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function assertNever(value: never, noThrow?: boolean): never {
if (noThrow) {
return value;
}

throw new Error(
`Unhandled discriminated union member: ${JSON.stringify(value)}`,
);
}
2 changes: 1 addition & 1 deletion apps/hyperdrive-trading/src/hyperdrive/getLpApy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export async function getLpApy({
if (rewardsFn) {
const rewards = await rewardsFn(publicClient);
rewards?.forEach((reward) => {
if (reward.type === "transferableToken") {
if (reward.type === "apy") {
netLpApy = fixed(reward.apy).add(netLpApy as bigint).bigint;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ async function calcNetRate(
}) as PublicClient;
const rewards = await rewardsFn(publicClient);
rewards?.forEach((reward) => {
if (reward.type === "transferableToken") {
if (reward.type === "apy") {
netRate = fixed(reward.apy).add(
// safe to cast because if we get here then lpApy has been set
netRate as bigint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export function PointsMarkets(): ReactElement | null {
});
// Only show pools that have a points reward type
const poolsWithPoints = pools
? pools.filter((pool) => pool.rewards?.find(({ type }) => type === "info"))
? pools.filter((pool) =>
pool.rewards?.find(({ type }) => type === "pointMultiplier"),
)
: [];

return (
Expand Down
199 changes: 116 additions & 83 deletions apps/hyperdrive-trading/src/ui/rewards/RewardsTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fixed } from "@delvtech/fixed-point-wasm";
import { fixed, parseFixed } from "@delvtech/fixed-point-wasm";
import {
appConfig,
getHyperdriveConfig,
Expand All @@ -8,6 +8,7 @@ import { SparklesIcon } from "@heroicons/react/16/solid";
import { ChartBarIcon } from "@heroicons/react/24/solid";
import * as Tooltip from "@radix-ui/react-tooltip";
import { PropsWithChildren, ReactNode } from "react";
import { assertNever } from "src/base/assertNever";
import { calculateMarketYieldMultiplier } from "src/hyperdrive/calculateMarketYieldMultiplier";
import { useIsNewPool } from "src/ui/hyperdrive/hooks/useIsNewPool";
import { useCurrentLongPrice } from "src/ui/hyperdrive/longs/hooks/useCurrentLongPrice";
Expand Down Expand Up @@ -37,10 +38,13 @@ export function RewardsTooltip({
hyperdriveAddress: hyperdrive.address,
});

const multiplierLabel =
const multiplier =
longPriceStatus === "success" && longPrice
? `${calculateMarketYieldMultiplier(longPrice).format({ decimals: 1 })}x`
: undefined;
? calculateMarketYieldMultiplier(longPrice)
: null;
const multiplierLabel = multiplier
? `${multiplier.format({ decimals: 1 })}x`
: undefined;

if (!appConfigRewards?.length && multiplierLabel && (!netRate || !baseRate)) {
return (
Expand Down Expand Up @@ -94,93 +98,122 @@ export function RewardsTooltip({
</div>

{appConfigRewards?.map((reward) => {
if (reward.type === "info") {
return (
<div
key={reward.iconUrl}
className="flex flex-col items-start justify-start gap-2 border-b border-neutral-content/30 p-3 [&:nth-last-child(2)]:border-none"
>
<div className="flex items-center gap-4">
<img
src={reward.iconUrl}
alt={`${reward.message}`}
className="h-8"
/>
<p>{reward.message}</p>
switch (reward.type) {
case "info":
return (
<div
key={reward.iconUrl}
className="flex flex-col items-start justify-start gap-2 border-b border-neutral-content/30 p-3 [&:nth-last-child(2)]:border-none"
>
<div className="flex items-center gap-4">
<img
src={reward.iconUrl}
alt={`${reward.message}`}
className="h-8"
/>
<p>{reward.message}</p>
</div>
</div>
</div>
);
}
if (reward.type === "transferableToken") {
// safe to cast because we assume all rewards tokens are
// available in appConfig
const token = getToken({
tokenAddress: reward.tokenAddress,
chainId: reward.chainId,
appConfig,
})!;
);
case "apy": {
// safe to cast because we assume all rewards tokens are
// available in appConfig
const token = getToken({
tokenAddress: reward.tokenAddress,
chainId: reward.chainId,
appConfig,
})!;

return (
<div
key={reward.tokenAddress}
className="flex items-center justify-between border-b border-neutral-content/30 p-3 [&:nth-last-child(2)]:border-none"
>
<div className="flex items-center gap-1">
<img
src={token.iconUrl}
alt={`${token.name} logo`}
className="h-4"
/>
{token.name}
</div>
return (
<div
key={reward.tokenAddress}
className="flex items-center justify-between border-b border-neutral-content/30 p-3 [&:nth-last-child(2)]:border-none"
>
<div className="flex items-center gap-1">
<img
src={token.iconUrl}
alt={`${token.name} logo`}
className="h-4"
/>
{token.name}
</div>

<div className="grid justify-items-end">
<p className="flex items-center gap-1">
+
{fixed(reward.apy).format({
percent: true,
decimals: 2,
})}
</p>
<div className="grid justify-items-end">
<p className="flex items-center gap-1">
+
{fixed(reward.apy).format({
percent: true,
decimals: 2,
})}
</p>
</div>
</div>
</div>
);
}
);
}
case "tokenAmount": {
// safe to cast because we assume all rewards tokens are
// available in appConfig
const token = getToken({
tokenAddress: reward.tokenAddress,
chainId: reward.chainId,
appConfig,
})!;
return (
<div
key={reward.tokenAddress}
className="flex items-center justify-between border-b border-neutral-content/30 p-3 [&:nth-last-child(2)]:border-none"
>
<div className="flex items-center gap-1">
<img
src={appConfig.protocols.morpho.iconUrl}
className="h-4"
/>
{token.name}
</div>

if (reward.type === "nonTransferableToken") {
// safe to cast because we assume all rewards tokens are
// available in appConfig
const token = getToken({
tokenAddress: reward.tokenAddress,
chainId: reward.chainId,
appConfig,
})!;
return (
<div
key={reward.tokenAddress}
className="flex items-center justify-between border-b border-neutral-content/30 p-3 [&:nth-last-child(2)]:border-none"
>
<div className="flex items-center gap-1">
<img
src={appConfig.protocols.morpho.iconUrl}
className="h-4"
/>
{token.name}
<div className="grid justify-items-end">
<p className="flex items-center gap-1">
+
{fixed(reward.tokensPerThousandUsd).format({
decimals: token.places,
})}
</p>
<p className="text-2xs text-neutral-content">
per $1000 / yr
</p>
</div>
</div>
);
}

<div className="grid justify-items-end">
<p className="flex items-center gap-1">
+
{fixed(reward.tokensPerThousandUsd).format({
decimals: token.places,
})}
</p>
<p className="text-2xs text-neutral-content">
per $1000 / yr
</p>
case "pointMultiplier":
return (
<div
key={reward.pointTokenLabel}
className="flex items-center justify-between border-b border-neutral-content/30 p-3 [&:nth-last-child(2)]:border-none"
>
<div className="flex items-center gap-1">
<img
src={reward.iconUrl}
alt={`${reward.pointTokenLabel} logo`}
className="h-4"
/>
{reward.pointTokenLabel}
</div>

<div className="grid justify-items-end">
<p className="flex items-center gap-1">
{multiplier
?.mul(parseFixed(reward.pointMultiplier))
.format({ decimals: 0 })}
x
</p>
</div>
</div>
</div>
);
);

default:
assertNever(reward);
}
})}

Expand Down
Loading

0 comments on commit 7c4515b

Please sign in to comment.