Skip to content

Commit

Permalink
feat: add liquid staking info on main page
Browse files Browse the repository at this point in the history
  • Loading branch information
GarageInc committed Oct 9, 2024
1 parent f520a08 commit dcb5d9f
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 25 deletions.
186 changes: 175 additions & 11 deletions libs/main/src/lib/components/my-account-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
useIndexerBalanceQuery,
useStakingUnbondingsQuery,
} from '@haqq/shell-shared';
import { useStislmBalance, useStrideRates } from '@haqq/shell-staking';
import {
Tooltip,
Button,
Expand All @@ -36,11 +37,13 @@ import {
function MyAccountAmountBlock({
title,
value,
subValue,
isGreen = false,
valueClassName,
}: {
title: string;
value: string | ReactNode;
subValue?: string;
isGreen?: boolean;
valueClassName?: string;
}) {
Expand All @@ -60,6 +63,9 @@ function MyAccountAmountBlock({
>
{value}
</div>
{subValue ? (
<div className="text-[14px] text-white/50">{subValue}</div>
) : null}
</div>
);
}
Expand Down Expand Up @@ -154,9 +160,29 @@ function MyAccountConnected({
}, 2500);
}
}, [copyText, haqqAddress]);
const {
isHovered: isHoveredLocked,
handleMouseEnter: handleMouseEnterLocked,
handleMouseLeave: handleMouseLeaveLocked,
} = useHoverPopover(100);

const { isHovered, handleMouseEnter, handleMouseLeave } =
useHoverPopover(100);
const {
isHovered: isHoveredStaking,
handleMouseEnter: handleMouseEnterStaking,
handleMouseLeave: handleMouseLeaveStaking,
} = useHoverPopover(100);

const {
isHovered: isHoveredLiquidStaking,
handleMouseEnter: handleMouseEnterLiquidStaking,
handleMouseLeave: handleMouseLeaveLiquidStaking,
} = useHoverPopover(100);

const unbonding = useUnbonding(haqqAddress);

const { stIslmBalance } = useStislmBalance();

const { data: { islmAmountFromStIslm } = {} } = useStrideRates(stIslmBalance);

if (!balances) {
return null;
Expand Down Expand Up @@ -186,11 +212,11 @@ function MyAccountConnected({
<div className="font-clash text-[20px] font-[500] leading-[26px] text-white">
{`${formatNumber(balances.balance)} ${symbol.toLocaleUpperCase()}`}
</div>
<div className="leading-[0px]">
<Popover open={isHovered} placement="top-start">
<div className="flex flex-col gap-[4px] leading-[0px]">
<Popover open={isHoveredLocked} placement="top-start">
<PopoverTrigger
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseEnter={handleMouseEnterLocked}
onMouseLeave={handleMouseLeaveLocked}
>
<div
className={clsx(
Expand All @@ -201,24 +227,91 @@ function MyAccountConnected({
)}
>
<span>Locked: {formatNumber(balances.locked)}</span>
<InfoIcon className="ml-[2px] inline h-[18px] w-[18px]" />

{balances && unbonding ? (

Check warning

Code scanning / CodeQL

Useless conditional Warning

This use of variable 'balances' always evaluates to true.
<InfoIcon className="ml-[2px] inline h-[18px] w-[18px]" />
) : null}
</div>
</PopoverTrigger>

<PopoverContent className="outline-none">
<LockedBalancePopup haqqAddress={haqqAddress} />
</PopoverContent>
</Popover>

<Popover open={isHoveredStaking} placement="top-start">
<PopoverTrigger
onMouseEnter={handleMouseEnterStaking}
onMouseLeave={handleMouseLeaveStaking}
>
<div
className={clsx(
'font-guise inline-flex cursor-help flex-row justify-center gap-[4px]',
'text-white hover:text-white/50',
'text-[12px] font-[500] leading-[18px]',
'transition-colors duration-150 ease-in-out',
)}
>
<span>
Available for staking:{' '}
{formatNumber(balances.available)}
</span>
<InfoIcon className="ml-[2px] inline h-[18px] w-[18px]" />
</div>
</PopoverTrigger>

<PopoverContent className="outline-none">
<StakingBalancePopup
haqqAddress={haqqAddress}
title="In regular staking you can use coins in liquid staking"
/>
</PopoverContent>
</Popover>

<Popover open={isHoveredLiquidStaking} placement="top-start">
<PopoverTrigger
onMouseEnter={handleMouseEnterLiquidStaking}
onMouseLeave={handleMouseLeaveLiquidStaking}
>
<div
className={clsx(
'font-guise inline-flex cursor-help flex-row justify-center gap-[4px]',
'text-white hover:text-white/50',
'text-[12px] font-[500] leading-[18px]',
'transition-colors duration-150 ease-in-out',
)}
>
<span>
Available for liquid staking:{' '}
{formatNumber(balances.availableForStake)}
</span>
<InfoIcon className="ml-[2px] inline h-[18px] w-[18px]" />
</div>
</PopoverTrigger>

<PopoverContent className="outline-none">
<StakingBalancePopup
haqqAddress={haqqAddress}
isLiquidStaking
/>
</PopoverContent>
</Popover>
</div>
</div>
</div>
<MyAccountAmountBlock
title="Staked"
title="Regular STAKED"
value={`${formatNumber(balances.staked)} ${symbol.toLocaleUpperCase()}`}
/>
<MyAccountAmountBlock
title="Rewards"
value={`${formatNumber(rewards)} ${symbol.toLocaleUpperCase()}`}
/>
<MyAccountAmountBlock
title="Liquid STAKED"
value={`${formatNumber(stIslmBalance)} stISLM`}
subValue={`≈${formatNumber(islmAmountFromStIslm)} ISLM`}
/>
<MyAccountAmountBlock
title="Address"
value={
Expand Down Expand Up @@ -281,8 +374,7 @@ function MyAccountConnected({
);
}

function LockedBalancePopup({ haqqAddress }: { haqqAddress: string }) {
const { data: balances } = useIndexerBalanceQuery(haqqAddress);
const useUnbonding = (haqqAddress: string) => {
const { data: undelegations } = useStakingUnbondingsQuery(haqqAddress);
const unbonding = useMemo(() => {
const allUnbound: number[] = (undelegations ?? []).map((validator) => {
Expand All @@ -302,7 +394,14 @@ function LockedBalancePopup({ haqqAddress }: { haqqAddress: string }) {
return Number.parseFloat(formatUnits(BigInt(result), 18));
}, [undelegations]);

if (!balances || !unbonding) {
return unbonding;
};

function LockedBalancePopup({ haqqAddress }: { haqqAddress: string }) {
const { data: balances } = useIndexerBalanceQuery(haqqAddress);
const unbonding = useUnbonding(haqqAddress);

if (!balances) {
return null;
}

Expand Down Expand Up @@ -349,3 +448,68 @@ function LockedBalancePopup({ haqqAddress }: { haqqAddress: string }) {
</div>
);
}

function StakingBalancePopup({
haqqAddress,
title,
isLiquidStaking,
}: {
haqqAddress: string;
title?: string;
isLiquidStaking?: boolean;
}) {
const { data: balances } = useIndexerBalanceQuery(haqqAddress);
const unbonding = useUnbonding(haqqAddress);

const { stIslmBalance } = useStislmBalance();

if (!balances) {
return null;
}

console.log(balances);

return (
<div className="bg-haqq-black font-guise border-haqq-border max-w-[320px] transform-gpu rounded-lg border bg-opacity-90 text-white shadow-lg backdrop-blur">
<div className="flex flex-col divide-y divide-white/15 px-[8px]">
{title ? (
<div className="py-[8px]">
<p className="text-[12px] leading-[18px] text-[#8E8E8E]">{title}</p>
</div>
) : null}
<div className="py-[8px]">
<div className="flex flex-row items-center gap-[4px]">
<CoinIcon />
<div className="text-[14px] leading-[22px] text-white">
Available: {formatNumber(balances.availableForStake)}
</div>
</div>
</div>
<div className="py-[8px]">
<div className="flex flex-col gap-[8px]">
<div className="flex flex-row items-center gap-[4px]">
<LockIcon />
<div className="text-[14px] leading-[22px] text-white">
Locked:{' '}
{isLiquidStaking
? formatNumber(stIslmBalance)
: formatNumber(balances.locked)}
</div>
</div>

<div>
<StakedVestedBalance
available={balances.availableForStake}
locked={isLiquidStaking ? stIslmBalance : balances.locked}
staked={isLiquidStaking ? stIslmBalance : balances.staked}
vested={balances.vested}
daoLocked={balances.daoLocked}
unbonding={unbonding}
/>
</div>
</div>
</div>
</div>
</div>
);
}
24 changes: 12 additions & 12 deletions libs/staking/src/lib/components/stride/statistics/stride-stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import {
} from '../../staking-stats';

const MIN_BALANCE = 0.2;
const MIN_DELEGATION = 0.01; // '0x4FEBDDe47Ab9a76200e57eFcC80b212a07b3e6cE'; // '0x12fEFEAc0568503F7C0D934c149f29a42B05C48f';
const MIN_DELEGATION = 0.01;

export function StrideStats() {
const { openSelectWallet, isHaqqWallet } = useWallet();
const { isHaqqWallet } = useWallet();

const chains = useChains();
const { chain = chains[0] } = useAccount();
Expand All @@ -44,10 +44,10 @@ export function StrideStats() {

const balance = balances?.availableForStake ?? 0;

const { data: { redemption_rate: redemptionRate } = {} } = useStrideRates();

const { stIslmBalance } = useStislmBalance();

const { data: { islmAmountFromStIslm } = {} } = useStrideRates(stIslmBalance);

const isWalletConnected = Boolean(ethAddress && haqqAddress);

if (!isWalletConnected) {
Expand All @@ -73,13 +73,13 @@ export function StrideStats() {
<StrideStatsMobile
balance={balance}
stIslmBalance={stIslmBalance}
redemptionRate={redemptionRate}
islmAmountFromStIslm={islmAmountFromStIslm}
/>
) : (
<StrideStatsDesktop
balance={balance}
stIslmBalance={stIslmBalance}
redemptionRate={redemptionRate}
islmAmountFromStIslm={islmAmountFromStIslm}
/>
)}
</section>
Expand Down Expand Up @@ -112,11 +112,11 @@ export const useHandleDelegateContinue = () => {
function StrideStatsDesktop({
balance,
stIslmBalance,
redemptionRate,
islmAmountFromStIslm,
}: {
balance: number;
stIslmBalance: number;
redemptionRate: number;
islmAmountFromStIslm: number;
}) {
const { handleDelegateContinue, handleUndelegateContinue } =
useHandleDelegateContinue();
Expand Down Expand Up @@ -152,7 +152,7 @@ function StrideStatsDesktop({
<div className="w-[210px]">
<StakingStatsDesktopAmountBlock
title="stISLM in ISLM"
value={`≈${formatNumber(stIslmBalance * (redemptionRate ?? 1))}`}
value={`≈${formatNumber(islmAmountFromStIslm)}`}
symbol="ISLM"
uppercaseSymbol={false}
/>
Expand Down Expand Up @@ -197,11 +197,11 @@ function StrideStatsDesktop({
function StrideStatsMobile({
balance,
stIslmBalance,
redemptionRate,
islmAmountFromStIslm,
}: {
balance: number;
stIslmBalance: number;
redemptionRate: number;
islmAmountFromStIslm: number;
}) {
const { handleDelegateContinue, handleUndelegateContinue } =
useHandleDelegateContinue();
Expand Down Expand Up @@ -230,7 +230,7 @@ function StrideStatsMobile({
/>
<StakingStatsMobileAmountBlock
title="stISLM in ISLM"
value={`≈${formatNumber(stIslmBalance * (redemptionRate ?? 1))}`}
value={`≈${formatNumber(islmAmountFromStIslm)}`}
symbol="ISLM"
uppercaseSymbol={false}
/>
Expand Down
11 changes: 9 additions & 2 deletions libs/staking/src/lib/hooks/use-stride-rates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ async function fetchRedemptionRate() {
}
}

export function useStrideRates() {
export function useStrideRates(stIslmBalance: number) {
const { data, isLoading, error } = useQuery({
queryKey: ['redemptionRate'],
queryFn: fetchRedemptionRate,
refetchInterval: 10000,
});

return { data, isLoading, error };
return {
data: {
...data,
islmAmountFromStIslm: stIslmBalance * data?.redemption_rate ?? 1,
},
isLoading,
error,
};
}

0 comments on commit dcb5d9f

Please sign in to comment.