diff --git a/package.json b/package.json index 761c3dfe02..474bdb9909 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "packages/*" ], "resolutions": { - "@polkadot/api": "^0.97.0-beta.33", - "@polkadot/api-contract": "^0.97.0-beta.33", + "@polkadot/api": "^0.97.0-beta.34", + "@polkadot/api-contract": "^0.97.0-beta.34", "@polkadot/keyring": "^1.7.0-beta.8", - "@polkadot/types": "^0.97.0-beta.33", + "@polkadot/types": "^0.97.0-beta.34", "@polkadot/util": "^1.7.0-beta.8", "@polkadot/util-crypto": "^1.7.0-beta.8", "babel-core": "^7.0.0-bridge.0", diff --git a/packages/app-contracts/package.json b/packages/app-contracts/package.json index b1828567da..040daaa3c1 100644 --- a/packages/app-contracts/package.json +++ b/packages/app-contracts/package.json @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.7.4", - "@polkadot/api-contract": "^0.97.0-beta.33", + "@polkadot/api-contract": "^0.97.0-beta.34", "@polkadot/react-components": "^0.37.0-beta.128" } } diff --git a/packages/app-staking/src/Targets/Summary.tsx b/packages/app-staking/src/Targets/Summary.tsx new file mode 100644 index 0000000000..fc1dcd1205 --- /dev/null +++ b/packages/app-staking/src/Targets/Summary.tsx @@ -0,0 +1,79 @@ +// Copyright 2017-2019 @polkadot/app-staking 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 { Balance } from '@polkadot/types/interfaces'; +import { I18nProps } from '@polkadot/react-components/types'; + +import BN from 'bn.js'; +import React, { useEffect, useState } from 'react'; +import { SummaryBox, CardSummary } from '@polkadot/react-components'; +import { useApi, trackStream } from '@polkadot/react-hooks'; +import { formatBalance } from '@polkadot/util'; + +import translate from '../translate'; + +interface Props extends I18nProps { + totalStaked: BN; +} + +interface StakeInfo { + percentage: string; + staked: string | null; + total: string | null; +} + +function Summary ({ t, totalStaked }: Props): React.ReactElement { + const { api } = useApi(); + const totalInsurance = trackStream(api.query.balances.totalIssuance, []); + const [{ percentage, staked, total }, setStakeInfo] = useState({ percentage: '-', staked: null, total: null }); + + useEffect((): void => { + if (totalInsurance && totalStaked?.gtn(0)) { + setStakeInfo({ + percentage: `${(totalStaked.muln(10000).div(totalInsurance).toNumber() / 100).toFixed(2)}%`, + staked: totalStaked.toString(), + total: totalInsurance.toString() + }); + } + }, [totalInsurance, totalStaked]); + + return ( + +
+ + { + staked + ? `${formatBalance(staked, false)}${formatBalance.calcSi(staked).value}` + : '-' + } + + / + + { + total + ? `${formatBalance(total, false)}${formatBalance.calcSi(total).value}` + : '-' + } + +
+ + {percentage} + +
+ ); +} + +export default translate(Summary); diff --git a/packages/app-staking/src/Targets/Validator.tsx b/packages/app-staking/src/Targets/Validator.tsx new file mode 100644 index 0000000000..472cfcfba2 --- /dev/null +++ b/packages/app-staking/src/Targets/Validator.tsx @@ -0,0 +1,54 @@ +// Copyright 2017-2019 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details.v + +import { I18nProps } from '@polkadot/react-components/types'; +import { ValidatorInfo } from './types'; + +import React from 'react'; +import { AddressMini, Icon } from '@polkadot/react-components'; +import { FormatBalance } from '@polkadot/react-query'; +import { formatNumber } from '@polkadot/util'; + +import translate from '../translate'; + +interface Props extends I18nProps { + info: ValidatorInfo; + toggleFavorite: (accountId: string) => void; +} + +function Validator ({ info: { accountId, bondOther, bondOwn, bondTotal, commission, isFavorite, key, numNominators, rankOverall, rewardPayout }, toggleFavorite }: Props): React.ReactElement { + const _onFavorite = (): void => toggleFavorite(key); + const _onQueryStats = (): void => { + window.location.hash = `/staking/query/${key}`; + }; + + return ( + + + + + {formatNumber(rankOverall)} + + + {formatNumber(numNominators)} + + + + + + + + + ); +} + +export default translate(Validator); diff --git a/packages/app-staking/src/Targets/index.tsx b/packages/app-staking/src/Targets/index.tsx new file mode 100644 index 0000000000..677c7827a0 --- /dev/null +++ b/packages/app-staking/src/Targets/index.tsx @@ -0,0 +1,224 @@ +// Copyright 2017-2019 @polkadot/app-staking 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 { DerivedStakingElected } from '@polkadot/api-derive/types'; +import { I18nProps } from '@polkadot/react-components/types'; +import { ComponentProps } from '../types'; +import { ValidatorInfo } from './types'; + +import BN from 'bn.js'; +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { InputBalance, Icon, Tooltip } from '@polkadot/react-components'; +import { useApi, useFavorites, trackStream } from '@polkadot/react-hooks'; + +import { STORE_FAVS_BASE } from '../constants'; +import translate from '../translate'; +import Summary from './Summary'; +import Validator from './Validator'; + +interface Props extends I18nProps, ComponentProps { +} + +interface AllInfo { + totalStaked: BN; + validators: ValidatorInfo[]; +} + +function sortValidators (list: ValidatorInfo[]): ValidatorInfo[] { + return list + .sort((a, b): number => b.bondTotal.cmp(a.bondTotal)) + .map((info, index): ValidatorInfo => { + info.rankBonded = index + 1; + + return info; + }) + .sort((a, b): number => b.commission.cmp(a.commission)) + .map((info, index): ValidatorInfo => { + info.rankCommission = index + 1; + + return info; + }) + .sort((a, b): number => a.rewardSplit.cmp(b.rewardSplit)) + .map((info, index): ValidatorInfo => { + info.rankReward = index + 1; + + return info; + }) + .sort((a, b): number => + a.rankReward === b.rankReward + ? a.rankCommission === b.rankCommission + ? a.rankBonded === b.rankBonded + ? 0 + : a.rankBonded < b.rankBonded + ? 1 + : -1 + : a.rankCommission < b.rankCommission + ? 1 + : -1 + : a.rankReward < b.rankReward + ? 1 + : -1 + ) + .map((info, index): ValidatorInfo => { + info.rankOverall = index + 1; + + return info; + }) + .sort((a, b): number => + a.isFavorite === b.isFavorite + ? 0 + : a.isFavorite + ? -1 + : 1 + ); +} + +function Targets ({ className, sessionRewards, t }: Props): React.ReactElement { + const { api } = useApi(); + const [amount, setAmount] = useState(new BN(1000)); + const electedInfo = trackStream(api.derive.staking.electedInfo, []); + const [favorites, toggleFavorite] = useFavorites(STORE_FAVS_BASE); + const [{ validators, totalStaked }, setWorkable] = useState({ totalStaked: new BN(0), validators: [] }); + + useEffect((): void => { + if (electedInfo) { + let totalStaked = new BN(0); + const numValidators = electedInfo.info.length; + const lastReward = sessionRewards[sessionRewards.length - 1] + ? sessionRewards[sessionRewards.length - 1].reward.divn(numValidators) + : new BN(0); + const validators = sortValidators( + electedInfo.info.map(({ accountId, stakers, validatorPrefs }): ValidatorInfo => { + const exposure = stakers || { + total: api.createType('Compact'), + own: api.createType('Compact'), + others: api.createType('Vec') + }; + const prefs = validatorPrefs || { + validatorPayment: api.createType('Compact') + }; + const bondOwn = exposure.own.unwrap(); + const bondTotal = exposure.total.unwrap(); + const commission = prefs.validatorPayment.unwrap(); + const key = accountId.toString(); + const rewardSplit = lastReward.sub(commission); + const calcAmount = amount || new BN(0); + const rewardPayout = rewardSplit.gtn(0) + ? calcAmount.mul(rewardSplit).div(calcAmount.add(bondTotal)) + : new BN(0); + + totalStaked = totalStaked.add(bondTotal); + + return { + accountId, + bondOther: bondTotal.sub(bondOwn), + bondOwn, + bondShare: 0, + bondTotal, + isFavorite: favorites.includes(key), + key, + commission, + numNominators: exposure.others.length, + rankBonded: 0, + rankCommission: 0, + rankOverall: 0, + rankReward: 0, + rewardPayout, + rewardSplit + }; + }) + ); + + setWorkable({ totalStaked, validators }); + } + }, [amount, electedInfo, favorites, sessionRewards]); + + return ( +
+ + + + + + + + + + + + + + + + + + + {validators.map((info): React.ReactNode => + + )} + +
  + + + validatorcommissionnominatorstotal stakeown stakeother stakepayout (est.) 
+
+ ); +} + +export default translate( + styled(Targets)` + text-align: center; + + table { + margin: 1.5rem auto; + + tr { + &:nth-child(even), &.header { + background: #f2f2f2; + } + + td, th { + padding: 0.25rem 0.5rem; + text-align: left; + + &.number { + text-align: right; + } + } + + th { + color: rgba(0,0,0,.6); + font-family: sans-serif; + font-weight: 100; + font-size: 1.25rem; + } + } + } + + i.icon { + cursor: pointer; + } + + .favorite.isSelected { + color: darkorange; + } + ` +); diff --git a/packages/app-staking/src/Targets/types.ts b/packages/app-staking/src/Targets/types.ts new file mode 100644 index 0000000000..5805d1cdc5 --- /dev/null +++ b/packages/app-staking/src/Targets/types.ts @@ -0,0 +1,25 @@ +// Copyright 2017-2019 @polkadot/app-staking 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 { AccountId, Balance } from '@polkadot/types/interfaces'; + +import BN from 'bn.js'; + +export interface ValidatorInfo { + accountId: AccountId; + bondOther: BN; + bondOwn: Balance; + bondShare: number; + bondTotal: Balance; + commission: Balance; + isFavorite: boolean; + key: string; + numNominators: number; + rankBonded: number; + rankCommission: number; + rankOverall: number; + rankReward: number; + rewardPayout: BN; + rewardSplit: BN; +} diff --git a/packages/app-staking/src/index.tsx b/packages/app-staking/src/index.tsx index dd421d0157..7b38851fcc 100644 --- a/packages/app-staking/src/index.tsx +++ b/packages/app-staking/src/index.tsx @@ -16,10 +16,11 @@ import { HelpOverlay } from '@polkadot/react-components'; import Tabs from '@polkadot/react-components/Tabs'; import { trackStream, useAccounts, useApi } from '@polkadot/react-hooks'; -import Actions from './Actions'; import basicMd from './md/basic.md'; +import Actions from './Actions'; import Overview from './Overview'; import Query from './Query'; +import Targets from './Targets'; import { MAX_SESSIONS } from './constants'; import translate from './translate'; import useSessionRewards from './useSessionRewards'; @@ -89,6 +90,10 @@ function App ({ basePath, className, t }: Props): React.ReactElement { name: 'actions', text: t('Account actions') }, + { + name: 'returns', + text: t('Returns') + }, { hasParams: true, name: 'query', @@ -101,6 +106,7 @@ function App ({ basePath, className, t }: Props): React.ReactElement { {_renderComponent(Actions)} {_renderComponent(Query)} {_renderComponent(Query)} + {_renderComponent(Targets)} {_renderComponent(Overview, routeMatch?.isExact ? '' : 'staking--hidden')} diff --git a/packages/react-api/package.json b/packages/react-api/package.json index abb6debed7..d564cfd45f 100644 --- a/packages/react-api/package.json +++ b/packages/react-api/package.json @@ -31,7 +31,7 @@ "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-reactive#readme", "dependencies": { "@babel/runtime": "^7.7.4", - "@polkadot/api": "^0.97.0-beta.33", + "@polkadot/api": "^0.97.0-beta.34", "@polkadot/extension-dapp": "^0.14.0-beta.6", "edgeware-node-types": "^1.0.10", "rxjs-compat": "^6.5.3" diff --git a/yarn.lock b/yarn.lock index 2abbc97201..fdeee532f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2029,35 +2029,35 @@ dependencies: "@types/node" ">= 8" -"@polkadot/api-contract@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/api-contract/-/api-contract-0.97.0-beta.33.tgz#5a1952ddfdb0690aba19cda6b2730d4e26a17d57" - integrity sha512-Q5XDZnHYu+ABXyBPjKgc7I9IXGSMBZQWdvyAF3OVr0kJimQUYJwUQP5j9DQi2cWFi7Pg9P1aNXfEPp89aABN7g== +"@polkadot/api-contract@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/api-contract/-/api-contract-0.97.0-beta.34.tgz#4d4a38c8447468c50d7731b9c644e8582f3cbb7c" + integrity sha512-S33bKfLNZ8LGXQmhu9/cQLa630HhnsqZaht++Blg4FgwSGPzY6CN4OYPFlazM7z58tLDOFXrWtSSHzypREDmww== dependencies: "@babel/runtime" "^7.7.4" - "@polkadot/types" "^0.97.0-beta.33" + "@polkadot/types" "^0.97.0-beta.34" -"@polkadot/api-derive@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-0.97.0-beta.33.tgz#19f6b94adb677c09e27f3a066517189059f88254" - integrity sha512-jVzBrdp8o3My3yhU7+ip7KI06SL0uYAY7VlvbYmIy3iPkttm0cbp1HD9sZhqbntQo15w8TT6KkgE16tm8xHr+w== +"@polkadot/api-derive@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-0.97.0-beta.34.tgz#5cac3de8d6670e76483987e8cd66e985a0c69a17" + integrity sha512-NMrEI2aj6jtK5J83rZ8eO65J4L1O8S2cUgYWOI7Cio1rCK3hm05S+2Yi376fO0LfKt5nDgnCLUBl9ADHhHT4Pg== dependencies: "@babel/runtime" "^7.7.4" - "@polkadot/api" "^0.97.0-beta.33" - "@polkadot/types" "^0.97.0-beta.33" + "@polkadot/api" "^0.97.0-beta.34" + "@polkadot/types" "^0.97.0-beta.34" -"@polkadot/api@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.97.0-beta.33.tgz#0edc5cf5047fcfa018f3c1b75b73a5cacb515f65" - integrity sha512-FMOprhm7sNUiX1hepFZftoBm+8DgBg4IByqhKfNLOhAXPpYvU7owwGxQIyZ9EXCUsT8LxMxhCArh4TcE/pKQGQ== +"@polkadot/api@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.97.0-beta.34.tgz#846d34726450d6796ce44c1a56bfc1ff267657c0" + integrity sha512-B4vT8/bjV046LHOdeJFLrCDmEagfhyUF4/JvOk18mTM9wYg9fUEfkVcFVTMgrDj0QpXBsNSEv+W1goizbXtxog== dependencies: "@babel/runtime" "^7.7.4" - "@polkadot/api-derive" "^0.97.0-beta.33" + "@polkadot/api-derive" "^0.97.0-beta.34" "@polkadot/keyring" "^1.7.0-beta.8" - "@polkadot/metadata" "^0.97.0-beta.33" - "@polkadot/rpc-core" "^0.97.0-beta.33" - "@polkadot/rpc-provider" "^0.97.0-beta.33" - "@polkadot/types" "^0.97.0-beta.33" + "@polkadot/metadata" "^0.97.0-beta.34" + "@polkadot/rpc-core" "^0.97.0-beta.34" + "@polkadot/rpc-provider" "^0.97.0-beta.34" + "@polkadot/types" "^0.97.0-beta.34" "@polkadot/util-crypto" "^1.7.0-beta.8" "@polkadot/dev-react@^0.32.0-beta.15": @@ -2161,10 +2161,10 @@ dependencies: "@babel/runtime" "^7.7.4" -"@polkadot/jsonrpc@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.97.0-beta.33.tgz#0ad64eeb395767dc0a0c226e68d5bf92771011fe" - integrity sha512-1kd+1YOqqaHhIMbGCCtYrAkEUblHERlZPQ7+eJy5qZIxYQPuCknMzG6McnUAtEqjhiEpfnlfxDRTVyOWlIEvFg== +"@polkadot/jsonrpc@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.97.0-beta.34.tgz#b4e58b2c689d1dd115e62e95423850204f0814db" + integrity sha512-kF/R+XeGfY4iI+EE2HRjm6exj35r8l/Zf7q5Y7sUuh54cNKJ6ILVUtIT5Io2LjimvJ3Csf8d5GOUSraNOHnO4A== dependencies: "@babel/runtime" "^7.7.4" @@ -2177,13 +2177,13 @@ "@polkadot/util" "^1.7.0-beta.8" "@polkadot/util-crypto" "^1.7.0-beta.8" -"@polkadot/metadata@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/metadata/-/metadata-0.97.0-beta.33.tgz#53fd7f447aead53f226013c8cce6e4f3a6823b75" - integrity sha512-JRqbcsMEDso6vUa/mHS1Ri7kocbeKcyjHIBwW/QO1nr7aLLmTCv5RVq2xnFtbzGsvEg0sN0QVbbaNzp1MJ7INQ== +"@polkadot/metadata@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/metadata/-/metadata-0.97.0-beta.34.tgz#db74e4547fd88bd1ebebd487b43d245f37b5fd96" + integrity sha512-B9go5UcIMEANsKlcHYSXYDZsgFLLQ82izqdS+njqc6V2F4bCMQsUmGa8T7miCa4W6EkOb+HqtPLooXpa3+pA/w== dependencies: "@babel/runtime" "^7.7.4" - "@polkadot/types" "^0.97.0-beta.33" + "@polkadot/types" "^0.97.0-beta.34" "@polkadot/util" "^1.7.0-beta.8" "@polkadot/util-crypto" "^1.7.0-beta.8" @@ -2212,25 +2212,25 @@ qrcode-generator "^1.4.4" react-qr-reader "^2.2.1" -"@polkadot/rpc-core@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-0.97.0-beta.33.tgz#fa7bbf4cc65ca49ab8fc1f7eb1c360b935825b9f" - integrity sha512-HuU4yqKLUjiPFSxzx8TrMoX0OTbfch3sLfw9cFJ9Yqbei2shf7j8ndLjwAH5oUPEdN/qHqdsLjQ3m54lt5Qcfg== +"@polkadot/rpc-core@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-0.97.0-beta.34.tgz#acb73b711f7a6286e2b610908cc6df2d162e08bd" + integrity sha512-qm9R83DuTktT1jDsNfY/yD6AFIrbpOPV3mdF7ZltDxY89OD1fmkM//8xY3gUV4lwrKu9oManVYtKTBlHwHVDTQ== dependencies: "@babel/runtime" "^7.7.4" - "@polkadot/jsonrpc" "^0.97.0-beta.33" - "@polkadot/rpc-provider" "^0.97.0-beta.33" - "@polkadot/types" "^0.97.0-beta.33" + "@polkadot/jsonrpc" "^0.97.0-beta.34" + "@polkadot/rpc-provider" "^0.97.0-beta.34" + "@polkadot/types" "^0.97.0-beta.34" "@polkadot/util" "^1.7.0-beta.8" rxjs "^6.5.3" -"@polkadot/rpc-provider@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-0.97.0-beta.33.tgz#af4055e0734e9349e2d33aa3b94a293eea553166" - integrity sha512-dbDa0rhWXEbi+/on8u/fl94tZxyt79wnNevxF+T8sAKrD5avZmKbcXOizsBml1Ax8y/EJjl34xPJpBMDx1uB/g== +"@polkadot/rpc-provider@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-0.97.0-beta.34.tgz#28d612d0086cba553522d97b3a632630a3f5bf42" + integrity sha512-QEtqcGMf70DeFq6wKOB529NIV2+3fnJej97LfShHj5Us/LpQ/WXscCIuYxMnA2T3GaltFtzxYxYWLWg/anf7yA== dependencies: "@babel/runtime" "^7.7.4" - "@polkadot/metadata" "^0.97.0-beta.33" + "@polkadot/metadata" "^0.97.0-beta.34" "@polkadot/util" "^1.7.0-beta.8" "@polkadot/util-crypto" "^1.7.0-beta.8" eventemitter3 "^4.0.0" @@ -2244,10 +2244,10 @@ dependencies: "@types/chrome" "^0.0.91" -"@polkadot/types@^0.97.0-beta.33": - version "0.97.0-beta.33" - resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-0.97.0-beta.33.tgz#d83d46eabc7b6d5c496f21792bff42dc736eefc9" - integrity sha512-MWHxn9pdh7BCR7tZOPp90jnRdhh1T4GP6sctYYDpskTldneMB+URP38rUoCJuH0c/zJ5zl0z1RUiFksU8Qy7Gg== +"@polkadot/types@^0.97.0-beta.34": + version "0.97.0-beta.34" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-0.97.0-beta.34.tgz#6cae02b4170a1712eb3e7ad7be7e3326b675b947" + integrity sha512-ET/nFYfbXmYkNbVW1++3dEg+Ci56gfT2a+LZbiMgrjDF/08BEztBagD6F1A2UceY2+NfITtSeJjK6HhOrWF12Q== dependencies: "@babel/runtime" "^7.7.4" "@polkadot/util" "^1.7.0-beta.8"