diff --git a/package.json b/package.json index fd07e0115f..496589aa7e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ ], "resolutions": { "babel-core": "^7.0.0-bridge.0", + "rxjs": "^6.2.2", "typescript": "^2.9.2" }, "scripts": { diff --git a/packages/app-staking/src/Summary.tsx b/packages/app-staking/src/Summary.tsx index d2a83d9081..7207200164 100644 --- a/packages/app-staking/src/Summary.tsx +++ b/packages/app-staking/src/Summary.tsx @@ -5,6 +5,7 @@ import { Header } from '@polkadot/primitives/header'; import { I18nProps } from '@polkadot/ui-app/types'; import { ApiProps } from '@polkadot/ui-react-rx/types'; +import { Balance, BalanceMap } from './observables/types'; import BN from 'bn.js'; import React from 'react'; @@ -14,7 +15,9 @@ import classes from '@polkadot/ui-app/util/classes'; import withApiCall from '@polkadot/ui-react-rx/with/apiCall'; import withMulti from '@polkadot/ui-react-rx/with/multi'; import withStorage from '@polkadot/ui-react-rx/with/storage'; +import numberFormat from '@polkadot/ui-react-rx/util/numberFormat'; +import validatingBalances from './observables/validatingBalances'; import translate from './translate'; type Props = ApiProps & I18nProps & { @@ -25,16 +28,11 @@ type Props = ApiProps & I18nProps & { validators: Array }; -type StateBalances = { - [index: string]: BN -}; - type State = { - balances: StateBalances, - subscriptions: Array + balances: BalanceMap, + subBalances: any }; -const DEFAULT_BALANCE = new BN(0); const DEFAULT_BLOCKNUMBER = new BN(0); const DEFAULT_SESSION_CHANGE = new BN(0); const DEFAULT_SESSION_LENGTH = new BN(60); @@ -45,7 +43,7 @@ class Summary extends React.PureComponent { this.state = { balances: {}, - subscriptions: [] + subBalances: null }; } @@ -57,46 +55,31 @@ class Summary extends React.PureComponent { } } - private subscribeBalances (accounts: string[]) { + private unsubscribeBalances () { + const { subBalances } = this.state; + + if (subBalances) { + subBalances.unsubscribe(); + } + } + + private subscribeBalances (accounts: string[] = []) { const { api } = this.props; - const { balances, subscriptions } = this.state; - const newBalances = { ...balances }; - accounts.forEach((account) => { - if (newBalances[account]) { - return; - } else { - newBalances[account] = DEFAULT_BALANCE; - } + this.unsubscribeBalances(); - subscriptions.push( - api.state - // Here we pass a parameter to the key generator, so it points to the correct storage entry - .getStorage(storage.staking.public.freeBalanceOf, account) - .subscribe((balance: BN) => { - this.setState(({ balances }: State) => { - const newBalances = { ...balances }; - - newBalances[account] = balance; - - return { - balances: newBalances - } as State; - }); - }) - ); + // FIXME: We want these as a HOC on the class + const subBalances = validatingBalances(api, accounts).subscribe((balances: BalanceMap) => { + this.setState({ balances }); }); this.setState({ - balances: newBalances, - subscriptions + subBalances } as State); } componentWillUnmount () { - const { subscriptions } = this.state; - - subscriptions.forEach((sub) => sub.unsubscribe()); + this.unsubscribeBalances(); } render () { @@ -122,19 +105,23 @@ class Summary extends React.PureComponent {
{t('summary.balance.validator', { defaultValue: 'lowest validator balance is {{validatorLow}}', replace: { - validatorLow: validatorLow ? validatorLow.toString() : 'unknown' + validatorLow: validatorLow && validatorLow.stakingBalance + ? `${numberFormat(validatorLow.stakingBalance)} (+${numberFormat(validatorLow.nominatedBalance)})` + : 'unknown' } })}
{t('summary.balance.stake', { defaultValue: ' highest balance intending to stake is {{intentionHigh}}', replace: { - intentionHigh: intentionHigh ? intentionHigh.toString() : 'unknown' + intentionHigh: intentionHigh + ? `${numberFormat(intentionHigh.stakingBalance)} (+${numberFormat(intentionHigh.nominatedBalance)})` + : 'unknown' } })}
{t('summary.countdown', { defaultValue: 'session block {{remainder}} / {{length}} at #{{blockNumber}}', replace: { - blockNumber: blockNumber.toString(), + blockNumber: numberFormat(blockNumber), remainder: Math.max(1, blockNumber.sub(lastLengthChange).mod(sessionLength).addn(1).toNumber()).toString(), length: sessionLength.toString() } @@ -143,16 +130,16 @@ class Summary extends React.PureComponent { ); } - private calcIntentionsHigh (): BN | null { + private calcIntentionsHigh (): Balance | null { const { intentions, validators } = this.props; const { balances } = this.state; - return intentions.reduce((high: BN | null, addr) => { - const balance = validators.includes(addr) + return intentions.reduce((high: Balance | null, addr) => { + const balance = validators.includes(addr) || !balances[addr] ? null - : balances[addr] || null; + : balances[addr]; - if (high === null || (balance && high.lt(balance))) { + if (high === null || (balance && high.stakingBalance.lt(balance.stakingBalance))) { return balance; } @@ -160,14 +147,14 @@ class Summary extends React.PureComponent { }, null); } - private calcValidatorLow (): BN | null { + private calcValidatorLow (): Balance | null { const { validators } = this.props; const { balances } = this.state; - return validators.reduce((low: BN | null, addr) => { + return validators.reduce((low: Balance | null, addr) => { const balance = balances[addr] || null; - if (low === null || (balance && low.gt(balance))) { + if (low === null || (balance && low.stakingBalance.gt(balance.stakingBalance))) { return balance; } diff --git a/packages/app-staking/src/observables/types.d.ts b/packages/app-staking/src/observables/types.d.ts new file mode 100644 index 0000000000..f2e39aa7c7 --- /dev/null +++ b/packages/app-staking/src/observables/types.d.ts @@ -0,0 +1,19 @@ +// Copyright 2017-2018 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import BN from 'bn.js'; + +export type Balance = { + address: string, + freeBalance: BN, + nominatedBalance: BN, + reservedBalance: BN, + votingBalance: BN, + stakingBalance: BN, + nominators?: Array +} + +export type BalanceMap = { + [index: string]: Balance +} diff --git a/packages/app-staking/src/observables/validatingBalance.ts b/packages/app-staking/src/observables/validatingBalance.ts new file mode 100644 index 0000000000..0ac8d61f14 --- /dev/null +++ b/packages/app-staking/src/observables/validatingBalance.ts @@ -0,0 +1,45 @@ +// Copyright 2017-2018 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import { RxApiInterface } from '@polkadot/api-rx/types'; +import { Balance } from './types'; + +import BN from 'bn.js'; +import { Observable, combineLatest } from 'rxjs'; +import { map, switchMap, toArray } from 'rxjs/operators'; +import storage from '@polkadot/storage'; +import encodeAddress from '@polkadot/util-keyring/address/encode'; + +import votingBalance from './votingBalance'; +import votingBalances from './/votingBalances'; + +type Result = [Balance, [Array]]; + +export default function validatingBalance (api: RxApiInterface, address: string): Observable { + return combineLatest( + votingBalance(api, address), + api.state + .getStorage(storage.staking.public.nominatorsFor, address) + .pipe( + switchMap((nominators = []) => + // FIXME: Will not be needed after https://github.com/polkadot-js/apps/issues/128 + votingBalances(api, nominators.map(encodeAddress)) + ), + toArray() + ) + ).pipe( + map(([balance, [nominators = []]]: Result): Balance => { + const nominatedBalance = nominators.reduce((total, nominator: Balance) => { + return total.add(nominator.votingBalance); + }, new BN(0)); + + return { + ...balance, + nominators, + nominatedBalance, + stakingBalance: nominatedBalance.add(balance.votingBalance) + }; + }) + ); +} diff --git a/packages/app-staking/src/observables/validatingBalances.ts b/packages/app-staking/src/observables/validatingBalances.ts new file mode 100644 index 0000000000..850f923095 --- /dev/null +++ b/packages/app-staking/src/observables/validatingBalances.ts @@ -0,0 +1,27 @@ +// Copyright 2017-2018 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import { RxApiInterface } from '@polkadot/api-rx/types'; +import { Balance, BalanceMap } from './types'; + +import { Observable, combineLatest } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import validatingBalance from './validatingBalance'; + +export default function validatingBalances (api: RxApiInterface, addresses: Array): Observable { + return combineLatest( + addresses.map((address) => + validatingBalance(api, address) + ) + ).pipe( + map((result: Array): BalanceMap => { + return result.reduce((balances, balance) => { + balances[balance.address] = balance; + + return balances; + }, {} as BalanceMap); + }) + ); +} diff --git a/packages/app-staking/src/observables/votingBalance.ts b/packages/app-staking/src/observables/votingBalance.ts new file mode 100644 index 0000000000..8a4a1bd40d --- /dev/null +++ b/packages/app-staking/src/observables/votingBalance.ts @@ -0,0 +1,27 @@ +// Copyright 2017-2018 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import { RxApiInterface } from '@polkadot/api-rx/types'; +import { Balance } from './types'; + +import BN from 'bn.js'; +import { Observable, combineLatest } from 'rxjs'; +import { map } from 'rxjs/operators'; +import storage from '@polkadot/storage'; + +export default function votingBalance (api: RxApiInterface, address: string): Observable { + return combineLatest( + api.state.getStorage(storage.staking.public.freeBalanceOf, address), + api.state.getStorage(storage.staking.public.reservedBalanceOf, address) + ).pipe( + map(([freeBalance = new BN(0), reservedBalance = new BN(0)]): Balance => ({ + address, + freeBalance, + nominatedBalance: new BN(0), + reservedBalance, + stakingBalance: new BN(0), + votingBalance: freeBalance.add(reservedBalance) + })) + ); +} diff --git a/packages/app-staking/src/observables/votingBalances.ts b/packages/app-staking/src/observables/votingBalances.ts new file mode 100644 index 0000000000..0ea248d9e0 --- /dev/null +++ b/packages/app-staking/src/observables/votingBalances.ts @@ -0,0 +1,18 @@ +// Copyright 2017-2018 @polkadot/app-staking authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import { RxApiInterface } from '@polkadot/api-rx/types'; +import { Balance } from './types'; + +import { Observable, combineLatest } from 'rxjs'; + +import votingBalance from './votingBalance'; + +export default function votingBalances (api: RxApiInterface, addresses: Array): Observable { + return combineLatest( + addresses.map((address) => + votingBalance(api, address) + ) + ); +} diff --git a/packages/ui-react-rx/package.json b/packages/ui-react-rx/package.json index 14c5163fe7..8cd800b572 100644 --- a/packages/ui-react-rx/package.json +++ b/packages/ui-react-rx/package.json @@ -36,9 +36,10 @@ }, "dependencies": { "@babel/runtime": "^7.0.0-beta.51", - "@polkadot/api-rx": "^0.14.29", + "@polkadot/api-rx": "^0.15.6", "@polkadot/extrinsics": "^0.27.9", - "@polkadot/storage": "^0.27.9" + "@polkadot/storage": "^0.27.9", + "rxjs-compat": "^6.2.2" }, "devDependencies": { "babel-loader": "^8.0.0-beta.2", diff --git a/packages/ui-react-rx/src/Api/index.tsx b/packages/ui-react-rx/src/Api/index.tsx index 89ac439d10..afd6c72745 100644 --- a/packages/ui-react-rx/src/Api/index.tsx +++ b/packages/ui-react-rx/src/Api/index.tsx @@ -10,7 +10,7 @@ import { ApiProps } from '../types'; import React from 'react'; import shouldUseLatestChain from '@polkadot/ui-react-rx/util/shouldUseLatestChain'; -import createWsProvider from '@polkadot/api-provider/ws'; +import WsProvider from '@polkadot/api-provider/ws'; import createApi from '@polkadot/api-rx'; import defaults from '@polkadot/api-rx/defaults'; @@ -40,7 +40,7 @@ export default class Api extends React.PureComponent { const { provider, url = '' } = props; const api = props.api || createApi( url && url.length - ? createWsProvider(url) + ? new WsProvider(url) : provider ); const setApi = (api: RxApiInterface): void => { @@ -51,7 +51,7 @@ export default class Api extends React.PureComponent { const setApiProvider = (provider?: ProviderInterface): void => setApi(createApi(provider)); const setApiWsUrl = (url: string = defaults.WS_URL): void => - setApiProvider(createWsProvider(url)); + setApiProvider(new WsProvider(url)); this.state = { api, diff --git a/yarn.lock b/yarn.lock index c31ad61aff..c4f7e69d64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -765,9 +765,9 @@ version "1.1.0" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a" -"@polkadot/api-format@^0.14.29": - version "0.14.29" - resolved "https://registry.yarnpkg.com/@polkadot/api-format/-/api-format-0.14.29.tgz#02b1cc84d19219909d4dc82ab9ecd57f07aa888c" +"@polkadot/api-format@^0.15.6": + version "0.15.6" + resolved "https://registry.yarnpkg.com/@polkadot/api-format/-/api-format-0.15.6.tgz#9d50258df3fcd9e11ad591d43a3f25db3647ff7a" dependencies: "@babel/runtime" "^7.0.0-beta.51" "@polkadot/jsonrpc" "^0.27.9" @@ -775,9 +775,9 @@ "@polkadot/util" "^0.27.9" "@polkadot/util-keyring" "^0.27.9" -"@polkadot/api-provider@^0.14.29": - version "0.14.29" - resolved "https://registry.yarnpkg.com/@polkadot/api-provider/-/api-provider-0.14.29.tgz#d1bb7e8e879e62f868e0149e25a55995e600536e" +"@polkadot/api-provider@^0.15.6": + version "0.15.6" + resolved "https://registry.yarnpkg.com/@polkadot/api-provider/-/api-provider-0.15.6.tgz#e3c664687d9d82d59d16e9b5a8e3d10fd285a7a0" dependencies: "@babel/runtime" "^7.0.0-beta.51" "@polkadot/storage" "^0.27.9" @@ -789,23 +789,23 @@ isomorphic-fetch "^2.2.1" websocket "^1.0.25" -"@polkadot/api-rx@^0.14.29": - version "0.14.29" - resolved "https://registry.yarnpkg.com/@polkadot/api-rx/-/api-rx-0.14.29.tgz#e71c3b2dec8dd0e7cbc391024bd17b9616c8b453" +"@polkadot/api-rx@^0.15.6": + version "0.15.6" + resolved "https://registry.yarnpkg.com/@polkadot/api-rx/-/api-rx-0.15.6.tgz#849adc73d129d87eaf3c9463f9eb7201b8d6bfa7" dependencies: "@babel/runtime" "^7.0.0-beta.51" - "@polkadot/api" "^0.14.29" - "@polkadot/api-provider" "^0.14.29" + "@polkadot/api" "^0.15.6" + "@polkadot/api-provider" "^0.15.6" "@types/rx" "^4.1.1" - rxjs "^5.5.10" + rxjs "^6.2.2" -"@polkadot/api@^0.14.29": - version "0.14.29" - resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.14.29.tgz#6a3f08ee1bd98e5ee96d85361f195496ebab65b7" +"@polkadot/api@^0.15.6": + version "0.15.6" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.15.6.tgz#6bf02708c5ef77d71ca0b00bdbbf6154abe80f6f" dependencies: "@babel/runtime" "^7.0.0-beta.51" - "@polkadot/api-format" "^0.14.29" - "@polkadot/api-provider" "^0.14.29" + "@polkadot/api-format" "^0.15.6" + "@polkadot/api-provider" "^0.15.6" "@polkadot/jsonrpc" "^0.27.9" "@polkadot/params" "^0.27.9" "@polkadot/util" "^0.27.9" @@ -9865,15 +9865,13 @@ rx-lite@*, rx-lite@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" -rxjs@^5.5.10, rxjs@^5.5.2: - version "5.5.11" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.11.tgz#f733027ca43e3bec6b994473be4ab98ad43ced87" - dependencies: - symbol-observable "1.0.1" +rxjs-compat@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/rxjs-compat/-/rxjs-compat-6.2.2.tgz#3c0fcdb46130cc70aa55412c2b1147905ab4680a" -rxjs@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1" +rxjs@^5.5.2, rxjs@^6.1.0, rxjs@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9" dependencies: tslib "^1.9.0" @@ -10521,10 +10519,6 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" -symbol-observable@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" - symbol-observable@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"