Explorar o código

Merge branch '0.37.0-beta.63-with-api-0.96.1' into joystream-update-membership-roles

Mokhtar Naamani %!s(int64=5) %!d(string=hai) anos
pai
achega
ce188ec00b
Modificáronse 100 ficheiros con 1795 adicións e 1164 borrados
  1. 1 1
      .123trigger
  2. 2 1
      .github/workflows/push-master.yml
  3. 15 7
      CHANGELOG.md
  4. 1 1
      lerna.json
  5. 15 15
      package.json
  6. 3 3
      packages/app-123code/package.json
  7. 12 27
      packages/app-123code/src/SummaryBar.tsx
  8. 5 5
      packages/app-accounts/package.json
  9. 45 3
      packages/app-accounts/src/Account.tsx
  10. 33 20
      packages/app-accounts/src/modals/Create.tsx
  11. 1 0
      packages/app-accounts/src/modals/CreateConfirmation.tsx
  12. 235 0
      packages/app-accounts/src/modals/Derive.tsx
  13. 1 0
      packages/app-accounts/src/modals/Import.tsx
  14. 4 4
      packages/app-accounts/src/modals/Qr.tsx
  15. 3 3
      packages/app-accounts/src/modals/Transfer.tsx
  16. 3 3
      packages/app-address-book/package.json
  17. 4 2
      packages/app-address-book/src/Address.tsx
  18. 4 2
      packages/app-address-book/src/modals/Create.tsx
  19. 3 3
      packages/app-claims/package.json
  20. 1 1
      packages/app-claims/src/index.tsx
  21. 4 4
      packages/app-contracts/package.json
  22. 1 1
      packages/app-contracts/src/ABI.tsx
  23. 1 1
      packages/app-contracts/src/Codes/Add.tsx
  24. 1 1
      packages/app-contracts/src/Codes/Upload.tsx
  25. 1 1
      packages/app-contracts/src/Contracts/Add.tsx
  26. 146 148
      packages/app-contracts/src/Contracts/Call.tsx
  27. 10 12
      packages/app-contracts/src/Contracts/Contract.tsx
  28. 104 0
      packages/app-contracts/src/Contracts/Outcome.tsx
  29. 55 31
      packages/app-contracts/src/Contracts/index.tsx
  30. 16 21
      packages/app-contracts/src/Contracts/util.tsx
  31. 9 7
      packages/app-contracts/src/Deploy.tsx
  32. 1 1
      packages/app-contracts/src/index.tsx
  33. 4 4
      packages/app-council/package.json
  34. 5 5
      packages/app-council/src/Motions/Propose.tsx
  35. 11 3
      packages/app-council/src/Overview/Candidate.tsx
  36. 11 7
      packages/app-council/src/Overview/Member.tsx
  37. 51 14
      packages/app-council/src/Overview/Members.tsx
  38. 9 11
      packages/app-council/src/Overview/SubmitCandidacy.tsx
  39. 25 15
      packages/app-council/src/Overview/Summary.tsx
  40. 161 179
      packages/app-council/src/Overview/Vote.tsx
  41. 38 0
      packages/app-council/src/Overview/VoteValue.tsx
  42. 38 0
      packages/app-council/src/Overview/Voters.tsx
  43. 20 14
      packages/app-council/src/Overview/index.tsx
  44. 4 4
      packages/app-dashboard/package.json
  45. 4 4
      packages/app-democracy/package.json
  46. 3 3
      packages/app-explorer/package.json
  47. 37 39
      packages/app-explorer/src/Events.tsx
  48. 1 1
      packages/app-explorer/src/Summary.tsx
  49. 15 16
      packages/app-explorer/src/SummarySession.tsx
  50. 10 54
      packages/app-explorer/src/index.tsx
  51. 5 5
      packages/app-extrinsics/package.json
  52. 3 3
      packages/app-generic-asset/package.json
  53. 1 1
      packages/app-generic-asset/src/AssetRow.tsx
  54. 3 3
      packages/app-generic-asset/src/Transfer.tsx
  55. 3 3
      packages/app-js/package.json
  56. 2 1
      packages/app-js/src/Playground.tsx
  57. 4 4
      packages/app-parachains/package.json
  58. 4 4
      packages/app-settings/package.json
  59. 5 5
      packages/app-staking/package.json
  60. 26 14
      packages/app-staking/src/Actions/Account/BondExtra.tsx
  61. 61 0
      packages/app-staking/src/Actions/Account/InputValidateAmount.tsx
  62. 14 16
      packages/app-staking/src/Actions/Account/InputValidationController.tsx
  63. 1 1
      packages/app-staking/src/Actions/Account/SetControllerAccount.tsx
  64. 1 1
      packages/app-staking/src/Actions/Account/SetRewardDestination.tsx
  65. 32 56
      packages/app-staking/src/Actions/Account/index.tsx
  66. 2 2
      packages/app-staking/src/Actions/Accounts.tsx
  67. 17 4
      packages/app-staking/src/Actions/NewStake.tsx
  68. 120 88
      packages/app-staking/src/Overview/Address.tsx
  69. 63 23
      packages/app-staking/src/Overview/CurrentList.tsx
  70. 31 22
      packages/app-staking/src/Overview/Summary.tsx
  71. 24 46
      packages/app-staking/src/Overview/index.tsx
  72. 10 46
      packages/app-staking/src/index.tsx
  73. 12 8
      packages/app-staking/src/types.ts
  74. 0 31
      packages/app-staking/src/util.ts
  75. 4 4
      packages/app-storage/package.json
  76. 4 2
      packages/app-storage/src/Query.tsx
  77. 2 5
      packages/app-storage/src/Selection/Modules.tsx
  78. 3 3
      packages/app-sudo/package.json
  79. 3 3
      packages/app-toolbox/package.json
  80. 4 4
      packages/app-transfer/package.json
  81. 4 4
      packages/app-treasury/package.json
  82. 2 2
      packages/apps-routing/package.json
  83. 4 1
      packages/apps-routing/src/council.ts
  84. 6 6
      packages/apps/package.json
  85. 11 0
      packages/apps/public/locales/en/app-accounts.json
  86. 1 0
      packages/apps/public/locales/en/app-address-book.json
  87. 4 1
      packages/apps/public/locales/en/app-contracts.json
  88. 10 1
      packages/apps/public/locales/en/app-council.json
  89. 2 1
      packages/apps/public/locales/en/app-generic-asset.json
  90. 8 1
      packages/apps/public/locales/en/app-staking.json
  91. 6 1
      packages/apps/public/locales/en/react-components.json
  92. 3 1
      packages/apps/public/locales/en/react-signer.json
  93. 35 0
      packages/apps/public/locales/en/ui.json
  94. 20 0
      packages/apps/src/Apps.tsx
  95. 5 18
      packages/apps/src/Content/index.tsx
  96. 11 5
      packages/apps/src/index.tsx
  97. 3 3
      packages/joy-election/package.json
  98. 3 3
      packages/joy-forum/package.json
  99. 3 3
      packages/joy-help/package.json
  100. 3 3
      packages/joy-media/package.json

+ 1 - 1
.123trigger

@@ -1 +1 @@
-2
+5

+ 2 - 1
.github/workflows/push-master.yml

@@ -14,6 +14,8 @@ jobs:
         node-version: [12.x]
     steps:
     - uses: actions/checkout@v1
+      with:
+        token: ${{ secrets.GH_PAT }}
     - name: Use Node.js ${{ matrix.node-version }}
       uses: actions/setup-node@v1
       with:
@@ -23,7 +25,6 @@ jobs:
         CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
         GH_PAGES_SRC: packages/apps/build
         GH_PAT: ${{ secrets.GH_PAT }}
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
       run: |
         yarn install --frozen-lockfile

+ 15 - 7
CHANGELOG.md

@@ -1,21 +1,29 @@
-# 0.36.0-beta.x
+# 0.36.1
 
+- Api 0.95.1, Util 1.6.1, Extension 0.13.1
+- Support latest contracts ABI (via API), incl. rework of contracts UI
 - Support for Kusama CC2
 - Support for Edgeware mainnet
 - Experimental Ledger support
 - Display forks on explorer (limited to Babe)
-- Change settings to have Save as well as Save & Reload (depending on hanges made)
+- Change settings to have Save as well as Save & Reload (depending on changes made)
 - Updates to struct & enum rendering (as per extrinsic app)
-- Bakc, Password change & Delete don't show for built-in dev accounts
+- Backup, Password change & Delete don't show for built-in dev accounts
+- Add commissions to the staking overview
 - UI theme update
+- A large number of components refactored for React functional components
+- Allow dismiss of all notifications (via bounty)
 - Migrate all buttons to have icons (via bounty)
+- Proposal submission via modal (via bounty)
+- i18n string extraction (via bounty)
+- adjust signature validity (via bounty)
 - Make the network selection clickable on network name (via bounty)
-- A large number of components refactored for React functional components
+- ... and a number of cleanups all around
 
 # 0.35.1
 
 - Api 0.91.1, Util 1.2.1, Extension 0.10.1
-- Support for accouns added via Qr (for instance, the Parity Signer)
+- Support for accounts added via Qr (for instance, the Parity Signer)
 - Support for accounts tied to specific chains (instead of just available to all)
 - GenericAsset app transfers
 - Support for Edgeware with default types
@@ -39,12 +47,12 @@
 - Allow for externally injected accounts (i.e. via extension, polkadot-js & SingleSource)
 - Links to extrnisics & addresses on Polkascan
 - Rework Account & Address layouts with cards
-- Transfer can happen from any popint (via Transfer modal)
+- Transfer can happen from any point (via Transfer modal)
 - Use new api.derive functions
 - Introduce multi support (most via api.derive.*)
 - Update all account and address modals
 - Add seconding of proposals
-- Staking updates, including unbonding & withdrawals
+- Staking updates, including un-bonding & withdrawals
 - Update explorer with global query on hash/blocks
 - Add filters on the staking page
 - Vanitygen now supports sr25519 as well

+ 1 - 1
lerna.json

@@ -10,5 +10,5 @@
   "packages": [
     "packages/*"
   ],
-  "version": "0.36.0-beta.101"
+  "version": "0.37.0-beta.63"
 }

+ 15 - 15
package.json

@@ -1,5 +1,5 @@
 {
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "private": true,
   "engines": {
     "node": ">=10.13.0",
@@ -10,14 +10,14 @@
     "packages/*"
   ],
   "resolutions": {
-    "@polkadot/api": "^0.95.0-beta.36",
-    "@polkadot/api-contract": "^0.95.0-beta.36",
-    "@polkadot/keyring": "^1.6.1",
-    "@polkadot/types": "^0.95.0-beta.36",
-    "@polkadot/util": "^1.6.1",
-    "@polkadot/util-crypto": "^1.6.1",
+    "@polkadot/api": "^0.96.1",
+    "@polkadot/api-contract": "^0.96.1",
+    "@polkadot/keyring": "^1.7.0-beta.5",
+    "@polkadot/types": "^0.96.1",
+    "@polkadot/util": "^1.7.0-beta.5",
+    "@polkadot/util-crypto": "^1.7.0-beta.5",
     "babel-core": "^7.0.0-bridge.0",
-    "typescript": "^3.6.4"
+    "typescript": "^3.7.2"
   },
   "scripts": {
     "analyze": "yarn run build && cd packages/apps && yarn run source-map-explorer build/main.*.js",
@@ -35,23 +35,23 @@
     "storybook": "start-storybook -p 3001"
   },
   "devDependencies": {
-    "@babel/core": "^7.6.4",
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/dev-react": "^0.32.0-beta.11",
-    "@polkadot/ts": "^0.1.82",
-    "autoprefixer": "^9.6.5",
+    "@babel/core": "^7.7.0",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/dev-react": "^0.32.0-beta.13",
+    "@polkadot/ts": "^0.1.84",
     "@storybook/addon-knobs": "^5.2.5",
     "@storybook/addon-storysource": "^5.2.5",
+    "autoprefixer": "^9.7.1",
     "empty": "^0.10.1",
     "html-loader": "^0.5.5",
     "i18next-scanner": "^2.10.3",
     "markdown-loader": "^5.1.0",
-    "postcss": "^7.0.18",
+    "postcss": "^7.0.21",
     "postcss-clean": "^1.1.0",
     "postcss-flexbugs-fixes": "^4.1.0",
     "postcss-import": "^12.0.0",
     "postcss-loader": "^3.0.0",
-    "postcss-nested": "^4.1.2",
+    "postcss-nested": "^4.2.1",
     "postcss-sass": "^0.4.1",
     "postcss-simple-vars": "^5.0.0",
     "precss": "^4.0.0",

+ 3 - 3
packages/app-123code/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-123code",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A basic app that shows the ropes on customisation",
   "main": "index.js",
   "scripts": {},
@@ -10,7 +10,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 12 - 27
packages/app-123code/src/SummaryBar.tsx

@@ -7,7 +7,7 @@ import { AccountId } from '@polkadot/types/interfaces';
 import { BareProps, I18nProps } from '@polkadot/react-components/types';
 
 import BN from 'bn.js';
-import React, { useContext, useState, useEffect } from 'react';
+import React, { useContext } from 'react';
 import { ApiContext, withCalls } from '@polkadot/react-api';
 import { Bubble, IdentityIcon } from '@polkadot/react-components';
 import { formatBalance, formatNumber } from '@polkadot/util';
@@ -18,23 +18,11 @@ interface Props extends BareProps, I18nProps {
   balances_totalIssuance?: BN;
   chain_bestNumber?: BN;
   chain_bestNumberLag?: BN;
-  session_validators?: AccountId[];
-  staking_intentions?: AccountId[];
+  staking_validators?: AccountId[];
 }
 
-function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumberLag, staking_intentions, session_validators }: Props): React.ReactElement<Props> {
+function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumberLag, staking_validators }: Props): React.ReactElement<Props> {
   const { api, systemChain, systemName, systemVersion } = useContext(ApiContext);
-  const [nextUp, setNextUp] = useState<AccountId[]>([]);
-
-  useEffect((): void => {
-    if (staking_intentions && session_validators) {
-      setNextUp(staking_intentions.filter((accountId): boolean =>
-        !session_validators.find((validatorId): boolean =>
-          validatorId.eq(accountId)
-        )
-      ));
-    }
-  }, [staking_intentions, session_validators]);
 
   return (
     <summary>
@@ -51,16 +39,13 @@ function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumbe
         <Bubble icon='bullseye' label='best #'>
           {formatNumber(chain_bestNumber)} ({formatNumber(chain_bestNumberLag)} lag)
         </Bubble>
-        <Bubble icon='chess queen' label='validators'>{
-          (session_validators || []).map((accountId, index): React.ReactNode => (
-            <IdentityIcon key={index} value={accountId} size={20} />
-          ))
-        }</Bubble>
-        <Bubble icon='chess bishop' label='next up'>{
-          nextUp.map((accountId, index): React.ReactNode => (
-            <IdentityIcon key={index} value={accountId} size={20} />
-          ))
-        }</Bubble>
+        {staking_validators && (
+          <Bubble icon='chess queen' label='validators'>{
+            staking_validators.map((accountId, index): React.ReactNode => (
+              <IdentityIcon key={index} value={accountId} size={20} />
+            ))
+          }</Bubble>
+        )}
         <Bubble icon='circle' label='total tokens'>
           {formatBalance(balances_totalIssuance)}
         </Bubble>
@@ -74,7 +59,7 @@ export default translate(
   withCalls<Props>(
     'derive.chain.bestNumber',
     'derive.chain.bestNumberLag',
-    'query.balances.totalIssuance',
-    'query.session.validators'
+    'derive.staking.validators',
+    'query.balances.totalIssuance'
   )(SummaryBar)
 );

+ 5 - 5
packages/app-accounts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-accounts",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,13 +10,13 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-qr": "^0.46.0-beta.16",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-qr": "^0.47.0-beta.3",
     "@types/file-saver": "^2.0.0",
     "@types/yargs": "^13.0.2",
     "babel-plugin-module-resolver": "^3.1.1",
-    "detect-browser": "^4.7.0",
+    "detect-browser": "^4.8.0",
     "file-saver": "^2.0.0",
     "yargs": "^14.2.0"
   }

+ 45 - 3
packages/app-accounts/src/Account.tsx

@@ -6,12 +6,14 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
 import { I18nProps } from '@polkadot/react-components/types';
 
 import React, { useState, useEffect } from 'react';
+import { Popup } from 'semantic-ui-react';
 import styled from 'styled-components';
-import { AddressCard, AddressInfo, Button, ChainLock, Forget } from '@polkadot/react-components';
+import { AddressCard, AddressInfo, Button, ChainLock, Forget, Menu } from '@polkadot/react-components';
 import keyring from '@polkadot/ui-keyring';
 
 import Backup from './modals/Backup';
 import ChangePass from './modals/ChangePass';
+import Derive from './modals/Derive';
 import Transfer from './modals/Transfer';
 import translate from './translate';
 
@@ -24,8 +26,10 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
   const [genesisHash, setGenesisHash] = useState<string | null>(null);
   const [isBackupOpen, setIsBackupOpen] = useState(false);
   const [{ isDevelopment, isEditable, isExternal }, setFlags] = useState({ isDevelopment: false, isEditable: false, isExternal: false });
+  const [isDeriveOpen, setIsDeriveOpen] = useState(false);
   const [isForgetOpen, setIsForgetOpen] = useState(false);
   const [isPasswordOpen, setIsPasswordOpen] = useState(false);
+  const [isSettingPopupOpen, setIsSettingPopupOpen] = useState(false);
   const [isTransferOpen, setIsTransferOpen] = useState(false);
 
   useEffect((): void => {
@@ -40,9 +44,11 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
   }, [address]);
 
   const _toggleBackup = (): void => setIsBackupOpen(!isBackupOpen);
+  const _toggleDerive = (): void => setIsDeriveOpen(!isDeriveOpen);
   const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
   const _togglePass = (): void => setIsPasswordOpen(!isPasswordOpen);
   const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
+  const _toggleSettingPopup = (): void => setIsSettingPopupOpen(!isSettingPopupOpen);
   const _onForget = (): void => {
     if (!address) {
       return;
@@ -66,6 +72,8 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
     const account = keyring.getPair(address);
 
     account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash });
+
+    setGenesisHash(genesisHash);
   };
 
   // FIXME It is a bit heavy-handled switching of being editable here completely
@@ -110,6 +118,33 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
               size='small'
               tooltip={t('Send funds from this account')}
             />
+            {isEditable && !isExternal && (
+              <Popup
+                onClose={_toggleSettingPopup}
+                open={isSettingPopupOpen}
+                position='bottom left'
+                trigger={
+                  <Button
+                    icon='setting'
+                    onClick={_toggleSettingPopup}
+                    size='small'
+                  />
+                }
+              >
+                <Menu
+                  vertical
+                  text
+                  onClick={_toggleSettingPopup}
+                >
+                  <Menu.Item onClick={_toggleDerive}>
+                    {t('Derive account from source')}
+                  </Menu.Item>
+                  <Menu.Item disabled>
+                    {t('Change on-chain nickname')}
+                  </Menu.Item>
+                </Menu>
+              </Popup>
+            )}
           </div>
           {isEditable && !isExternal && (
             <div className='others'>
@@ -126,16 +161,23 @@ function Account ({ address, className, t }: Props): React.ReactElement<Props> {
       type='account'
       value={address}
       withExplorer
-      withIndex
+      withIndexOrAddress={false}
       withTags
     >
       {address && (
         <>
           {isBackupOpen && (
             <Backup
+              address={address}
               key='modal-backup-account'
               onClose={_toggleBackup}
-              address={address}
+            />
+          )}
+          {isDeriveOpen && (
+            <Derive
+              from={address}
+              key='modal-derive-account'
+              onClose={_toggleDerive}
             />
           )}
           {isForgetOpen && (

+ 33 - 20
packages/app-accounts/src/modals/Create.tsx

@@ -4,6 +4,7 @@
 
 import { I18nProps } from '@polkadot/react-components/types';
 import { ActionStatus } from '@polkadot/react-components/Status/types';
+import { CreateResult } from '@polkadot/ui-keyring/types';
 import { KeypairType } from '@polkadot/util-crypto/types';
 import { ModalProps } from '../types';
 
@@ -121,11 +122,39 @@ function updateAddress (seed: string, derivePath: string, seedType: SeedType, pa
   };
 }
 
+export function downloadAccount ({ json, pair }: CreateResult): void {
+  const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
+
+  FileSaver.saveAs(blob, `${pair.address}.json`);
+  InputAddress.setLastValue('account', pair.address);
+}
+
+function createAccount (suri: string, pairType: KeypairType, name: string, password: string, success: string): ActionStatus {
+  // we will fill in all the details below
+  const status = { action: 'create' } as ActionStatus;
+
+  try {
+    const result = keyring.addUri(suri, password, { name: name.trim(), tags: [] }, pairType);
+    const { address } = result.pair;
+
+    status.account = address;
+    status.status = 'success';
+    status.message = success;
+
+    downloadAccount(result);
+  } catch (error) {
+    status.status = 'error';
+    status.message = error.message;
+  }
+
+  return status;
+}
+
 function Create ({ className, onClose, onStatusChange, seed: propsSeed, t, type: propsType }: Props): React.ReactElement<Props> {
   const { isDevelopment } = useContext(ApiContext);
   const [{ address, deriveError, derivePath, isSeedValid, pairType, seed, seedType }, setAddress] = useState<AddressState>(generateSeed(propsSeed, '', propsSeed ? 'raw' : 'bip', propsType));
   const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
-  const [{ isNameValid, name }, setName] = useState({ isNameValid: true, name: 'new account' });
+  const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
   const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' });
   const isValid = !!address && !deriveError && isNameValid && isPassValid && isSeedValid;
 
@@ -150,25 +179,7 @@ function Create ({ className, onClose, onStatusChange, seed: propsSeed, t, type:
       return;
     }
 
-    // we will fill in all the details below
-    const status = { action: 'create' } as ActionStatus;
-
-    try {
-      const { json, pair } = keyring.addUri(`${seed}${derivePath}`, password, { name: name.trim(), tags: [] }, pairType);
-      const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
-      const { address } = pair;
-
-      FileSaver.saveAs(blob, `${address}.json`);
-
-      status.account = address;
-      status.status = pair ? 'success' : 'error';
-      status.message = t('created account');
-
-      InputAddress.setLastValue('account', address);
-    } catch (error) {
-      status.status = 'error';
-      status.message = error.message;
-    }
+    const status = createAccount(`${seed}${derivePath}`, pairType, name, password, t('created account'));
 
     _toggleConfirmation();
     onStatusChange(status);
@@ -193,6 +204,7 @@ function Create ({ className, onClose, onStatusChange, seed: propsSeed, t, type:
       <Modal.Content>
         <AddressRow
           defaultName={name}
+          noDefaultNameOpacity
           value={isSeedValid ? address : ''}
         >
           <Input
@@ -203,6 +215,7 @@ function Create ({ className, onClose, onStatusChange, seed: propsSeed, t, type:
             label={t('name')}
             onChange={_onChangeName}
             onEnter={_onCommit}
+            placeholder={t('new account')}
             value={name}
           />
           <Input

+ 1 - 0
packages/app-accounts/src/modals/CreateConfirmation.tsx

@@ -29,6 +29,7 @@ function CreateConfirmation ({ address, name, onClose, onCommit, t }: Props): Re
         <AddressRow
           defaultName={name}
           isInline
+          noDefaultNameOpacity
           value={address}
         >
           <p>{t('We will provide you with a generated backup file after your account is created. As long as you have access to your account you can always download this file later by clicking on "Backup" button from the Accounts section.')}</p>

+ 235 - 0
packages/app-accounts/src/modals/Derive.tsx

@@ -0,0 +1,235 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { KeyringPair } from '@polkadot/keyring/types';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+import { I18nProps } from '@polkadot/react-components/types';
+import { KeypairType } from '@polkadot/util-crypto/types';
+
+import React, { useContext, useEffect, useState } from 'react';
+import { AddressRow, Button, Input, InputAddress, Modal, Password, StatusContext } from '@polkadot/react-components';
+import { useDebounce } from '@polkadot/react-components/hooks';
+import keyring from '@polkadot/ui-keyring';
+import { keyExtractPath } from '@polkadot/util-crypto';
+
+import translate from '../translate';
+import { downloadAccount } from './Create';
+import CreateConfirmation from './CreateConfirmation';
+
+interface Props extends I18nProps {
+  from: string;
+  onClose: () => void;
+}
+
+interface DerivedAddress {
+  address: string | null;
+  deriveError: string | null;
+}
+
+function deriveValidate (suri: string, pairType: KeypairType): string | null {
+  try {
+    const { path } = keyExtractPath(suri);
+
+    // we don't allow soft for ed25519
+    if (pairType === 'ed25519' && path.some(({ isSoft }): boolean => isSoft)) {
+      return 'Soft derivation paths are not allowed on ed25519';
+    }
+  } catch (error) {
+    return error.message;
+  }
+
+  return null;
+}
+
+function createAccount (source: KeyringPair, suri: string, name: string, password: string, success: string): ActionStatus {
+  // we will fill in all the details below
+  const status = { action: 'create' } as ActionStatus;
+
+  try {
+    const derived = source.derive(suri);
+
+    derived.setMeta({ ...derived.meta, name, tags: [] });
+
+    const result = keyring.addPair(derived, password || '');
+    const { address } = result.pair;
+
+    status.account = address;
+    status.status = 'success';
+    status.message = success;
+
+    downloadAccount(result);
+  } catch (error) {
+    status.status = 'error';
+    status.message = error.message;
+  }
+
+  return status;
+}
+
+function Derive ({ className, from, onClose, t }: Props): React.ReactElement {
+  const { queueAction } = useContext(StatusContext);
+  const [source] = useState(keyring.getPair(from));
+  const [{ address, deriveError }, setDerived] = useState<DerivedAddress>({ address: null, deriveError: null });
+  const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
+  const [isLocked, setIsLocked] = useState(source.isLocked);
+  const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
+  const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' });
+  const [rootPass, setRootPass] = useState('');
+  const [suri, setSuri] = useState('');
+  const debouncedSuri = useDebounce(suri);
+  const isValid = !!address && !deriveError && isNameValid && isPassValid;
+
+  useEffect((): void => {
+    setIsLocked(source.isLocked);
+  }, [source]);
+
+  useEffect((): void => {
+    setDerived((): DerivedAddress => {
+      let address: string | null = null;
+      const deriveError = deriveValidate(debouncedSuri, source.type);
+
+      if (!deriveError) {
+        const result = source.derive(suri);
+
+        address = result.address;
+      }
+
+      return { address, deriveError };
+    });
+  }, [debouncedSuri]);
+
+  const _onChangeName = (name: string): void => setName({ isNameValid: !!name.trim(), name });
+  const _onChangePass = (password: string): void => setPassword({ isPassValid: keyring.isPassValid(password), password });
+  const _toggleConfirmation = (): void => setIsConfirmationOpen(!isConfirmationOpen);
+  const _onUnlock = (): void => {
+    try {
+      source.decodePkcs8(rootPass);
+    } catch (error) {
+      console.error(error);
+    }
+
+    setIsLocked(source.isLocked);
+  };
+
+  const _onCommit = (): void => {
+    if (!isValid) {
+      return;
+    }
+
+    const status = createAccount(source, suri, name, password, t('created account'));
+
+    _toggleConfirmation();
+    queueAction(status);
+    onClose();
+  };
+
+  const sourceStatic = (
+    <InputAddress
+      help={t('The selected account to perform the derivation on.')}
+      isDisabled
+      label={t('derive root account')}
+      value={from}
+    />
+  );
+
+  return (
+    <Modal
+      className={className}
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Derive account from pair')}</Modal.Header>
+      {address && isConfirmationOpen && (
+        <CreateConfirmation
+          address={address}
+          name={name}
+          onCommit={_onCommit}
+          onClose={_toggleConfirmation}
+        />
+      )}
+      <Modal.Content>
+        {isLocked && (
+          <>
+            {sourceStatic}
+            <Password
+              autoFocus
+              help={t('The password to unlock the selected account.')}
+              label={t('password')}
+              onChange={setRootPass}
+              value={rootPass}
+            />
+          </>
+        )}
+        {!isLocked && (
+          <AddressRow
+            defaultName={name}
+            noDefaultNameOpacity
+            value={deriveError ? '' : address}
+          >
+            {sourceStatic}
+            <Input
+              autoFocus
+              help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>///<password>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`. The "///password" is optional and should only occur once.')}
+              label={t('derivation path')}
+              onChange={setSuri}
+              placeholder={t('//hard/soft')}
+            />
+            <Input
+              className='full'
+              help={t('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
+              isError={!isNameValid}
+              label={t('name')}
+              onChange={_onChangeName}
+              onEnter={_onCommit}
+              placeholder={t('new account')}
+              value={name}
+            />
+            <Password
+              className='full'
+              help={t('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
+              isError={!isPassValid}
+              label={t('password')}
+              onChange={_onChangePass}
+              onEnter={_onCommit}
+              value={password}
+            />
+          </AddressRow>
+        )}
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          {isLocked
+            ? (
+              <Button
+                icon='lock'
+                isDisabled={!rootPass}
+                isPrimary
+                label={t('Unlock')}
+                onClick={_onUnlock}
+              />
+            )
+            : (
+              <Button
+                icon='plus'
+                isDisabled={!isValid}
+                isPrimary
+                label={t('Save')}
+                onClick={_toggleConfirmation}
+              />
+            )
+          }
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(Derive);

+ 1 - 0
packages/app-accounts/src/modals/Import.tsx

@@ -76,6 +76,7 @@ class Import extends TxComponent<Props, State> {
       <Modal.Content>
         <AddressRow
           defaultName={isFileValid && json ? json.meta.name : null}
+          noDefaultNameOpacity
           value={isFileValid && address ? address : null}
         >
           <InputFile

+ 4 - 4
packages/app-accounts/src/modals/Qr.tsx

@@ -23,11 +23,10 @@ interface Props extends I18nProps, ModalProps {
 }
 
 function QrModal ({ className, onClose, onStatusChange, t }: Props): React.ReactElement<Props> {
-  const [name, setName] = useState('');
+  const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
   const [scanned, setScanned] = useState<Scanned | null>(null);
-  const isNameValid = !!name;
 
-  const _onNameChange = (name: string): void => setName(name.trim());
+  const _onNameChange = (name: string): void => setName({ isNameValid: !!name.trim(), name });
   const _onSave = (): void => {
     if (!scanned || !isNameValid) {
       return;
@@ -35,7 +34,7 @@ function QrModal ({ className, onClose, onStatusChange, t }: Props): React.React
 
     const { address, genesisHash } = scanned;
 
-    keyring.addExternal(address, { genesisHash, name });
+    keyring.addExternal(address, { genesisHash, name: name.trim() });
     InputAddress.setLastValue('account', address);
 
     onStatusChange({
@@ -61,6 +60,7 @@ function QrModal ({ className, onClose, onStatusChange, t }: Props): React.React
               <>
                 <AddressRow
                   defaultName={name}
+                  noDefaultNameOpacity
                   value={scanned.address}
                 />
                 <Input

+ 3 - 3
packages/app-accounts/src/modals/Transfer.tsx

@@ -84,7 +84,7 @@ function Transfer ({ className, onClose, recipientId: propRecipientId, senderId:
     }
   }, [amount, recipientId, senderId]);
 
-  const available = <span className='label'>{t('available ')}</span>;
+  const transferrable = <span className='label'>{t('transferrable ')}</span>;
 
   return (
     <Modal
@@ -100,7 +100,7 @@ function Transfer ({ className, onClose, recipientId: propRecipientId, senderId:
             help={t('The account you will send funds from.')}
             isDisabled={!!propSenderId}
             label={t('send from account')}
-            labelExtra={<Available label={available} params={senderId} />}
+            labelExtra={<Available label={transferrable} params={senderId} />}
             onChange={setSenderId}
             type='account'
           />
@@ -109,7 +109,7 @@ function Transfer ({ className, onClose, recipientId: propRecipientId, senderId:
             help={t('Select a contact or paste the address you want to send funds to.')}
             isDisabled={!!propRecipientId}
             label={t('send to address')}
-            labelExtra={<Available label={available} params={recipientId} />}
+            labelExtra={<Available label={transferrable} params={recipientId} />}
             onChange={setRecipientId}
             type='allPlus'
           />

+ 3 - 3
packages/app-address-book/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-address-book",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,7 +10,7 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 4 - 2
packages/app-address-book/src/Address.tsx

@@ -20,7 +20,7 @@ interface Props extends I18nProps {
   className?: string;
 }
 
-const WITH_BALANCE = { available: true, bonded: true, free: true, total: true };
+const WITH_BALANCE = { available: true, bonded: true, free: true, locked: true, reserved: true, total: true };
 const WITH_EXTENDED = { nonce: true };
 
 const isEditable = true;
@@ -63,6 +63,8 @@ function Address ({ address, className, t }: Props): React.ReactElement<Props> {
     const account = keyring.getAddress(address);
 
     account && keyring.saveAddress(address, { ...account.meta, genesisHash });
+
+    setGenesisHash(genesisHash);
   };
 
   return (
@@ -105,7 +107,7 @@ function Address ({ address, className, t }: Props): React.ReactElement<Props> {
       type='address'
       value={address}
       withExplorer
-      withIndex
+      withIndexOrAddress={false}
       withTags
     >
       {address && current && (

+ 4 - 2
packages/app-address-book/src/modals/Create.tsx

@@ -16,7 +16,7 @@ import translate from '../translate';
 interface Props extends ModalProps, I18nProps {}
 
 function Create ({ onClose, onStatusChange, t }: Props): React.ReactElement<Props> {
-  const [{ isNameValid, name }, setName] = useState<{ isNameValid: boolean; name: string }>({ isNameValid: true, name: 'new address' });
+  const [{ isNameValid, name }, setName] = useState<{ isNameValid: boolean; name: string }>({ isNameValid: false, name: '' });
   const [{ address, isAddressExisting, isAddressValid }, setAddress] = useState<{ address: string; isAddressExisting: boolean; isAddressValid: boolean }>({ address: '', isAddressExisting: false, isAddressValid: false });
   const isValid = isAddressValid && isNameValid;
 
@@ -40,7 +40,7 @@ function Create ({ onClose, onStatusChange, t }: Props): React.ReactElement<Prop
           isAddressExisting = true;
           isAddressValid = true;
 
-          setName({ isNameValid: !!newName, name: newName });
+          setName({ isNameValid: !!(newName || '').trim(), name: newName });
         }
       }
     } catch (error) {
@@ -85,6 +85,7 @@ function Create ({ onClose, onStatusChange, t }: Props): React.ReactElement<Prop
       <Modal.Content>
         <AddressRow
           defaultName={name}
+          noDefaultNameOpacity
           value={address}
         >
           <Input
@@ -95,6 +96,7 @@ function Create ({ onClose, onStatusChange, t }: Props): React.ReactElement<Prop
             label={t('address')}
             onChange={_onChangeAddress}
             onEnter={_onCommit}
+            placeholder={t('new address')}
             value={address}
           />
           <Input

+ 3 - 3
packages/app-claims/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-claims",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "An app for claiming Polkadot tokens",
   "main": "index.js",
   "scripts": {},
@@ -11,7 +11,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 1 - 1
packages/app-claims/src/index.tsx

@@ -75,7 +75,7 @@ const Signature = styled.textarea`
 `;
 
 class App extends TxModal<Props, State> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.defaultState = {

+ 4 - 4
packages/app-contracts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-contracts",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "Deployment and management of substrate contracts",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/api-contract": "^0.95.0-beta.36",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/api-contract": "^0.96.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 1 - 1
packages/app-contracts/src/ABI.tsx

@@ -45,7 +45,7 @@ class ABI extends React.PureComponent<Props, State> {
     isError: false
   };
 
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     const { contractAbi, isError, isRequired } = this.props;

+ 1 - 1
packages/app-contracts/src/Codes/Add.tsx

@@ -18,7 +18,7 @@ interface State extends ContractModalState {
 }
 
 class Add extends ContractModal<Props, State> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
     this.defaultState = {
       ...this.defaultState,

+ 1 - 1
packages/app-contracts/src/Codes/Upload.tsx

@@ -26,7 +26,7 @@ interface State extends ContractModalState {
 }
 
 class Upload extends ContractModal<Props, State> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.defaultState = {

+ 1 - 1
packages/app-contracts/src/Contracts/Add.tsx

@@ -24,7 +24,7 @@ interface State extends ContractModalState {
 }
 
 class Add extends ContractModal<Props, State> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
     this.defaultState = {
       ...this.defaultState,

+ 146 - 148
packages/app-contracts/src/Contracts/Call.tsx

@@ -2,63 +2,76 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
+import { ContractCallOutcome } from '@polkadot/api-contract/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { BareProps, CallContract, I18nProps, StringOrNull } from '@polkadot/react-components/types';
-import { QueueProps } from '@polkadot/react-components/Status/types';
+import { BareProps, I18nProps, StringOrNull } from '@polkadot/react-components/types';
 import { ContractExecResult } from '@polkadot/types/interfaces/contracts';
 
 import BN from 'bn.js';
-import React, { useState } from 'react';
-import rpc from '@polkadot/jsonrpc';
-import { Button, Dropdown, InputAddress, InputBalance, InputNumber, Modal, Output, TxButton } from '@polkadot/react-components';
-import { QueueConsumer } from '@polkadot/react-components/Status/Context';
+import React, { useState, useEffect } from 'react';
+import styled from 'styled-components';
+import { Button, Dropdown, IconLink, InputAddress, InputBalance, InputNumber, Modal, Toggle, TxButton } from '@polkadot/react-components';
+import { PromiseContract as ApiContract } from '@polkadot/api-contract';
 import { withApi, withMulti } from '@polkadot/react-api';
-import { isNull, isUndefined } from '@polkadot/util';
+import { createValue } from '@polkadot/react-params/values';
+import { isNull } from '@polkadot/util';
 
 import Params from '../Params';
+import Outcome from './Outcome';
 
 import translate from '../translate';
 import { GAS_LIMIT } from '../constants';
-import { findCallMethod, getContractForAddress, getCallMethodOptions, getContractMethodFn } from './util';
+import { getCallMessageOptions } from './util';
 
 interface Props extends BareProps, I18nProps, ApiProps {
-  callContract: CallContract | null;
-  callMethodIndex: number | null;
+  callContract: ApiContract | null;
+  callMessageIndex: number | null;
+  callResults: ContractExecResult[];
   isOpen: boolean;
-  onChangeCallContract: (callContract: CallContract) => void;
-  onChangeCallMethodIndex: (callMethodIndex: number) => void;
+  onChangeCallContractAddress: (callContractAddress: StringOrNull) => void;
+  onChangeCallMessageIndex: (callMessageIndex: number) => void;
   onClose: () => void;
 }
 
 function Call (props: Props): React.ReactElement<Props> | null {
-  const { isOpen, callContract, callMethodIndex, onChangeCallContract, onChangeCallMethodIndex, onClose, api, t } = props;
+  const { className, isOpen, callContract, callMessageIndex, onChangeCallContractAddress, onChangeCallMessageIndex, onClose, api, t } = props;
 
-  if (isNull(callContract) || isNull(callMethodIndex)) {
+  if (isNull(callContract) || isNull(callMessageIndex)) {
     return null;
   }
 
   const hasRpc = api.rpc.contracts && api.rpc.contracts.call;
-  const callMethod = findCallMethod(callContract, callMethodIndex);
-  const useRpc = hasRpc && callMethod && !callMethod.mutates;
-  // const isRpc = false;
+  let callMessage = callContract.getMessage(callMessageIndex);
 
   const [accountId, setAccountId] = useState<StringOrNull>(null);
   const [endowment, setEndowment] = useState<BN>(new BN(0));
   const [gasLimit, setGasLimit] = useState<BN>(new BN(GAS_LIMIT));
   const [isBusy, setIsBusy] = useState(false);
-  const [params, setParams] = useState<any[]>([]);
+  const [outcomes, setOutcomes] = useState<ContractCallOutcome[]>([]);
+  const [params, setParams] = useState<any[]>(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []);
+  const [useRpc, setUseRpc] = useState(callMessage && !callMessage.def.mutates);
 
-  const _onChangeAccountId = (accountId: StringOrNull): void => setAccountId(accountId);
+  useEffect((): void => {
+    callMessage = callContract.getMessage(callMessageIndex);
 
-  const _onChangeCallAddress = (callAddress: StringOrNull): void => {
-    const callContract = getContractForAddress(callAddress);
+    setParams(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []);
+    if (!callMessage || callMessage.def.mutates) {
+      setUseRpc(false);
+    } else {
+      setUseRpc(true);
+    }
+  }, [callContract, callMessageIndex]);
 
-    onChangeCallContract && callContract.abi && onChangeCallContract(callContract);
-  };
+  useEffect((): void => {
+    setOutcomes([]);
+  }, [callContract]);
+
+  const _onChangeAccountId = (accountId: StringOrNull): void => setAccountId(accountId);
 
-  const _onChangeCallMethodString = (callMethodString: string): void => {
-    setParams([]);
-    onChangeCallMethodIndex && onChangeCallMethodIndex(parseInt(callMethodString, 10) || 0);
+  const _onChangeCallMessageIndexString = (callMessageIndexString: string): void => {
+    onChangeCallMessageIndex && onChangeCallMessageIndex(
+      parseInt(callMessageIndexString, 10) || 0
+    );
   };
 
   const _onChangeEndowment = (endowment?: BN): void => endowment && setEndowment(endowment);
@@ -68,28 +81,29 @@ function Call (props: Props): React.ReactElement<Props> | null {
   const _toggleBusy = (): void => setIsBusy(!isBusy);
 
   const _constructTx = (): any[] => {
-    const fn = getContractMethodFn(callContract, callMethod);
-    if (!fn || !callContract || !callContract.address) {
+    if (!accountId || !callMessage || !callMessage.fn || !callContract || !callContract.address) {
       return [];
     }
 
-    return [callContract.address, endowment, gasLimit, fn(...params)];
+    return [callContract.address.toString(), endowment, gasLimit, callMessage.fn(...params)];
   };
 
-  const _constructRpc = (): [any] | null => {
-    const fn = getContractMethodFn(callContract, callMethod);
-    if (!fn || !accountId || !callContract || !callContract.address || !callContract.abi || !callMethod) {
-      return null;
-    }
-    return [
-      {
-        origin: accountId,
-        dest: callContract.address,
-        value: endowment,
-        gasLimit,
-        inputData: fn(...params)
-      }
-    ];
+  const _onSubmitRpc = (): void => {
+    if (!accountId) return;
+
+    callContract
+      .call('rpc', callMessage.def.name, endowment, gasLimit, ...params)
+      .send(accountId)
+      .then(
+        (outcome: ContractCallOutcome): void => {
+          setOutcomes([outcome, ...outcomes]);
+        }
+      );
+  };
+
+  const _onClearOutcomes = (): void => setOutcomes([]);
+  const _onClearOutcome = (outcomeIndex: number) => (): void => {
+    setOutcomes(outcomes.slice(0, outcomeIndex).concat(outcomes.slice(outcomeIndex + 1)));
   };
 
   const isEndowmentValid = true;
@@ -98,7 +112,7 @@ function Call (props: Props): React.ReactElement<Props> | null {
 
   return (
     <Modal
-      className='app--contracts-Modal'
+      className={[className || '', 'app--contracts-Modal'].join(' ')}
       dimmer='inverted'
       onClose={onClose}
       open={isOpen}
@@ -122,27 +136,28 @@ function Call (props: Props): React.ReactElement<Props> | null {
               help={t('A deployed contract that has either been deployed or attached. The address and ABI are used to construct the parameters.')}
               isDisabled={isBusy}
               label={t('contract to use')}
-              onChange={_onChangeCallAddress}
+              onChange={onChangeCallContractAddress}
               type='contract'
-              value={callContract.address}
+              value={callContract.address.toString()}
             />
-            {callMethodIndex !== null && (
+            {callMessageIndex !== null && (
               <>
                 <Dropdown
+                  defaultValue={`${callMessage.index}`}
                   help={t('The message to send to this contract. Parameters are adjusted based on the ABI provided.')}
                   isDisabled={isBusy}
-                  isError={callMethod === null}
+                  isError={callMessage === null}
                   label={t('message to send')}
-                  onChange={_onChangeCallMethodString}
-                  options={getCallMethodOptions(callContract)}
-                  value={`${callMethodIndex}`}
+                  onChange={_onChangeCallMessageIndexString}
+                  options={getCallMessageOptions(callContract)}
+                  value={`${callMessage.index}`}
                 />
                 <Params
                   isDisabled={isBusy}
                   onChange={_onChangeParams}
                   params={
-                    callMethod
-                      ? callMethod.args
+                    callMessage
+                      ? callMessage.def.args
                       : undefined
                   }
                 />
@@ -167,109 +182,92 @@ function Call (props: Props): React.ReactElement<Props> | null {
             />
           </div>
         )}
-        <QueueConsumer>
-          {
-            ({ queueRpc, txqueue }: QueueProps): React.ReactNode => {
-              const _onSubmitRpc = (): void => {
-                const values = _constructRpc();
-
-                if (values) {
-                  queueRpc({
-                    accountId,
-                    rpc: rpc.contracts.methods.call,
-                    values
-                  });
-                }
-              };
-
-              const results = txqueue
-                .filter(({ error, result, rpc, values }): boolean =>
-                  ((!isUndefined(error) || !isUndefined(result)) &&
-                  rpc.section === 'contracts' && rpc.method === 'call' && !!values && values[0].dest === callContract.address)
-                )
-                .reverse();
-
-              return (
-                <>
-                  <Button.Group>
-                    <Button
-                      icon='cancel'
-                      isNegative
-                      onClick={onClose}
-                      label={t('Cancel')}
-                    />
-                    <Button.Or />
-                    {useRpc
-                      ? (
-                        <Button
-                          icon='sign-in'
-                          isDisabled={!isValid}
-                          isPrimary
-                          label={t('Call')}
-                          onClick={_onSubmitRpc}
-                        />
-                      )
-                      : (
-                        <TxButton
-                          accountId={accountId}
-                          icon='sign-in'
-                          isDisabled={!isValid}
-                          isPrimary
-                          label={t('Call')}
-                          onClick={_toggleBusy}
-                          onFailed={_toggleBusy}
-                          onSuccess={_toggleBusy}
-                          params={_constructTx}
-                          tx={api.tx.contracts ? 'contracts.call' : 'contract.call'}
-                        />
-                      )
-                    }
-                  </Button.Group>
-                  {results.length > 0 && (
-                    <>
-                      <h3>{t('Call results')}</h3>
-                      <div>
-                        {
-                          results.map(
-                            (tx, index): React.ReactNode => {
-                              let output: string;
-                              const contractExecResult = tx.result as ContractExecResult;
-                              if (contractExecResult.isSuccess) {
-                                const { data } = contractExecResult.asSuccess;
-                                output = data.toHex();
-                              } else {
-                                output = 'Error';
-                              }
-
-                              return (
-                                <Output
-                                  isError={contractExecResult.isError}
-                                  key={`result-${tx.id}`}
-                                  label={t(`#${results.length - 1 - index}`)}
-                                  style={{ fontFamily: 'monospace' }}
-                                  value={output}
-                                  withCopy
-                                  withLabel
-                                />
-                              );
-                            }
-                          )
-                        }
-                      </div>
-                    </>
-                  )}
-                </>
-              );
+        {hasRpc && (
+          <Toggle
+            className='rpc-toggle'
+            isDisabled={!!callMessage && callMessage.def.mutates}
+            label={
+              useRpc
+                ? t('send as RPC call')
+                : t('send as transaction')
             }
+            onChange={setUseRpc}
+            value={useRpc || false}
+          />
+        )}
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            onClick={onClose}
+            label={t('Cancel')}
+          />
+          <Button.Or />
+          {useRpc
+            ? (
+              <Button
+                icon='sign-in'
+                isDisabled={!isValid}
+                isPrimary
+                label={t('Call')}
+                onClick={_onSubmitRpc}
+              />
+            )
+            : (
+              <TxButton
+                accountId={accountId}
+                icon='sign-in'
+                isDisabled={!isValid}
+                isPrimary
+                label={t('Call')}
+                onClick={_toggleBusy}
+                onFailed={_toggleBusy}
+                onSuccess={_toggleBusy}
+                params={_constructTx}
+                tx={api.tx.contracts ? 'contracts.call' : 'contract.call'}
+              />
+            )
           }
-        </QueueConsumer>
+        </Button.Group>
+        {outcomes.length > 0 && (
+          <>
+            <h3>
+              {t('Call results')}
+              <IconLink
+                className='clear-all'
+                icon='close'
+                label={t('Clear all')}
+                onClick={_onClearOutcomes}
+              />
+            </h3>
+            <div>
+              {outcomes.map((outcome, index): React.ReactNode => (
+                <Outcome
+                  key={`outcome-${index}`}
+                  onClear={_onClearOutcome(index)}
+                  outcome={outcome}
+                />
+              ))}
+            </div>
+          </>
+        )}
       </Modal.Content>
     </Modal>
   );
 }
 
 export default withMulti(
-  Call,
+  styled(Call)`
+    .rpc-toggle {
+      margin-top: 1rem;
+      display: flex;
+      justify-content: flex-end;
+    }
+
+    .clear-all {
+      float: right;
+    }
+  `,
   translate,
   withApi
 );

+ 10 - 12
packages/app-contracts/src/Contracts/Contract.tsx

@@ -3,21 +3,22 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { ActionStatus } from '@polkadot/react-components/Status/types';
-import { CallContract, I18nProps } from '@polkadot/react-components/types';
+import { I18nProps } from '@polkadot/react-components/types';
 
 import React, { useState } from 'react';
 import styled from 'styled-components';
 import { RouteComponentProps } from 'react-router';
 import { withRouter } from 'react-router-dom';
 import keyring from '@polkadot/ui-keyring';
+import { PromiseContract as ApiContract } from '@polkadot/api-contract';
 import { AddressRow, Button, Card, Forget, Messages } from '@polkadot/react-components';
 
 import translate from '../translate';
 
 interface Props extends I18nProps, RouteComponentProps {
   basePath: string;
-  contract: CallContract;
-  onCall: (_: CallContract) => (_?: number) => () => void;
+  contract: ApiContract;
+  onCall: (_?: number) => () => void;
 }
 
 const ContractCard = styled(Card)`
@@ -28,7 +29,7 @@ const ContractCard = styled(Card)`
 `;
 
 function Contract (props: Props): React.ReactElement<Props> | null {
-  const { contract, contract: { abi, address }, onCall, t } = props;
+  const { contract: { abi, address }, onCall, t } = props;
 
   if (!address || !abi) {
     return null;
@@ -48,7 +49,7 @@ function Contract (props: Props): React.ReactElement<Props> | null {
     };
 
     try {
-      keyring.forgetContract(address);
+      keyring.forgetContract(address.toString());
       status.status = 'success';
       status.message = t('address forgotten');
     } catch (error) {
@@ -58,15 +59,12 @@ function Contract (props: Props): React.ReactElement<Props> | null {
     _toggleForget();
   };
 
-  const _onCallMessage = onCall(contract);
-  const _onCall = _onCallMessage();
-
   return (
     <ContractCard>
       {
         isForgetOpen && (
           <Forget
-            address={address}
+            address={address.toString()}
             mode='contract'
             onForget={_onForget}
             key='modal-forget-contract'
@@ -88,7 +86,7 @@ function Contract (props: Props): React.ReactElement<Props> | null {
               icon='play'
               isPrimary
               label={t('execute')}
-              onClick={_onCall}
+              onClick={onCall()}
               size='small'
               tooltip={t('Call a method on this contract')}
             />
@@ -105,10 +103,10 @@ function Contract (props: Props): React.ReactElement<Props> | null {
         <details>
           <summary>{t('Messages')}</summary>
           <Messages
-            address={address}
+            address={address.toString()}
             contractAbi={abi}
             isRemovable={false}
-            onSelect={_onCallMessage}
+            onSelect={onCall}
           />
         </details>
       </AddressRow>

+ 104 - 0
packages/app-contracts/src/Contracts/Outcome.tsx

@@ -0,0 +1,104 @@
+// Copyright 2017-2019 @polkadot/app-contracts 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 { ContractCallOutcome } from '@polkadot/api-contract/types';
+
+import React from 'react';
+import styled from 'styled-components';
+import { AddressMini, Button, MessageSignature, Output } from '@polkadot/react-components';
+
+interface Props {
+  className?: string;
+  onClear?: () => void;
+  outcome: ContractCallOutcome;
+}
+
+function Outcome (props: Props): React.ReactElement<Props> | null {
+  const { className, onClear, outcome: { message, origin, output, params, isSuccess, time } } = props;
+  const dateTime = new Date(time);
+
+  return (
+    <div className={className}>
+      <div className='info'>
+        <AddressMini
+          className='origin'
+          value={origin}
+          withAddress={false}
+          isPadded={false}
+        />
+        <MessageSignature
+          message={message}
+          params={params}
+        />
+        <span className='date-time'>
+          {dateTime.toLocaleDateString()}
+          {' '}
+          {dateTime.toLocaleTimeString()}
+        </span>
+        <Button
+          className='icon-button clear-btn'
+          icon='close'
+          size='mini'
+          isPrimary
+          onClick={onClear}
+        />
+      </div>
+      <Output
+        isError={!isSuccess}
+        className='output'
+        value={(output || '()').toString()}
+        withCopy
+        withLabel={false}
+      />
+    </div>
+  );
+}
+
+export default styled(Outcome)`
+  & {
+    .info {
+      display: inline-flex;
+      justify-content: center;
+      align-items: center;
+      padding: 0.5rem;
+
+      & > *:not(:first-child) {
+        padding-left: 1.5rem !important;
+      }
+    }
+
+    .clear-btn {
+      opacity: 0;
+    }
+
+    .date-time {
+      color: #aaa;
+      white-space: nowrap;
+    }
+
+    .origin {
+      padding-left: 0 !important;
+
+      * {
+        margin-left: 0 !important;
+      }
+    }
+
+    .output {
+      font-family: monospace;
+      margin-left: 3.5rem;
+
+      .ui--output {
+        border-color: #aaa;
+        margin: 0;
+      }
+    }
+
+    &:hover {
+      .clear-btn {
+        opacity: 1;
+      }
+    }
+  }
+`;

+ 55 - 31
packages/app-contracts/src/Contracts/index.tsx

@@ -2,48 +2,70 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { CallContract, NullContract, I18nProps } from '@polkadot/react-components/types';
+import { ApiProps } from '@polkadot/react-api/types';
+import { I18nProps, StringOrNull } from '@polkadot/react-components/types';
 import { ComponentProps } from '../types';
 
-import React, { useState } from 'react';
-import { RouteComponentProps } from 'react-router';
-import { withRouter } from 'react-router-dom';
+import React, { useState, useEffect } from 'react';
+import { PromiseContract as ApiContract } from '@polkadot/api-contract';
+import { withApi, withMulti } from '@polkadot/react-api';
 import { Button, CardGrid } from '@polkadot/react-components';
 
 import translate from '../translate';
 import Add from './Add';
-import Contract from './Contract';
+import ContractCard from './Contract';
 import Call from './Call';
 import { getContractForAddress } from './util';
 
-interface Props extends ComponentProps, I18nProps, RouteComponentProps {}
+interface Props extends ComponentProps, I18nProps, ApiProps {}
+
+function filterContracts ({ api, accounts, contracts: keyringContracts }: Props): ApiContract[] {
+  return accounts && keyringContracts && Object.keys(keyringContracts)
+    .map((address): ApiContract | null => getContractForAddress(api, address))
+    .filter((contract: ApiContract | null): boolean => !!contract) as ApiContract[];
+}
 
 function Contracts (props: Props): React.ReactElement<Props> {
-  const { accounts, basePath, contracts, hasCode, showDeploy, t } = props;
-  // const { callAddress, callMethod, isAddOpen, isCallOpen } = this.state;
+  const { accounts, basePath, contracts: keyringContracts, hasCode, showDeploy, t } = props;
+  // const { callAddress, callMessage, isAddOpen, isCallOpen } = this.state;
 
-  const [callContract, setCallContract] = useState<CallContract | null>(null);
-  const [callMethodIndex, setCallMethodIndex] = useState<number | null>(null);
+  const [contracts, setContracts] = useState<ApiContract[]>(filterContracts(props));
+  const [callContractIndex, setCallContractIndex] = useState<number>(0);
+  const [callMessageIndex, setCallMessageIndex] = useState<number>(0);
   const [isAddOpen, setIsAddOpen] = useState(false);
   const [isCallOpen, setIsCallOpen] = useState(false);
 
+  useEffect((): void => {
+    setContracts(filterContracts(props));
+  }, [accounts, keyringContracts]);
+
+  let callContract = contracts[callContractIndex] || null;
+
+  useEffect((): void => {
+    callContract = contracts[callContractIndex];
+  }, [callContractIndex]);
+
   const _toggleAdd = (): void => setIsAddOpen(!isAddOpen);
   const _toggleCall = (): void => setIsCallOpen(!isCallOpen);
 
-  const _onChangeCallContract = (newCallContract: CallContract): void => {
-    if (callContract && newCallContract.address !== callContract.address) {
-      setCallMethodIndex(0);
+  const _onChangeCallContractAddress = (newCallContractAddress: StringOrNull): void => {
+    const index = contracts.findIndex(({ address }: ApiContract): boolean => newCallContractAddress === address.toString());
+
+    if (index > -1) {
+      index !== callContractIndex && setCallMessageIndex(0);
+      setCallContractIndex(index);
     }
-    setCallContract(callContract);
   };
-  const _onChangeCallMethodIndex = (callMethodIndex: number): void => {
-    !!callContract && setCallMethodIndex(callMethodIndex);
+
+  const _onChangeCallMessageIndex = (callMessageIndex: number): void => {
+    !!callContract && setCallMessageIndex(callMessageIndex);
   };
-  const _onCall = (callContract: CallContract): (_?: number) => () => void => {
-    return function (callMethodIndex?: number): () => void {
+
+  const _onCall = (callContractIndex: number): (_?: number) => () => void => {
+    return function (callMessageIndex?: number): () => void {
       return function (): void {
-        setCallContract(callContract);
-        setCallMethodIndex(callMethodIndex || 0);
+        setCallContractIndex(callContractIndex);
+        setCallMessageIndex(callMessageIndex || 0);
         setIsCallOpen(true);
       };
     };
@@ -75,16 +97,14 @@ function Contracts (props: Props): React.ReactElement<Props> {
           </Button.Group>
         }
       >
-        {(accounts && contracts && Object.keys(contracts)
-          .map((address): CallContract | NullContract => getContractForAddress(address))
-          .filter(({ abi, address }: CallContract | NullContract): boolean => !!address && !!abi) as CallContract[])
-          .map((contract: CallContract): React.ReactNode => {
+        {contracts
+          .map((contract: ApiContract, index): React.ReactNode => {
             return (
-              <Contract
+              <ContractCard
                 basePath={basePath}
                 contract={contract}
-                key={contract.address}
-                onCall={_onCall}
+                key={contract.address.toString()}
+                onCall={_onCall(index)}
               />
             );
           })}
@@ -96,14 +116,18 @@ function Contracts (props: Props): React.ReactElement<Props> {
       />
       <Call
         callContract={callContract}
-        callMethodIndex={callMethodIndex}
+        callMessageIndex={callMessageIndex}
         isOpen={isCallOpen}
-        onChangeCallContract={_onChangeCallContract}
-        onChangeCallMethodIndex={_onChangeCallMethodIndex}
+        onChangeCallContractAddress={_onChangeCallContractAddress}
+        onChangeCallMessageIndex={_onChangeCallMessageIndex}
         onClose={_toggleCall}
       />
     </>
   );
 }
 
-export default translate(withRouter(Contracts));
+export default withMulti(
+  Contracts,
+  translate,
+  withApi
+);

+ 16 - 21
packages/app-contracts/src/Contracts/util.tsx

@@ -2,52 +2,47 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { ContractABIFn, ContractABIMethod } from '@polkadot/api-contract/types';
-import { CallContract, NullContract, StringOrNull } from '@polkadot/react-components/types';
-import { CONTRACT_NULL } from '../constants';
+import { ContractABIFn, ContractABIMessage } from '@polkadot/api-contract/types';
+import { StringOrNull } from '@polkadot/react-components/types';
 
 import React from 'react';
+import { ApiPromise } from '@polkadot/api';
+import { PromiseContract as Contract } from '@polkadot/api-contract';
 import { MessageSignature } from '@polkadot/react-components';
 import { getContractAbi } from '@polkadot/react-components/util';
-import { stringCamelCase } from '@polkadot/util';
 
-export function findCallMethod (callContract: CallContract | null, callMethodIndex = 0): ContractABIMethod | null {
+export function findCallMethod (callContract: Contract | null, callMethodIndex = 0): ContractABIMessage | null {
   const message = callContract && callContract.abi.abi.contract.messages[callMethodIndex];
 
   return message || null;
 }
 
-export function getContractMethodFn (callContract: CallContract | null, callMethod: ContractABIMethod | null): ContractABIFn | null {
-  const fn = callContract && callContract.abi && callMethod && callContract.abi.messages[stringCamelCase(callMethod.name)];
+export function getContractMethodFn (callContract: Contract | null, callMethodIndex: number | null): ContractABIFn | null {
+  const fn = callContract && callContract.abi && callMethodIndex !== null && callContract.abi.messages[callMethodIndex];
 
   return fn || null;
 }
 
-export function getContractForAddress (address: StringOrNull): CallContract | NullContract {
+export function getContractForAddress (api: ApiPromise, address: StringOrNull): Contract | null {
   if (!address) {
-    return CONTRACT_NULL;
+    return null;
   } else {
     const abi = getContractAbi(address);
     return abi
-      ? {
-        address,
-        abi
-      }
-      : CONTRACT_NULL;
+      ? new Contract(api, abi, address)
+      : null;
   }
 }
 
-export function getCallMethodOptions (callContract: CallContract | null): any[] {
-  return callContract && callContract.abi
-    ? callContract.abi.abi.contract.messages.map((message, messageIndex): { key: string; text: React.ReactNode; value: string } => {
-      const key = message.name;
-
+export function getCallMessageOptions (callContract: Contract | null): any[] {
+  return callContract
+    ? callContract.messages.map(({ def: message, def: { name }, index }): { key: string; text: React.ReactNode; value: string } => {
       return {
-        key,
+        key: name,
         text: (
           <MessageSignature message={message} />
         ),
-        value: `${messageIndex}`
+        value: `${index}`
       };
     })
     : [];

+ 9 - 7
packages/app-contracts/src/Deploy.tsx

@@ -44,7 +44,7 @@ class Deploy extends ContractModal<Props, State> {
 
   public isContract = true;
 
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.defaultState = {
@@ -124,16 +124,16 @@ class Deploy extends ContractModal<Props, State> {
     const { abi: { contract: { constructors } } } = contractAbi;
     const constructor = constructors[constructorIndex];
     const constructOptions: ConstructOptions = constructors.map(
-      (constr) => {
+      (constr, index) => {
         return {
-          key: `${constructorIndex}`,
+          key: `${index}`,
           text: (
             <MessageSignature
               asConstructor
               message={constr}
             />
           ),
-          value: `${constructorIndex}`
+          value: `${index}`
         };
       });
 
@@ -292,10 +292,12 @@ class Deploy extends ContractModal<Props, State> {
     const { api, history } = this.props;
 
     const section = api.tx.contracts ? 'contracts' : 'contract';
-    const record = result.findRecord(section, 'Instantiated');
+    const records = result.filterRecords(section, 'Instantiated');
 
-    if (record) {
-      const address = record.event.data[1] as unknown as AccountId;
+    if (records.length) {
+      // find the last EventRecord (in the case of multiple contracts deployed - we should really be
+      // more clever here to find the exact contract deployed, this works for eg. Delegator)
+      const address = records[records.length - 1].event.data[1] as unknown as AccountId;
 
       this.setState(({ abi, name, tags }): Pick<State, never> | unknown => {
         if (!abi || !name) {

+ 1 - 1
packages/app-contracts/src/index.tsx

@@ -42,7 +42,7 @@ class App extends React.PureComponent<Props, State> {
     updated: 0
   };
 
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     store.on('new-code', this.triggerUpdate);

+ 4 - 4
packages/app-council/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-council",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "Council",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-query": "^0.37.0-beta.63"
   }
 }

+ 5 - 5
packages/app-council/src/Motions/Propose.tsx

@@ -2,15 +2,15 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { Call, Proposal } from '@polkadot/types/interfaces';
 import { ApiProps } from '@polkadot/react-api/types';
+import { Call, Proposal } from '@polkadot/types/interfaces';
 
 import BN from 'bn.js';
 import React from 'react';
 import { createType } from '@polkadot/types';
 import { Button, Extrinsic, InputNumber } from '@polkadot/react-components';
 import TxModal, { TxModalState, TxModalProps } from '@polkadot/react-components/TxModal';
-import { withApi, withCalls, withMulti } from '@polkadot/react-api';
+import { withCalls, withMulti } from '@polkadot/react-api';
 
 import translate from '../translate';
 
@@ -24,7 +24,7 @@ interface State extends TxModalState {
 }
 
 class Propose extends TxModal<Props, State> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.defaultState = {
@@ -136,9 +136,9 @@ class Propose extends TxModal<Props, State> {
 export default withMulti(
   Propose,
   translate,
-  withApi,
   withCalls(
-    ['query.elections.members', {
+    ['query.electionsPhragmen.members', {
+      fallbacks: ['query.elections.members'],
       propName: 'memberCount',
       transform: (value: any[]): number =>
         value.length

+ 11 - 3
packages/app-council/src/Overview/Candidate.tsx

@@ -9,17 +9,25 @@ import React from 'react';
 import { AddressCard } from '@polkadot/react-components';
 
 import translate from '../translate';
+import Voters from './Voters';
 
 interface Props extends I18nProps {
   address: AccountId;
+  isRunnerUp?: boolean;
+  voters?: AccountId[];
 }
 
-function Candidate ({ address, t }: Props): React.ReactElement<Props> {
+function Candidate ({ address, isRunnerUp, t, voters }: Props): React.ReactElement<Props> {
   return (
     <AddressCard
-      defaultName={t('candidate')}
+      defaultName={isRunnerUp ? t('runner up') : t('candidate')}
       value={address}
-    />
+      withIndexOrAddress
+    >
+      {voters && voters.length !== 0 && (
+        <Voters voters={voters} />
+      )}
+    </AddressCard>
   );
 }
 

+ 11 - 7
packages/app-council/src/Overview/Member.tsx

@@ -2,27 +2,31 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { BlockNumber } from '@polkadot/types/interfaces';
 import { I18nProps } from '@polkadot/react-components/types';
+import { AccountId } from '@polkadot/types/interfaces';
 
 import React from 'react';
 import { AddressCard } from '@polkadot/react-components';
-import { formatNumber } from '@polkadot/util';
 
 import translate from '../translate';
+import Voters from './Voters';
 
 interface Props extends I18nProps {
-  address: string;
-  block: BlockNumber;
+  address: AccountId;
+  voters?: AccountId[];
 }
 
-function Member ({ address, block, t }: Props): React.ReactElement<Props> {
+function Member ({ address, t, voters }: Props): React.ReactElement<Props> {
   return (
     <AddressCard
-      buttons={<div><label>{t('active until')}</label>#{formatNumber(block)}</div>}
       defaultName={t('council member')}
       value={address}
-    />
+      withIndexOrAddress
+    >
+      {voters && voters.length !== 0 && (
+        <Voters voters={voters} />
+      )}
+    </AddressCard>
   );
 }
 

+ 51 - 14
packages/app-council/src/Overview/Members.tsx

@@ -3,46 +3,83 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
+import { AccountId } from '@polkadot/types/interfaces';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ComponentProps } from './types';
 
 import React from 'react';
+import { withCalls } from '@polkadot/react-api';
 import { Columar, Column } from '@polkadot/react-components';
 
 import translate from '../translate';
 import Candidate from './Candidate';
 import Member from './Member';
 
-interface Props extends I18nProps, ComponentProps {}
+interface Props extends I18nProps, ComponentProps {
+  allVotes?: Record<string, AccountId[]>;
+}
 
-function Members ({ electionsInfo: { candidates, members }, t }: Props): React.ReactElement<Props> {
+function Members ({ allVotes = {}, electionsInfo: { candidates, members, runnersUp }, t }: Props): React.ReactElement<Props> {
   return (
     <Columar>
       <Column
         emptyText={t('No members found')}
         headerText={t('members')}
       >
-        {Object.entries(members).map(([address, block]): React.ReactNode => (
+        {members.map(([accountId]): React.ReactNode => (
           <Member
-            address={address}
-            block={block}
-            key={address}
+            address={accountId}
+            key={accountId.toString()}
+            voters={allVotes[accountId.toString()]}
           />
         ))}
       </Column>
       <Column
-        emptyText={t('No members found')}
+        emptyText={t('No candidates found')}
         headerText={t('candidates')}
       >
-        {candidates.map((address): React.ReactNode => (
-          <Candidate
-            address={address}
-            key={address.toString()}
-          />
-        ))}
+        {(!!candidates.length || !!runnersUp.length) && (
+          <>
+            {runnersUp.map(([accountId]): React.ReactNode => (
+              <Candidate
+                address={accountId}
+                isRunnerUp
+                key={accountId.toString()}
+                voters={allVotes[accountId.toString()]}
+              />
+            ))}
+            {candidates.map((accountId): React.ReactNode => (
+              <Candidate
+                address={accountId}
+                key={accountId.toString()}
+                voters={allVotes[accountId.toString()]}
+              />
+            ))}
+          </>
+        )}
       </Column>
     </Columar>
   );
 }
 
-export default translate(Members);
+export default translate(
+  withCalls<Props>(
+    ['query.electionsPhragmen.votesOf', {
+      propName: 'allVotes',
+      transform: ([voters, casted]: [AccountId[], AccountId[][]]): Record<string, AccountId[]> =>
+        voters.reduce((result: Record<string, AccountId[]>, voter, index): Record<string, AccountId[]> => {
+          casted[index].forEach((candidate): void => {
+            const address = candidate.toString();
+
+            if (!result[address]) {
+              result[address] = [];
+            }
+
+            result[address].push(voter);
+          });
+
+          return result;
+        }, {})
+    }]
+  )(Members)
+);

+ 9 - 11
packages/app-council/src/Overview/SubmitCandidacy.tsx

@@ -2,16 +2,17 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
+import { ApiProps } from '@polkadot/react-api/types';
 import { ComponentProps } from './types';
 
-import BN from 'bn.js';
 import React from 'react';
+import { withApi } from '@polkadot/react-api';
 import { Button } from '@polkadot/react-components';
 import TxModal, { TxModalState as State, TxModalProps } from '@polkadot/react-components/TxModal';
 
 import translate from '../translate';
 
-interface Props extends ComponentProps, TxModalProps {}
+interface Props extends ApiProps, ComponentProps, TxModalProps {}
 
 class SubmitCandidacy extends TxModal<Props, State> {
   protected headerText = (): string => this.props.t('Submit your council candidacy');
@@ -20,15 +21,12 @@ class SubmitCandidacy extends TxModal<Props, State> {
 
   protected accountHelp = (): string => this.props.t('This account will be nominated to fill the council slot you specify.');
 
-  protected txMethod = (): string => 'elections.submitCandidacy';
+  protected txMethod = (): string =>
+    this.props.api.tx.electionsPhragmen
+      ? 'electionsPhragmen.submitCandidacy'
+      : 'elections.submitCandidacy';
 
-  protected txParams = (): [BN] => {
-    const { electionsInfo: { candidateCount } } = this.props;
-
-    return [
-      candidateCount
-    ];
-  }
+  protected txParams = (): [] => [];
 
   protected isDisabled = (): boolean => {
     const { accountId } = this.state;
@@ -50,4 +48,4 @@ class SubmitCandidacy extends TxModal<Props, State> {
   }
 }
 
-export default translate(SubmitCandidacy);
+export default translate(withApi(SubmitCandidacy));

+ 25 - 15
packages/app-council/src/Overview/Summary.tsx

@@ -4,6 +4,7 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { I18nProps } from '@polkadot/react-components/types';
+import { BlockNumber } from '@polkadot/types/interfaces';
 import { ComponentProps } from './types';
 
 import React from 'react';
@@ -12,30 +13,39 @@ import { formatNumber } from '@polkadot/util';
 
 import translate from '../translate';
 
-interface Props extends I18nProps, ComponentProps {}
+interface Props extends I18nProps, ComponentProps {
+  bestNumber?: BlockNumber;
+}
 
-function Summary ({ electionsInfo: { members, candidateCount, desiredSeats, termDuration, voteCount }, t }: Props): React.ReactElement<Props> {
+function Summary ({ bestNumber, electionsInfo: { members, candidateCount, desiredSeats, runnersUp, termDuration, voteCount }, t }: Props): React.ReactElement<Props> {
   return (
     <SummaryBox>
       <section>
         <CardSummary label={t('seats')}>
-          {formatNumber(Object.keys(members).length)}/{formatNumber(desiredSeats)}
+          {formatNumber(members.length)}/{formatNumber(desiredSeats)}
         </CardSummary>
         <CardSummary label={t('candidates')}>
-          {formatNumber(candidateCount)}
-        </CardSummary>
-      </section>
-      <section>
-        <CardSummary label={t('voting round')}>
-          #{formatNumber(voteCount)}
-        </CardSummary>
-      </section>
-
-      <section>
-        <CardSummary label={t('term duration')}>
-          {formatNumber(termDuration)}
+          {formatNumber(candidateCount.addn(runnersUp.length))}
         </CardSummary>
       </section>
+      {voteCount && (
+        <section>
+          <CardSummary label={t('voting round')}>
+            #{formatNumber(voteCount)}
+          </CardSummary>
+        </section>
+      )}
+      {bestNumber && termDuration && termDuration.gtn(0) && (
+        <section>
+          <CardSummary
+            label={t('term progress')}
+            progress={{
+              total: termDuration,
+              value: bestNumber.mod(termDuration)
+            }}
+          />
+        </section>
+      )}
     </SummaryBox>
   );
 }

+ 161 - 179
packages/app-council/src/Overview/Vote.tsx

@@ -2,7 +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 { VoteIndex } from '@polkadot/types/interfaces';
+import { AccountId, VoteIndex } from '@polkadot/types/interfaces';
+import { Codec } from '@polkadot/types/types';
 import { DerivedVoterPositions } from '@polkadot/api-derive/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { ComponentProps } from './types';
@@ -11,35 +12,36 @@ import BN from 'bn.js';
 import React from 'react';
 import styled from 'styled-components';
 import { createType } from '@polkadot/types';
-import { withApi, withCalls, withMulti } from '@polkadot/react-api';
-import { AddressRow, Button, Icon, Toggle, TxButton } from '@polkadot/react-components';
+import { withCalls, withMulti } from '@polkadot/react-api';
+import { AddressRow, Button, Toggle } from '@polkadot/react-components';
 import TxModal, { TxModalState, TxModalProps } from '@polkadot/react-components/TxModal';
 
 import translate from '../translate';
+import VoteValue from './VoteValue';
 
 interface Props extends ApiProps, ComponentProps, TxModalProps {
   voterPositions?: DerivedVoterPositions;
 }
 
 interface State extends TxModalState {
-  approvals: boolean[] | null;
-  oldApprovals: boolean[] | null;
+  votes: Record<string, boolean>;
+  voteValue: BN;
   // voterPositions: DerivedVoterPositions;
 }
 
-const AlreadyVoted = styled.article`
-  display: flex;
-  align-items: center;
-  margin: 0.5rem 0;
+// const AlreadyVoted = styled.article`
+//   display: flex;
+//   align-items: center;
+//   margin: 0.5rem 0;
 
-  & > :first-child {
-    flex: 1 1;
-  }
+//   & > :first-child {
+//     flex: 1 1;
+//   }
 
-  & > :not(:first-child) {
-    margin: 0;
-  }
-`;
+//   & > :not(:first-child) {
+//     margin: 0;
+//   }
+// `;
 
 const Candidates = styled.div`
   display: flex;
@@ -52,8 +54,8 @@ const Candidate = styled.div`
   min-width: calc(50% - 1rem);
   border-radius: 0.5rem;
   border: 1px solid #eee;
-  padding: 0.5rem;
-  margin: 0.5rem;
+  padding: 0.75rem 0.5rem 0.25rem;
+  margin: 0.25rem;
   transition: all 0.2s;
 
   b {
@@ -71,6 +73,11 @@ const Candidate = styled.div`
   &.nay {
     background-color: rgba(0, 0, 0, 0.05);
   }
+
+  .ui--Row-children {
+    text-align: right;
+    width: 100%;
+  }
 `;
 
 class Vote extends TxModal<Props, State> {
@@ -78,35 +85,13 @@ class Vote extends TxModal<Props, State> {
     return [...new Array(length).keys()].map((): boolean => false);
   }
 
-  public static getDerivedStateFromProps ({ electionsInfo: { candidateCount } }: Props, { approvals }: State): Partial<State> {
-    const state: Partial<State> = {};
-    // if (voterPositions) {
-    //   state.voters = Object.keys(voterSets).reduce(
-    //     (result: Record<string, VoterPosition>, accountId, globalIndex): Record<string, VoterPosition> => {
-    //       result[accountId] = {
-    //         setIndex: voterSets[accountId],
-    //         globalIndex: new BN(globalIndex)
-    //       };
-    //       return result;
-    //     },
-    //     {}
-    //   );
-    // }
-
-    if (candidateCount && !approvals) {
-      state.approvals = state.oldApprovals || Vote.emptyApprovals(candidateCount.toNumber());
-    }
-
-    return state;
-  }
-
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.defaultState = {
       ...this.defaultState,
-      approvals: null,
-      oldApprovals: null
+      votes: {},
+      voteValue: new BN(0)
     };
 
     this.state = {
@@ -114,51 +99,60 @@ class Vote extends TxModal<Props, State> {
     };
   }
 
-  public componentDidMount (): void {
-    this.fetchApprovals();
-  }
-
-  public componentDidUpdate (_: Props, prevState: State): void {
-    const { accountId } = this.state;
-
-    if (accountId !== prevState.accountId) {
-      this.fetchApprovals();
-    }
-  }
-
   protected headerText = (): string => this.props.t('Vote for current candidates');
 
   protected accountLabel = (): string => this.props.t('Voting account');
 
   protected accountHelp = (): string => this.props.t('This account will be use to approve or disapprove each candidate.');
 
-  protected txMethod = (): string => 'elections.setApprovals';
+  protected txMethod = (): string =>
+    this.props.api.tx.electionsPhragmen
+      ? 'electionsPhragmen.vote'
+      : 'elections.setApprovals';
 
-  protected txParams = (): [boolean[] | null, VoteIndex, BN | null] => {
-    const { electionsInfo: { nextVoterSet, voteCount }, voterPositions } = this.props;
-    const { accountId, approvals } = this.state;
+  protected txParams = (): [boolean[] | null, VoteIndex, BN | null] | [string[], BN] => {
+    const { api, electionsInfo: { candidates, nextVoterSet, voteCount }, voterPositions } = this.props;
+    const { accountId, votes, voteValue } = this.state;
+
+    if (api.tx.electionsPhragmen) {
+      return [
+        Object.entries(votes).filter(([, vote]): boolean => vote).map(([accountId]): string => accountId),
+        voteValue
+      ];
+    }
+
+    const approvals = candidates.map((accountId): boolean => votes[accountId.toString()] === true);
 
     return [
-      approvals ? approvals.slice(0, 1 + approvals.lastIndexOf(true)) : [],
+      approvals
+        ? approvals.slice(0, 1 + approvals.lastIndexOf(true))
+        : [],
       createType('VoteIndex', voteCount),
       voterPositions && accountId && voterPositions[accountId]
         ? voterPositions[accountId].setIndex
-        : nextVoterSet
+        : nextVoterSet || null
     ];
   }
 
   protected isDisabled = (): boolean => {
-    const { accountId, oldApprovals } = this.state;
+    const { accountId, votes } = this.state;
+    const hasApprovals = Object.values(votes).some((vote): boolean => vote);
 
-    return !accountId || !!oldApprovals;
+    return !accountId || !hasApprovals;
   }
 
   protected renderTrigger = (): React.ReactNode => {
-    const { electionsInfo: { candidates }, t } = this.props;
+    const { api, electionsInfo: { candidates, members, runnersUp }, t } = this.props;
+    const available = api.tx.electionsPhragmen
+      ? members
+        .map(([accountId]): AccountId => accountId)
+        .concat(runnersUp.map(([accountId]): AccountId => accountId))
+        .concat(candidates)
+      : candidates;
 
     return (
       <Button
-        isDisabled={candidates.length === 0}
+        isDisabled={available.length === 0}
         isPrimary
         label={t('Vote')}
         icon='check'
@@ -168,102 +162,85 @@ class Vote extends TxModal<Props, State> {
   }
 
   protected renderContent = (): React.ReactNode => {
-    const { electionsInfo: { candidates }, voterPositions, t } = this.props;
-    const { accountId, approvals, oldApprovals } = this.state;
+    const { api, electionsInfo: { candidates, members, runnersUp }, t } = this.props;
+    const { accountId, votes } = this.state;
+    const _candidates = candidates.map((accountId): [AccountId, boolean] => [accountId, false]);
+    const available = api.tx.electionsPhragmen
+      ? members
+        .map(([accountId]): [AccountId, boolean] => [accountId, true])
+        .concat(runnersUp.map(([accountId]): [AccountId, boolean] => [accountId, false]))
+        .concat(_candidates)
+      : _candidates;
 
     return (
       <>
-        {
-          (oldApprovals && accountId && voterPositions && voterPositions[accountId]) && (
-            <AlreadyVoted className='warning padded'>
-              <div>
-                <Icon name='warning sign' />
-                {t('You have already voted in this round')}
-              </div>
-              <Button.Group>
-                <TxButton
-                  accountId={accountId}
-                  isNegative
-                  label={t('Retract vote')}
-                  icon='delete'
-                  onSuccess={this.onRetractVote}
-                  params={[voterPositions[accountId].globalIndex]}
-                  tx='elections.retractVoter'
-                />
-              </Button.Group>
-            </AlreadyVoted>
-          )
-        }
+        {api.tx.electionsPhragmen && (
+          <VoteValue
+            accountId={accountId}
+            onChange={this.setVoteValue}
+          />
+        )}
+        {/* {(oldApprovals && accountId && voterPositions && voterPositions[accountId]) && (
+          <AlreadyVoted className='warning padded'>
+            <div>
+              <Icon name='warning sign' />
+              {t('You have already voted in this round')}
+            </div>
+            <Button.Group>
+              <TxButton
+                accountId={accountId}
+                isNegative
+                label={t('Retract vote')}
+                icon='delete'
+                onSuccess={this.onRetractVote}
+                params={[voterPositions[accountId].globalIndex]}
+                tx='elections.retractVoter'
+              />
+            </Button.Group>
+          </AlreadyVoted>
+        )} */}
         <Candidates>
-          {
-            candidates.map((accountId, index): React.ReactNode => {
-              if (!approvals) {
-                return null;
-              }
-              const { [index]: isAye } = approvals;
-              return (
-                <Candidate
-                  className={isAye ? 'aye' : 'nay'}
-                  key={accountId.toString()}
-                  {...(
-                    !oldApprovals
-                      ? { onClick: (): void => this.onChangeVote(index)() }
-                      : {}
-                  )}
+          {available.map(([accountId, isMember]): React.ReactNode => {
+            const key = accountId.toString();
+            const isAye = votes[key] || false;
+
+            return (
+              <Candidate
+                className={isAye ? 'aye' : 'nay'}
+                key={key}
+              >
+                <AddressRow
+                  defaultName={isMember ? t('member') : t('candidate')}
+                  isInline
+                  value={accountId}
+                  withIndexOrAddress
                 >
-                  <AddressRow
-                    isInline
-                    value={accountId}
-                  >
-                    {this.renderToggle(index)}
-                  </AddressRow>
-                </Candidate>
-              );
-            })
-          }
+                  <Toggle
+                    label={
+                      isAye
+                        ? t('Aye')
+                        : t('Nay')
+                    }
+                    onChange={this.onChangeVote(key)}
+                    value={isAye}
+                  />
+                </AddressRow>
+              </Candidate>
+            );
+          })}
         </Candidates>
       </>
     );
   }
 
-  private renderToggle = (index: number): React.ReactNode => {
-    const { t } = this.props;
-    const { approvals, oldApprovals } = this.state;
-
-    if (!approvals) {
-      return null;
-    }
-
-    const { [index]: bool } = approvals;
-
-    return (
-      <Toggle
-        isDisabled={!!oldApprovals}
-        label={
-          bool
-            ? (
-              <b>{t('Aye')}</b>
-            )
-            : (
-              <b>{t('No vote')}</b>
-            )
-        }
-        value={bool}
-      />
-    );
-  }
-
-  private emptyApprovals = (): boolean[] => {
-    const { electionsInfo: { candidateCount } } = this.props;
-
-    return Vote.emptyApprovals(candidateCount.toNumber());
+  private setVoteValue = (voteValue?: BN): void => {
+    this.setState({ voteValue: voteValue || new BN(0) });
   }
 
-  private fetchApprovals = (): void => {
-    const { api, electionsInfo: { voteCount }, voterPositions } = this.props;
-    const { accountId } = this.state;
+  private fetchApprovals = (accountId: string | null): void => {
+    const { api, electionsInfo: { candidates, voteCount } } = this.props;
 
-    if (!accountId) {
+    if (!accountId || !voteCount) {
       return;
     }
 
@@ -271,50 +248,55 @@ class Vote extends TxModal<Props, State> {
     api.derive.elections
       .approvalsOfAt(accountId as any, voteCount)
       .then((approvals: boolean[]): void => {
-        if ((voterPositions && voterPositions[accountId.toString()]) && approvals && approvals.length && approvals !== this.state.approvals) {
-          this.setState({
-            approvals,
-            oldApprovals: approvals
-          });
-        } else {
-          this.setState({
-            approvals: this.emptyApprovals()
-          });
-        }
+        this.setState({
+          votes: candidates.reduce((votes: Record<string, boolean>, accountId, index): Record<string, boolean> => ({
+            ...votes,
+            [accountId.toString()]: approvals[index] || false
+          }), {})
+        });
+      });
+  }
+
+  private fetchVotes = (accountId: string | null): void => {
+    const { api } = this.props;
+
+    if (!accountId || !api.tx.electionsPhragmen) {
+      return;
+    }
+
+    api.query.electionsPhragmen
+      .votesOf<[AccountId[]] & Codec>(accountId)
+      .then(([existingVotes]): void => {
+        existingVotes.forEach((accountId): void => {
+          this.onChangeVote(accountId.toString())(true);
+        });
       });
   }
 
   protected onChangeAccount = (accountId: string | null): void => {
-    this.setState({
-      accountId,
-      oldApprovals: null
-    });
+    const { api } = this.props;
+
+    this.setState({ accountId });
+
+    api.tx.electionsPhragmen
+      ? this.fetchVotes(accountId)
+      : this.fetchApprovals(accountId);
   }
 
-  private onChangeVote = (index: number): (isChecked?: boolean) => void =>
-    (isChecked?: boolean): void => {
-      this.setState(({ approvals }: State): Pick<State, never> => {
-        if (!approvals) {
-          return {};
+  private onChangeVote = (accountId: string): (isChecked: boolean) => void =>
+    (isChecked: boolean): void => {
+      this.setState(({ votes }: State): Pick<State, never> => ({
+        votes: {
+          ...votes,
+          [accountId]: isChecked
         }
-        return {
-          approvals: approvals.map((b, i): boolean => i === index ? isChecked || !approvals[index] : b)
-        };
-      });
+      }));
     }
-
-  private onRetractVote = (): void => {
-    this.setState({
-      approvals: this.emptyApprovals(),
-      oldApprovals: null
-    });
-  }
 }
 
 export default withMulti(
   Vote,
   translate,
-  withApi,
   withCalls<Props>(
     ['derive.elections.voterPositions', { propName: 'voterPositions' }]
   )

+ 38 - 0
packages/app-council/src/Overview/VoteValue.tsx

@@ -0,0 +1,38 @@
+// Copyright 2017-2019 @polkadot/ui-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 { DerivedBalances } from '@polkadot/api-derive/types';
+import { I18nProps } from '@polkadot/react-components/types';
+
+import BN from 'bn.js';
+import React from 'react';
+import { InputBalance } from '@polkadot/react-components';
+import { BalanceVoting } from '@polkadot/react-query';
+
+import translate from '../translate';
+
+interface Props extends I18nProps {
+  accountId?: string | null;
+  allBalances?: DerivedBalances;
+  onChange: (value: BN) => void;
+}
+
+const ZERO = new BN(0);
+
+function VoteValue ({ accountId, onChange, t }: Props): React.ReactElement<Props> {
+  const _setVoteValue = (value?: BN): void => {
+    onChange(value || ZERO);
+  };
+
+  return (
+    <InputBalance
+      help={t('The amount that is associated with this vote. This value is is locked for the duration of the vote.')}
+      label={t('vote value')}
+      labelExtra={<BalanceVoting label={t('voting balance ')} params={accountId} />}
+      onChange={_setVoteValue}
+    />
+  );
+}
+
+export default translate(VoteValue);

+ 38 - 0
packages/app-council/src/Overview/Voters.tsx

@@ -0,0 +1,38 @@
+// Copyright 2017-2019 @polkadot/app-democracy 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 { I18nProps } from '@polkadot/react-components/types';
+import { AccountId } from '@polkadot/types/interfaces';
+
+import React from 'react';
+import { AddressMini } from '@polkadot/react-components';
+
+import translate from '../translate';
+
+interface Props extends I18nProps {
+  voters: AccountId[];
+}
+
+function Voters ({ voters, t }: Props): React.ReactElement<Props> | null {
+  return (
+    <details>
+      <summary>
+        {t('Voters ({{count}})', {
+          replace: {
+            count: voters.length
+          }
+        })}
+      </summary>
+      {voters.map((who): React.ReactNode =>
+        <AddressMini
+          key={who.toString()}
+          value={who}
+          withLockedVote
+        />
+      )}
+    </details>
+  );
+}
+
+export default translate(Voters);

+ 20 - 14
packages/app-council/src/Overview/index.tsx

@@ -3,7 +3,8 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { DerivedElectionsInfo } from '@polkadot/api-derive/types';
-import { ComponentProps as Props } from './types';
+import { BlockNumber } from '@polkadot/types/interfaces';
+import { ComponentProps } from './types';
 
 import React from 'react';
 import { withCalls } from '@polkadot/react-api';
@@ -15,21 +16,26 @@ import SubmitCandidacy from './SubmitCandidacy';
 import Summary from './Summary';
 import Vote from './Vote';
 
+interface Props extends ComponentProps {
+  bestNumber?: BlockNumber;
+}
+
 const NULL_INFO: DerivedElectionsInfo = {
-  members: {},
   candidates: [],
   candidateCount: createType('u32'),
   desiredSeats: createType('u32'),
-  nextVoterSet: createType('SetIndex'),
-  termDuration: createType('BlockNumber'),
-  voteCount: createType('VoteIndex'),
-  voterCount: createType('SetIndex')
+  members: [],
+  runnersUp: [],
+  termDuration: createType('BlockNumber')
 };
 
-function Overview ({ electionsInfo = NULL_INFO }: Props): React.ReactElement<Props> {
+function Overview ({ bestNumber, electionsInfo = NULL_INFO }: Props): React.ReactElement<Props> {
   return (
     <>
-      <Summary electionsInfo={electionsInfo} />
+      <Summary
+        bestNumber={bestNumber}
+        electionsInfo={electionsInfo}
+      />
       <Button.Group>
         <SubmitCandidacy electionsInfo={electionsInfo} />
         <Button.Or />
@@ -41,10 +47,10 @@ function Overview ({ electionsInfo = NULL_INFO }: Props): React.ReactElement<Pro
 }
 
 export default withCalls<Props>(
-  [
-    'derive.elections.info',
-    {
-      propName: 'electionsInfo'
-    }
-  ]
+  ['derive.elections.info', {
+    propName: 'electionsInfo'
+  }],
+  ['derive.chain.bestNumber', {
+    propName: 'bestNumber'
+  }]
 )(Overview);

+ 4 - 4
packages/app-dashboard/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-dashboard",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "Dashboard for all apps, allowing for an overview and quick navigation",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/apps-routing": "^0.36.0-beta.101",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/apps-routing": "^0.37.0-beta.63",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 4 - 4
packages/app-democracy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-democracy",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A referendum & proposal app",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-query": "^0.37.0-beta.63"
   }
 }

+ 3 - 3
packages/app-explorer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-explorer",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,7 +10,7 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 37 - 39
packages/app-explorer/src/Events.tsx

@@ -29,47 +29,45 @@ function Events ({ emptyLabel, eventClassName, events, withoutIndex, t }: Props)
 
   return (
     <>
-      {events.map(
-        ({ key, record: { event, phase } }: KeyedEvent): React.ReactNode => {
-          const extIndex = !withoutIndex && phase.isApplyExtrinsic
-            ? phase.asApplyExtrinsic
-            : -1;
+      {events.map(({ key, record: { event, phase } }: KeyedEvent): React.ReactNode => {
+        const extIndex = !withoutIndex && phase.isApplyExtrinsic
+          ? phase.asApplyExtrinsic
+          : -1;
 
-          if (!event.method || !event.section) {
-            return null;
-          }
-
-          return (
-            <article
-              className={`explorer--Container ${eventClassName}`}
-              key={key}
-            >
-              <div className='header'>
-                <h3>
-                  {event.section}.{event.method}&nbsp;{
-                    extIndex !== -1
-                      ? `(#${formatNumber(extIndex)})`
-                      : ''
-                  }
-                </h3>
-              </div>
-              <details>
-                <summary>
-                  {
-                    event.meta && event.meta.documentation
-                      ? event.meta.documentation.join(' ')
-                      : 'Details'
-                  }
-                </summary>
-                <EventDisplay
-                  className='details'
-                  value={event}
-                />
-              </details>
-            </article>
-          );
+        if (!event.method || !event.section) {
+          return null;
         }
-      )}
+
+        return (
+          <article
+            className={`explorer--Container ${eventClassName}`}
+            key={key}
+          >
+            <div className='header'>
+              <h3>
+                {event.section}.{event.method}&nbsp;{
+                  extIndex !== -1
+                    ? `(#${formatNumber(extIndex)})`
+                    : ''
+                }
+              </h3>
+            </div>
+            <details>
+              <summary>
+                {
+                  event.meta && event.meta.documentation
+                    ? event.meta.documentation.join(' ')
+                    : 'Details'
+                }
+              </summary>
+              <EventDisplay
+                className='details'
+                value={event}
+              />
+            </details>
+          </article>
+        );
+      })}
     </>
   );
 }

+ 1 - 1
packages/app-explorer/src/Summary.tsx

@@ -32,7 +32,7 @@ function Summary ({ t }: Props): React.ReactElement<Props> {
         </CardSummary>
       </section>
       <section className='ui--media-large'>
-        <SummarySession />
+        <SummarySession withEra={false} />
       </section>
       <section>
         <CardSummary label={t('finalized')}>

+ 15 - 16
packages/app-explorer/src/SummarySession.tsx

@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 // Copyright 2017-2019 @polkadot/app-explorer authors & contributors
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
@@ -14,57 +13,57 @@ import translate from './translate';
 import { formatNumber } from '@polkadot/util';
 
 interface Props extends I18nProps {
-  session_info?: DerivedSessionInfo;
+  sessionInfo?: DerivedSessionInfo;
   withEra?: boolean;
   withSession?: boolean;
 }
 
-function renderSession ({ session_info, t, withSession = true }: Props): React.ReactNode {
-  if (!withSession || !session_info) {
+function renderSession ({ sessionInfo, t, withSession = true }: Props): React.ReactNode {
+  if (!withSession || !sessionInfo) {
     return null;
   }
 
-  const label = session_info.isEpoch && session_info.sessionLength.gtn(1)
+  const label = sessionInfo.isEpoch && sessionInfo.sessionLength.gtn(1)
     ? t('epoch')
     : t('session');
 
-  return session_info.sessionLength.gtn(0)
+  return sessionInfo.sessionLength.gtn(0)
     ? (
       <CardSummary
         label={label}
         progress={{
-          total: session_info.sessionLength,
-          value: session_info.sessionProgress
+          total: sessionInfo.sessionLength,
+          value: sessionInfo.sessionProgress
         }}
       />
     )
     : (
       <CardSummary label={label}>
-        {formatNumber(session_info.currentIndex)}
+        {formatNumber(sessionInfo.currentIndex)}
       </CardSummary>
     );
 }
 
-function renderEra ({ session_info, t, withEra = true }: Props): React.ReactNode {
-  if (!withEra || !session_info) {
+function renderEra ({ sessionInfo, t, withEra = true }: Props): React.ReactNode {
+  if (!withEra || !sessionInfo) {
     return null;
   }
 
   const label = t('era');
 
-  return session_info.sessionLength.gtn(0)
+  return sessionInfo.sessionLength.gtn(0)
     ? (
       <CardSummary
         label={label}
         progress={{
-          total: session_info && session_info.eraLength,
-          value: session_info && session_info.eraProgress
+          total: sessionInfo.eraLength,
+          value: sessionInfo.eraProgress
         }}
       />
     )
     : (
       <CardSummary label={label}>
-        {formatNumber(session_info.currentEra)}
+        {formatNumber(sessionInfo.currentEra)}
       </CardSummary>
     );
 }
@@ -80,6 +79,6 @@ function SummarySession (props: Props): React.ReactElement<Props> {
 
 export default translate(
   withCalls<Props>(
-    'derive.session.info'
+    ['derive.session.info', { propName: 'sessionInfo' }]
   )(SummarySession)
 );

+ 10 - 54
packages/app-explorer/src/index.tsx

@@ -3,20 +3,16 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import uiSettings from '@polkadot/joy-settings/';
-import { ApiProps } from '@polkadot/react-api/types';
 import { AppProps, BareProps, I18nProps } from '@polkadot/react-components/types';
-import { EventRecord } from '@polkadot/types/interfaces';
 import { KeyedEvent } from './types';
 
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useContext } from 'react';
 import { Route, Switch } from 'react-router';
 import styled from 'styled-components';
-import { HeaderExtended } from '@polkadot/api-derive';
-import { ApiContext, withCalls, withMulti } from '@polkadot/react-api';
+import { ApiContext } from '@polkadot/react-api';
 import Tabs from '@polkadot/react-components/Tabs';
 
-import { stringToU8a } from '@polkadot/util';
-import { xxhashAsHex } from '@polkadot/util-crypto';
+import { BlockAuthorsContext, EventsContext } from '@polkadot/react-query';
 
 import BlockInfo from './BlockInfo';
 import Forks from './Forks';
@@ -24,43 +20,14 @@ import Main from './Main';
 import NodeInfo from './NodeInfo';
 import translate from './translate';
 
-interface Props extends ApiProps, AppProps, BareProps, I18nProps {
-  newHeader?: HeaderExtended;
+interface Props extends AppProps, BareProps, I18nProps {
   newEvents?: KeyedEvent[];
 }
 
-const MAX_ITEMS = 15;
-
-function ExplorerApp ({ basePath, className, newEvents, newHeader, t }: Props): React.ReactElement<Props> {
-  const [headers, setHeaders] = useState<HeaderExtended[]>([]);
-  const [{ prevEventHash, events }, setEvents] = useState<{ prevEventHash: string; events: KeyedEvent[] }>({ prevEventHash: '', events: [] });
+function ExplorerApp ({ basePath, className, t }: Props): React.ReactElement<Props> {
   const { api } = useContext(ApiContext);
-
-  useEffect((): void => {
-    const newEventHash = xxhashAsHex(stringToU8a(JSON.stringify(newEvents)));
-
-    if (newEventHash !== prevEventHash && newEvents) {
-      setEvents({
-        prevEventHash: newEventHash,
-        events: newEvents.concat(events).filter((_, index): boolean => index < MAX_ITEMS)
-      });
-    }
-  }, [newEvents]);
-
-  useEffect((): void => {
-    if (newHeader) {
-      setHeaders(
-        headers
-          .filter((old, index): boolean => index < MAX_ITEMS && old.number.unwrap().lt(newHeader.number.unwrap()))
-          .reduce((next, header): HeaderExtended[] => {
-            next.push(header);
-
-            return next;
-          }, [newHeader])
-          .sort((a, b): number => b.number.unwrap().cmp(a.number.unwrap()))
-      );
-    }
-  }, [newHeader]);
+  const { lastHeaders } = useContext(BlockAuthorsContext);
+  const events = useContext(EventsContext);
 
   return (
     <main className={className}>
@@ -102,7 +69,7 @@ function ExplorerApp ({ basePath, className, newEvents, newHeader, t }: Props):
         <Route render={(): React.ReactElement<{}> => (
           <Main
             events={events}
-            headers={headers}
+            headers={lastHeaders}
           />
         )} />
       </Switch>
@@ -110,21 +77,10 @@ function ExplorerApp ({ basePath, className, newEvents, newHeader, t }: Props):
   );
 }
 
-export default withMulti(
+export default translate(
   styled(ExplorerApp)`
     .rx--updated {
       background: transparent !important;
     }
-  `,
-  translate,
-  withCalls<Props>(
-    ['query.system.events', {
-      propName: 'newEvents',
-      transform: (records: EventRecord[]): KeyedEvent[] =>
-        records
-          .filter(({ event }): boolean => event.section !== 'system')
-          .map((record, index): KeyedEvent => ({ key: `${Date.now()}-${index}`, record }))
-    }],
-    ['derive.chain.subscribeNewHeads', { propName: 'newHeader' }]
-  )
+  `
 );

+ 5 - 5
packages/app-extrinsics/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-extrinsics",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,9 +10,9 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-params": "^0.36.0-beta.101",
-    "@polkadot/react-signer": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-params": "^0.37.0-beta.63",
+    "@polkadot/react-signer": "^0.37.0-beta.63"
   }
 }

+ 3 - 3
packages/app-generic-asset/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-generic-asset",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A basic GenericAsset transfer app",
   "main": "index.js",
   "scripts": {},
@@ -10,7 +10,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 1 - 1
packages/app-generic-asset/src/AssetRow.tsx

@@ -16,7 +16,7 @@ type Props = I18nProps & RowProps & {
 }
 
 class AssetRow extends Row<Props, RowState> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.state.name = this.props.defaultName || 'New Asset';

+ 3 - 3
packages/app-generic-asset/src/Transfer.tsx

@@ -63,7 +63,7 @@ function Transfer ({ assets, className, onClose, recipientId: propRecipientId, s
     }
   };
 
-  const available = <span className='label'>{t('available ')}</span>;
+  const transferrable = <span className='label'>{t('transferrable ')}</span>;
 
   return (
     <div>
@@ -73,7 +73,7 @@ function Transfer ({ assets, className, onClose, recipientId: propRecipientId, s
           help={t('The account you will send funds from.')}
           isDisabled={!!propSenderId}
           label={t('send from account')}
-          labelExtra={<Available label={available} params={senderId} />}
+          labelExtra={<Available label={transferrable} params={senderId} />}
           onChange={setSenderId}
           type='account'
         />
@@ -82,7 +82,7 @@ function Transfer ({ assets, className, onClose, recipientId: propRecipientId, s
           help={t('Select a contact or paste the address you want to send funds to.')}
           isDisabled={!!propRecipientId}
           label={t('send to address')}
-          labelExtra={<Available label={available} params={recipientId} />}
+          labelExtra={<Available label={transferrable} params={recipientId} />}
           onChange={setRecipientId}
           type='allPlus'
         />

+ 3 - 3
packages/app-js/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-js",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A simple JavaScript console for playing with the API",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
     "snappyjs": "^0.6.0"
   }
 }

+ 2 - 1
packages/app-js/src/Playground.tsx

@@ -180,7 +180,6 @@ function Playground ({ className, history, match: { params: { base64 } }, t }: P
   };
   const _runJs = async (): Promise<void> => {
     setIsRunning(true);
-    _stopJs();
     _clearConsole();
 
     injectedRef.current = {
@@ -207,6 +206,8 @@ function Playground ({ className, history, match: { params: { base64 } }, t }: P
 
     // eslint-disable-next-line no-new-func
     new Function('injected', exec)(injectedRef.current);
+
+    setIsRunning(false);
   };
   const _selectExample = (value: string): void => {
     _stopJs();

+ 4 - 4
packages/app-parachains/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-parachains",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "Parachains",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-query": "^0.37.0-beta.63"
   }
 }

+ 4 - 4
packages/app-settings/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-settings",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "Settings management",
   "main": "index.js",
   "scripts": {},
@@ -10,9 +10,9 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-query": "^0.37.0-beta.63",
     "query-string": "^6.8.3"
   }
 }

+ 5 - 5
packages/app-staking/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-staking",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A basic staking app",
   "main": "index.js",
   "scripts": {},
@@ -10,9 +10,9 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/app-explorer": "^0.36.0-beta.101",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/app-explorer": "^0.37.0-beta.63",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-query": "^0.37.0-beta.63"
   }
 }

+ 26 - 14
packages/app-staking/src/Actions/Account/BondExtra.tsx

@@ -18,6 +18,7 @@ import { bnMax } from '@polkadot/util';
 
 import translate from '../../translate';
 import detectUnsafe from '../../unsafeChains';
+import ValidateAmount from './InputValidateAmount';
 
 interface Props extends I18nProps, ApiProps, CalculateBalanceProps {
   controllerId: string;
@@ -27,6 +28,7 @@ interface Props extends I18nProps, ApiProps, CalculateBalanceProps {
 }
 
 interface State {
+  amountError: string | null;
   extrinsic: SubmittableExtrinsic | null;
   maxAdditional?: BN;
   maxBalance?: BN;
@@ -36,6 +38,7 @@ const ZERO = new BN(0);
 
 class BondExtra extends TxComponent<Props, State> {
   public state: State = {
+    amountError: null,
     extrinsic: null
   };
 
@@ -53,9 +56,9 @@ class BondExtra extends TxComponent<Props, State> {
   }
 
   public render (): React.ReactNode {
-    const { balances_all = ZERO_BALANCE, isOpen, onClose, stashId, t } = this.props;
-    const { extrinsic, maxAdditional, maxBalance = balances_all.availableBalance } = this.state;
-    const canSubmit = !!maxAdditional && maxAdditional.gtn(0) && maxAdditional.lte(maxBalance);
+    const { isOpen, onClose, stashId, t } = this.props;
+    const { extrinsic, maxAdditional } = this.state;
+    const canSubmit = !!maxAdditional && maxAdditional.gtn(0);
 
     if (!isOpen) {
       return null;
@@ -96,8 +99,8 @@ class BondExtra extends TxComponent<Props, State> {
 
   private renderContent (): React.ReactNode {
     const { stashId, systemChain, t } = this.props;
-    const { maxBalance } = this.state;
-    const available = <span className='label'>{t('available ')}</span>;
+    const { amountError, maxAdditional, maxBalance } = this.state;
+    const transferrable = <span className='label'>{t('transferrable ')}</span>;
     const isUnsafeChain = detectUnsafe(systemChain);
 
     return (
@@ -111,18 +114,24 @@ class BondExtra extends TxComponent<Props, State> {
             defaultValue={stashId}
             isDisabled
             label={t('stash account')}
-            labelExtra={<Available label={available} params={stashId} />}
+            labelExtra={<Available label={transferrable} params={stashId} />}
           />
           <InputBalance
             autoFocus
             className='medium'
             help={t('Amount to add to the currently bonded funds. This is adjusted using the available funds on the account.')}
+            isError={!!amountError || !maxAdditional || maxAdditional.eqn(0)}
             label={t('additional bonded funds')}
             maxValue={maxBalance}
             onChange={this.onChangeValue}
             onEnter={this.sendTx}
             withMax={!isUnsafeChain}
           />
+          <ValidateAmount
+            accountId={stashId}
+            onError={this.setAmountError}
+            value={maxAdditional}
+          />
         </Modal.Content>
       </>
     );
@@ -131,12 +140,13 @@ class BondExtra extends TxComponent<Props, State> {
   private nextState (newState: Partial<State>): void {
     this.setState((prevState: State): State => {
       const { api } = this.props;
-      const { maxAdditional = prevState.maxAdditional, maxBalance = prevState.maxBalance } = newState;
+      const { amountError = prevState.amountError, maxAdditional = prevState.maxAdditional, maxBalance = prevState.maxBalance } = newState;
       const extrinsic = (maxAdditional && maxAdditional.gte(ZERO))
         ? api.tx.staking.bondExtra(maxAdditional)
         : null;
 
       return {
+        amountError,
         extrinsic,
         maxAdditional,
         maxBalance
@@ -145,11 +155,11 @@ class BondExtra extends TxComponent<Props, State> {
   }
 
   private setMaxBalance = (): void => {
-    const { api, system_accountNonce = ZERO, balances_fees = ZERO_FEES, balances_all = ZERO_BALANCE } = this.props;
+    const { api, balances_fees = ZERO_FEES, balances_all = ZERO_BALANCE } = this.props;
     const { maxAdditional } = this.state;
 
     const { transactionBaseFee, transactionByteFee } = balances_fees;
-    const { availableBalance } = balances_all;
+    const { accountNonce, freeBalance } = balances_all;
 
     let prevMax = new BN(0);
     let maxBalance = new BN(1);
@@ -161,10 +171,10 @@ class BondExtra extends TxComponent<Props, State> {
         ? api.tx.staking.bondExtra(maxAdditional)
         : null;
 
-      const txLength = calcTxLength(extrinsic, system_accountNonce);
+      const txLength = calcTxLength(extrinsic, accountNonce);
       const fees = transactionBaseFee.add(transactionByteFee.mul(txLength));
 
-      maxBalance = bnMax(availableBalance.sub(fees), ZERO);
+      maxBalance = bnMax(freeBalance.sub(fees), ZERO);
     }
 
     this.nextState({
@@ -177,6 +187,10 @@ class BondExtra extends TxComponent<Props, State> {
   private onChangeValue = (maxAdditional?: BN): void => {
     this.nextState({ maxAdditional });
   }
+
+  private setAmountError = (amountError: string | null): void => {
+    this.setState({ amountError });
+  }
 }
 
 export default withMulti(
@@ -184,8 +198,6 @@ export default withMulti(
   translate,
   withApi,
   withCalls<Props>(
-    'derive.balances.fees',
-    ['derive.balances.all', { paramName: 'stashId' }],
-    ['query.system.accountNonce', { paramName: 'stashId' }]
+    'derive.balances.fees'
   )
 );

+ 61 - 0
packages/app-staking/src/Actions/Account/InputValidateAmount.tsx

@@ -0,0 +1,61 @@
+// Copyright 2017-2019 @polkadot/ui-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 { DerivedBalances } from '@polkadot/api-derive/types';
+import { ApiProps } from '@polkadot/react-api/types';
+import { I18nProps } from '@polkadot/react-components/types';
+
+import BN from 'bn.js';
+import React, { useEffect, useState } from 'react';
+import { Icon } from '@polkadot/react-components';
+import { withCalls } from '@polkadot/react-api';
+
+import translate from '../../translate';
+
+interface Props extends ApiProps, I18nProps {
+  allBalances?: DerivedBalances;
+  accountId: string | null;
+  onError: (error: string | null) => void;
+  value?: BN | null;
+}
+
+function ValidateAmount ({ allBalances, onError, value, t }: Props): React.ReactElement<Props> | null {
+  const [error, setError] = useState<string | null>(null);
+
+  useEffect((): void => {
+    // don't show an error if the selected controller is the default
+    // this applies when changing controller
+    if (allBalances && value) {
+      let newError: string | null = null;
+
+      if (value.gt(allBalances.freeBalance)) {
+        newError = t('The specified value is greater than your free balance. The node will bond the maximum amount available.');
+      }
+
+      if (error !== newError) {
+        onError(newError);
+        setError(newError);
+      }
+    }
+  }, [allBalances, value]);
+
+  if (!error) {
+    return null;
+  }
+
+  return (
+    <article className='warning'>
+      <div><Icon name='warning sign' />{error}</div>
+    </article>
+  );
+}
+
+export default translate(
+  withCalls<Props>(
+    ['derive.balances.all', {
+      paramName: 'accountId',
+      propName: 'allBalances'
+    }]
+  )(ValidateAmount)
+);

+ 14 - 16
packages/app-staking/src/Actions/Account/InputValidationController.tsx

@@ -29,29 +29,27 @@ function ValidateController ({ accountId, bondedId, controllerId, defaultControl
   const [error, setError] = useState<string | null>(null);
 
   useEffect((): void => {
-    const newError = ((): string | null => {
-      if (defaultController === controllerId) {
-        // don't show an error if the selected controller is the default
-        // this applies when changing controller
-        return null;
-      } else if (controllerId === accountId) {
-        return isUnsafeChain
+    // don't show an error if the selected controller is the default
+    // this applies when changing controller
+    if (defaultController !== controllerId) {
+      let newError: string | null = null;
+
+      if (controllerId === accountId) {
+        newError = isUnsafeChain
           ? t(`${DISTINCT} You will be allowed to make the transaction, but take care to not tie up all funds, only use a portion of the available funds during this period.`)
           : t(DISTINCT);
       } else if (bondedId) {
-        return t('A controller account should not map to another stash. This selected controller is a stash, controlled by {{bondedId}}', { replace: { bondedId } });
+        newError = t('A controller account should not map to another stash. This selected controller is a stash, controlled by {{bondedId}}', { replace: { bondedId } });
       } else if (stashId) {
-        return t('A controller account should not be set to manages multiple stashes. The selected controller is already controlling {{stashId}}', { replace: { stashId } });
+        newError = t('A controller account should not be set to manages multiple stashes. The selected controller is already controlling {{stashId}}', { replace: { stashId } });
       }
 
-      return null;
-    })();
-
-    if (error !== newError) {
-      onError(newError);
-      setError(newError);
+      if (error !== newError) {
+        onError(newError);
+        setError(newError);
+      }
     }
-  }, [accountId, controllerId, defaultController]);
+  }, [accountId, bondedId, controllerId, defaultController, stashId]);
 
   if (!error || !accountId) {
     return null;

+ 1 - 1
packages/app-staking/src/Actions/Account/SetControllerAccount.tsx

@@ -26,7 +26,7 @@ interface State {
 }
 
 class SetControllerAccount extends TxComponent<Props, State> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.state = {

+ 1 - 1
packages/app-staking/src/Actions/Account/SetRewardDestination.tsx

@@ -22,7 +22,7 @@ interface State {
 }
 
 class SetRewardDestination extends TxComponent<Props, State> {
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.state = {

+ 32 - 56
packages/app-staking/src/Actions/Account/index.tsx

@@ -3,10 +3,10 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { DerivedBalances, DerivedStaking, DerivedStakingOnlineStatus } from '@polkadot/api-derive/types';
+import { DerivedBalances, DerivedStaking, DerivedStakingOnlineStatus, DerivedHeartbeats } from '@polkadot/api-derive/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { AccountId, BlockNumber, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces';
+import { AccountId, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces';
 import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
 
 import { Popup } from 'semantic-ui-react';
@@ -26,14 +26,12 @@ import Unbond from './Unbond';
 import Validate from './Validate';
 import { u8aToHex, u8aConcat } from '@polkadot/util';
 
-import { updateOnlineStatus } from '../../util';
-
 interface Props extends ApiProps, I18nProps {
   accountId: string;
   allStashes?: string[];
   balances_all?: DerivedBalances;
   className?: string;
-  recentlyOnline: Record<string, BlockNumber>;
+  recentlyOnline?: DerivedHeartbeats;
   staking_info?: DerivedStaking;
   stashOptions: KeyringSectionOption[];
 }
@@ -70,6 +68,14 @@ const DEFAULT_BALANCES = {
   unlocking: false
 };
 
+const CONTROLLER_BALANCES = {
+  available: true,
+  bonded: false,
+  free: false,
+  redeemable: false,
+  unlocking: false
+};
+
 function toIdString (id?: AccountId | null): string | null {
   return id
     ? id.toString()
@@ -97,12 +103,12 @@ class Account extends React.PureComponent<Props, State> {
     stashId: null
   };
 
-  public static getDerivedStateFromProps ({ allStashes, recentlyOnline, staking_info }: Props): Pick<State, never> | null {
+  public static getDerivedStateFromProps ({ allStashes, staking_info }: Props): Pick<State, never> | null {
     if (!staking_info) {
       return null;
     }
 
-    const { controllerId, nextSessionIds, nominators, online, offline, rewardDestination, sessionIds, stakers, stakingLedger, stashId, validatorPrefs } = staking_info;
+    const { controllerId, nextSessionIds, nominators, rewardDestination, sessionIds, stakers, stakingLedger, stashId, validatorPrefs } = staking_info;
     const isStashNominating = nominators && !!nominators.length;
     const _stashId = toIdString(stashId);
     const isStashValidating = !!allStashes && !!_stashId && allStashes.includes(_stashId);
@@ -118,7 +124,6 @@ class Account extends React.PureComponent<Props, State> {
       isStashNominating,
       isStashValidating,
       nominees: nominators && nominators.map(toIdString),
-      onlineStatus: updateOnlineStatus(recentlyOnline)(sessionIds || null, { online, offline }),
       sessionIds: (
         nextSessionIds.length
           ? nextSessionIds
@@ -133,7 +138,7 @@ class Account extends React.PureComponent<Props, State> {
 
   public render (): React.ReactNode {
     const { className, isSubstrateV2, t } = this.props;
-    const { controllerId, hexSessionId, isBondExtraOpen, isInjectOpen, isStashValidating, isUnbondOpen, nominees, sessionIds, stashId } = this.state;
+    const { controllerId, hexSessionId, isBondExtraOpen, isInjectOpen, isStashValidating, isUnbondOpen, nominees, onlineStatus, sessionIds, stashId } = this.state;
 
     if (!stashId) {
       return null;
@@ -146,7 +151,12 @@ class Account extends React.PureComponent<Props, State> {
     return (
       <AddressCard
         buttons={this.renderButtons()}
-        iconInfo={this.renderOnlineStatus()}
+        iconInfo={onlineStatus && (
+          <OnlineStatus
+            isTooltip
+            value={onlineStatus}
+          />
+        )}
         label={t('stash')}
         type='account'
         value={stashId}
@@ -176,7 +186,16 @@ class Account extends React.PureComponent<Props, State> {
         {this.renderValidate()}
         <div className={className}>
           <div className='staking--Accounts'>
-            {this.renderControllerAccount()}
+            {controllerId && (
+              <div className='staking--Account-detail actions'>
+                <AddressRow
+                  label={t('controller')}
+                  value={controllerId}
+                  withAddressOrName
+                  withBalance={CONTROLLER_BALANCES}
+                />
+              </div>
+            )}
             {!isSubstrateV2 && !!sessionIds.length && (
               <div className='staking--Account-detail actions'>
                 <AddressRow
@@ -229,49 +248,6 @@ class Account extends React.PureComponent<Props, State> {
     );
   }
 
-  private renderOnlineStatus (): React.ReactNode {
-    const { onlineStatus, controllerId } = this.state;
-
-    if (!controllerId || !onlineStatus) {
-      return null;
-    }
-
-    return (
-      <OnlineStatus
-        accountId={controllerId}
-        value={onlineStatus}
-        tooltip
-      />
-    );
-  }
-
-  private renderControllerAccount (): React.ReactNode {
-    const { t } = this.props;
-    const { controllerId } = this.state;
-
-    if (!controllerId) {
-      return null;
-    }
-
-    return (
-      <div className='staking--Account-detail actions'>
-        <AddressRow
-          label={t('controller')}
-          value={controllerId}
-          withAddressOrName
-          withBalance={{
-            available: true,
-            bonded: false,
-            free: false,
-            redeemable: false,
-            unlocking: false
-          }}
-        />
-      </div>
-
-    );
-  }
-
   private renderNominate (): React.ReactNode {
     const { stashOptions } = this.props;
     const { controllerId, isNominateOpen, nominees, stashId } = this.state;
@@ -399,7 +375,7 @@ class Account extends React.PureComponent<Props, State> {
 
     // only show a "Bond Additional" button if this stash account actually doesn't bond everything already
     // staking_ledger.total gives the total amount that can be slashed (any active amount + what is being unlocked)
-    const canBondExtra = balances_all && balances_all.availableBalance.gtn(0);
+    const canBondExtra = balances_all && balances_all.freeBalance.gtn(0);
 
     return (
       <Menu
@@ -428,7 +404,7 @@ class Account extends React.PureComponent<Props, State> {
         }
         {!isStashNominating && (!!sessionIds.length || (isSubstrateV2 && hexSessionId !== '0x')) &&
           <Menu.Item onClick={this.toggleSetSessionAccount}>
-            {isSubstrateV2 ? t('Rotate session keys') : t('Change session account')}
+            {isSubstrateV2 ? t('Change session keys') : t('Change session account')}
           </Menu.Item>
         }
         {isStashNominating &&

+ 2 - 2
packages/app-staking/src/Actions/Accounts.tsx

@@ -12,8 +12,8 @@ import { withCalls, withMulti } from '@polkadot/react-api/with';
 import React, { useState } from 'react';
 import styled from 'styled-components';
 import { Button, CardGrid } from '@polkadot/react-components';
+import { AccountName } from '@polkadot/react-query';
 import createOption from '@polkadot/ui-keyring/options/item';
-import { getAddressName } from '@polkadot/react-components/util';
 
 import Account from './Account';
 import StartStaking from './NewStake';
@@ -43,7 +43,7 @@ function Accounts ({ allAccounts, allStashes, className, myControllers, recently
   const [isNewStakeOpen, setIsNewStateOpen] = useState(false);
   const myStashes = getMyStashes(myControllers, allAccounts);
   const stashOptions = allStashes.map((stashId): KeyringSectionOption =>
-    createOption(stashId, getAddressName(stashId, 'account'))
+    createOption(stashId, (<AccountName params={stashId} />) as any)
   );
   const isEmpty = !isNewStakeOpen && (!myStashes || myStashes.length === 0);
 

+ 17 - 4
packages/app-staking/src/Actions/NewStake.tsx

@@ -14,6 +14,7 @@ import { withApi, withMulti } from '@polkadot/react-api';
 
 import translate from '../translate';
 import detectUnsafe from '../unsafeChains';
+import InputValidateAmount from './Account/InputValidateAmount';
 import InputValidationController from './Account/InputValidationController';
 import { rewardDestinationOptions } from './constants';
 
@@ -22,6 +23,7 @@ interface Props extends ApiProps, I18nProps, CalculateBalanceProps {
 }
 
 interface State {
+  amountError: string | null;
   bondValue?: BN;
   controllerError: string | null;
   controllerId: string | null;
@@ -33,10 +35,11 @@ interface State {
 class NewStake extends TxComponent<Props, State> {
   public state: State;
 
-  public constructor (props: Props) {
+  constructor (props: Props) {
     super(props);
 
     this.state = {
+      amountError: null,
       controllerError: null,
       controllerId: null,
       destination: 0,
@@ -47,7 +50,7 @@ class NewStake extends TxComponent<Props, State> {
 
   public render (): React.ReactNode {
     const { onClose, systemChain, t } = this.props;
-    const { bondValue, controllerError, controllerId, destination, extrinsic, stashId } = this.state;
+    const { amountError, bondValue, controllerError, controllerId, destination, extrinsic, stashId } = this.state;
     const hasValue = !!bondValue && bondValue.gtn(0);
     const isUnsafeChain = detectUnsafe(systemChain);
     const canSubmit = (hasValue && (isUnsafeChain || (!controllerError && !!controllerId)));
@@ -92,13 +95,18 @@ class NewStake extends TxComponent<Props, State> {
             destination={destination}
             extrinsicProp={'staking.bond'}
             help={t('The total amount of the stash balance that will be at stake in any forthcoming rounds (should be less than the total amount available)')}
-            isError={!hasValue}
+            isError={!hasValue || !!amountError}
             label={t('value bonded')}
             onChange={this.onChangeValue}
             onEnter={this.sendTx}
             stashId={stashId}
             withMax={!isUnsafeChain}
           />
+          <InputValidateAmount
+            accountId={stashId}
+            onError={this.onAmountError}
+            value={bondValue}
+          />
           <Dropdown
             className='medium'
             defaultValue={0}
@@ -137,12 +145,13 @@ class NewStake extends TxComponent<Props, State> {
   private nextState (newState: Partial<State>): void {
     this.setState((prevState: State): State => {
       const { api } = this.props;
-      const { bondValue = prevState.bondValue, controllerError = prevState.controllerError, controllerId = prevState.controllerId, destination = prevState.destination, stashId = prevState.stashId } = newState;
+      const { amountError = prevState.amountError, bondValue = prevState.bondValue, controllerError = prevState.controllerError, controllerId = prevState.controllerId, destination = prevState.destination, stashId = prevState.stashId } = newState;
       const extrinsic = (bondValue && controllerId)
         ? api.tx.staking.bond(controllerId, bondValue, destination)
         : null;
 
       return {
+        amountError,
         bondValue,
         controllerError,
         controllerId,
@@ -153,6 +162,10 @@ class NewStake extends TxComponent<Props, State> {
     });
   }
 
+  private onAmountError = (amountError: string | null): void => {
+    this.nextState({ amountError });
+  }
+
   private onChangeController = (controllerId: string | null): void => {
     this.nextState({ controllerId });
   }

+ 120 - 88
packages/app-staking/src/Overview/Address.tsx

@@ -2,8 +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 { AccountId, Balance, BlockNumber } from '@polkadot/types/interfaces';
-import { DerivedStaking, DerivedStakingOnlineStatus } from '@polkadot/api-derive/types';
+import { AccountId, Balance, Points } from '@polkadot/types/interfaces';
+import { DerivedStaking, DerivedHeartbeats } from '@polkadot/api-derive/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ValidatorFilter } from '../types';
 
@@ -11,122 +11,126 @@ import BN from 'bn.js';
 import React, { useContext, useEffect, useState } from 'react';
 import styled from 'styled-components';
 import { ApiContext, withCalls, withMulti } from '@polkadot/react-api';
-import { AddressCard, AddressMini, OnlineStatus } from '@polkadot/react-components';
+import { AddressCard, AddressMini, Badge, Expander, Icon } from '@polkadot/react-components';
 import { classes } from '@polkadot/react-components/util';
-import keyring from '@polkadot/ui-keyring';
-import { formatBalance } from '@polkadot/util';
-import { updateOnlineStatus } from '../util';
+import { formatNumber } from '@polkadot/util';
 
 import translate from '../translate';
 
 interface Props extends I18nProps {
-  address: string;
+  address: AccountId | string;
+  authorsMap: Record<string, string>;
   className?: string;
   defaultName: string;
   filter: ValidatorFilter;
-  lastAuthor: string;
-  lastBlock: string;
-  recentlyOnline?: Record<string, BlockNumber>;
+  isElected: boolean;
+  lastAuthors?: string[];
+  myAccounts: string[];
+  points?: Points;
+  recentlyOnline?: DerivedHeartbeats;
   stakingInfo?: DerivedStaking;
+  withNominations?: boolean;
 }
 
 interface StakingState {
   balanceOpts: { bonded: boolean | BN[] };
   controllerId?: string;
   hasNominators: boolean;
+  isNominatorMe: boolean;
   nominators: [AccountId, Balance][];
-  stashActive: string | null;
-  stashTotal: string | null;
   sessionId?: string;
   stashId?: string;
 }
 
-interface OnlineState {
-  hasOfflineWarnings: boolean;
-  onlineStatus: DerivedStakingOnlineStatus;
-}
-
 const WITH_VALIDATOR_PREFS = { validatorPayment: true };
 
-function Address ({ address, className, defaultName, filter, lastAuthor, lastBlock, recentlyOnline, stakingInfo, t }: Props): React.ReactElement<Props> | null {
+function Address ({ address, authorsMap, className, defaultName, filter, isElected, lastAuthors, myAccounts, points, recentlyOnline, stakingInfo, t, withNominations = true }: Props): React.ReactElement<Props> | null {
   const { isSubstrateV2 } = useContext(ApiContext);
-  const [isNominatorMe, seIsNominatorMe] = useState(false);
-  const [{ hasOfflineWarnings, onlineStatus }, setOnlineStatus] = useState<OnlineState>({
-    hasOfflineWarnings: false,
-    onlineStatus: {}
-  });
-  const [{ balanceOpts, controllerId, hasNominators, nominators, sessionId, stashId }, setStakingState] = useState<StakingState>({
+  const [extraInfo, setExtraInfo] = useState<[React.ReactNode, React.ReactNode][] | undefined>();
+  const [hasActivity, setHasActivity] = useState(true);
+  const [{ balanceOpts, controllerId, hasNominators, isNominatorMe, nominators, sessionId, stashId }, setStakingState] = useState<StakingState>({
     balanceOpts: { bonded: true },
     hasNominators: false,
-    nominators: [],
-    stashActive: null,
-    stashTotal: null
+    isNominatorMe: false,
+    nominators: []
   });
 
+  useEffect((): void => {
+    if (points) {
+      const formatted = formatNumber(points);
+
+      if (!extraInfo || extraInfo[0][1] !== formatted) {
+        setExtraInfo([[t('era points'), formatted]]);
+      }
+    }
+  }, [extraInfo, points]);
+
   useEffect((): void => {
     if (stakingInfo) {
-      const { controllerId, nextSessionId, stakers, stakingLedger, stashId } = stakingInfo;
-      const nominators = stakers
+      const { controllerId, nextSessionIds, stakers, stashId } = stakingInfo;
+      const nominators = withNominations && stakers
         ? stakers.others.map(({ who, value }): [AccountId, Balance] => [who, value.unwrap()])
         : [];
-      const myAccounts = keyring.getAccounts().map(({ address }): string => address);
+      const stakersOwn = stakers && !stakers.own.isEmpty && stakers.own.unwrap();
 
-      seIsNominatorMe(nominators.some(([who]): boolean =>
-        myAccounts.includes(who.toString())
-      ));
       setStakingState({
-        balanceOpts: {
-          bonded: stakers && !stakers.own.isEmpty
-            ? [stakers.own.unwrap(), stakers.total.unwrap().sub(stakers.own.unwrap())]
-            : true
-        },
-        controllerId: controllerId && controllerId.toString(),
+        balanceOpts: stakers && stakersOwn
+          ? { bonded: [stakersOwn, stakers.total.unwrap().sub(stakersOwn)] }
+          : { bonded: true },
+        controllerId: controllerId?.toString(),
         hasNominators: nominators.length !== 0,
+        isNominatorMe: nominators.some(([who]): boolean =>
+          myAccounts.includes(who.toString())
+        ),
         nominators,
-        sessionId: nextSessionId && nextSessionId.toString(),
-        stashActive: stakingLedger
-          ? formatBalance(stakingLedger.active)
-          : null,
-        stashId: stashId && stashId.toString(),
-        stashTotal: stakingLedger
-          ? formatBalance(stakingLedger.total)
-          : null
+        sessionId: nextSessionIds && nextSessionIds[0]?.toString(),
+        stashId: stashId?.toString()
       });
     }
   }, [stakingInfo]);
 
   useEffect((): void => {
-    if (stakingInfo) {
-      const { online, offline, sessionIds, stashId } = stakingInfo;
-      const onlineStatus = updateOnlineStatus(recentlyOnline || {})(sessionIds, { offline, online });
-
-      setOnlineStatus({
-        hasOfflineWarnings: !!(stashId && onlineStatus.offline && onlineStatus.offline.length),
-        onlineStatus
-      });
-    }
-  }, [recentlyOnline, stakingInfo]);
+    setHasActivity(
+      recentlyOnline && recentlyOnline[stashId || '']
+        ? recentlyOnline[stashId || ''].isOnline
+        : true
+    );
+  }, [recentlyOnline, stashId]);
 
   if ((filter === 'hasNominators' && !hasNominators) ||
     (filter === 'noNominators' && hasNominators) ||
-    (filter === 'hasWarnings' && !hasOfflineWarnings) ||
-    (filter === 'noWarnings' && hasOfflineWarnings) ||
-    (filter === 'iNominated' && !isNominatorMe)) {
+    (filter === 'hasWarnings' && hasActivity) ||
+    (filter === 'noWarnings' && !hasActivity) ||
+    (filter === 'iNominated' && !isNominatorMe) ||
+    (filter === 'nextSet' && !isElected)) {
     return null;
   }
 
-  const isAuthor = !!lastBlock && !!lastAuthor && [address, controllerId, stashId].includes(lastAuthor);
+  if (!stashId) {
+    return (
+      <AddressCard
+        className={className}
+        defaultName={defaultName}
+        isDisabled
+        value={address}
+        withBalance={false}
+      />
+    );
+  }
+
+  const lastBlockNumber = authorsMap[stashId];
+  const isAuthor = lastAuthors && lastAuthors.includes(stashId);
 
   return (
     <AddressCard
       buttons={
         <div className='staking--Address-info'>
-          {isAuthor && (
-            <div className={classes(isSubstrateV2 ? 'blockNumberV2' : 'blockNumberV1')}>#{lastBlock}</div>
+          {lastBlockNumber && (
+            <div className={`blockNumberV${isSubstrateV2 ? '2' : '1'} ${isAuthor && 'isCurrent'}`}>#{lastBlockNumber}</div>
           )}
           {controllerId && (
             <div>
-              <label className={classes('staking--label', isSubstrateV2 && !isAuthor && 'controllerSpacer')}>{t('controller')}</label>
+              <label className={classes('staking--label', isSubstrateV2 && !lastBlockNumber && 'controllerSpacer')}>{t('controller')}</label>
               <AddressMini value={controllerId} />
             </div>
           )}
@@ -140,27 +144,46 @@ function Address ({ address, className, defaultName, filter, lastAuthor, lastBlo
       }
       className={className}
       defaultName={defaultName}
-      iconInfo={controllerId && onlineStatus && (
-        <OnlineStatus
-          accountId={controllerId}
-          value={onlineStatus}
-          tooltip
-        />
-      )}
-      key={stashId || controllerId || undefined}
-      value={stashId || address}
+      extraInfo={extraInfo}
+      iconInfo={
+        <>
+          {isElected && (
+            <Badge
+              hover={t('Selected for the next session')}
+              info={<Icon name='chevron right' />}
+              isTooltip
+              type='next'
+            />
+          )}
+          {recentlyOnline && hasActivity && recentlyOnline[stashId] && (
+            <Badge
+              hover={t('Active with {{blocks}} blocks authored{{hasMessage}} heartbeat message', {
+                replace: {
+                  blocks: formatNumber(recentlyOnline[stashId].blockCount),
+                  hasMessage: recentlyOnline[stashId].hasMessage ? ' and a' : ', no'
+                }
+              })}
+              info={<Icon name='check' />}
+              isTooltip
+              type='online'
+            />
+          )}
+        </>
+      }
+      isDisabled={isSubstrateV2 && !hasActivity}
+      stakingInfo={stakingInfo}
+      value={stashId}
       withBalance={balanceOpts}
       withValidatorPrefs={WITH_VALIDATOR_PREFS}
     >
-      {hasNominators && (
-        <details>
-          <summary>
-            {t('Nominators ({{count}})', {
-              replace: {
-                count: nominators.length
-              }
-            })}
-          </summary>
+      {withNominations && hasNominators && (
+        <Expander
+          summary={t('Nominators ({{count}})', {
+            replace: {
+              count: nominators.length
+            }
+          })}
+        >
           {nominators.map(([who, bonded]): React.ReactNode =>
             <AddressMini
               bonded={bonded}
@@ -169,7 +192,7 @@ function Address ({ address, className, defaultName, filter, lastAuthor, lastBlo
               withBonded
             />
           )}
-        </details>
+        </Expander>
       )}
     </AddressCard>
   );
@@ -179,22 +202,31 @@ export default withMulti(
   styled(Address)`
     .blockNumberV1,
     .blockNumberV2 {
-      background: #3f3f3f;
       border-radius: 0.25rem;
-      box-shadow: 0 3px 3px rgba(0,0,0,.2);
-      color: #eee;
       font-size: 1.5rem;
       font-weight: 100;
       line-height: 1.5rem;
+      opacity: 0.5;
       vertical-align: middle;
       z-index: 1;
+
+      &.isCurrent {
+        background: #3f3f3f;
+        box-shadow: 0 3px 3px rgba(0,0,0,.2);
+        color: #eee;
+        opacity: 1;
+      }
     }
 
     .blockNumberV2 {
       display: inline-block;
       margin-bottom: 0.75rem;
-      margin-right: -0.25rem;
-      padding: 0.25rem 0.75rem;
+      padding: 0.25rem 0;
+
+      &.isCurrent {
+        margin-right: -0.25rem;
+        padding: 0.25rem 0.75rem;
+      }
     }
 
     .blockNumberV1 {

+ 63 - 23
packages/app-staking/src/Overview/CurrentList.tsx

@@ -2,40 +2,74 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { BlockNumber } from '@polkadot/types/interfaces';
+import { AccountId } from '@polkadot/types/interfaces';
+import { DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ValidatorFilter } from '../types';
 
-import React, { useState } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
+import { ApiContext } from '@polkadot/react-api';
 import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/react-components';
+import keyring from '@polkadot/ui-keyring';
 
 import translate from '../translate';
 import Address from './Address';
 
 interface Props extends I18nProps {
-  currentValidators: string[];
-  lastAuthor?: string;
-  lastBlock: string;
+  authorsMap: Record<string, string>;
+  lastAuthors?: string[];
   next: string[];
-  recentlyOnline: Record<string, BlockNumber>;
+  recentlyOnline?: DerivedHeartbeats;
+  stakingOverview?: DerivedStakingOverview;
 }
 
-function CurrentList ({ currentValidators, lastAuthor, lastBlock, next, recentlyOnline, t }: Props): React.ReactElement<Props> {
+function renderColumn (myAccounts: string[], addresses: AccountId[] | string[], defaultName: string, withOnline: boolean, filter: string, { authorsMap, lastAuthors, recentlyOnline, stakingOverview }: Props, pointIndexes?: number[]): React.ReactNode {
+  return (addresses as AccountId[]).map((address, index): React.ReactNode => (
+    <Address
+      address={address}
+      authorsMap={authorsMap}
+      defaultName={defaultName}
+      filter={filter}
+      isElected={stakingOverview && stakingOverview.currentElected.some((accountId): boolean => accountId.eq(address))}
+      lastAuthors={lastAuthors}
+      key={address.toString()}
+      myAccounts={myAccounts}
+      points={
+        stakingOverview && pointIndexes && pointIndexes[index] !== -1
+          ? stakingOverview.eraPoints.individual[pointIndexes[index]]
+          : undefined
+      }
+      recentlyOnline={
+        withOnline
+          ? recentlyOnline
+          : undefined
+      }
+    />
+  ));
+}
+
+function filterAccounts (list: string[] = [], without: AccountId[] | string[]): string[] {
+  return list.filter((accountId): boolean => !without.includes(accountId as any));
+}
+
+function CurrentList (props: Props): React.ReactElement<Props> {
+  const { isSubstrateV2 } = useContext(ApiContext);
   const [filter, setFilter] = useState<ValidatorFilter>('all');
+  const [myAccounts] = useState(keyring.getAccounts().map(({ address }): string => address));
+  const [{ electedFiltered, nextFiltered, pointIndexes }, setFiltered] = useState<{ electedFiltered: string[]; nextFiltered: string[]; pointIndexes: number[] }>({ electedFiltered: [], nextFiltered: [], pointIndexes: [] });
+  const { next, stakingOverview, t } = props;
+
+  useEffect((): void => {
+    if (stakingOverview) {
+      const elected = stakingOverview.currentElected.map((accountId): string => accountId.toString());
 
-  const _renderColumn = (addresses: string[], defaultName: string): React.ReactNode => {
-    return addresses.map((address): React.ReactNode => (
-      <Address
-        address={address}
-        defaultName={defaultName}
-        key={address}
-        filter={filter}
-        lastAuthor={lastAuthor}
-        lastBlock={lastBlock}
-        recentlyOnline={recentlyOnline}
-      />
-    ));
-  };
+      setFiltered({
+        electedFiltered: isSubstrateV2 ? filterAccounts(elected, stakingOverview.validators) : [],
+        nextFiltered: filterAccounts(next, elected),
+        pointIndexes: stakingOverview.validators.map((validator): number => elected.indexOf(validator.toString()))
+      });
+    }
+  }, [next, stakingOverview]);
 
   return (
     <div>
@@ -48,7 +82,8 @@ function CurrentList ({ currentValidators, lastAuthor, lastBlock, next, recently
             { text: t('Show only with nominators'), value: 'hasNominators' },
             { text: t('Show only without nominators'), value: 'noNominators' },
             { text: t('Show only with warnings'), value: 'hasWarnings' },
-            { text: t('Show only without warnings'), value: 'noWarnings' }
+            { text: t('Show only without warnings'), value: 'noWarnings' },
+            { text: t('Show only elected for next session'), value: 'nextSet' }
           ]}
           value={filter}
           withLabel={false}
@@ -59,13 +94,18 @@ function CurrentList ({ currentValidators, lastAuthor, lastBlock, next, recently
           emptyText={t('No addresses found')}
           headerText={t('validators')}
         >
-          {_renderColumn(currentValidators, t('validator'))}
+          {stakingOverview && renderColumn(myAccounts, stakingOverview.validators, t('validator'), true, filter, props, pointIndexes)}
         </Column>
         <Column
           emptyText={t('No addresses found')}
           headerText={t('next up')}
         >
-          {_renderColumn(next, t('intention'))}
+          {(electedFiltered.length !== 0 || nextFiltered.length !== 0) && (
+            <>
+              {renderColumn(myAccounts, electedFiltered, t('intention'), false, filter, props)}
+              {renderColumn(myAccounts, nextFiltered, t('intention'), false, filter, props)}
+            </>
+          )}
         </Column>
       </Columar>
     </div>

+ 31 - 22
packages/app-staking/src/Overview/Summary.tsx

@@ -1,52 +1,57 @@
-/* eslint-disable @typescript-eslint/camelcase */
 // 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 { DerivedStakingOverview } from '@polkadot/api-derive/types';
 import { I18nProps } from '@polkadot/react-components/types';
 
-import BN from 'bn.js';
 import React from 'react';
 import styled from 'styled-components';
 import SummarySession from '@polkadot/app-explorer/SummarySession';
 import { CardSummary, IdentityIcon, SummaryBox } from '@polkadot/react-components';
-import { withCalls, withMulti } from '@polkadot/react-api';
 
 import translate from '../translate';
 
 interface Props extends I18nProps {
   allControllers: string[];
   className?: string;
-  currentValidators: string[];
-  lastAuthor?: string;
-  lastBlock: string;
-  staking_validatorCount?: BN;
+  lastAuthors?: string[];
+  lastBlock?: string;
   next: string[];
+  stakingOverview?: DerivedStakingOverview;
 }
 
-function Summary ({ className, currentValidators, lastAuthor, lastBlock, next, style, staking_validatorCount, t }: Props): React.ReactElement<Props> {
+function Summary ({ className, lastAuthors, lastBlock, next, stakingOverview, style, t }: Props): React.ReactElement<Props> {
   return (
     <SummaryBox
       className={className}
       style={style}
     >
       <section>
-        <CardSummary label={t('validators')}>
-          {currentValidators.length}/{staking_validatorCount ? staking_validatorCount.toString() : '-'}
-        </CardSummary>
-        <CardSummary label={t('waiting')}>
-          {next.length}
-        </CardSummary>
+        {stakingOverview && (
+          <CardSummary label={t('validators')}>
+            {stakingOverview.validators.length}{`/${stakingOverview.validatorCount.toString()}`}
+          </CardSummary>
+        )}
+        {next && (
+          <CardSummary label={t('waiting')}>
+            {next.length}
+          </CardSummary>
+        )}
       </section>
       <section>
-        <CardSummary label={t('last block')}>
-          {lastAuthor && (
+        <CardSummary
+          className='validator--Summary-authors'
+          label={t('last block')}
+        >
+          {lastAuthors && lastAuthors.map((author): React.ReactNode => (
             <IdentityIcon
               className='validator--Account-block-icon'
+              key={author}
               size={24}
-              value={lastAuthor}
+              value={author}
             />
-          )}
+          ))}
           {lastBlock}
         </CardSummary>
       </section>
@@ -57,14 +62,18 @@ function Summary ({ className, currentValidators, lastAuthor, lastBlock, next, s
   );
 }
 
-export default withMulti(
+export default translate(
   styled(Summary)`
     .validator--Account-block-icon {
       margin-right: 0.75rem;
       margin-top: -0.25rem;
       vertical-align: middle;
     }
-  `,
-  translate,
-  withCalls<Props>('query.staking.validatorCount')
+
+    .validator--Summary-authors {
+      .validator--Account-block-icon+.validator--Account-block-icon {
+        margin-left: -1.5rem;
+      }
+    }
+  `
 );

+ 24 - 46
packages/app-staking/src/Overview/index.tsx

@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 // 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.
@@ -6,68 +5,47 @@
 import { BareProps } from '@polkadot/react-components/types';
 import { ComponentProps } from '../types';
 
-import React, { useContext } from 'react';
-import { HeaderExtended } from '@polkadot/api-derive';
+import React, { useContext, useEffect, useState } from 'react';
 import { ApiContext } from '@polkadot/react-api';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
-import { formatNumber } from '@polkadot/util';
+import { BlockAuthorsContext } from '@polkadot/react-query';
 
 import CurrentList from './CurrentList';
 import Summary from './Summary';
 
-interface Props extends BareProps, ComponentProps {
-  chain_subscribeNewHeads?: HeaderExtended;
-}
+interface Props extends BareProps, ComponentProps {}
 
-// TODO: Switch to useState
-function Overview (props: Props): React.ReactElement<Props> {
+export default function Overview ({ allControllers, allStashes, recentlyOnline, stakingOverview }: Props): React.ReactElement<Props> {
   const { isSubstrateV2 } = useContext(ApiContext);
-  const { chain_subscribeNewHeads, allControllers, allStashes, currentValidators, recentlyOnline } = props;
-  let nextSorted: string[];
-
-  if (isSubstrateV2) {
-    // this is a V2 node currentValidators is a list of stashes
-    nextSorted = allStashes.filter((address): boolean =>
-      !currentValidators.includes(address)
-    );
-  } else {
-    // this is a V1 node currentValidators is a list of controllers
-    nextSorted = allControllers.filter((address): boolean =>
-      !currentValidators.includes(address)
+  const { byAuthor, lastBlockAuthors, lastBlockNumber } = useContext(BlockAuthorsContext);
+  const [next, setNext] = useState<string[]>([]);
+  const validators = stakingOverview && stakingOverview.validators;
+
+  useEffect((): void => {
+    validators && setNext(
+      isSubstrateV2
+        // this is a V2 node currentValidators is a list of stashes
+        ? allStashes.filter((address): boolean => !validators.includes(address as any))
+        // this is a V1 node currentValidators is a list of controllers
+        : allControllers.filter((address): boolean => !validators.includes(address as any))
     );
-  }
-
-  let lastBlock = '';
-  let lastAuthor: string | undefined;
-
-  if (chain_subscribeNewHeads) {
-    lastBlock = formatNumber(chain_subscribeNewHeads.number);
-    lastAuthor = (chain_subscribeNewHeads.author || '').toString();
-  }
+  }, [allControllers, allStashes, validators]);
 
   return (
     <div className='staking--Overview'>
       <Summary
         allControllers={allControllers}
-        currentValidators={currentValidators}
-        lastBlock={lastBlock}
-        lastAuthor={lastAuthor}
-        next={nextSorted}
+        lastBlock={lastBlockNumber}
+        lastAuthors={lastBlockAuthors}
+        next={next}
+        stakingOverview={stakingOverview}
       />
       <CurrentList
-        currentValidators={currentValidators}
-        lastBlock={lastBlock}
-        lastAuthor={lastAuthor}
-        next={nextSorted}
+        authorsMap={byAuthor}
+        lastAuthors={lastBlockAuthors}
+        next={next}
         recentlyOnline={recentlyOnline}
+        stakingOverview={stakingOverview}
       />
     </div>
   );
 }
-
-export default withMulti(
-  Overview,
-  withCalls<Props>(
-    'derive.chain.subscribeNewHeads'
-  )
-);

+ 10 - 46
packages/app-staking/src/index.tsx

@@ -2,13 +2,14 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { AccountId, BlockNumber, EventRecord } from '@polkadot/types/interfaces';
+import { DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
 import { AppProps, I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
+import { AccountId, BlockNumber } from '@polkadot/types/interfaces';
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { ComponentProps } from './types';
 
-import React, { useEffect, useReducer } from 'react';
+import React from 'react';
 import { Route, Switch } from 'react-router';
 import styled from 'styled-components';
 import { Option } from '@polkadot/types';
@@ -26,36 +27,14 @@ interface Props extends AppProps, ApiProps, I18nProps {
   allAccounts?: SubjectInfo;
   allStashesAndControllers?: [string[], string[]];
   bestNumber?: BlockNumber;
-  currentValidators?: string[];
-  recentlyOnline?: string[];
+  recentlyOnline?: DerivedHeartbeats;
+  stakingOverview?: DerivedStakingOverview;
 }
 
 const EMPY_ACCOUNTS: string[] = [];
 const EMPTY_ALL: [string[], string[]] = [EMPY_ACCOUNTS, EMPY_ACCOUNTS];
 
-function offlineReducer (prev: Record<string, BlockNumber>, { bestNumber, recentlyOnline }: { bestNumber: BlockNumber; recentlyOnline: string[] }): Record<string, BlockNumber> {
-  return {
-    ...prev,
-    ...recentlyOnline.reduce(
-      (result: Record<string, BlockNumber>, authorityId): Record<string, BlockNumber> => ({
-        ...result,
-        [authorityId]: bestNumber
-      }),
-      {}
-    )
-  };
-}
-
-function App ({ allAccounts, allStashesAndControllers: [allStashes, allControllers] = EMPTY_ALL, bestNumber, className, currentValidators = EMPY_ACCOUNTS, basePath, recentlyOnline, t }: Props): React.ReactElement<Props> {
-  const [online, dispatchOffline] = useReducer(offlineReducer, {});
-
-  // dispatch a combinator for the new recentlyOnline events
-  useEffect((): void => {
-    if (bestNumber && recentlyOnline && recentlyOnline.length) {
-      dispatchOffline({ bestNumber, recentlyOnline });
-    }
-  }, [bestNumber, recentlyOnline]);
-
+function App ({ allAccounts, allStashesAndControllers: [allStashes, allControllers] = EMPTY_ALL, basePath, className, recentlyOnline, stakingOverview, t }: Props): React.ReactElement<Props> {
   const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
     // eslint-disable-next-line react/display-name
     return (): React.ReactNode => {
@@ -68,8 +47,8 @@ function App ({ allAccounts, allStashesAndControllers: [allStashes, allControlle
           allAccounts={allAccounts}
           allControllers={allControllers}
           allStashes={allStashes}
-          currentValidators={currentValidators}
-          recentlyOnline={online}
+          recentlyOnline={recentlyOnline}
+          stakingOverview={stakingOverview}
         />
       );
     };
@@ -115,7 +94,7 @@ export default withMulti(
   `,
   translate,
   withCalls<Props>(
-    ['derive.chain.bestNumber', { propName: 'bestNumber' }],
+    ['derive.imOnline.receivedHeartbeats', { propName: 'recentlyOnline' }],
     ['derive.staking.controllers', {
       propName: 'allStashesAndControllers',
       transform: ([stashes, controllers]: [AccountId[], Option<AccountId>[]]): [string[], string[]] => [
@@ -125,22 +104,7 @@ export default withMulti(
           .map((accountId): string => accountId.unwrap().toString())
       ]
     }],
-    ['query.session.validators', {
-      propName: 'currentValidators',
-      transform: (validators: AccountId[]): string[] =>
-        validators.map((accountId): string => accountId.toString())
-    }],
-    ['query.system.events', {
-      propName: 'recentlyOnline',
-      transform: (value?: EventRecord[]): string[] =>
-        (value || [])
-          .filter(({ event: { method, section } }): boolean =>
-            section === 'imOnline' && method === 'HeartbeatReceived'
-          )
-          .map(({ event: { data: [authorityId] } }): string =>
-            authorityId.toString()
-          )
-    }]
+    ['derive.staking.overview', { propName: 'stakingOverview' }]
   ),
   withObservable(accountObservable.subject, { propName: 'allAccounts' })
 );

+ 12 - 8
packages/app-staking/src/types.ts

@@ -2,28 +2,32 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { BlockNumber } from '@polkadot/types/interfaces';
-import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
+import { DerivedFees, DerivedBalances, DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
-
-import BN from 'bn.js';
+import { EraPoints } from '@polkadot/types/interfaces';
+import { u32 } from '@polkadot/types';
 
 export type Nominators = Record<string, string[]>;
 
+export interface DerivedStakingOverviewExt {
+  currentElected: string[];
+  eraPointsEarned: EraPoints;
+  validatorCount: u32;
+}
+
 export interface ComponentProps {
   allAccounts?: SubjectInfo;
   allControllers: string[];
   allStashes: string[];
-  currentValidators: string[];
-  recentlyOnline: Record<string, BlockNumber>;
+  recentlyOnline?: DerivedHeartbeats;
+  stakingOverview?: DerivedStakingOverview;
 }
 
 export interface CalculateBalanceProps {
   balances_fees?: DerivedFees;
   balances_all?: DerivedBalances;
-  system_accountNonce?: BN;
 }
 
 export type AccountFilter = 'all' | 'controller' | 'session' | 'stash' | 'unbonded';
 
-export type ValidatorFilter = 'all' | 'hasNominators' | 'noNominators' | 'hasWarnings' | 'noWarnings' | 'iNominated';
+export type ValidatorFilter = 'all' | 'hasNominators' | 'noNominators' | 'hasWarnings' | 'noWarnings' | 'iNominated' | 'nextSet';

+ 0 - 31
packages/app-staking/src/util.ts

@@ -1,31 +0,0 @@
-/* eslint-disable @typescript-eslint/camelcase */
-// 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, BlockNumber } from '@polkadot/types/interfaces';
-import { DerivedStakingOnlineStatus } from '@polkadot/api-derive/types';
-
-export function updateOnlineStatus (recentlyOnline?: Record<string, BlockNumber>): (sessionIds: AccountId[] | null, onlineStatus: DerivedStakingOnlineStatus) => DerivedStakingOnlineStatus {
-  return (sessionIds: AccountId[] | null, onlineStatus: DerivedStakingOnlineStatus): DerivedStakingOnlineStatus => {
-    if (!recentlyOnline || !sessionIds) {
-      return onlineStatus;
-    }
-
-    const sessionId: AccountId | undefined = sessionIds.find((accountId): boolean => Object.keys(recentlyOnline).includes(accountId.toString()));
-
-    return {
-      ...onlineStatus,
-      ...(
-        sessionId
-          ? {
-            online: {
-              blockNumber: recentlyOnline[sessionId.toString()],
-              isOnline: true
-            }
-          }
-          : {}
-      )
-    };
-  };
-}

+ 4 - 4
packages/app-storage/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-storage",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,8 +10,8 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-params": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-params": "^0.37.0-beta.63"
   }
 }

+ 4 - 2
packages/app-storage/src/Query.tsx

@@ -97,14 +97,16 @@ function getCachedComponent (query: QueryTypes): CacheInstance {
           paramName: 'params',
           paramValid: true,
           params: [[key]],
-          transform: ([data]: Option<Data>[]): Option<Data> => data
+          transform: ([data]: Option<Data>[]): Option<Data> => data,
+          withIndicator: true
         });
       } else {
         // render function to create an element for the query results which is plugged to the api
         renderHelper = withCallDiv('subscribe', {
           paramName: 'params',
           paramValid: true,
-          params: [key, ...values]
+          params: [key, ...values],
+          withIndicator: true
         });
       }
 

+ 2 - 5
packages/app-storage/src/Selection/Modules.tsx

@@ -58,11 +58,8 @@ function Modules ({ onAdd, t }: Props): React.ReactElement<Props> {
     const isLinked = isMap && key.creator.meta.type.asMap.linked.isTrue;
 
     setKey({
-      defaultValues: key.creator.section === 'session'
-        ? [{
-          isValid: true,
-          value: api.consts.session.dedupKeyPrefix.toHex()
-        }]
+      defaultValues: key.creator.section === 'session' && key.creator.meta.type.isDoubleMap
+        ? [{ isValid: true, value: api.consts.session.dedupKeyPrefix.toHex() }]
         : null,
       isLinked,
       key,

+ 3 - 3
packages/app-sudo/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-sudo",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A basic app that shows the ropes on customisation",
   "main": "index.js",
   "scripts": {},
@@ -11,7 +11,7 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 3 - 3
packages/app-toolbox/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-toolbox",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,7 +10,7 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
   }
 }

+ 4 - 4
packages/app-transfer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-transfer",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A basic transfer app",
   "main": "index.js",
   "scripts": {},
@@ -10,8 +10,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-query": "^0.37.0-beta.63"
   }
 }

+ 4 - 4
packages/app-treasury/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-treasury",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "A referendum & proposal app",
   "main": "index.js",
   "scripts": {},
@@ -11,8 +11,8 @@
   ],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101"
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-query": "^0.37.0-beta.63"
   }
 }

+ 2 - 2
packages/apps-routing/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/apps-routing",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -10,6 +10,6 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/runtime": "^7.6.3"
+    "@babel/runtime": "^7.7.1"
   }
 }

+ 4 - 1
packages/apps-routing/src/council.ts

@@ -11,7 +11,10 @@ export default ([
     Component: Council,
     display: {
       needsApi: [
-        'query.elections.candidates'
+        [
+          'query.electionsPhragmen.candidates',
+          'query.elections.candidates'
+        ]
       ]
     },
     i18n: {

+ 6 - 6
packages/apps/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/apps",
-  "version": "0.36.0-beta.101",
+  "version": "0.37.0-beta.63",
   "description": "An Apps portal into the Polkadot network",
   "main": "index.js",
   "homepage": ".",
@@ -12,11 +12,11 @@
   "contributors": [],
   "license": "Apache-2.0",
   "dependencies": {
-    "@babel/polyfill": "^7.6.0",
-    "@babel/runtime": "^7.6.3",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-signer": "^0.36.0-beta.101",
-    "@polkadot/ui-assets": "^0.46.0-beta.16",
+    "@babel/polyfill": "^7.7.0",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-signer": "^0.37.0-beta.63",
+    "@polkadot/ui-assets": "^0.47.0-beta.3",
     "query-string": "^6.8.3"
   }
 }

+ 11 - 0
packages/apps/public/locales/en/app-accounts.json

@@ -80,5 +80,16 @@
   "Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)": "Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)",
   "You can set a custom derivation path for this account using the following syntax \"/<soft-key>//<hard-key>///<password>\". The \"/<soft-key>\" and \"//<hard-key>\" may be repeated and mixed`. The \"///password\" is optional and should only occur once.": "You can set a custom derivation path for this account using the following syntax \"/<soft-key>//<hard-key>///<password>\". The \"/<soft-key>\" and \"//<hard-key>\" may be repeated and mixed`. The \"///password\" is optional and should only occur once.",
   "//hard/soft///password": "//hard/soft///password",
+  "new account": "new account",
+  "transferrable ": "transferrable ",
+  "Derive account from source": "Derive account from source",
+  "Change on-chain nickname": "Change on-chain nickname",
+  "The selected account to perform the derivation on.": "The selected account to perform the derivation on.",
+  "derive root account": "derive root account",
+  "Derive account from pair": "Derive account from pair",
+  "The password to unlock the selected account.": "The password to unlock the selected account.",
+  "derivation path": "derivation path",
+  "//hard/soft": "//hard/soft",
+  "Unlock": "Unlock",
   "My memo": "My memo"
 }

+ 1 - 0
packages/apps/public/locales/en/app-address-book.json

@@ -15,5 +15,6 @@
   "Save": "Save",
   "Add contact": "Add contact",
   "No contacts found.": "No contacts found.",
+  "new address": "new address",
   "View memo": "View memo"
 }

+ 4 - 1
packages/apps/public/locales/en/app-contracts.json

@@ -57,5 +57,8 @@
   "Remove": "Remove",
   "The maximum amount of gas that can be used by this call. If the code requires more, the call will fail.": "The maximum amount of gas that can be used by this call. If the code requires more, the call will fail.",
   "Call results": "Call results",
-  "constructor ": "constructor "
+  "constructor ": "constructor ",
+  "send as RPC call": "send as RPC call",
+  "send as transaction": "send as transaction",
+  "Clear all": "Clear all"
 }

+ 10 - 1
packages/apps/public/locales/en/app-council.json

@@ -30,5 +30,14 @@
   "Vote": "Vote",
   "You have already voted in this round": "You have already voted in this round",
   "Retract vote": "Retract vote",
-  "No vote": "No vote"
+  "No vote": "No vote",
+  "No candidates found": "No candidates found",
+  "value": "value",
+  "member": "member",
+  "term progress": "term progress",
+  "Voters ({{count}})": "Voters ({{count}})",
+  "The amount that is associated with this vote. This value is is locked for the duration of the vote.": "The amount that is associated with this vote. This value is is locked for the duration of the vote.",
+  "vote value": "vote value",
+  "voting balance ": "voting balance ",
+  "runner up": "runner up"
 }

+ 2 - 1
packages/apps/public/locales/en/app-generic-asset.json

@@ -20,5 +20,6 @@
   "Enter the Asset ID of the token you want to transfer.": "Enter the Asset ID of the token you want to transfer.",
   "Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 milli is equivalent to sending 0.001.": "Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 milli is equivalent to sending 0.001.",
   "amount": "amount",
-  "Make Transfer": "Make Transfer"
+  "Make Transfer": "Make Transfer",
+  "transferrable ": "transferrable "
 }

+ 8 - 1
packages/apps/public/locales/en/app-staking.json

@@ -87,5 +87,12 @@
   "next up": "next up",
   "intention": "intention",
   "waiting": "waiting",
-  "last block": "last block"
+  "last block": "last block",
+  "era points": "era points",
+  "Selected for the next session": "Selected for the next session",
+  "Show only elected for next session": "Show only elected for next session",
+  "Active with {{blocks}} blocks authored{{hasMessage}} heartbeat message": "Active with {{blocks}} blocks authored{{hasMessage}} heartbeat message",
+  "transferrable ": "transferrable ",
+  "The specified value is greater than your free balance. The node will bond the maximum amount available.": "The specified value is greater than your free balance. The node will bond the maximum amount available.",
+  "Change session keys": "Change session keys"
 }

+ 6 - 1
packages/apps/public/locales/en/react-components.json

@@ -61,5 +61,10 @@
   "record my vote as": "record my vote as",
   "Vote": "Vote",
   "Aye, I approve": "Aye, I approve",
-  "Nay, I do not approve": "Nay, I do not approve"
+  "Nay, I do not approve": "Nay, I do not approve",
+  "reserved": "reserved",
+  "locked": "locked",
+  "Remove ABI": "Remove ABI",
+  "transferrable": "transferrable",
+  "No documentation provided": "No documentation provided"
 }

+ 3 - 1
packages/apps/public/locales/en/react-signer.json

@@ -21,5 +21,7 @@
   "Tip (optional)": "Tip (optional)",
   "wrong password": "wrong password",
   "sending from my account": "sending from my account",
-  "unlock account with password": "unlock account with password"
+  "unlock account with password": "unlock account with password",
+  "Include an optional tip for faster processing": "Include an optional tip for faster processing",
+  "Do not include a tip for the block author": "Do not include a tip for the block author"
 }

+ 35 - 0
packages/apps/public/locales/en/ui.json

@@ -575,6 +575,41 @@
   "The maximum amount of gas that can be used by this call. If the code requires more, the call will fail.": "",
   "Call results": "",
   "constructor ": "",
+  "new account": "",
+  "new address": "",
+  "No candidates found": "",
+  "member": "",
+  "term progress": "",
+  "reserved": "",
+  "locked": "",
+  "Voters ({{count}})": "",
+  "era points": "",
+  "Selected for the next session": "",
+  "Show only elected for next session": "",
+  "send as RPC call": "",
+  "send as transaction": "",
+  "Clear all": "",
+  "Active with {{blocks}} blocks authored{{hasMessage}} heartbeat message": "",
+  "Remove ABI": "",
+  "transferrable ": "",
+  "transferrable": "",
+  "Include an optional tip for faster processing": "",
+  "Do not include a tip for the block author": "",
+  "The specified value is greater than your free balance. The node will bond the maximum amount available.": "",
+  "The amount that is associated with this vote. This value is is locked for the duration of the vote.": "",
+  "vote value": "",
+  "voting balance ": "",
+  "Change session keys": "",
+  "No documentation provided": "",
+  "Derive account from source": "",
+  "Change on-chain nickname": "",
+  "The selected account to perform the derivation on.": "",
+  "derive root account": "",
+  "Derive account from pair": "",
+  "The password to unlock the selected account.": "",
+  "derivation path": "",
+  "//hard/soft": "",
+  "runner up": "",
   "My memo": "",
   "View memo": "",
   "Dashboard": "",

+ 20 - 0
packages/apps/src/Apps.tsx

@@ -11,6 +11,7 @@ import { BareProps as Props } from '@polkadot/react-components/types';
 import React, { useState } from 'react';
 import store from 'store';
 import styled from 'styled-components';
+import { withCalls } from '@polkadot/react-api';
 import GlobalStyle from '@polkadot/react-components/styles';
 import Signer from '@polkadot/react-signer';
 
@@ -26,6 +27,24 @@ interface SidebarState {
   transition: SideBarTransition;
 }
 
+function Placeholder (): React.ReactElement {
+  return (
+    <div className='api-warm' />
+  );
+}
+
+const WarmUp = withCalls<{}>(
+  'derive.accounts.indexes',
+  'derive.balances.fees',
+  'derive.staking.overview'
+  // This are very ineffective queries that
+  //   (a) adds load to the RPC node when activated globally
+  //   (b) is used in additional information (next-up)
+  // 'derive.staking.all'
+  // 'derive.staking.controllers'
+  // 'query.staking.nominators'
+)(Placeholder);
+
 function Apps ({ className }: Props): React.ReactElement<Props> {
   const [sidebar, setSidebar] = useState<SidebarState>({
     isCollapsed: false,
@@ -75,6 +94,7 @@ function Apps ({ className }: Props): React.ReactElement<Props> {
         </Signer>
         <ConnectingOverlay />
       </div>
+      <WarmUp />
     </>
   );
 }

+ 5 - 18
packages/apps/src/Content/index.tsx

@@ -3,13 +3,12 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 import { I18nProps } from '@polkadot/react-components/types';
-import { ApiProps } from '@polkadot/react-api/types';
 
 import React, { useContext } from 'react';
 import { withRouter, RouteComponentProps } from 'react-router';
 import styled from 'styled-components';
 import routing from '@polkadot/apps-routing';
-import { withCalls, withMulti } from '@polkadot/react-api';
+import { ApiContext } from '@polkadot/react-api';
 import { StatusContext } from '@polkadot/react-components';
 
 import Status from './Status';
@@ -18,7 +17,7 @@ import NotFound from './NotFound';
 import TopBar from '../TopBar';
 import { MyAccountProvider } from '@polkadot/joy-utils/MyAccountContext';
 
-interface Props extends I18nProps, ApiProps, RouteComponentProps {}
+interface Props extends I18nProps, RouteComponentProps {}
 
 const Wrapper = styled.div`
   background: #fafafa;
@@ -44,7 +43,8 @@ const unknown = {
   name: ''
 };
 
-function Content ({ isApiConnected, isApiReady, className, location, t }: Props): React.ReactElement<Props> {
+function Content ({ className, location, t }: Props): React.ReactElement<Props> {
+  const { isApiConnected, isApiReady } = useContext(ApiContext);
   const { queueAction, stqueue, txqueue } = useContext(StatusContext);
   const app = location.pathname.slice(1) || '';
   const { Component, display: { needsApi }, name } = routing.routes.find((route): boolean =>
@@ -80,7 +80,7 @@ function Content ({ isApiConnected, isApiReady, className, location, t }: Props)
 }
 
 // React-router needs to be first, otherwise we have blocked updates
-export default withMulti(
+export default translate(
   withRouter(
     styled(Content)`
       background: #fafafa;
@@ -102,18 +102,5 @@ export default withMulti(
         padding: 1rem 0;
       }
     `
-  ),
-  translate,
-  // These API queries are used in a number of places, warm them up
-  // to avoid constant un-/re-subscribe on these
-  withCalls<Props>(
-    'derive.accounts.indexes',
-    'derive.balances.fees',
-    'query.session.validators'
-    // This are very ineffective queries that
-    //   (a) adds load to the RPC node when activated globally
-    //   (b) is used in additional information (next-up)
-    // 'derive.staking.controllers'
-    // 'query.staking.nominators'
   )
 );

+ 11 - 5
packages/apps/src/index.tsx

@@ -19,6 +19,8 @@ import { Api } from '@polkadot/react-api';
 import { QueueConsumer } from '@polkadot/react-components/Status/Context';
 import Queue from '@polkadot/react-components/Status/Queue';
 import { MyAccountProvider } from '@polkadot/joy-utils/MyAccountContext';
+import { BlockAuthors, Events } from '@polkadot/react-query';
+
 import Apps from './Apps';
 
 const rootId = 'root';
@@ -73,11 +75,15 @@ ReactDOM.render(
             queueSetTxStatus={queueSetTxStatus}
             url={wsEndpoint}
           >
-            <HashRouter>
-              <ThemeProvider theme={theme}>
-                <Apps />
-              </ThemeProvider>
-            </HashRouter>
+            <BlockAuthors>
+              <Events>
+                <HashRouter>
+                  <ThemeProvider theme={theme}>
+                    <Apps />
+                  </ThemeProvider>
+                </HashRouter>
+              </Events>
+            </BlockAuthors>
           </Api>
         )}
       </QueueConsumer>

+ 3 - 3
packages/joy-election/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "0.37.0-beta.63",
+    "@polkadot/react-query": "0.37.0-beta.63",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 3 - 3
packages/joy-forum/package.json

@@ -7,10 +7,10 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
+    "@babel/runtime": "^7.7.1",
     "@polkadot/joy-utils": "^0.1.1",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101",
+    "@polkadot/react-components": "0.37.0-beta.63",
+    "@polkadot/react-query": "0.37.0-beta.63",
     "lodash": "^4.17.15"
   }
 }

+ 3 - 3
packages/joy-help/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "0.37.0-beta.63",
+    "@polkadot/react-query": "0.37.0-beta.63",
     "@polkadot/joy-utils": "^0.1.1"
   }
 }

+ 3 - 3
packages/joy-media/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "maintainers": [],
   "dependencies": {
-    "@babel/runtime": "^7.6.0",
-    "@polkadot/react-components": "^0.36.0-beta.101",
-    "@polkadot/react-query": "^0.36.0-beta.101",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "0.37.0-beta.63",
+    "@polkadot/react-query": "0.37.0-beta.63",
     "@polkadot/joy-utils": "^0.1.1",
     "@types/mime-types": "^2.1.0",
     "aplayer": "^1.10.1",

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio