Skip to content

Commit

Permalink
EVM Staking - Top Level Stats (#1786)
Browse files Browse the repository at this point in the history
Co-authored-by: Trung-Tin Pham <[email protected]>
  • Loading branch information
devpavan04 and AtelyPham authored Oct 22, 2023
1 parent 42e32c3 commit 70ae7b4
Show file tree
Hide file tree
Showing 33 changed files with 759 additions and 36 deletions.
18 changes: 12 additions & 6 deletions apps/tangle-dapp/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { Typography } from '@webb-tools/webb-ui-components';
import { HeaderChipsContainer } from '../containers/HeaderChipsContainer';
import { HeaderChipsContainer, KeyMetricsTableContainer } from '../containers';

export default async function Index() {
return (
<div className="flex items-center justify-between">
<Typography variant="h4" fw="bold">
Staking Overview
</Typography>
<div>
<div className="flex items-center justify-between">
<Typography variant="h4" fw="bold">
Staking Overview
</Typography>

<HeaderChipsContainer />
<HeaderChipsContainer />
</div>

<div className="mt-12">
<KeyMetricsTableContainer />
</div>
</div>
);
}
3 changes: 2 additions & 1 deletion apps/tangle-dapp/components/HeaderChip/HeaderChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SkeletonLoader,
} from '@webb-tools/webb-ui-components';
import { HeaderChipItemProps } from './types';
import { getRoundedDownNumberWith2Decimals } from '../../utils';

export const HeaderChip: FC<HeaderChipItemProps> = ({
Icon,
Expand Down Expand Up @@ -48,5 +49,5 @@ const HeaderChipValue = async ({
}: Pick<HeaderChipItemProps, 'dataFetcher'>) => {
const value = await dataFetcher();

return <>{value}</>;
return <>{getRoundedDownNumberWith2Decimals(value)}</>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { FC } from 'react';
import { IconWithTooltip } from '@webb-tools/webb-ui-components';
import { InformationLine } from '@webb-tools/icons';

import { InfoIconWithTooltipProps } from './types';

export const InfoIconWithTooltip: FC<InfoIconWithTooltipProps> = ({
content,
}) => {
return (
<IconWithTooltip
overrideTooltipBodyProps={{
className: 'max-w-[200px]',
}}
icon={<InformationLine className="fill-mono-140 dark:fill-mono-40" />}
content={<p className="break-normal max-w-max">{content}</p>}
/>
);
};
1 change: 1 addition & 0 deletions apps/tangle-dapp/components/InfoIconWithTooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './InfoIconWithTooltip';
3 changes: 3 additions & 0 deletions apps/tangle-dapp/components/InfoIconWithTooltip/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface InfoIconWithTooltipProps {
content: string;
}
62 changes: 62 additions & 0 deletions apps/tangle-dapp/components/KeyMetricItem/KeyMetricItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { FC, Suspense } from 'react';
import { Typography, SkeletonLoader } from '@webb-tools/webb-ui-components';
import { InfoIconWithTooltip } from '..';
import { getRoundedDownNumberWith2Decimals } from '../../utils';
import { MetricItemProps } from './types';
import { twMerge } from 'tailwind-merge';

export const KeyMetricItem: FC<MetricItemProps> = ({
title,
tooltip,
className,
...restProps
}) => {
return (
<div className={twMerge('px-2 py-2 space-y-2 md:px-4', className)}>
<div className="flex items-center gap-0.5">
<Typography variant="body1" className="text-mono-140 dark:text-mono-40">
{title}
</Typography>
{tooltip && <InfoIconWithTooltip content={tooltip} />}
</div>

<Suspense fallback={<SkeletonLoader size="lg" />}>
<KeyMetricItemValue {...restProps} />
</Suspense>
</div>
);
};

const KeyMetricItemValue = async (
props: Omit<MetricItemProps, 'title' | 'tooltip'>
) => {
const { dataFetcher, prefix, suffix } = props;

const { value1, value2 } = await dataFetcher();

return (
<div className="flex flex-col gap-1 sm:flex-row sm:items-center">
<div className="flex items-center gap-0.5">
<Typography
variant="h4"
fw="bold"
className="text-mono-140 dark:text-mono-40"
>
{typeof value1 === 'number' && (prefix ?? '')}
{getRoundedDownNumberWith2Decimals(value1)}
{value2 && <> / {getRoundedDownNumberWith2Decimals(value2)}</>}
</Typography>

{typeof value1 === 'number' && suffix && (
<Typography
variant="h4"
fw="bold"
className="text-mono-140 dark:text-mono-40"
>
{suffix}
</Typography>
)}
</div>
</div>
);
};
1 change: 1 addition & 0 deletions apps/tangle-dapp/components/KeyMetricItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './KeyMetricItem';
10 changes: 10 additions & 0 deletions apps/tangle-dapp/components/KeyMetricItem/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MetricReturnType } from '../../types';

export interface MetricItemProps {
title: string;
prefix?: string;
suffix?: string;
tooltip?: string;
dataFetcher: () => Promise<MetricReturnType>;
className?: string;
}
4 changes: 3 additions & 1 deletion apps/tangle-dapp/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './Sidebar';
export * from './sideBar';
export * from './Breadcrumbs';
export * from './HeaderChip';
export * from './InfoIconWithTooltip';
export * from './KeyMetricItem';
4 changes: 2 additions & 2 deletions apps/tangle-dapp/components/sideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { type FC } from 'react';
import { SideBar as SideBarCmp } from '@webb-tools/webb-ui-components';
import { setSideBarCookieOnToggle } from '@webb-tools/webb-ui-components/next-utils';
import SideBarProps from './sideBarProps';
import sideBarProps from './sideBarProps';

interface SideBarProps {
isExpandedAtDefault?: boolean;
Expand All @@ -12,7 +12,7 @@ interface SideBarProps {
const SideBar: FC<SideBarProps> = ({ isExpandedAtDefault }) => {
return (
<SideBarCmp
{...SideBarProps}
{...sideBarProps}
className="hidden lg:block"
isExpandedAtDefault={isExpandedAtDefault}
onSideBarToggle={() => setSideBarCookieOnToggle()}
Expand Down
12 changes: 6 additions & 6 deletions apps/tangle-dapp/components/sideBar/sideBarProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
WEBB_FAUCET_URL,
} from '@webb-tools/webb-ui-components/constants';

const SideBarItems: SideBarItemProps[] = [
const sideBarItems: SideBarItemProps[] = [
{
name: 'EVM Staking',
href: '/',
Expand Down Expand Up @@ -78,20 +78,20 @@ const SideBarItems: SideBarItemProps[] = [
},
];

const SideBarFooter: SideBarFooterType = {
const sideBarFooter: SideBarFooterType = {
Icon: DocumentationIcon,
href: WEBB_TANGLE_DOCS_URL,
isInternal: false,
name: 'Tangle Docs',
useNextThemesForThemeToggle: true,
};

const SideBarProps: SidebarProps = {
const sideBarProps: SidebarProps = {
ClosedLogo: SidebarTangleClosedIcon,
Logo: TangleLogo,
footer: SideBarFooter,
items: SideBarItems,
footer: sideBarFooter,
items: sideBarItems,
logoLink: TANGLE_MKT_URL,
};

export default SideBarProps;
export default sideBarProps;
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { BlockIcon } from '@webb-tools/icons';
import { HeaderChip } from '../../components';
import { getEra, getSession } from '../../data';
import { getEraCount, getSessionCount } from '../../data';

export const HeaderChipsContainer = () => {
return (
<div className="items-center hidden gap-2 md:flex lg:gap-4">
<HeaderChip Icon={BlockIcon} label="ERA" dataFetcher={getEra} />
<HeaderChip Icon={BlockIcon} label="ERA" dataFetcher={getEraCount} />

<HeaderChip Icon={BlockIcon} label="Session" dataFetcher={getSession} />
<HeaderChip
Icon={BlockIcon}
label="Session"
dataFetcher={getSessionCount}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import cx from 'classnames';
import { cache } from 'react';
import { KeyMetricItem } from '../../components/KeyMetricItem';
import {
getValidatorsCount,
getWaitingCount,
getActiveAndDelegationCount,
getIdealStakedPercentage,
getInflationPercentage,
} from '../../data';

const getValidatorsCountData = cache(getValidatorsCount);
const getWaitingCountData = cache(getWaitingCount);
const getActiveAndDelegationCountData = cache(getActiveAndDelegationCount);
const getIdealStakedPercentageData = cache(getIdealStakedPercentage);
const getInflationPercentageData = cache(getInflationPercentage);

export const KeyMetricsTableContainer = () => {
return (
<div
className={cx(
'w-full rounded-lg overflow-hidden',
'bg-glass dark:bg-glass_dark',
'border-2 border-mono-0 dark:border-mono-160'
)}
>
<div
className={cx(
'grid gap-1 grid-cols-2 lg:grid-cols-5',
'[&>div]:border-r [&>div]:border-r-mono-40 [&>div]:dark:border-r-mono-160',
'[&>div]:even:border-none',
'lg:[&>div]:even:border-r',
'[&>div]:border-b [&>div]:border-b-mono-40 [&>div]:dark:border-b-mono-160',
'lg:[&>div]:nth-last-child(-n+5):border-b-0',
'[&>div]:nth-last-child(-n+2):border-b-0'
)}
>
{/* Validators */}
<KeyMetricItem
title="Validators"
tooltip="Current # of active validators out of the total allowed."
dataFetcher={() => getValidatorsCountData()}
className="col-span-2 lg:col-span-1"
/>
{/* Waiting */}
<KeyMetricItem
title="Waiting"
tooltip="Nodes waiting in line to become active validators."
dataFetcher={() => getWaitingCountData()}
/>
{/* Active/Delegation */}
<KeyMetricItem
title="Active/Delegation"
tooltip="Current active delegations out of the total possible."
dataFetcher={() => getActiveAndDelegationCountData()}
/>
{/* Ideal Staked */}
<KeyMetricItem
title="Ideal Staked"
tooltip="The ideal % of all network tokens that should be staked."
suffix="%"
dataFetcher={() => getIdealStakedPercentageData()}
/>
{/* Inflation */}
<KeyMetricItem
title="Inflation"
tooltip="The yearly % increase in the network’s total token supply."
suffix="%"
dataFetcher={() => getInflationPercentageData()}
/>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './KeyMetricsTableContainer';
2 changes: 2 additions & 0 deletions apps/tangle-dapp/containers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { Layout } from './Layout';
export * from './KeyMetricsTableContainer';
export * from './HeaderChipsContainer';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getPolkadotApi } from '../../constants';

export const getEra = async (): Promise<number> => {
export const getEraCount = async (): Promise<number> => {
const api = await getPolkadotApi();

if (!api) return NaN;
Expand All @@ -13,6 +13,6 @@ export const getEra = async (): Promise<number> => {
} catch (e: any) {
console.error(e);

return 0;
return NaN;
}
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getPolkadotApi } from '../../constants';

export const getSession = async (): Promise<number> => {
export const getSessionCount = async (): Promise<number> => {
const api = await getPolkadotApi();

if (!api) return NaN;
Expand All @@ -13,6 +13,6 @@ export const getSession = async (): Promise<number> => {
} catch (e: any) {
console.error(e);

return 0;
return NaN;
}
};
4 changes: 2 additions & 2 deletions apps/tangle-dapp/data/HeaderChips/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './getEra';
export * from './getSession';
export * from './getEraCount';
export * from './getSessionCount';
46 changes: 46 additions & 0 deletions apps/tangle-dapp/data/TopLevelStats/getActiveAndDelegationCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { getPolkadotApi } from '../../constants';
import { MetricReturnType } from '../../types';

export const getActiveAndDelegationCount =
async (): Promise<MetricReturnType> => {
const api = await getPolkadotApi();

if (!api) return { value1: NaN, value2: NaN };

try {
const totalNominatorCount =
await api.query.staking.counterForNominators();

// Get the current active era
const activeEra = await api.query.staking.activeEra();
const currentEra = activeEra.unwrap().index;

// Get the list of current validators
const currentValidators = await api.query.session.validators();

// Collection of unique nominator addresses - Set is used to avoid duplicates, as a nominator can nominate multiple validators
const activeNominators = new Set();

// For each validator, get their nominators
for (const validator of currentValidators) {
const exposure = await api.query.staking.erasStakers(
currentEra,
validator
);
for (const nominator of exposure.others) {
activeNominators.add(nominator.who.toString());
}
}

const activeNominatorsCount = activeNominators.size;

return {
value1: activeNominatorsCount,
value2: Number(totalNominatorCount.toString()),
};
} catch (e: any) {
console.error(e);

return { value1: NaN, value2: NaN };
}
};
Loading

0 comments on commit 70ae7b4

Please sign in to comment.