Bläddra i källkod

Move Tokenomics to Alexandria, initial changes

Edvin 4 år sedan
förälder
incheckning
b1b0c955be

+ 3 - 0
pioneer/packages/apps-routing/src/index.ts

@@ -27,11 +27,13 @@ import proposals from './joy-proposals';
 import roles from './joy-roles';
 import media from './joy-media';
 import forum from './joy-forum';
+import tokenomics from './joy-tokenomics';
 
 export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
   return appSettings.uiMode === 'light'
     ? [
       media(t),
+      tokenomics(t),
       members(t),
       roles(t),
       election(t),
@@ -49,6 +51,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
     ]
     : [
       media(t),
+      tokenomics(t),
       members(t),
       roles(t),
       election(t),

+ 15 - 0
pioneer/packages/apps-routing/src/joy-tokenomics.ts

@@ -0,0 +1,15 @@
+import { Route } from './types';
+
+import Tokenomics from '@polkadot/joy-tokenomics/index';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Tokenomics,
+    display: {
+      needsApi: []
+    },
+    text: t<string>('nav.tokenomics', 'Overview', { ns: 'apps-routing' }),
+    icon: 'th',
+    name: 'tokenomics'
+  };
+}

+ 0 - 38
pioneer/packages/apps-routing/src/tokenomics.ts

@@ -1,38 +0,0 @@
-// Copyright 2017-2019 @polkadot/apps-routing authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { Routes } from './types';
-
-import Tokenomics from '@polkadot/joy-tokenomics';
-
-export default ([
-  {
-    Component: Tokenomics,
-    display: {
-      needsAccounts: true,
-      needsApi: [
-        'query.balances.totalIssuance',
-        'query.recurringRewards.rewardRelationships',
-        'query.contentWorkingGroup.curatorById',
-        'query.contentWorkingGroup.currentLeadId',
-        'query.council.activeCouncil',
-        'query.council.payoutInterval',
-        'query.council.amountPerPayout',
-        'query.councilElection.newTermDuration',
-        'query.councilElection.votingPeriod',
-        'query.councilElection.revealingPeriod',
-        'query.councilElection.announcingPeriod',
-        'query.storageWorkingGroup.currentLead',
-        'query.storageWorkingGroup.workerById',
-        'query.stake.stakes',
-        'query.staking.currentElected'
-      ]
-    },
-    i18n: {
-      defaultValue: 'Overview'
-    },
-    icon: 'th',
-    name: 'tokenomics'
-  }
-] as Routes);

+ 1 - 0
pioneer/packages/apps/public/locales/en/index.json

@@ -25,6 +25,7 @@
   "joy-media.json",
   "joy-members.json",
   "joy-roles.json",
+  "joy-tokenomics.json",
   "joy-utils.json",
   "react-components.json",
   "react-params.json",

+ 3 - 0
pioneer/packages/apps/public/locales/en/joy-tokenomics.json

@@ -0,0 +1,3 @@
+{
+  "Tokenomics": "Tokenomics"
+}

+ 1 - 0
pioneer/packages/apps/public/locales/en/translation.json

@@ -667,6 +667,7 @@
   "Tip (optional)": "",
   "To council": "",
   "To ensure optimal fund security using the same stash/controller is strongly discouraged, but not forbidden.": "",
+  "Tokenomics": "",
   "Transfer": "",
   "Translate": "",
   "Treasury overview": "",

+ 3 - 4
pioneer/packages/joy-tokenomics/src/Overview/OverviewTable.tsx

@@ -2,8 +2,7 @@ import React from 'react';
 import { Table, Popup, Icon } from 'semantic-ui-react';
 import styled from 'styled-components';
 
-import { TokenomicsData } from '../lib/getTokenomicsData';
-import { StatusServerData } from './index';
+import { TokenomicsData, StatusServerData } from '@polkadot/joy-utils/src/types/tokenomics';
 
 const StyledTableRow = styled(Table.Row)`
   .help-icon{
@@ -25,7 +24,7 @@ const OverviewTableRow: React.FC<{item: string; value: string; help?: string}> =
           {item}
           {help &&
             <Popup
-              trigger={<Icon className='help-icon' name="help circle" color="grey"/>}
+              trigger={<Icon className='help-icon' name='help circle' color='grey'/>}
               content={help}
               position='right center'
             />}
@@ -37,7 +36,7 @@ const OverviewTableRow: React.FC<{item: string; value: string; help?: string}> =
 };
 
 const OverviewTable: React.FC<{data?: TokenomicsData; statusData?: StatusServerData | null}> = ({ data, statusData }) => {
-  const displayStatusData = (val?: string, unit?: string): string => (
+  const displayStatusData = (val: string, unit: string): string => (
     statusData === null ? 'Data currently unavailable...' : statusData ? `${val} ${unit}` : 'Loading...'
   );
 

+ 5 - 4
pioneer/packages/joy-tokenomics/src/Overview/SpendingAndStakeDistributionTable.tsx

@@ -3,13 +3,13 @@ import { Table, Popup, Icon } from 'semantic-ui-react';
 import styled from 'styled-components';
 import { useWindowDimensions } from '../../../joy-utils/src/react/hooks';
 
-import { TokenomicsData } from '../lib/getTokenomicsData';
-import { StatusServerData } from './index';
+import { TokenomicsData, StatusServerData } from '@polkadot/joy-utils/src/types/tokenomics';
 
 const round = (num: number): number => Math.round((num + Number.EPSILON) * 100) / 100;
 
 const applyCss = (columns: number[]): string => {
   let columnString = '';
+
   columns.forEach((column, index) => {
     if (index === 0) {
       columnString += `td:nth-of-type(${column}), th:nth-of-type(${column})`;
@@ -17,6 +17,7 @@ const applyCss = (columns: number[]): string => {
       columnString += ` ,td:nth-of-type(${column}), th:nth-of-type(${column})`;
     }
   });
+
   return columnString;
 };
 
@@ -101,7 +102,7 @@ const SpendingAndStakeTableRow: React.FC<{
       <Table.Cell>
         {active ? <strong>{role}</strong> : role}
         {helpContent && <Popup
-          trigger={<Icon className='help-icon' name="help circle" color="grey"/>}
+          trigger={<Icon className='help-icon' name='help circle' color='grey'/>}
           content={helpContent}
           position='right center'
         />}
@@ -118,7 +119,7 @@ const SpendingAndStakeTableRow: React.FC<{
   );
 };
 
-const SpendingAndStakeDistributionTable: React.FC<{data: TokenomicsData | undefined; statusData: StatusServerData | undefined | null}> = ({ data, statusData }) => {
+const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; statusData?: StatusServerData | null}> = ({ data, statusData }) => {
   const { width } = useWindowDimensions();
 
   const displayStatusData = (group: 'validators' | 'council' | 'storageProviders' | 'storageProviderLead' | 'contentCurators', action: 'rewardsPerWeek' | 'totalStake'): string | undefined => {

+ 1 - 1
pioneer/packages/joy-tokenomics/src/Overview/TokenomicsCharts.tsx

@@ -3,7 +3,7 @@ import { Icon, Label } from 'semantic-ui-react';
 import PieChart from '../../../react-components/src/Chart/PieChart';
 import styled from 'styled-components';
 
-import { TokenomicsData } from '../lib/getTokenomicsData';
+import { TokenomicsData } from '@polkadot/joy-utils/src/types/tokenomics';
 
 const StyledPieChart = styled(PieChart)`
   width:15rem;

+ 15 - 22
pioneer/packages/joy-tokenomics/src/Overview/index.tsx

@@ -1,12 +1,12 @@
 import React from 'react';
-import getTokenomicsData from '../lib/getTokenomicsData';
-import { api } from '@polkadot/react-api';
 import OverviewTable from './OverviewTable';
 import SpendingAndStakeDistributionTable from './SpendingAndStakeDistributionTable';
 import TokenomicsCharts from './TokenomicsCharts';
 import styled from 'styled-components';
 
 import usePromise from '@polkadot/joy-utils/react/hooks/usePromise';
+import { useTransport } from '@polkadot/joy-utils/react/hooks';
+import { StatusServerData } from '@polkadot/joy-utils/src/types/tokenomics';
 
 const SpendingAndStakeContainer = styled('div')`
   display:flex;
@@ -38,28 +38,21 @@ const StyledTokenomicsCharts = styled(TokenomicsCharts)`
   }
 `;
 
-export type StatusServerData = {
-  dollarPool: {
-    size: number;
-    replenishAmount: number;
-  };
-  price: string;
-}
-
-const Overview: React.FC<{}> = () => {
-  const [tokenomicsData] = usePromise(() => getTokenomicsData(api), undefined, []);
-  const [statusDataValue, statusDataError] = usePromise(() => fetch('https://status.joystream.org/status').then((res) => res.json()), undefined, []);
+const Overview: React.FC = () => {
+  const transport = useTransport();
+  const [statusDataValue, statusDataError] = usePromise<StatusServerData | undefined>(() => fetch('https://status.joystream.org/status').then((res) => res.json().then((data) => data as StatusServerData)), undefined, []);
+  const [tokenomicsData] = usePromise(() => transport.tokenomics.getTokenomicsData(), undefined, []);
 
   return (
-        <>
-          <Title> Overview </Title>
-          <OverviewTable data={tokenomicsData} statusData={statusDataError ? null : statusDataValue}/>
-          <Title> Spending and Stake Distribution </Title>
-          <SpendingAndStakeContainer>
-            <SpendingAndStakeDistributionTable data={tokenomicsData} statusData={statusDataError ? null : statusDataValue}/>
-            <StyledTokenomicsCharts data={tokenomicsData} />
-          </SpendingAndStakeContainer>
-        </>
+    <>
+      <Title> Overview </Title>
+      <OverviewTable data={tokenomicsData} statusData={statusDataError ? null : statusDataValue}/>
+      <Title> Spending and Stake Distribution </Title>
+      <SpendingAndStakeContainer>
+        <SpendingAndStakeDistributionTable data={tokenomicsData} statusData={statusDataError ? null : statusDataValue}/>
+        <StyledTokenomicsCharts data={tokenomicsData} />
+      </SpendingAndStakeContainer>
+    </>
   );
 };
 

+ 25 - 21
pioneer/packages/joy-tokenomics/src/index.tsx

@@ -1,33 +1,37 @@
 import React from 'react';
-import translate from './translate';
+import { useTranslation } from './translate';
 import { Route, Switch } from 'react-router';
 import { Tabs } from '@polkadot/react-components';
 import Overview from './Overview';
-
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
+import { TransportProvider } from '@polkadot/joy-utils/react/context';
 
 interface Props extends AppProps, I18nProps {}
 
-function App ({ basePath, t }: Props): React.ReactElement<Props> {
+function App ({ basePath }: Props): React.ReactElement<Props> {
+  const { t } = useTranslation();
+
   return (
-    <main>
-      <header>
-        <Tabs
-          basePath={basePath}
-          items={[
-            {
-              isRoot: true,
-              name: 'overview',
-              text: t('Tokenomics')
-            }
-          ]}
-        />
-      </header>
-      <Switch>
-        <Route component={Overview} />
-      </Switch>
-    </main>
+    <TransportProvider>
+      <main>
+        <header>
+          <Tabs
+            basePath={basePath}
+            items={[
+              {
+                isRoot: true,
+                name: 'overview',
+                text: t('Tokenomics')
+              }
+            ]}
+          />
+        </header>
+        <Switch>
+          <Route component={Overview} />
+        </Switch>
+      </main>
+    </TransportProvider>
   );
 }
 
-export default translate(App);
+export default App;

+ 0 - 61
pioneer/packages/joy-tokenomics/src/lib/getContentCuratorData.ts

@@ -1,61 +0,0 @@
-import { ApiPromise } from '@polkadot/api';
-import { Reward } from './getTokenomicsData';
-import { LeadId } from '@joystream/types/content-working-group';
-import { Option } from '@polkadot/types';
-
-type ContentCurator = {
-  reward_relationship: number;
-  role_stake_profile: {
-    stake_id: number;
-  };
-}
-
-const getCurators = async (api: ApiPromise): Promise<Array<ContentCurator>> => {
-  const curators: Array<ContentCurator> = [];
-  const [workerIds, curatorData] = ((await api.query.contentWorkingGroup.curatorById()).toJSON() as [Array<number>, Array<object>]);
-  curatorData.map((data: any, index: number) => {
-    if (Object.keys(data.stage)[0] === 'Active') {
-      curators.push({ ...data, workerId: workerIds[index] });
-    }
-  });
-  return curators;
-};
-
-const calculateRewards = (curators: Array<ContentCurator>, recurringRewards: Array<Reward>): number => {
-  let rewardsPerBlock = 0;
-  curators.forEach((curator) => {
-    const reward: Reward = recurringRewards.filter((reward: Reward) => reward.recipient === curator.reward_relationship)[0];
-    if (reward && reward.amount_per_payout && reward.payout_interval) {
-      rewardsPerBlock += reward.amount_per_payout / reward.payout_interval;
-    }
-  });
-  return rewardsPerBlock;
-};
-
-const calculateStake = async (api: ApiPromise, curators: Array<ContentCurator>): Promise<number> => {
-  const stakeIds: Array<number> = []; let totalContentCuratorStake = 0;
-  curators.forEach((curator) => {
-    if (curator.role_stake_profile) {
-      stakeIds.push(curator.role_stake_profile.stake_id);
-    }
-  });
-  const curatorStakeData = await api.query.stake.stakes.multi(stakeIds);
-  curatorStakeData.map((stakeData: any) => {
-    totalContentCuratorStake += stakeData.toJSON()[0].staking_status.Staked.staked_amount;
-  });
-  return totalContentCuratorStake;
-};
-
-export default async (api: ApiPromise, recurringRewards: Array<Reward>) => {
-  const optLeadId = (await api.query.contentWorkingGroup.currentLeadId()) as Option<LeadId>;
-  const currentLead = optLeadId.unwrapOr(null);
-  const curators = await getCurators(api);
-  const rewardsPerBlock = calculateRewards(curators, recurringRewards);
-  const totalContentCuratorStake = await calculateStake(api, curators);
-  return {
-    numberOfCurators: curators.length,
-    contentCuratorLead: currentLead ? 1 : 0,
-    curatorRewardsPerWeeek: rewardsPerBlock * 100800,
-    totalContentCuratorStake
-  };
-};

+ 0 - 74
pioneer/packages/joy-tokenomics/src/lib/getCouncilData.ts

@@ -1,74 +0,0 @@
-import { Vec, Option } from '@polkadot/types';
-import { ApiPromise } from '@polkadot/api';
-import { BlockNumber, BalanceOf } from '@polkadot/types/interfaces';
-import { Seats, Backer } from '@joystream/types/src/council';
-
-const getCouncilMembers = async (api: ApiPromise) => {
-  let totalStake = 0;
-  const activeCouncil = await api.query.council.activeCouncil() as Seats;
-  const payoutInterval = Number((await api.query.council.payoutInterval() as Option<BlockNumber>).unwrapOr(0));
-  const amountPerPayout = (await api.query.council.amountPerPayout() as BalanceOf).toNumber();
-  activeCouncil.map((member) => {
-    let stakeAmount = 0;
-    stakeAmount += Number(member.get('stake'));
-    const backers = member.get('backers') as Vec<Backer>;
-    if (!backers?.isEmpty) {
-      backers.forEach((backer) => {
-        stakeAmount += Number(backer.get('stake'));
-      });
-    }
-    totalStake += stakeAmount;
-  });
-  return {
-    numberOfCouncilMembers: activeCouncil.length,
-    totalCouncilRewardsPerBlock: (amountPerPayout && payoutInterval) ? (amountPerPayout * activeCouncil.length) / payoutInterval : 0,
-    totalCouncilStake: totalStake
-  };
-};
-
-const calculateCouncilRewards = async (api: ApiPromise, totalCouncilRewardsPerBlock: number): Promise<number> => {
-  let weekInBlocks = 100800;
-  let councilRewardsInOneWeek = 0;
-  const termDuration = (await api.query.councilElection.newTermDuration() as BlockNumber).toNumber();
-  const votingPeriod = (await api.query.councilElection.votingPeriod() as BlockNumber).toNumber();
-  const revealingPeriod = (await api.query.councilElection.revealingPeriod() as BlockNumber).toNumber();
-  const announcingPeriod = (await api.query.councilElection.announcingPeriod() as BlockNumber).toNumber();
-  while (weekInBlocks > 0) {
-    if (weekInBlocks > termDuration) {
-      councilRewardsInOneWeek += termDuration * totalCouncilRewardsPerBlock;
-      weekInBlocks -= termDuration;
-    } else {
-      councilRewardsInOneWeek += weekInBlocks * totalCouncilRewardsPerBlock;
-      return councilRewardsInOneWeek;
-    }
-    // -----------------------------
-    if (weekInBlocks > revealingPeriod) {
-      weekInBlocks -= revealingPeriod;
-    } else {
-      return councilRewardsInOneWeek;
-    }
-    // -----------------------------
-    if (weekInBlocks > votingPeriod) {
-      weekInBlocks -= votingPeriod;
-    } else {
-      return councilRewardsInOneWeek;
-    }
-    // -----------------------------
-    if (weekInBlocks > announcingPeriod) {
-      weekInBlocks -= announcingPeriod;
-    } else {
-      return councilRewardsInOneWeek;
-    }
-  }
-  return councilRewardsInOneWeek;
-};
-
-export default async (api: ApiPromise) => {
-  const { numberOfCouncilMembers, totalCouncilRewardsPerBlock, totalCouncilStake } = await getCouncilMembers(api);
-  const totalCouncilRewardsInOneWeek = await calculateCouncilRewards(api, totalCouncilRewardsPerBlock);
-  return {
-    numberOfCouncilMembers,
-    totalCouncilRewardsInOneWeek,
-    totalCouncilStake
-  };
-};

+ 0 - 99
pioneer/packages/joy-tokenomics/src/lib/getStorageProviderData.ts

@@ -1,99 +0,0 @@
-import { ApiPromise } from '@polkadot/api';
-import { Reward } from './getTokenomicsData';
-
-type StorageProvider = {
-  reward_relationship: number;
-  role_stake_profile: {
-    stake_id: number;
-  };
-  workerId: number;
-  lead?: boolean;
-}
-
-const getStorageProviders = async (api: ApiPromise): Promise<any> => {
-  const storageProviders: Array<StorageProvider> = [];
-  let [numberOfStorageProviders, leadNumber] = [0, 0];
-  const currentLead = (await api.query.storageWorkingGroup.currentLead()).toJSON() as number;
-  const [workerIds, storageProviderData] = (await api.query.storageWorkingGroup.workerById()).toJSON() as [Array<number>, Array<object>];
-  storageProviderData.map((providerData: any, index: number) => {
-    if (workerIds[index] === currentLead) {
-      leadNumber += 1;
-      storageProviders.push({
-        workerId: workerIds[index],
-        lead: true,
-        ...providerData
-      });
-    } else {
-      numberOfStorageProviders += 1;
-      storageProviders.push({
-        workerId: workerIds[index],
-        ...providerData
-      });
-    }
-  });
-  return {
-    numberOfStorageProviders,
-    leadNumber,
-    storageProviders
-  };
-};
-
-const calculateProviders = async (api: ApiPromise, storageProviders: Array<StorageProvider>, recurringRewards: Array<Reward>): Promise<any> => {
-  let totalProviderStake = 0; let providerRewardsPerBlock = 0; let leadRewardsPerBlock = 0; let leadStake = 0;
-  const providerStakeIds: Array<number> = [];
-  let leadStakeId: number | null = null;
-  storageProviders.forEach((provider: StorageProvider) => {
-    // CALCULATE REWARDS
-    recurringRewards.forEach((reward: Reward) => {
-      if (provider.reward_relationship && reward.recipient === provider.reward_relationship) {
-        if (!provider.lead) {
-          if (reward.amount_per_payout && reward.payout_interval) {
-            providerRewardsPerBlock += reward.amount_per_payout / reward.payout_interval;
-          }
-        } else {
-          if (reward.amount_per_payout && reward.payout_interval) {
-            leadRewardsPerBlock += reward.amount_per_payout / reward.payout_interval;
-          }
-        }
-      }
-    });
-    // ADD STAKING DATA FOR PROVIDERS
-    if (provider.role_stake_profile && provider.workerId) {
-      if (!provider.lead) {
-        providerStakeIds.push(provider.role_stake_profile.stake_id);
-      } else {
-        leadStakeId = provider.role_stake_profile.stake_id;
-      }
-    }
-  });
-
-  // CALCULATE PROVIDER STAKE
-  ((await api.query.stake.stakes.multi(providerStakeIds.map((id: number) => id)))).map((data: any) => {
-    totalProviderStake += data[0].toJSON().staking_status.Staked.staked_amount;
-  });
-
-  // CALCULATE LEAD STAKE
-  if (leadStakeId !== null) {
-    leadStake += ((await api.query.stake.stakes(leadStakeId)).toJSON() as Array<any>)[0].staking_status.Staked.staked_amount;
-  }
-
-  return {
-    totalProviderStake,
-    leadStake,
-    providerRewardsPerBlock,
-    leadRewardsPerBlock
-  };
-};
-
-export default async (api: ApiPromise, recurringRewards: Array<Reward>): Promise<any> => {
-  const { numberOfStorageProviders, leadNumber, storageProviders } = await getStorageProviders(api);
-  const { totalProviderStake, leadStake, providerRewardsPerBlock, leadRewardsPerBlock } = await calculateProviders(api, storageProviders, recurringRewards);
-  return {
-    numberOfStorageProviders,
-    totalProviderStake,
-    leadStake,
-    providerRewardsPerWeek: providerRewardsPerBlock * 100800,
-    leadRewardsPerWeek: leadRewardsPerBlock * 100800,
-    storageProviderLead: leadNumber
-  };
-};

+ 0 - 116
pioneer/packages/joy-tokenomics/src/lib/getTokenomicsData.ts

@@ -1,116 +0,0 @@
-import { ApiPromise } from '@polkadot/api';
-
-import getStorageProviderData from './getStorageProviderData';
-import getCouncilData from './getCouncilData';
-import getValidatorData from './getValidatorData';
-import getContentCuratorData from './getContentCuratorData';
-
-export type TokenomicsData = {
-  totalIssuance: number;
-  currentlyStakedTokens: number;
-  totalWeeklySpending: number;
-  totalNumberOfActors: number;
-  validators: {
-    number: number;
-    nominators: {
-      number: number;
-    };
-    rewardsPerWeek: number;
-    rewardsShare: number;
-    totalStake: number;
-    stakeShare: number;
-  };
-  council: {
-    number: number;
-    rewardsPerWeek: number;
-    rewardsShare: number;
-    totalStake: number;
-    stakeShare: number;
-  };
-  storageProviders: {
-    number: number;
-    totalStake: number;
-    stakeShare: number;
-    rewardsPerWeek: number;
-    rewardsShare: number;
-    lead: {
-      number: number;
-      totalStake: number;
-      stakeShare: number;
-      rewardsPerWeek: number;
-      rewardsShare: number;
-    };
-  };
-  contentCurators: {
-    number: number;
-    contentCuratorLead: number;
-    rewardsPerWeek: number;
-    rewardsShare: number;
-    totalStake: number;
-    stakeShare: number;
-  };
-}
-
-export type Reward = {
-  recipient: number;
-  amount_per_payout: number;
-  payout_interval: number;
-}
-
-export default async (api: ApiPromise): Promise<TokenomicsData> => {
-  const totalIssuance = (await api.query.balances.totalIssuance()).toJSON() as number;
-  const [, recurringRewards] = (await api.query.recurringRewards.rewardRelationships()).toJSON() as [Array<number>, Array<Reward>];
-  const { numberOfValidators, numberOfNominators, validatorRewardsPerWeek, totalValidatorStake } = await getValidatorData(api, totalIssuance);
-  const { numberOfCouncilMembers, totalCouncilRewardsInOneWeek, totalCouncilStake } = await getCouncilData(api);
-  const { numberOfStorageProviders, totalProviderStake, leadStake, providerRewardsPerWeek, leadRewardsPerWeek, storageProviderLead } = await getStorageProviderData(api, recurringRewards);
-  const { numberOfCurators, contentCuratorLead, curatorRewardsPerWeeek, totalContentCuratorStake } = await getContentCuratorData(api, recurringRewards);
-  const currentlyStakedTokens = totalValidatorStake + totalCouncilStake + totalProviderStake + leadStake + totalContentCuratorStake;
-  const totalWeeklySpending = validatorRewardsPerWeek + totalCouncilRewardsInOneWeek + providerRewardsPerWeek + leadRewardsPerWeek + curatorRewardsPerWeeek;
-  const totalNumberOfActors = numberOfValidators + numberOfCouncilMembers + numberOfStorageProviders + storageProviderLead + numberOfCurators + contentCuratorLead;
-
-  return {
-    totalIssuance,
-    currentlyStakedTokens,
-    totalWeeklySpending,
-    totalNumberOfActors,
-    validators: {
-      number: numberOfValidators,
-      nominators: {
-        number: numberOfNominators
-      },
-      rewardsPerWeek: validatorRewardsPerWeek,
-      rewardsShare: validatorRewardsPerWeek / totalWeeklySpending,
-      totalStake: totalValidatorStake,
-      stakeShare: totalValidatorStake / currentlyStakedTokens
-    },
-    council: {
-      number: numberOfCouncilMembers,
-      rewardsPerWeek: totalCouncilRewardsInOneWeek,
-      rewardsShare: totalCouncilRewardsInOneWeek / totalWeeklySpending,
-      totalStake: totalCouncilStake,
-      stakeShare: totalCouncilStake / currentlyStakedTokens
-    },
-    storageProviders: {
-      number: numberOfStorageProviders,
-      totalStake: totalProviderStake,
-      stakeShare: totalProviderStake / currentlyStakedTokens,
-      rewardsPerWeek: providerRewardsPerWeek,
-      rewardsShare: providerRewardsPerWeek / totalWeeklySpending,
-      lead: {
-        number: storageProviderLead,
-        totalStake: leadStake,
-        stakeShare: leadStake / currentlyStakedTokens,
-        rewardsPerWeek: leadRewardsPerWeek,
-        rewardsShare: leadRewardsPerWeek / totalWeeklySpending
-      }
-    },
-    contentCurators: {
-      number: numberOfCurators,
-      contentCuratorLead,
-      rewardsPerWeek: curatorRewardsPerWeeek,
-      rewardsShare: curatorRewardsPerWeeek / totalWeeklySpending,
-      totalStake: totalContentCuratorStake,
-      stakeShare: totalContentCuratorStake / currentlyStakedTokens
-    }
-  };
-};

+ 0 - 51
pioneer/packages/joy-tokenomics/src/lib/getValidatorData.ts

@@ -1,51 +0,0 @@
-import { ApiPromise } from '@polkadot/api';
-import { Vec } from '@polkadot/types';
-import { AccountId } from '@polkadot/types/interfaces';
-
-const getValidators = async (api: ApiPromise) => {
-  let [totalValidatorStake, slotStake, numberOfNominators, numberOfValidators] = [0, 0, 0, 0];
-  const validatorIds = await api.query.staking.currentElected() as Vec<AccountId>;
-  await Promise.all(validatorIds.map(async (id: AccountId, index: number) => {
-    const nominators = (await api.derive.staking.info(id)).stakers?.others.toArray() || [];
-    const totalStake = (await api.derive.staking.info(id)).stakers?.total.toNumber() || 0;
-    numberOfValidators += 1;
-    totalValidatorStake += totalStake;
-    numberOfNominators += nominators.length;
-    if (index === 0) { slotStake = totalStake; }
-    if (slotStake > totalStake) { slotStake = totalStake; }
-  }));
-  return {
-    numberOfValidators,
-    totalValidatorStake,
-    effectiveStake: slotStake * validatorIds.length,
-    numberOfNominators
-  };
-};
-
-const calculateRewards = (totalIssuance: number, effectiveStake: number): any => {
-  let validatorRewards = 0;
-  const [idealStakingRate, minimumInflation, maximumInflation, fallOff, eraLength, year] = [0.3, 0.025, 0.3, 0.05, 3600, (365.2425 * 24 * 60 * 60)];
-  const stakingRate = effectiveStake / totalIssuance;
-  if (stakingRate > 0.3) {
-    validatorRewards = totalIssuance * (minimumInflation + (maximumInflation - minimumInflation) * 2 ** ((idealStakingRate - stakingRate) / fallOff)) * eraLength / year;
-  } else if (stakingRate === 0.3) {
-    validatorRewards = (totalIssuance * maximumInflation * eraLength) / year;
-  } else {
-    validatorRewards = (totalIssuance * minimumInflation + totalIssuance * (maximumInflation - minimumInflation) * (stakingRate / idealStakingRate)) * eraLength / year;
-  }
-  return {
-    validatorRewardsPerEra: validatorRewards
-  };
-};
-
-export default async (api: ApiPromise, totalIssuance: number): Promise<any> => {
-  const { numberOfValidators, totalValidatorStake, effectiveStake, numberOfNominators } = await getValidators(api);
-  const { validatorRewardsPerEra } = await calculateRewards(totalIssuance, effectiveStake);
-
-  return {
-    numberOfValidators,
-    numberOfNominators,
-    validatorRewardsPerWeek: validatorRewardsPerEra * 168,
-    totalValidatorStake
-  };
-};

+ 4 - 2
pioneer/packages/joy-tokenomics/src/translate.ts

@@ -2,6 +2,8 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { withTranslation } from 'react-i18next';
+import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export default withTranslation(['joy-tokenomics']);
+export function useTranslation (): UseTranslationResponse {
+  return useTranslationBase('joy-tokenomics');
+}

+ 2 - 0
pioneer/packages/joy-utils/src/react/hooks/useWindowDimensions.ts

@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
 
 function getWindowDimensions () {
   const { innerWidth: width, innerHeight: height } = window;
+
   return {
     width,
     height
@@ -17,6 +18,7 @@ export default function useWindowDimensions () {
     }
 
     window.addEventListener('resize', handleResize);
+
     return () => window.removeEventListener('resize', handleResize);
   }, []);
 

+ 3 - 0
pioneer/packages/joy-utils/src/transport/index.ts

@@ -7,6 +7,7 @@ import CouncilTransport from './council';
 import ValidatorsTransport from './validators';
 import WorkingGroupsTransport from './workingGroups';
 import { APIQueryCache } from './APIQueryCache';
+import TokenomicsTransport from './tokenomics';
 
 export default class Transport {
   protected api: ApiPromise;
@@ -19,6 +20,7 @@ export default class Transport {
   public contentWorkingGroup: ContentWorkingGroupTransport;
   public validators: ValidatorsTransport;
   public workingGroups: WorkingGroupsTransport;
+  public tokenomics: TokenomicsTransport
 
   constructor (api: ApiPromise) {
     this.api = api;
@@ -30,5 +32,6 @@ export default class Transport {
     this.contentWorkingGroup = new ContentWorkingGroupTransport(api, this.cacheApi, this.members);
     this.proposals = new ProposalsTransport(api, this.cacheApi, this.members, this.chain, this.council);
     this.workingGroups = new WorkingGroupsTransport(api, this.cacheApi, this.members);
+    this.tokenomics = new TokenomicsTransport(api, this.cacheApi, this.council, this.workingGroups);
   }
 }

+ 362 - 0
pioneer/packages/joy-utils/src/transport/tokenomics.ts

@@ -0,0 +1,362 @@
+import BaseTransport from './base';
+import { ApiPromise } from '@polkadot/api';
+import CouncilTransport from './council';
+import WorkingGroupsTransport from './workingGroups';
+import { APIQueryCache } from './APIQueryCache';
+import { Seats } from '@joystream/types/council';
+import { Option } from '@polkadot/types';
+import { BlockNumber, BalanceOf, Exposure } from '@polkadot/types/interfaces';
+import { WorkerId } from '@joystream/types/working-group';
+import { RewardRelationshipId, RewardRelationship } from '@joystream/types/recurring-rewards';
+import { StakeId, Stake } from '@joystream/types/stake';
+import { CuratorId, Curator, LeadId } from '@joystream/types/content-working-group';
+import { TokenomicsData } from '@polkadot/joy-utils/src/types/tokenomics';
+
+export default class TokenomicsTransport extends BaseTransport {
+  private councilT: CouncilTransport;
+  private workingGroupT: WorkingGroupsTransport;
+
+  constructor (api: ApiPromise, cacheApi: APIQueryCache, councilTransport: CouncilTransport, workingGroups: WorkingGroupsTransport) {
+    super(api, cacheApi);
+    this.councilT = councilTransport;
+    this.workingGroupT = workingGroups;
+  }
+
+  async getCouncilMembers () {
+    let totalCouncilStake = 0;
+    const activeCouncil = await this.api.query.council.activeCouncil() as Seats;
+
+    activeCouncil.map((member) => {
+      let stakeAmount = 0;
+
+      stakeAmount += member.stake.toNumber();
+      member.backers.forEach((backer) => {
+        stakeAmount += backer.stake.toNumber();
+      });
+      totalCouncilStake += stakeAmount;
+    });
+
+    return {
+      numberOfCouncilMembers: activeCouncil.length,
+      totalCouncilStake
+    };
+  }
+
+  async calculateCouncilRewards (numberOfCouncilMembers: number) {
+    let weekInBlocks = 100800; let councilRewardsInOneWeek = 0; let totalCouncilRewardsPerBlock = 0;
+    const payoutInterval = Number((await this.api.query.council.payoutInterval() as Option<BlockNumber>).unwrapOr(0));
+    const amountPerPayout = (await this.api.query.council.amountPerPayout() as BalanceOf).toNumber();
+
+    totalCouncilRewardsPerBlock = (amountPerPayout && payoutInterval) ? (amountPerPayout * numberOfCouncilMembers) / payoutInterval : 0;
+    const { new_term_duration, voting_period, revealing_period, announcing_period } = await this.councilT.electionParameters();
+    const termDuration = new_term_duration.toNumber(); const votingPeriod = voting_period.toNumber(); const revealingPeriod = revealing_period.toNumber(); const announcingPeriod = announcing_period.toNumber();
+
+    while (weekInBlocks > 0) {
+      if (weekInBlocks > termDuration) {
+        councilRewardsInOneWeek += termDuration * totalCouncilRewardsPerBlock;
+        weekInBlocks -= termDuration;
+      } else {
+        councilRewardsInOneWeek += weekInBlocks * totalCouncilRewardsPerBlock;
+
+        return councilRewardsInOneWeek;
+      }
+
+      // -----------------------------
+      if (weekInBlocks > revealingPeriod) {
+        weekInBlocks -= revealingPeriod;
+      } else {
+        return councilRewardsInOneWeek;
+      }
+
+      // -----------------------------
+      if (weekInBlocks > votingPeriod) {
+        weekInBlocks -= votingPeriod;
+      } else {
+        return councilRewardsInOneWeek;
+      }
+
+      // -----------------------------
+      if (weekInBlocks > announcingPeriod) {
+        weekInBlocks -= announcingPeriod;
+      } else {
+        return councilRewardsInOneWeek;
+      }
+    }
+
+    return councilRewardsInOneWeek;
+  }
+
+  async getCouncilData () {
+    const { numberOfCouncilMembers, totalCouncilStake } = await this.getCouncilMembers();
+    const totalCouncilRewardsInOneWeek = await this.calculateCouncilRewards(numberOfCouncilMembers);
+
+    return {
+      numberOfCouncilMembers,
+      totalCouncilRewardsInOneWeek,
+      totalCouncilStake
+    };
+  }
+
+  async getStorageProviders () {
+    const stakeIds: StakeId[] = []; const rewardIds: RewardRelationshipId[] = []; let leadStakeId = null as (StakeId | null); let leadRewardId = null as (RewardRelationshipId | null); let numberOfStorageProviders = 0; let leadNumber = 0;
+    const allWorkers = await this.workingGroupT.allWorkers('Storage');
+    const currentLeadId = (await this.api.query.storageWorkingGroup.currentLead() as Option<WorkerId>).unwrapOr(null)?.toNumber();
+
+    allWorkers.forEach(([workerId, worker]) => {
+      const stakeId = worker.role_stake_profile.isSome ? worker.role_stake_profile.unwrap().stake_id : null;
+      const rewardId = worker.reward_relationship.unwrapOr(null);
+
+      if (currentLeadId !== undefined && currentLeadId === workerId.toNumber()) {
+        leadStakeId = stakeId;
+        leadRewardId = rewardId;
+        leadNumber += 1;
+      } else {
+        numberOfStorageProviders += 1;
+
+        if (stakeId) {
+          stakeIds.push(stakeId);
+        }
+
+        if (rewardId) {
+          rewardIds.push(rewardId);
+        }
+      }
+    });
+
+    return {
+      numberOfStorageProviders,
+      stakeIds,
+      rewardIds,
+      leadNumber,
+      leadRewardId,
+      leadStakeId
+    };
+  }
+
+  async calcuateStorageProvider (stakeIds: StakeId[], leadStakeId: StakeId | null, rewardIds: RewardRelationshipId[], leadRewardId: RewardRelationshipId | null) {
+    let totalStorageProviderStake = 0; let leadStake = 0; let storageProviderRewardsPerBlock = 0; let storageLeadRewardsPerBlock = 0;
+
+    (await this.api.query.stake.stakes.multi(stakeIds) as Stake[]).forEach((stake) => {
+      totalStorageProviderStake += stake.value.toNumber();
+    });
+    (await this.api.query.recurringRewards.rewardRelationships.multi(rewardIds) as RewardRelationship[]).map((rewardRelationship) => {
+      const amount = rewardRelationship.amount_per_payout.toNumber();
+      const payoutInterval = rewardRelationship.payout_interval.isSome ? rewardRelationship.payout_interval.unwrap().toNumber() : null;
+
+      if (amount && payoutInterval) {
+        storageProviderRewardsPerBlock += amount / payoutInterval;
+      }
+    });
+
+    if (leadStakeId !== null) {
+      leadStake += (await this.api.query.stake.stakes(leadStakeId) as Stake).value.toNumber();
+    }
+
+    if (leadRewardId !== null) {
+      const leadRewardData = (await this.api.query.recurringRewards.rewardRelationships(leadRewardId) as RewardRelationship);
+      const leadAmount = leadRewardData.amount_per_payout.toNumber();
+      const leadRewardInterval = leadRewardData.payout_interval.isSome ? leadRewardData.payout_interval.unwrap().toNumber() : null;
+
+      if (leadAmount && leadRewardInterval) {
+        storageLeadRewardsPerBlock += leadAmount / leadRewardInterval;
+      }
+    }
+
+    return {
+      totalStorageProviderStake,
+      leadStake,
+      storageProviderRewardsPerWeek: storageProviderRewardsPerBlock * 100800,
+      storageProviderLeadRewardsPerWeek: storageLeadRewardsPerBlock * 100800
+    };
+  }
+
+  async getStorageProviderData () {
+    const { numberOfStorageProviders, leadNumber, stakeIds, rewardIds, leadRewardId, leadStakeId } = await this.getStorageProviders();
+    const { totalStorageProviderStake, leadStake, storageProviderRewardsPerWeek, storageProviderLeadRewardsPerWeek } = await this.calcuateStorageProvider(stakeIds, leadStakeId, rewardIds, leadRewardId);
+
+    return {
+      numberOfStorageProviders,
+      storageProviderLeadNumber: leadNumber,
+      totalStorageProviderStake,
+      totalStorageProviderLeadStake: leadStake,
+      storageProviderRewardsPerWeek,
+      storageProviderLeadRewardsPerWeek
+    };
+  }
+
+  async getContentCurators () {
+    const stakeIds: StakeId[] = []; const rewardIds: RewardRelationshipId[] = []; let numberOfContentCurators = 0; let leadNumber = 0;
+    const contentCurators = await this.entriesByIds<CuratorId, Curator>(this.api.query.contentWorkingGroup.curatorById);
+    const currentLeadId = (await this.api.query.contentWorkingGroup.currentLeadId() as Option<LeadId>).unwrapOr(null)?.toNumber();
+
+    contentCurators.forEach(([curatorId, curator]) => {
+      const stakeId = curator.role_stake_profile.isSome ? curator.role_stake_profile.unwrap().stake_id : null;
+      const rewardId = curator.reward_relationship.unwrapOr(null);
+
+      if (currentLeadId !== undefined && currentLeadId === curatorId.toNumber()) {
+        leadNumber += 1;
+      } else {
+        numberOfContentCurators += 1;
+
+        if (stakeId) {
+          stakeIds.push(stakeId);
+        }
+
+        if (rewardId) {
+          rewardIds.push(rewardId);
+        }
+      }
+    });
+
+    return {
+      stakeIds,
+      rewardIds,
+      numberOfContentCurators,
+      contentCuratorLeadNumber: leadNumber
+    };
+  }
+
+  async calculateContentCurator (stakeIds: StakeId[], rewardIds: RewardRelationshipId[]) {
+    let totalContentCuratorStake = 0; let contentCuratorRewardsPerBlock = 0;
+
+    (await this.api.query.stake.stakes.multi(stakeIds) as Stake[]).forEach((stake) => {
+      totalContentCuratorStake += stake.value.toNumber();
+    });
+    (await this.api.query.recurringRewards.rewardRelationships.multi(rewardIds) as RewardRelationship[]).map((rewardRelationship) => {
+      const amount = rewardRelationship.amount_per_payout.toNumber();
+      const payoutInterval = rewardRelationship.payout_interval.isSome ? rewardRelationship.payout_interval.unwrap().toNumber() : null;
+
+      if (amount && payoutInterval) {
+        contentCuratorRewardsPerBlock += amount / payoutInterval;
+      }
+    });
+
+    return {
+      totalContentCuratorStake,
+      contentCuratorRewardsPerBlock
+    };
+  }
+
+  async getContentCuratorData () {
+    const { stakeIds, rewardIds, numberOfContentCurators, contentCuratorLeadNumber } = await this.getContentCurators();
+    const { totalContentCuratorStake, contentCuratorRewardsPerBlock } = await this.calculateContentCurator(stakeIds, rewardIds);
+
+    return {
+      numberOfContentCurators,
+      contentCuratorLeadNumber,
+      totalContentCuratorStake,
+      contentCuratorRewardsPerWeek: contentCuratorRewardsPerBlock * 100800
+    };
+  }
+
+  async getValidators () {
+    const validatorIds = await this.api.query.session.validators();
+    const currentEra = (await this.api.query.staking.currentEra()).unwrapOr(null);
+    let totalValidatorStake = 0; let numberOfNominators = 0;
+
+    if (currentEra !== null) {
+      const validatorStakeData = await this.api.query.staking.erasStakers.multi(validatorIds.map((validatorId) => [currentEra, validatorId])) as Exposure[];
+
+      validatorStakeData.forEach((data) => {
+        if (!data.total.isEmpty) {
+          totalValidatorStake += data.total.toNumber();
+        }
+
+        if (!data.others.isEmpty) {
+          numberOfNominators += data.others.length;
+        }
+      });
+    }
+
+    return {
+      numberOfValidators: validatorIds.length,
+      numberOfNominators,
+      totalValidatorStake
+    };
+  }
+
+  calculateValidators (totalValidatorStake: number, totalIssuance: number) {
+    let validatorRewards = 0;
+    const [idealStakingRate, minimumInflation, maximumInflation, fallOff, eraLength, year] = [0.25, 0.05, 0.75, 0.05, 3600, (365.2425 * 24 * 60 * 60)];
+    const stakingRate = totalValidatorStake / totalIssuance;
+
+    if (stakingRate > idealStakingRate) {
+      validatorRewards = totalIssuance * (minimumInflation + (maximumInflation - minimumInflation) * 2 ** ((idealStakingRate - stakingRate) / fallOff)) * eraLength / year;
+    } else if (stakingRate === idealStakingRate) {
+      validatorRewards = (totalIssuance * maximumInflation * eraLength) / year;
+    } else {
+      validatorRewards = (totalIssuance * minimumInflation + totalIssuance * (maximumInflation - minimumInflation) * (stakingRate / idealStakingRate)) * eraLength / year;
+    }
+
+    return validatorRewards;
+  }
+
+  async getValidatorData (totalIssuance: number) {
+    const { numberOfValidators, numberOfNominators, totalValidatorStake } = await this.getValidators();
+    const validatorRewardsPerEra = this.calculateValidators(totalValidatorStake, totalIssuance);
+
+    return {
+      numberOfValidators,
+      numberOfNominators,
+      totalValidatorStake,
+      validatorRewardsPerWeek: validatorRewardsPerEra * 168
+    };
+  }
+
+  async getTokenomicsData (): Promise<TokenomicsData> {
+    const totalIssuance = (await this.api.query.balances.totalIssuance()).toNumber();
+    const { numberOfCouncilMembers, totalCouncilRewardsInOneWeek, totalCouncilStake } = await this.getCouncilData();
+    const { numberOfStorageProviders, storageProviderLeadNumber, totalStorageProviderStake, totalStorageProviderLeadStake, storageProviderLeadRewardsPerWeek, storageProviderRewardsPerWeek } = await this.getStorageProviderData();
+    const { numberOfContentCurators, contentCuratorLeadNumber, totalContentCuratorStake, contentCuratorRewardsPerWeek } = await this.getContentCuratorData();
+    const { numberOfValidators, numberOfNominators, totalValidatorStake, validatorRewardsPerWeek } = await this.getValidatorData(totalIssuance);
+    const currentlyStakedTokens = totalCouncilStake + totalStorageProviderStake + totalStorageProviderLeadStake + totalContentCuratorStake + totalValidatorStake;
+    const totalWeeklySpending = totalCouncilRewardsInOneWeek + storageProviderRewardsPerWeek + storageProviderLeadRewardsPerWeek + contentCuratorRewardsPerWeek + validatorRewardsPerWeek;
+    const totalNumberOfActors = numberOfCouncilMembers + numberOfStorageProviders + storageProviderLeadNumber + numberOfContentCurators + contentCuratorLeadNumber + numberOfValidators;
+
+    return {
+      totalIssuance,
+      currentlyStakedTokens,
+      totalWeeklySpending,
+      totalNumberOfActors,
+      validators: {
+        number: numberOfValidators,
+        nominators: {
+          number: numberOfNominators
+        },
+        rewardsPerWeek: validatorRewardsPerWeek,
+        rewardsShare: validatorRewardsPerWeek / totalWeeklySpending,
+        totalStake: totalValidatorStake,
+        stakeShare: totalValidatorStake / currentlyStakedTokens
+      },
+      council: {
+        number: numberOfCouncilMembers,
+        rewardsPerWeek: totalCouncilRewardsInOneWeek,
+        rewardsShare: totalCouncilRewardsInOneWeek / totalWeeklySpending,
+        totalStake: totalCouncilStake,
+        stakeShare: totalCouncilStake / currentlyStakedTokens
+      },
+      storageProviders: {
+        number: numberOfStorageProviders,
+        totalStake: totalStorageProviderStake,
+        stakeShare: totalStorageProviderStake / currentlyStakedTokens,
+        rewardsPerWeek: storageProviderRewardsPerWeek,
+        rewardsShare: storageProviderRewardsPerWeek / totalWeeklySpending,
+        lead: {
+          number: storageProviderLeadNumber,
+          totalStake: totalStorageProviderLeadStake,
+          stakeShare: totalStorageProviderLeadStake / currentlyStakedTokens,
+          rewardsPerWeek: storageProviderLeadRewardsPerWeek,
+          rewardsShare: storageProviderLeadRewardsPerWeek / totalWeeklySpending
+        }
+      },
+      contentCurators: {
+        number: numberOfContentCurators,
+        contentCuratorLead: contentCuratorLeadNumber,
+        rewardsPerWeek: contentCuratorRewardsPerWeek,
+        rewardsShare: contentCuratorRewardsPerWeek / totalWeeklySpending,
+        totalStake: totalContentCuratorStake,
+        stakeShare: totalContentCuratorStake / currentlyStakedTokens
+      }
+    };
+  }
+}

+ 53 - 0
pioneer/packages/joy-utils/src/types/tokenomics.ts

@@ -0,0 +1,53 @@
+export type TokenomicsData = {
+  totalIssuance: number;
+  currentlyStakedTokens: number;
+  totalWeeklySpending: number;
+  totalNumberOfActors: number;
+  validators: {
+    number: number;
+    nominators: {
+      number: number;
+    };
+    rewardsPerWeek: number;
+    rewardsShare: number;
+    totalStake: number;
+    stakeShare: number;
+  };
+  council: {
+    number: number;
+    rewardsPerWeek: number;
+    rewardsShare: number;
+    totalStake: number;
+    stakeShare: number;
+  };
+  storageProviders: {
+    number: number;
+    totalStake: number;
+    stakeShare: number;
+    rewardsPerWeek: number;
+    rewardsShare: number;
+    lead: {
+      number: number;
+      totalStake: number;
+      stakeShare: number;
+      rewardsPerWeek: number;
+      rewardsShare: number;
+    };
+  };
+  contentCurators: {
+    number: number;
+    contentCuratorLead: number;
+    rewardsPerWeek: number;
+    rewardsShare: number;
+    totalStake: number;
+    stakeShare: number;
+  };
+}
+
+export type StatusServerData = {
+  dollarPool: {
+    size: number;
+    replenishAmount: number;
+  };
+  price: string;
+};