Skip to content

Commit

Permalink
Feat/12 our node for staking (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
r8bywork authored Apr 17, 2024
2 parents 4d7c14e + 41b4f92 commit 1a5fd62
Show file tree
Hide file tree
Showing 20 changed files with 5,558 additions and 153 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=http://173.249.17.202
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml

NEXT_PUBLIC_RPC_HOST=wss://testnet-rpc.atleta.network:9944
2 changes: 2 additions & 0 deletions configs/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ const socketEndpoint = [
apiPort && ':' + apiPort,
].filter(Boolean).join('');

const rpcEndpoing = getEnvValue('NEXT_PUBLIC_RPC_HOST');
const api = Object.freeze({
host: apiHost,
protocol: apiSchema,
port: apiPort,
endpoint: apiEndpoint,
socket: socketEndpoint,
basePath: stripTrailingSlash(getEnvValue('NEXT_PUBLIC_API_BASE_PATH') || ''),
rpc: rpcEndpoing,
});

export default api;
2 changes: 2 additions & 0 deletions configs/envs/.env.localhost
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ NEXT_PUBLIC_STATS_API_HOST=http://173.249.17.202
NEXT_PUBLIC_VISUALIZE_API_HOST=http://173.249.17.202
NEXT_PUBLIC_IS_TESTNET=true
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
NEXT_PUBLIC_RPC_HOST=wss://testnet-rpc.atleta.network:9944

NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
2 changes: 2 additions & 0 deletions lib/date/dayjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ dayjs.updateLocale('en', {

dayjs.locale('en');

// eslint-disable-next-line no-restricted-imports
export type { Dayjs } from 'dayjs';
export default dayjs;
50 changes: 50 additions & 0 deletions lib/polkadot/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ApiPromise, WsProvider } from '@polkadot/api';
import { useQuery } from '@tanstack/react-query';
import React from 'react';

interface PolkadotApiContenxtType {
isLoading: boolean;
isError: boolean;
isSuccess: boolean;
api?: ApiPromise;
}

export const PolkadotApiContext = React.createContext<PolkadotApiContenxtType>({ isLoading: true, isError: false, isSuccess: false });

interface PolkadotApiProps {
children: React.ReactNode;
url?: string;
}

export function PolkadotApiProvider({ children, url }: PolkadotApiProps) {
const { isLoading, isError, isSuccess, data } = useQuery({
queryKey: [ 'PolkadotApi', url ],
queryFn: async() => {
const provider = new WsProvider(url);
const api = await ApiPromise.create({ provider });

return api;
},
});

return (
<PolkadotApiContext.Provider value={{
isLoading,
isSuccess,
isError,
api: data,
}}>
{ children }
</PolkadotApiContext.Provider>
);
}

export function usePolkadotApi() {
const context = React.useContext(PolkadotApiContext);

if (context?.isLoading === undefined) {
throw new Error('usePolkadotApi must be used within a PolkadotApiProvider');
}

return context;
}
12 changes: 12 additions & 0 deletions lib/polkadot/useRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getEnvValue } from 'configs/app/utils';

import { usePolkadotApi } from './context';

export const useRegistry = () => {
const { api, isSuccess } = usePolkadotApi();

return {
decimals: isSuccess ? api?.registry.chainDecimals[0].toString() : getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS'),
symbol: isSuccess ? api?.registry.chainTokens[0] : (getEnvValue('NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL') || '18'),
};
};
1 change: 1 addition & 0 deletions nextjs/csp/policies/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function app(): CspDev.DirectiveDescriptor {
// APIs
config.api.endpoint,
config.api.socket,
config.api.rpc,
getFeaturePayload(config.features.stats)?.api.endpoint,
getFeaturePayload(config.features.sol2uml)?.api.endpoint,
getFeaturePayload(config.features.verifiedTokens)?.api.endpoint,
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
"@opentelemetry/sdk-node": "^0.45.0",
"@opentelemetry/sdk-trace-node": "^1.18.0",
"@opentelemetry/semantic-conventions": "^1.18.0",
"@polkadot/api": "^10.12.6",
"@polkadot/types": "^10.12.6",
"@sentry/cli": "^2.21.2",
"@sentry/react": "7.24.0",
"@sentry/tracing": "7.24.0",
Expand Down
7 changes: 4 additions & 3 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import { ChakraProvider } from 'lib/contexts/chakra';
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
import { growthBook } from 'lib/growthbook/init';
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
import { PolkadotApiProvider } from 'lib/polkadot/context';
import { SocketProvider } from 'lib/socket/context';
import theme from 'theme';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
import GoogleAnalytics from 'ui/shared/GoogleAnalytics';
import Layout from 'ui/shared/layout/Layout';
import Web3ModalProvider from 'ui/shared/Web3ModalProvider';

import 'lib/setLocale';

type AppPropsWithLayout = AppProps & {
Expand Down Expand Up @@ -51,7 +51,6 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
}, []);

const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);

return (
<ChakraProvider theme={ theme } cookies={ pageProps.cookies }>
<AppErrorBoundary
Expand All @@ -64,7 +63,9 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<GrowthBookProvider growthbook={ growthBook }>
<ScrollDirectionProvider>
<SocketProvider url={ `${ config.api.socket }${ config.api.basePath }/socket/v2` }>
{ getLayout(<Component { ...pageProps }/>) }
<PolkadotApiProvider url={ config.api.rpc }>
{ getLayout(<Component { ...pageProps }/>) }
</PolkadotApiProvider>
</SocketProvider>
</ScrollDirectionProvider>
</GrowthBookProvider>
Expand Down
63 changes: 24 additions & 39 deletions ui/pages/Staking.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,62 @@
import { Flex, Grid, Heading } from '@chakra-ui/react';
import React from 'react';

import { usePolkadotApi } from 'lib/polkadot/context';
import { useRegistry } from 'lib/polkadot/useRegistry';
import ChartWidget from 'ui/shared/chart/ChartWidget';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import PageTitle from 'ui/shared/Page/PageTitle';
import InfoBlock from 'ui/staking/InfoBlock';
import StakingStats from 'ui/staking/StakingStats';
import { useAverageRewardRate } from 'ui/staking/utils/useAverageRewardRate';
import { useRecentPayouts } from 'ui/staking/utils/useRecentPayouts';
import { useStats } from 'ui/staking/utils/useStats';

const Staking = () => {
const chartData = [
{
date: '2024-01-01',
value: '22.754379629407154',
},
{
date: '2024-01-02',
value: '27.453057189560774',
},
{
date: '2024-01-03',
value: '43.10447337606563',
},
{
date: '2024-01-04',
value: '7.411615351008571',
},
];
const { isLoading } = usePolkadotApi();
const { averageRewardRate, inflationRate } = useAverageRewardRate();
const registry = useRegistry();
const stats = useStats();
const chartData = useRecentPayouts(4);

const detailsInfoItems = [
{
title: 'Total Validators',
hint: 'Validators secure the Polkadot Relay Chain by validating blocks.',
isLoading: false,
value: '1,017',
isLoading,
value: stats.totalValidators,
},
{
title: 'Total Nominators',
hint: 'Stakers in the network include accounts, whether active or inactive in the current session.',
isLoading: false,
value: '41,179',
isLoading,
value: stats.totalNominators,
},
{
title: 'Active Pools',
hint: 'The current amount of active nomination pools on Polkadot.',
isLoading: false,
value: '201',
isLoading,
value: stats.activePools,
},
{
title: 'Inflation Rate to Stakers',
hint: 'DOT has unlimited supply with ~10% annual inflation. Validator rewards depend on staked amounts.',
isLoading: false,
value: '8.99%',
hint: `${ registry.symbol } has unlimited supply with ~10% annual inflation. Validator rewards depend on staked amounts.`,
isLoading,
value: `${ inflationRate }%`,
},
];

const updatedChartData: Array<{ date: Date; value: number }> = chartData.map(
(item) => ({
date: new Date(item.date),
value: Number(item.value),
}),
);

return (
<div>
<PageTitle title="Overview"/>
<StakingStats/>
<StakingStats averageRewardRate={ averageRewardRate }/>
<ChartWidget
marginTop="20px"
items={ updatedChartData }
items={ chartData }
title="Recent Payouts"
units="DOT"
description="0 DOT"
isLoading={ false }
units={ registry.symbol }
description={ `0 ${ registry.symbol }` }
isLoading={ isLoading }
isError={ false }
minH="230px"
/>
Expand Down
24 changes: 16 additions & 8 deletions ui/staking/InfoBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import { Box, Flex } from '@chakra-ui/react';
import React from 'react';
import React, { useMemo } from 'react';

import { useRegistry } from 'lib/polkadot/useRegistry';

import { useInfo } from './utils/useInfo';

const InfoBlock = () => {
const poolInfo: Array<{ title: string; description: string }> = [
const info = useInfo();
const registry = useRegistry();

const poolInfo: Array<{ title: string; description: string }> = useMemo(() => [
{
title: '29,152 pool members are actively bonding in pools.',
title: `${ info.poolMembersCounter.toFormat(0) } members are actively bonding in pools.`,
description: 'The total number of accounts that have joined a pool.',
},
{
title: '14,050,812 DOT is currently bonded in pools.',
description: 'The total DOT currently bonded in nomination pools.',
title: `${ info.totalValueLocked.toFormat(0) } ${ registry.symbol } is currently bonded in pools.`,
description: `The total ${ registry.symbol } currently bonded in nomination pools.`,
},
{
title: '742,190,826 DOT is currently being staked on Polkadot.',
title: `${ info.totalStake.toFormat(0) } ${ registry.symbol } is currently being staked on Polkadot.`,
description:
'The total DOT currently being staked amongst all validators and nominators.',
`The total ${ registry.symbol } currently being staked amongst all validators and nominators.`,
},
];
], [ info, registry ]);

return (
<Flex
Expand Down
24 changes: 18 additions & 6 deletions ui/staking/StakingStats.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Grid } from '@chakra-ui/react';
import React from 'react';

import { useRegistry } from 'lib/polkadot/useRegistry';

import StatsItem from '../home/StatsItem';
import { useEraRemaining } from './utils/useEraRemaining';
import { useSupply } from './utils/useSupply';

const StakingStats = ({ averageRewardRate }: { averageRewardRate: string }) => {
const registry = useRegistry();

const eraRemaining = useEraRemaining();
const supply = useSupply();

const StakingStats = () => {
return (
<Grid
gridTemplateColumns={{ lg: `repeat(4, 1fr)`, base: '1fr 1fr' }}
Expand All @@ -14,19 +23,22 @@ const StakingStats = () => {
<StatsItem
icon="txn_batches"
title="Average Reward Rate"
value="17.43%"
value={ `${ averageRewardRate }%` }
tooltipLabel="Estimated annual return of staking rewards."
/>
<StatsItem
icon="block"
title="KSM Supply Staked"
value="52.99%"
tooltipLabel="Cumulative supply of DOT being staked globally relative to the total supply of DOT."
title={ `${ registry.symbol } Supply Staked` }
value={ `${ supply }%` }
tooltipLabel={
`Cumulative supply of ${ registry.symbol } being staked` +
` globally relative to the total supply of ${ registry.symbol }.`
}
/>
<StatsItem
icon="clock-light"
title="Time Remaining This Era"
value="55 mins"
value={ `${ Math.trunc(eraRemaining.asHours()) }h ${ eraRemaining.minutes() }m ${ eraRemaining.seconds() }s` }
tooltipLabel="At the end of each era, validators are rewarded DOT based on era points accumulated. 1 era is currently 24 hours in Polkadot."
/>
</Grid>
Expand Down
Loading

0 comments on commit 1a5fd62

Please sign in to comment.