Selaa lähdekoodia

Validators table

Joystream Stats 4 vuotta sitten
vanhempi
commit
7758ecd53d

+ 102 - 3
src/App.tsx

@@ -33,7 +33,6 @@ const initialState = {
   blocks: [],
   now: 0,
   block: 0,
-
   loading: true,
   nominators: [],
   validators: [],
@@ -51,6 +50,10 @@ const initialState = {
   reports: {},
   termEndsAt: 0,
   stage: {},
+  stakes: {},
+  stashes: [],
+  stars: {},
+  lastReward: 0,
 };
 
 class App extends React.Component<IProps, IState> {
@@ -63,6 +66,7 @@ class App extends React.Component<IProps, IState> {
 
     let blocks: Block[] = [];
     let lastBlock: Block = { id: 0, timestamp: 0, duration: 6 };
+    let era = 0;
 
     let termEndsAt = Number((await api.query.council.termEndsAt()).toJSON());
     this.save("termEndsAt", termEndsAt);
@@ -131,6 +135,27 @@ class App extends React.Component<IProps, IState> {
 
         lastBlock = block;
 
+        // validators
+        const currentEra = Number(await api.query.staking.currentEra());
+        if (currentEra > era) {
+          era = currentEra;
+          this.fetchStakes(api, era, this.state.validators);
+          this.save("era", era);
+
+          const lastReward = await api.query.staking.erasValidatorReward(
+            era - 1
+          );
+          this.save("lastReward", Number(lastReward));
+        }
+        if (era > 0 && this.state.lastReward === 0) {
+          const lastReward = await api.query.staking.erasValidatorReward(
+            era - 1
+          );
+          this.save("lastReward", Number(lastReward));
+        }
+
+        this.fetchEraRewardPoints(api, Number(era));
+
         // check election stage
         if (id < termEndsAt || id < stageEndsAt) return;
         const json = stage.toJSON();
@@ -388,11 +413,60 @@ class App extends React.Component<IProps, IState> {
     this.save("nominators", nominators);
   }
   async fetchValidators(api: Api) {
+    // session.disabledValidators: Vec<u32>
+    // TODO check online: imOnline.keys
+    //  imOnline.authoredBlocks: 2
+    // TODO session.currentIndex: 17,081
+    const stashes = await api.derive.staking.stashes();
+    this.save(
+      "stashes",
+      stashes.map((s: any) => String(s))
+    );
+
     const validatorEntries = await api.query.session.validators();
     const validators = await validatorEntries.map((v: any) => String(v));
     this.save("validators", validators);
   }
 
+  async fetchStakes(api: Api, era: number, validators: string[]) {
+    // TODO staking.bondedEras: Vec<(EraIndex,SessionIndex)>
+    console.debug(`fetching stakes`);
+    const { stashes } = this.state;
+    if (!stashes) return;
+    stashes.forEach(async (validator: string) => {
+      try {
+        const prefs = await api.query.staking.erasValidatorPrefs(
+          era,
+          validator
+        );
+        const commission = Number(prefs.commission) / 10000000;
+
+        const data = await api.query.staking.erasStakers(era, validator);
+        let { total, own, others } = data.toJSON();
+        let { stakes } = this.state;
+        if (!stakes) stakes = {};
+
+        stakes[validator] = { total, own, others, commission };
+        this.save("stakes", stakes);
+      } catch (e) {
+        console.warn(
+          `Failed to fetch stakes for ${validator} in era ${era}`,
+          e
+        );
+      }
+    });
+  }
+
+  async fetchEraRewardPoints(api: Api, era: number) {
+    const data = await api.query.staking.erasRewardPoints(era);
+    this.setState({ rewardPoints: data.toJSON() });
+  }
+
+  // data objects
+  fetchDataObjects() {
+    // TODO dataDirectory.knownContentIds: Vec<ContentId>
+  }
+
   // accounts
   async fetchMembers(api: Api, lastId: number) {
     for (let id = lastId; id > 0; id--) {
@@ -518,6 +592,9 @@ class App extends React.Component<IProps, IState> {
   loadValidators() {
     const validators = this.load("validators");
     if (validators) this.setState({ validators });
+
+    const stashes = this.load("stashes") || [];
+    if (stashes) this.setState({ stashes });
   }
   loadNominators() {
     const nominators = this.load("nominators");
@@ -540,6 +617,11 @@ class App extends React.Component<IProps, IState> {
     const mint = this.load("mint");
     if (mint) this.setState({ mint });
   }
+  loadStakes() {
+    const stakes = this.load("stakes");
+    if (stakes) this.setState({ stakes });
+  }
+
   clearData() {
     this.save("version", version);
     this.save("proposals", []);
@@ -561,11 +643,26 @@ class App extends React.Component<IProps, IState> {
     await this.loadHandles();
     await this.loadTokenomics();
     await this.loadReports();
+    await this.loadStakes();
     const block = this.load("block");
     const now = this.load("now");
+    const era = this.load("era") || `..`;
     const round = this.load("round");
     const stage = this.load("stage");
-    this.setState({ block, now, round, stage, termEndsAt, loading: false });
+    const stars = this.load("stars") || {};
+    const lastReward = this.load("lastReward") || 0;
+    const loading = false;
+    this.setState({
+      block,
+      era,
+      now,
+      round,
+      stage,
+      stars,
+      termEndsAt,
+      loading,
+      lastReward,
+    });
     console.debug(`Finished loading.`);
   }
 
@@ -590,7 +687,7 @@ class App extends React.Component<IProps, IState> {
 
   render() {
     if (this.state.loading) return <Loading />;
-    return <Routes {...this.state} />;
+    return <Routes load={this.load} save={this.save} {...this.state} />;
   }
 
   componentDidMount() {
@@ -607,6 +704,8 @@ class App extends React.Component<IProps, IState> {
     this.state = initialState;
     this.fetchTokenomics = this.fetchTokenomics.bind(this);
     this.fetchProposal = this.fetchProposal.bind(this);
+    this.load = this.load.bind(this);
+    this.save = this.save.bind(this);
   }
 }
 

+ 2 - 1
src/components/Council/ElectionStatus.tsx

@@ -22,7 +22,8 @@ const ElectionStage = (props: {
     return <div>election in {left}</div>;
   }
 
-  let stageString = Object.keys(JSON.parse(JSON.stringify(stage)))[0];
+  //const stageObject = JSON.parse(JSON.stringify(stage));
+  let stageString = Object.keys(stage)[0];
   const left = timeLeft(stage[stageString] - block);
 
   if (stageString === "Announcing")

+ 29 - 26
src/components/Council/index.tsx

@@ -42,36 +42,39 @@ const Council = (props: {
         <div className="d-flex flex-column">
           <div className="d-flex flex-row justify-content-between">
             {council.slice(0, half).map((m) => (
-              <MemberBox
-                key={m.member}
-                id={m.id || 0}
-                account={m.member}
-                handle={m.handle || handles[m.member]}
-                members={members}
-                councils={props.councils}
-                proposals={props.proposals}
-                placement={"top"}
-                posts={props.posts}
-                startTime={startTime}
-                validators={props.validators}
-              />
+              <div key={m.member} className="box">
+                <MemberBox
+                  id={m.id || 0}
+                  account={m.member}
+                  handle={m.handle || handles[m.member]}
+                  members={members}
+                  councils={props.councils}
+                  proposals={props.proposals}
+                  placement={"top"}
+                  posts={props.posts}
+                  startTime={startTime}
+                  validators={props.validators}
+                />
+              </div>
             ))}
           </div>
           <div className="d-flex flex-row justify-content-between">
             {council.slice(half).map((m) => (
-              <MemberBox
-                key={m.member}
-                id={m.id || 0}
-                account={m.member}
-                handle={m.handle || handles[m.member]}
-                members={members}
-                councils={props.councils}
-                proposals={props.proposals}
-                placement={"top"}
-                posts={props.posts}
-                startTime={startTime}
-                validators={props.validators}
-              />
+              <div key={m.member} className="box">
+                <MemberBox
+                  key={m.member}
+                  id={m.id || 0}
+                  account={m.member}
+                  handle={m.handle || handles[m.member]}
+                  members={members}
+                  councils={props.councils}
+                  proposals={props.proposals}
+                  placement={"top"}
+                  posts={props.posts}
+                  startTime={startTime}
+                  validators={props.validators}
+                />
+              </div>
             ))}
           </div>
         </div>

+ 29 - 13
src/components/Dashboard/index.tsx

@@ -1,16 +1,20 @@
 import React from "react";
 import { Link } from "react-router-dom";
 import { ActiveProposals, Council } from "..";
-import Nominators from "./Nominators";
-import Validators from "./Validators";
-import Loading from "../Loading";
+import Validators from "../Validators";
 import { IState } from "../../types";
 
 const Dashboard = (props: IState) => {
-  const { block, now, councils, domain, handles, members, proposals } = props;
+  const { block, now, domain, handles, members, proposals, tokenomics } = props;
 
   return (
     <div className="w-100 flex-grow-1 d-flex align-items-center justify-content-center d-flex flex-column">
+      <div className="box position-fixed " style={{ top: "0", right: "0" }}>
+        <Link to="/mint">Tools</Link>
+      </div>
+
+      <Channels channels={props.channels} />
+
       <div className="title">
         <h1>
           <a href={domain}>Joystream</a>
@@ -24,10 +28,6 @@ const Dashboard = (props: IState) => {
         </Link>
       </div>
 
-      <div className="box mt-3">
-        <h3>latest block</h3>
-        {block ? block : <Loading />}
-      </div>
       <div className="box">
         <h3>Active Proposals</h3>
         <ActiveProposals block={block} proposals={proposals} />
@@ -36,7 +36,7 @@ const Dashboard = (props: IState) => {
       </div>
 
       <Council
-        councils={councils}
+        councils={props.councils}
         members={members}
         councilElection={props.councilElection}
         block={block}
@@ -49,10 +49,26 @@ const Dashboard = (props: IState) => {
         proposals={props.proposals}
         validators={props.validators}
       />
-      <div className="d-flex flex-row">
-        <Validators validators={props.validators} handles={handles} />
-        <Nominators nominators={props.nominators} handles={handles} />
-      </div>
+
+      <Validators
+        block={block}
+        era={props.era}
+        now={now}
+        lastReward={props.lastReward}
+        councils={props.councils}
+        handles={handles}
+        members={members}
+        posts={props.posts}
+        proposals={props.proposals}
+        nominators={props.nominators}
+        validators={props.validators}
+        stashes={props.stashes}
+        stars={props.stars}
+        stakes={props.stakes}
+        save={props.save}
+        rewardPoints={props.rewardPoints}
+        issued={tokenomics ? Number(tokenomics.totalIssuance) : 0}
+      />
     </div>
   );
 };

+ 1 - 2
src/components/Forum/Posts.tsx

@@ -8,9 +8,8 @@ const Posts = (props: {
   thread?: Thread;
   posts: Post[];
 }) => {
-  const { thread, handles, startTime, posts } = props;
+  const { handles, startTime, posts } = props;
 
-  //if (!thread) return <div />;
   return (
     <div className="overflow-auto" style={{ height: `90%` }}>
       {posts

+ 1 - 3
src/components/Members/MemberBox.tsx

@@ -34,9 +34,7 @@ const MemberBox = (props: {
         </Tooltip>
       }
     >
-      <Link className="box" to={`/members/${handle}`}>
-        {handle}
-      </Link>
+      <Link to={`/members/${handle}`}>{handle}</Link>
     </OverlayTrigger>
   );
 };

+ 14 - 13
src/components/Members/index.tsx

@@ -55,19 +55,20 @@ class Members extends React.Component<IProps, IState> {
           {cols.map((col, index: number) => (
             <div key={`col-${index}`} className="d-flex flex-column col-3 p-0">
               {col.map((m) => (
-                <MemberBox
-                  key={String(m.account)}
-                  id={Number(m.id)}
-                  account={String(m.account)}
-                  handle={m.handle || handles[String(m.account)]}
-                  members={members}
-                  councils={councils}
-                  proposals={proposals}
-                  placement={index === 3 ? "left" : "bottom"}
-                  posts={posts}
-                  startTime={startTime}
-                  validators={this.props.validators}
-                />
+                <div key={String(m.account)} className="box">
+                  <MemberBox
+                    id={Number(m.id)}
+                    account={String(m.account)}
+                    handle={m.handle || handles[String(m.account)]}
+                    members={members}
+                    councils={councils}
+                    proposals={proposals}
+                    placement={index === 3 ? "left" : "bottom"}
+                    posts={posts}
+                    startTime={startTime}
+                    validators={this.props.validators}
+                  />
+                </div>
               ))}
             </div>
           ))}

+ 2 - 2
src/components/User/index.tsx

@@ -14,9 +14,9 @@ const User = (props: { id: string; handle?: string }) => {
       placement="bottom"
       overlay={<Tooltip id={id}>{id}</Tooltip>}
     >
-      <div className="user mx-1">
+      <span className="user mx-1">
         <Link to={`/members/${handle || id}`}>{handle || shortName(id)}</Link>
-      </div>
+      </span>
     </OverlayTrigger>
   );
 };

+ 58 - 0
src/components/Validators/MinMax.tsx

@@ -0,0 +1,58 @@
+import React from "react";
+import { Stakes } from "../../types";
+
+const MinMax = (props: {
+  stakes?: { [key: string]: Stakes };
+  issued: number;
+  validators: number;
+  nominators: number;
+  waiting: number;
+}) => {
+  const { issued, stakes, validators, nominators, waiting } = props;
+  if (!stakes || !Object.values(stakes).length) return <span />;
+
+  let sum = 0;
+  let minStake: number = 10000000;
+  let maxStake: number = 0;
+  Object.values(stakes).forEach((s: Stakes) => {
+    if (s.total > 0) sum += s.total;
+    else return;
+
+    if (s.total > maxStake) maxStake = s.total;
+    if (s.total < minStake) minStake = s.total;
+  });
+
+  return (
+    <div className="float-right text-right">
+      <div className="mb-2">
+        <div>
+          <div className="float-left mr-1">validators:</div>
+          {validators}
+        </div>
+        <div>
+          <div className="float-left mr-1">nominators:</div>
+          {nominators}
+        </div>
+        <div>
+          <div className="float-left mr-1">waiting:</div>
+          {waiting}
+        </div>
+      </div>
+
+      <b>total stake</b>
+      <div className="mb-2">
+        <div>{Math.floor(sum / 100000) / 10} M JOY</div>/{" "}
+        {Math.floor(issued / 100000) / 10} M JOY
+        <div>({Math.floor((sum / issued) * 1000) / 10}%)</div>
+      </div>
+      <div>
+        <div className="float-left mr-1">min:</div> {minStake} JOY
+      </div>
+      <div>
+        <div className="float-left mr-1">max:</div> {maxStake} JOY
+      </div>
+    </div>
+  );
+};
+
+export default MinMax;

+ 56 - 0
src/components/Validators/Nominators.tsx

@@ -0,0 +1,56 @@
+import React from "react";
+import User from "../User";
+import { Handles, Stake } from "../../types";
+
+// TODO use MemberBox after refactor
+
+const Nominators = (props: {
+  sortBy: (field: string) => void;
+  toggleExpand: () => void;
+  expand: boolean;
+  handles: Handles;
+  nominators?: Stake[];
+}) => {
+  const { sortBy, toggleExpand, expand, handles, nominators } = props;
+
+  if (!nominators || !nominators.length) return <div />;
+
+  let sum: number = 0;
+  nominators.forEach((n) => (sum += n.value));
+
+  if (nominators.length === 1)
+    return (
+      <div className="d-flex flex-row">
+        <div onClick={() => sortBy("othersStake")}>{nominators[0].value}</div>
+        <User id={nominators[0].who} handle={handles[nominators[0].who]} />
+      </div>
+    );
+
+  if (expand)
+    return (
+      <div>
+        <span onClick={() => sortBy("othersStake")}>{sum}</span>
+        <span onClick={toggleExpand}> -</span>
+        {nominators.map((n) => (
+          <div key={n.who} className="d-flex flex-row">
+            <div>{n.value}</div>
+            <User id={n.who} handle={handles[n.who]} />
+          </div>
+        ))}
+      </div>
+    );
+
+  return (
+    <div>
+      <span onClick={() => sortBy("othersStake")}> {sum}</span>
+      {nominators
+        .sort((a, b) => b.value - a.value)
+        .map((n) => (
+          <User key={n.who} id={n.who} handle={handles[n.who]} />
+        ))}
+      <span onClick={toggleExpand}> +</span>
+    </div>
+  );
+};
+
+export default Nominators;

+ 134 - 0
src/components/Validators/Validator.tsx

@@ -0,0 +1,134 @@
+import React, { Component } from "react";
+import { Activity, Minus, Star } from "react-feather";
+import Nominators from "./Nominators";
+import MemberBox from "../Members/MemberBox";
+import {
+  Handles,
+  Member,
+  Post,
+  ProposalDetail,
+  Seat,
+  Stakes,
+  RewardPoints,
+} from "../../types";
+import { domain } from "../../config";
+
+interface IProps {
+  toggleStar: (account: string) => void;
+  sortBy: (sortBy: string) => void;
+  validator: string;
+  councils: Seat[][];
+  handles: Handles;
+  members: Member[];
+  posts: Post[];
+  proposals: ProposalDetail[];
+  validators: string[];
+  startTime: number;
+  starred: string | undefined;
+  stakes?: { [key: string]: Stakes };
+  rewardPoints?: RewardPoints;
+}
+
+interface IState {
+  expandNominators: boolean;
+  hidden: boolean;
+}
+
+class Validator extends Component<IProps, IState> {
+  constructor(props: IProps) {
+    super(props);
+    this.state = { expandNominators: false, hidden: false };
+    this.hide = this.hide.bind(this);
+    this.toggleExpandNominators = this.toggleExpandNominators.bind(this);
+  }
+
+  hide() {
+    this.setState({ hidden: true });
+  }
+  toggleExpandNominators() {
+    this.setState({ expandNominators: !this.state.expandNominators });
+  }
+
+  render() {
+    const {
+      sortBy,
+      toggleStar,
+      handles,
+      members,
+      validator,
+      councils,
+      posts,
+      proposals,
+      startTime,
+      starred,
+      stakes,
+      rewardPoints,
+    } = this.props;
+    const { expandNominators, hidden } = this.state;
+    if (hidden) return <div />;
+
+    const handle = handles[validator] || validator;
+    const points = rewardPoints ? rewardPoints.individual[validator] : "";
+    const myStakes = stakes ? stakes[validator] : undefined;
+    const totalStake = myStakes ? myStakes.total : "";
+    const ownStake = myStakes ? myStakes.own : "";
+    const commission = myStakes ? `${myStakes.commission}%` : "";
+
+    return (
+      <div className="mx-3 d-flex flex-row">
+        <Minus width={15} color={"black"} onClick={this.hide} />
+        <Star
+          width={15}
+          color={"black"}
+          fill={starred ? "black" : "teal"}
+          onClick={() => toggleStar(validator)}
+        />
+        <a
+          href={`${domain}/#/staking/query/${validator}`}
+          title="Show Stats (External)"
+        >
+          <Activity width={15} />
+        </a>
+        <div className="col-1 text-right" onClick={() => sortBy("points")}>
+          {points}
+        </div>
+        <div className="col-4 text-right">
+          <MemberBox
+            id={0}
+            account={validator}
+            placement={"right"}
+            councils={councils}
+            handle={handle}
+            members={members}
+            posts={posts}
+            proposals={proposals}
+            startTime={startTime}
+            validators={this.props.validators}
+          />
+        </div>
+
+        <div className="col-1 text-right" onClick={() => sortBy("commission")}>
+          {commission}
+        </div>
+        <div className="col-1 text-right" onClick={() => sortBy("totalStake")}>
+          {totalStake}
+        </div>
+        <div className="col-1 text-right" onClick={() => sortBy("ownStake")}>
+          {ownStake}
+        </div>
+        <div className="col-3 mb-1 text-left">
+          <Nominators
+            toggleExpand={this.toggleExpandNominators}
+            sortBy={sortBy}
+            expand={expandNominators}
+            nominators={myStakes ? myStakes.others : undefined}
+            handles={handles}
+          />
+        </div>
+        <div onClick={() => sortBy("profit")}></div>
+      </div>
+    );
+  }
+}
+
+export default Validator;

+ 203 - 0
src/components/Validators/index.tsx

@@ -0,0 +1,203 @@
+import React, { Component } from "react";
+import MinMax from "./MinMax";
+import Validator from "./Validator";
+import {
+  Handles,
+  Member,
+  Post,
+  ProposalDetail,
+  Seat,
+  Stakes,
+  RewardPoints,
+} from "../../types";
+
+interface IProps {
+  era: number;
+  issued: number;
+  councils: Seat[][];
+  handles: Handles;
+  members: Member[];
+  posts: Post[];
+  proposals: ProposalDetail[];
+  now: number;
+  block: number;
+  validators: string[];
+  stashes: string[];
+  nominators: string[];
+  stars: { [key: string]: boolean };
+  save: (target: string, data: any) => void;
+  stakes?: { [key: string]: Stakes };
+  rewardPoints?: RewardPoints;
+  lastReward: number;
+}
+
+interface IState {
+  sortBy: string;
+}
+
+class Validators extends Component<IProps, IState> {
+  constructor(props: IProps) {
+    super(props);
+    this.state = { sortBy: "totalStake" };
+    this.toggleStar = this.toggleStar.bind(this);
+    this.setSortBy = this.setSortBy.bind(this);
+  }
+
+  toggleStar(account: string) {
+    let { stars } = this.props;
+    stars[account] = !stars[account];
+    this.props.save("stars", stars);
+  }
+
+  setSortBy(sortBy: string) {
+    this.setState({ sortBy });
+  }
+  sortBy(field: string, validators: string[]) {
+    const { stakes, rewardPoints } = this.props;
+    try {
+      if (field === "points")
+        return validators.sort((a, b) =>
+          rewardPoints
+            ? rewardPoints.individual[b] - rewardPoints.individual[a]
+            : 0
+        );
+
+      if (field === "commission")
+        return validators.sort((a, b) =>
+          stakes ? stakes[a].commission - stakes[b].commission : 0
+        );
+
+      if (field === "ownStake")
+        return validators.sort((a, b) =>
+          stakes ? stakes[b].own - stakes[a].own : 0
+        );
+
+      if (field === "totalStake")
+        return validators.sort((a, b) =>
+          stakes ? stakes[b].total - stakes[a].total : 0
+        );
+
+      if (field === "othersStake") {
+        const sumOf = (stakes: { value: number }[]) => {
+          let sum = 0;
+          stakes.forEach((s) => (sum += s.value));
+          return sum;
+        };
+
+        return validators.sort((a, b) =>
+          stakes ? sumOf(stakes[b].others) - sumOf(stakes[a].others) : 0
+        );
+      }
+    } catch (e) {
+      console.debug(`sorting failed`, e);
+    }
+
+    return validators;
+  }
+
+  render() {
+    const {
+      block,
+      era,
+      now,
+      councils,
+      handles,
+      members,
+      posts,
+      validators,
+      nominators,
+      stashes,
+      stars,
+      lastReward,
+      rewardPoints,
+      stakes,
+      issued,
+    } = this.props;
+    const { sortBy } = this.state;
+    const startTime = now - block * 6000;
+
+    const starred = stashes.filter((v) => stars[v]);
+    const unstarred = validators.filter((v) => !stars[v]);
+    const waiting = stashes.filter((s) => !stars[s] && !validators.includes(s));
+
+    return (
+      <div className="box w-100">
+        <div className="float-left">
+          last block: {block}, era {era}, last reward: {lastReward} JOY
+        </div>
+        <MinMax
+          stakes={stakes}
+          issued={issued}
+          validators={validators.length}
+          nominators={nominators.length}
+          waiting={waiting.length}
+        />
+
+        <h3>Validators</h3>
+
+        <div className="d-flex flex-column">
+          {this.sortBy(sortBy, starred).map((v) => (
+            <Validator
+              key={v}
+              sortBy={this.setSortBy}
+              starred={stars[v] ? `teal` : undefined}
+              toggleStar={this.toggleStar}
+              startTime={startTime}
+              validator={v}
+              councils={councils}
+              handles={handles}
+              members={members}
+              posts={posts}
+              proposals={this.props.proposals}
+              validators={this.props.validators}
+              stakes={stakes}
+              rewardPoints={rewardPoints}
+            />
+          ))}
+
+          {this.sortBy(sortBy, unstarred).map((v) => (
+            <Validator
+              key={v}
+              sortBy={this.setSortBy}
+              starred={stars[v] ? `teal` : undefined}
+              toggleStar={this.toggleStar}
+              startTime={startTime}
+              validator={v}
+              councils={councils}
+              handles={handles}
+              members={members}
+              posts={posts}
+              proposals={this.props.proposals}
+              validators={this.props.validators}
+              stakes={stakes}
+              rewardPoints={rewardPoints}
+            />
+          ))}
+
+          <h3>Waiting</h3>
+
+          {waiting.map((v) => (
+            <Validator
+              key={v}
+              sortBy={this.setSortBy}
+              starred={stars[v] ? `teal` : undefined}
+              toggleStar={this.toggleStar}
+              startTime={startTime}
+              validator={v}
+              councils={councils}
+              handles={handles}
+              members={members}
+              posts={posts}
+              proposals={this.props.proposals}
+              validators={this.props.validators}
+              stakes={stakes}
+              rewardPoints={rewardPoints}
+            />
+          ))}
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Validators;

+ 1 - 0
src/components/index.ts

@@ -14,3 +14,4 @@ export { default as User } from "./User";
 export { default as Member } from "./Members/Member";
 export { default as Members } from "./Members";
 export { default as Tokenomics } from "./Tokenomics";
+export { default as Validators } from "./Validators";

+ 23 - 0
src/types.ts

@@ -12,6 +12,7 @@ import { StorageKey } from "@polkadot/types/primitive";
 export interface Api {
   query: any;
   rpc: any;
+  derive: any;
 }
 
 export interface IState {
@@ -21,6 +22,7 @@ export interface IState {
   blocks: Block[];
   nominators: string[];
   validators: string[];
+  stashes: string[];
   loading: boolean;
   councils: Seat[][];
   councilElection?: { stage: any; round: number; termEndsAt: number };
@@ -37,6 +39,27 @@ export interface IState {
   tokenomics?: Tokenomics;
   reports: { [key: string]: string };
   [key: string]: any;
+  stars: { [key: string]: boolean };
+  stakes?: { [key: string]: Stakes };
+  rewardPoints?: RewardPoints;
+  lastReward: number;
+}
+
+export interface RewardPoints {
+  total: number;
+  individual: { [account: string]: number };
+}
+
+export interface Stake {
+  who: string;
+  value: number;
+}
+
+export interface Stakes {
+  total: number;
+  own: number;
+  others: Stake[];
+  commission: number;
 }
 
 export interface Seat {