· 6 years ago · Dec 02, 2019, 11:36 AM
1import React from "react";
2import { Link, NavLink, Route } from "react-router-dom";
3import {
4 Flex,
5 IconButton,
6 useColorMode,
7 Box,
8 Image,
9 Heading,
10 Text,
11 Input,
12 InputGroup,
13 InputRightAddon
14} from "@chakra-ui/core";
15import { ApiPromise, WsProvider } from "@polkadot/api";
16import { hexToString } from "@polkadot/util";
17import ValidatorTable from "./components/ValidatorTable";
18import HelpCenter from "./components/HelpCenter";
19
20function App() {
21 const { colorMode, toggleColorMode } = useColorMode();
22 const [validatorData, setValidatorData] = React.useState([]);
23 const [validatorTableData, setValidatorTableData] = React.useState([]);
24 const [stakeAmount, setStakeAmount] = React.useState(1000.00);
25
26 const createApi = async () => {
27 console.log(`Connecting to API...`);
28 const wsProvider = new WsProvider("wss://kusama-rpc.polkadot.io");
29 const api = await ApiPromise.create({ provider: wsProvider });
30 await api.isReady;
31 console.log(`API is ready`);
32
33 // Fetch recent reward events from Polkascan
34 const res = await fetch(
35 "https://polkascan.io/kusama-cc3/api/v1/event?&filter[module_id]=staking&filter[event_id]=Reward&page[size]=25"
36 );
37 const json = await res.json();
38 const rewardData = await json.data;
39
40 // Retrieve the last known era reward
41 const reward = await rewardData[0].attributes.attributes[0].value;
42 // Retrieve the hashes of the end of era blocks
43 const hash = await Promise.all(
44 rewardData.map(data =>
45 api.rpc.chain.getBlockHash(data.attributes.block_id - 1)
46 )
47 );
48 // Retrieve the era points for all end of era blocks
49 const eraPoints = await Promise.all(
50 hash.map(data =>
51 api.query.staking.currentEraPointsEarned.at(
52 `${data.toString()}`
53 )
54 )
55 );
56 // Retrieve an array of the list of all elected validators at the end of era blocks
57 const validatorList = await Promise.all(
58 hash.map(data =>
59 api.query.staking.currentElected.at(`${data.toString()}`)
60 )
61 );
62
63 let result = {};
64
65 await Promise.all(
66 validatorList.map(async validator => {
67 await Promise.all(
68 validator.map(async address => {
69 const commission = await api.query.staking.validators(
70 address
71 );
72 const name = await api.query.nicks.nameOf(
73 `${address.toString()}`
74 );
75 result[address] = {
76 stashId: address.toString(),
77 stashIdTruncated: `${address
78 .toString()
79 .slice(0, 4)}...${address
80 .toString()
81 .slice(-6, -1)}`,
82 points: [],
83 poolReward: "",
84 totalStake: "",
85 commission:
86 commission[0].commission.toNumber() / 10 ** 7,
87 name: name.raw[0]
88 ? hexToString(name.raw[0].toString())
89 : `Validator (...${address
90 .toString()
91 .slice(-6, -1)})`
92 };
93 })
94 );
95 })
96 );
97
98 eraPoints.map((eraPoint, index) => {
99 eraPoint.individual.map((point, validatorIndex) => {
100 result[validatorList[index][validatorIndex]].points.push(
101 point.toNumber() / eraPoint.total.toNumber()
102 );
103 return 0;
104 });
105 return 0;
106 });
107
108 const validatorData = await Promise.all(
109 Object.keys(result).map(async (key, index) => {
110 const validatorPoolReward =
111 ((result[key].points.reduce((acc, curr) => acc + curr, 0) /
112 result[key].points.length) *
113 reward) /
114 10 ** 12;
115 const stakeInfo = await api.derive.staking.info(key);
116 const totalStake =
117 stakeInfo.stakers.total.toString() / 10 ** 12;
118 result[key].totalStake = totalStake;
119 result[key].poolReward = isNaN(validatorPoolReward)
120 ? "Not enough data"
121 : (1 - result[key].commission / 100) * validatorPoolReward;
122 return result[key];
123 })
124 );
125 setValidatorData(validatorData);
126 return validatorData;
127 };
128
129 const calcReward = React.useCallback(() => {
130 const data = validatorData.map(validator => {
131 const {
132 stashId,
133 stashIdTruncated,
134 name,
135 commission,
136 totalStake,
137 poolReward
138 } = validator;
139 const userStakeFraction = stakeAmount / (stakeAmount + totalStake);
140 const dailyEarning = userStakeFraction * poolReward;
141 // console.log(
142 // `${name}\nstakeAmount: ${stakeAmount}\nuserStake: ${userStakeFraction}\ntotalStake: ${totalStake}\nreward: ${poolReward}\ndailyEarning: ${dailyEarning}`
143 // );
144 return {
145 stashId: stashId,
146 stashIdTruncated: stashIdTruncated,
147 name: name,
148 commission: commission,
149 dailyEarning: `${dailyEarning} KSM`
150 };
151 });
152 setValidatorTableData(data);
153 }, [stakeAmount, validatorData]);
154
155 React.useEffect(() => {
156 createApi();
157 calcReward();
158 }, [calcReward]);
159
160 return (
161 <Flex
162 className="App"
163 maxW="960px"
164 justify="center"
165 direction="column"
166 m="auto"
167 pb={8}
168 >
169 {/* Navbar */}
170 <Flex direction="row" justifyContent="space-between" p={2}>
171 {/* Polka Analytics Logo - Left hand part of navbar */}
172 <Flex justify="flex-start" alignItems="center">
173 <NavLink to="/">
174 <Box
175 as="span"
176 display="inline-flex"
177 alignItems="center"
178 >
179 <Image src="/logo192.png" height="2rem" mr={4} />
180 <Heading as="h3" size="lg">
181 Polka Analytics
182 </Heading>
183 </Box>
184 </NavLink>
185 </Flex>
186 {/* Navigation Menu & color mode toggle - Right hand part of navbar */}
187 <Flex justify="flex-end">
188 <Flex alignItems="center">
189 <Box mr={8}>
190 <NavLink to="/dashboard">Dashboard</NavLink>
191 </Box>
192 <Box mr={8}>
193 <NavLink to="/help-center">Help Center</NavLink>
194 </Box>
195 </Flex>
196 <IconButton
197 aria-label={
198 colorMode === "light"
199 ? "Switch to dark mode"
200 : "Switch to light mode"
201 }
202 icon={colorMode === "light" ? "moon" : "sun"}
203 size="lg"
204 onClick={toggleColorMode}
205 backgroundColor={
206 colorMode === "light" ? "#fff" : "gray.800"
207 }
208 />
209 </Flex>
210 </Flex>
211 {/* Homepage - Dashboard */}
212 <Route exact path="/(|dashboard)">
213 <Heading as="h2" size="xl" textAlign="center" mt={16}>
214 Put your KSM tokens to work
215 </Heading>
216 <Text fontSize="2xl" textAlign="center">
217 You could be earning{" "}
218 <Box as="span" color="brand.900">
219 0.15301
220 </Box>{" "}
221 KSM daily
222 </Text>
223 {/* Stake Amount Input */}
224 <Flex
225 flexDirection="column"
226 alignItems="center"
227 position="sticky"
228 top="0"
229 backgroundImage={
230 colorMode === "light"
231 ? "linear-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 1), rgba(255, 255, 255, 1), rgba(255, 255, 255, 0))"
232 : "linear-gradient(rgba(26, 32, 44, 1), rgba(26, 32, 44, 1), rgba(26, 32, 44, 1), rgba(26, 32, 44, 0))"
233 }
234 >
235 <InputGroup my={8}>
236 <Input
237 placeholder="Stake Amount"
238 value={stakeAmount}
239 textAlign="center"
240 roundedLeft="2rem"
241 onChange={e => setStakeAmount(e.target.value)}
242 />
243 <InputRightAddon
244 children="KSM"
245 backgroundColor="teal.500"
246 roundedRight="2rem"
247 />
248 </InputGroup>
249 </Flex>
250 <Box as="span" color="teal.500" textAlign="center">
251 <Link to="/help-center/guides/how-to-stake">
252 How to stake?
253 </Link>
254 </Box>
255 {/* Validator Table */}
256 <Text textAlign="center" mt={16} mb={8}>
257 Looking for a list of active validators to stake on? Look no
258 further!
259 </Text>
260 <ValidatorTable
261 dataSource={
262 validatorTableData !== undefined
263 ? validatorTableData
264 : []
265 }
266 />
267 </Route>
268 {/* Help Center */}
269 <Route path="/help-center">
270 <HelpCenter />
271 </Route>
272 </Flex>
273 );
274}
275
276export default App;