Browse Source

Rework staking accounts layout (#2031)

* Rework staking accounts layout

* Optimize sort, simple substract

* Unneeded ?.

* Remove AddressCard

* Bump API
Jaco Greeff 5 years ago
parent
commit
24526345dc

+ 3 - 3
package.json

@@ -10,10 +10,10 @@
     "packages/*"
   ],
   "resolutions": {
-    "@polkadot/api": "^0.99.0-beta.13",
-    "@polkadot/api-contract": "^0.99.0-beta.13",
+    "@polkadot/api": "^0.99.0-beta.15",
+    "@polkadot/api-contract": "^0.99.0-beta.15",
     "@polkadot/keyring": "^1.7.1",
-    "@polkadot/types": "^0.99.0-beta.13",
+    "@polkadot/types": "^0.99.0-beta.15",
     "@polkadot/util": "^1.7.1",
     "@polkadot/util-crypto": "^1.7.1",
     "babel-core": "^7.0.0-bridge.0",

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

@@ -11,7 +11,7 @@
   "license": "Apache-2.0",
   "dependencies": {
     "@babel/runtime": "^7.7.5",
-    "@polkadot/api-contract": "^0.99.0-beta.13",
+    "@polkadot/api-contract": "^0.99.0-beta.15",
     "@polkadot/react-components": "^0.38.0-beta.60"
   }
 }

+ 290 - 539
packages/app-staking/src/Actions/Account/index.tsx

@@ -1,57 +1,48 @@
-/* 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 { DerivedBalances, DerivedStakingAccount, DerivedStakingOverview, DerivedHeartbeats } from '@polkadot/api-derive/types';
-import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { AccountId, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces';
+import { Codec, ITuple } from '@polkadot/types/types';
 
-import React from 'react';
+import React, { useEffect, useState } from 'react';
 import styled from 'styled-components';
-import { AddressCard, AddressInfo, AddressMini, AddressRow, Button, Menu, Popup, TxButton } from '@polkadot/react-components';
-import { withCalls, withMulti } from '@polkadot/react-api';
+import { AddressInfo, AddressMini, AddressSmall, Button, Menu, Popup, TxButton } from '@polkadot/react-components';
+import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
+import { u8aConcat, u8aToHex } from '@polkadot/util';
 
+import translate from '../../translate';
 import BondExtra from './BondExtra';
 import InjectKeys from './InjectKeys';
 import Nominate from './Nominate';
 import SetControllerAccount from './SetControllerAccount';
 import SetRewardDestination from './SetRewardDestination';
 import SetSessionKey from './SetSessionKey';
-import translate from '../../translate';
 import Unbond from './Unbond';
 import Validate from './Validate';
-import { u8aToHex, u8aConcat } from '@polkadot/util';
 
-interface Props extends ApiProps, I18nProps {
+type ValidatorInfo = ITuple<[ValidatorPrefs, Codec]>;
+
+interface Props extends I18nProps {
   allStashes?: string[];
-  balances_all?: DerivedBalances;
-  className?: string;
   isOwnStash: boolean;
   next: string[];
+  onUpdateType: (stashId: string, type: 'validator' | 'nominator' | 'started' | 'other') => void;
   recentlyOnline?: DerivedHeartbeats;
-  staking_account?: DerivedStakingAccount;
   stakingOverview?: DerivedStakingOverview;
   stashId: string;
 }
 
-interface State {
+interface StakeState {
   controllerId: string | null;
   destination: number;
   hexSessionIdNext: string | null;
   hexSessionIdQueue: string | null;
-  isBondExtraOpen: boolean;
-  isInjectOpen: boolean;
-  isNominateOpen: boolean;
-  isSetControllerAccountOpen: boolean;
-  isSetRewardDestinationOpen: boolean;
-  isSetSessionAccountOpen: boolean;
-  isSettingPopupOpen: boolean;
+  isLoading: boolean;
   isStashNominating: boolean;
   isStashValidating: boolean;
-  isUnbondOpen: boolean;
-  isValidateOpen: boolean;
   nominees?: string[];
   sessionIds: string[];
   stakers?: Exposure;
@@ -59,552 +50,312 @@ interface State {
   validatorPrefs?: ValidatorPrefs;
 }
 
-const DEFAULT_BALANCES = {
-  available: true,
-  bonded: false,
-  total: false,
-  redeemable: false,
-  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()
     : null;
 }
 
-class Account extends React.PureComponent<Props, State> {
-  public state: State = {
-    controllerId: null,
-    destination: 0,
-    hexSessionIdNext: null,
-    hexSessionIdQueue: null,
-    isBondExtraOpen: false,
-    isInjectOpen: false,
-    isNominateOpen: false,
-    isSetControllerAccountOpen: false,
-    isSettingPopupOpen: false,
-    isSetRewardDestinationOpen: false,
-    isSetSessionAccountOpen: false,
-    isStashNominating: false,
-    isStashValidating: false,
-    isUnbondOpen: false,
-    isValidateOpen: false,
-    sessionIds: []
+function getStakeState (allStashes: string[] | undefined, { controllerId, nextSessionIds, nominators, rewardDestination, sessionIds, stakers, stakingLedger, validatorPrefs }: DerivedStakingAccount, stashId: string, validateInfo: ValidatorInfo): StakeState {
+  const isStashNominating = !!(nominators?.length);
+  const isStashValidating = !validateInfo[1].isEmpty || !!allStashes?.includes(stashId);
+  const nextConcat = u8aConcat(...nextSessionIds.map((id): Uint8Array => id.toU8a()));
+  const currConcat = u8aConcat(...sessionIds.map((id): Uint8Array => id.toU8a()));
+
+  return {
+    controllerId: toIdString(controllerId),
+    destination: rewardDestination?.toNumber() || 0,
+    hexSessionIdNext: u8aToHex(nextConcat, 48),
+    hexSessionIdQueue: u8aToHex(currConcat.length ? currConcat : nextConcat, 48),
+    isLoading: false,
+    isStashNominating,
+    isStashValidating,
+    // we assume that all ids are non-null
+    nominees: nominators?.map(toIdString) as string[],
+    sessionIds: (
+      nextSessionIds.length
+        ? nextSessionIds
+        : sessionIds
+    ).map(toIdString) as string[],
+    stakers,
+    stakingLedger,
+    validatorPrefs
   };
+}
 
-  public static getDerivedStateFromProps ({ allStashes, staking_account, stashId }: Props): Pick<State, never> | null {
-    if (!staking_account) {
-      return null;
+function Account ({ allStashes, className, isOwnStash, next, onUpdateType, stakingOverview, stashId, t }: Props): React.ReactElement<Props> {
+  const { api, isSubstrateV2 } = useApi();
+  const validateInfo = useCall<ValidatorInfo>(api.query.staking.validators, [stashId]);
+  const balancesAll = useCall<DerivedBalances>(api.derive.balances.all as any, [stashId]);
+  const stakingAccount = useCall<DerivedStakingAccount>(api.derive.staking.account as any, [stashId]);
+  const [{ controllerId, destination, hexSessionIdQueue, hexSessionIdNext, isLoading, isStashNominating, isStashValidating, nominees, sessionIds, validatorPrefs }, setStakeState] = useState<StakeState>({ controllerId: null, destination: 0, hexSessionIdNext: null, hexSessionIdQueue: null, isLoading: true, isStashNominating: false, isStashValidating: false, sessionIds: [] });
+  const [isBondExtraOpen, toggleBondExtra] = useToggle();
+  const [isInjectOpen, toggleInject] = useToggle();
+  const [isNominateOpen, toggleNominate] = useToggle();
+  const [isRewardDestinationOpen, toggleRewardDestination] = useToggle();
+  const [isSetControllerOpen, toggleSetController] = useToggle();
+  const [isSetSessionOpen, toggleSetSession] = useToggle();
+  const [isSettingsOpen, toggleSettings] = useToggle();
+  const [isUnbondOpen, toggleUnbond] = useToggle();
+  const [isValidateOpen, toggleValidate] = useToggle();
+
+  useEffect((): void => {
+    if (stakingAccount && validateInfo) {
+      const state = getStakeState(allStashes, stakingAccount, stashId, validateInfo);
+
+      setStakeState(state);
+
+      if (state.isStashValidating) {
+        onUpdateType(stashId, 'validator');
+      } else if (state.isStashNominating) {
+        onUpdateType(stashId, 'nominator');
+      } else {
+        onUpdateType(stashId, 'other');
+      }
     }
+  }, [allStashes, stakingAccount, stashId, validateInfo]);
 
-    const { controllerId, nextSessionIds, nominators, rewardDestination, sessionIds, stakers, stakingLedger, validatorPrefs } = staking_account;
-    const isStashNominating = nominators && !!nominators.length;
-    const isStashValidating = !!allStashes && !!stashId && allStashes.includes(stashId);
-    const nextConcat = u8aConcat(...nextSessionIds.map((id): Uint8Array => id.toU8a()));
-    const currConcat = u8aConcat(...sessionIds.map((id): Uint8Array => id.toU8a()));
-
-    return {
-      controllerId: toIdString(controllerId),
-      destination: rewardDestination && rewardDestination.toNumber(),
-      hexSessionIdNext: u8aToHex(nextConcat, 48),
-      hexSessionIdQueue: u8aToHex(currConcat.length ? currConcat : nextConcat, 48),
-      isStashNominating,
-      isStashValidating,
-      nominees: nominators && nominators.map(toIdString),
-      sessionIds: (
-        nextSessionIds.length
-          ? nextSessionIds
-          : sessionIds
-      ).map(toIdString),
-      stakers,
-      stakingLedger,
-      validatorPrefs
-    };
-  }
-
-  public render (): React.ReactNode {
-    const { className, isSubstrateV2, stashId, t } = this.props;
-    const { controllerId, hexSessionIdNext, hexSessionIdQueue, isBondExtraOpen, isInjectOpen, isStashValidating, isUnbondOpen, nominees, sessionIds } = this.state;
-
-    // Each component is rendered and gets a `is[Component]Open` passed in a `isOpen` props.
-    // These components will be loaded and return null at the first load (because is[Component]Open === false).
-    // This is deliberate in order to display the Component modals in a performant matter later on
-    // because their state will already be loaded.
-    return (
-      <AddressCard
-        buttons={this.renderButtons()}
-        className={className}
-        type='account'
-        value={stashId}
-        withAddressOrName
-        withBalance={DEFAULT_BALANCES}
-      >
+  return (
+    <tr className={className}>
+      <td className='top'>
         <BondExtra
           controllerId={controllerId}
           isOpen={isBondExtraOpen}
-          onClose={this.toggleBondExtra}
+          onClose={toggleBondExtra}
           stashId={stashId}
         />
         <Unbond
           controllerId={controllerId}
           isOpen={isUnbondOpen}
-          onClose={this.toggleUnbond}
+          onClose={toggleUnbond}
           stashId={stashId}
         />
+        <Validate
+          controllerId={controllerId}
+          isOpen={isValidateOpen}
+          onClose={toggleValidate}
+          stashId={stashId}
+          validatorPrefs={validatorPrefs}
+        />
         {isInjectOpen && (
-          <InjectKeys onClose={this.toggleInject} />
+          <InjectKeys onClose={toggleInject} />
         )}
-        {this.renderSetValidatorPrefs()}
-        {this.renderNominate()}
-        {this.renderSetControllerAccount()}
-        {this.renderSetRewardDestination()}
-        {this.renderSetSessionAccount()}
-        {this.renderValidate()}
-        <div className='staking--Card'>
-          <div className='staking--Accounts'>
-            {controllerId && (
-              <div className='staking--Account-detail actions'>
-                <AddressRow
-                  // label={t('controller')}
-                  value={controllerId}
-                  withAddressOrName
-                  withBalance={CONTROLLER_BALANCES}
-                  withSmallIcon
-                />
-              </div>
-            )}
-            {!isSubstrateV2 && !!sessionIds.length && (
-              <div className='staking--Account-detail actions'>
-                <AddressRow
-                  label={t('session')}
-                  value={sessionIds[0]}
-                  withAddressOrName
-                  withBalance={{
-                    available: true,
-                    bonded: false,
-                    free: false,
-                    redeemable: false,
-                    unlocking: false
-                  }}
-                />
-              </div>
-            )}
-          </div>
-          <div className='staking--Infos'>
-            <div className='staking--balances'>
-              <AddressInfo
-                address={stashId}
-                withBalance={{
-                  available: false,
-                  bonded: true,
-                  free: false,
-                  redeemable: true,
-                  unlocking: true
-                }}
-                withRewardDestination
-                withHexSessionId={ isSubstrateV2 && hexSessionIdNext !== '0x' && [hexSessionIdQueue, hexSessionIdNext]}
-                withValidatorPrefs={isStashValidating}
-              />
-            </div>
-          </div>
-        </div>
-        {nominees && !!nominees.length && (
-          <details className='staking--Account-Nominations'>
-            <summary>{t('Nominating ({{count}})', { replace: { count: nominees.length } })}</summary>
-            {nominees.map((nomineeId, index): React.ReactNode => (
-              <AddressMini
-                key={index}
-                value={nomineeId}
-                withBalance={false}
-                withBonded
-              />
-            ))}
-          </details>
+        {isNominateOpen && controllerId && (
+          <Nominate
+            controllerId={controllerId}
+            next={next}
+            nominees={nominees}
+            onClose={toggleNominate}
+            stakingOverview={stakingOverview}
+            stashId={stashId}
+          />
         )}
-      </AddressCard>
-    );
-  }
-
-  private renderNominate (): React.ReactNode {
-    const { next, stakingOverview, stashId } = this.props;
-    const { controllerId, isNominateOpen, nominees } = this.state;
-
-    if (!isNominateOpen || !stashId || !controllerId) {
-      return null;
-    }
-
-    return (
-      <Nominate
-        controllerId={controllerId}
-        next={next}
-        nominees={nominees}
-        onClose={this.toggleNominate}
-        stakingOverview={stakingOverview}
-        stashId={stashId}
-      />
-    );
-  }
-
-  private renderValidate (): React.ReactNode {
-    const { stashId } = this.props;
-    const { controllerId, isValidateOpen, validatorPrefs } = this.state;
-
-    if (!controllerId) {
-      return null;
-    }
-
-    return (
-      <Validate
-        controllerId={controllerId}
-        isOpen={isValidateOpen}
-        onClose={this.toggleValidate}
-        stashId={stashId}
-        validatorPrefs={validatorPrefs}
-      />
-    );
-  }
-
-  private renderButtons (): React.ReactNode {
-    const { isSubstrateV2, t } = this.props;
-    const { controllerId, hexSessionIdNext, isSettingPopupOpen, isStashNominating, isStashValidating, sessionIds } = this.state;
-    const buttons = [];
-
-    // if we are validating/nominating show stop
-    if (isStashNominating || isStashValidating) {
-      buttons.push(
-        <TxButton
-          accountId={controllerId}
-          isNegative
-          label={
-            isStashNominating
-              ? t('Stop Nominating')
-              : t('Stop Validating')
-          }
-          icon='stop'
-          key='stop'
-          tx='staking.chill'
-        />
-      );
-    } else {
-      if (!sessionIds.length || (isSubstrateV2 && hexSessionIdNext === '0x')) {
-        buttons.push(
-          <Button
-            isPrimary
-            key='set'
-            onClick={this.toggleSetSessionAccount}
-            label={t('Set Session Key')}
-            icon='sign-in'
+        {isSetControllerOpen && (
+          <SetControllerAccount
+            defaultControllerId={controllerId}
+            isValidating={isStashValidating}
+            onClose={toggleSetController}
+            stashId={stashId}
           />
-        );
-      } else {
-        buttons.push(
-          <Button
-            isPrimary
-            key='validate'
-            onClick={this.toggleValidate}
-            label={t('Validate')}
-            icon='check circle outline'
+        )}
+        {isRewardDestinationOpen && controllerId && (
+          <SetRewardDestination
+            controllerId={controllerId}
+            defaultDestination={destination}
+            onClose={toggleRewardDestination}
           />
-        );
-      }
-
-      buttons.push(<Button.Or key='nominate.or' />);
-      buttons.push(
-        <Button
-          isPrimary
-          key='nominate'
-          onClick={this.toggleNominate}
-          label={t('Nominate')}
-          icon='hand paper outline'
-        />
-      );
-    }
-
-    buttons.push(
-      <Popup
-        key='settings'
-        onClose={this.toggleSettingPopup}
-        open={isSettingPopupOpen}
-        position='bottom right'
-        trigger={
-          <Button
-            icon='setting'
-            onClick={this.toggleSettingPopup}
-            size='tiny'
+        )}
+        {controllerId && (
+          <SetSessionKey
+            controllerId={controllerId}
+            isOpen={isSetSessionOpen}
+            onClose={toggleSetSession}
+            sessionIds={sessionIds}
+            stashId={stashId}
           />
-        }
-      >
-        {this.renderPopupMenu()}
-      </Popup>
-    );
-
-    return (
-      <>
-        <Button.Group>
-          {buttons}
-        </Button.Group>
-      </>
-    );
-  }
-
-  private renderPopupMenu (): React.ReactNode {
-    const { balances_all, isOwnStash, isSubstrateV2, t } = this.props;
-    const { isStashNominating, isStashValidating } = this.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?.freeBalance.gtn(0);
-
-    return (
-      <Menu
-        vertical
-        text
-        onClick={this.toggleSettingPopup}
-      >
-        {canBondExtra &&
-          <Menu.Item
-            disabled={!isOwnStash}
-            onClick={this.toggleBondExtra}
-          >
-            {t('Bond more funds')}
-          </Menu.Item>
-        }
-        <Menu.Item onClick={this.toggleUnbond}>
-          {t('Unbond funds')}
-        </Menu.Item>
-        <Menu.Item
-          disabled={!isOwnStash}
-          onClick={this.toggleSetControllerAccount}
-        >
-          {t('Change controller account')}
-        </Menu.Item>
-        <Menu.Item onClick={this.toggleSetRewardDestination}>
-          {t('Change reward destination')}
-        </Menu.Item>
-        {isStashValidating &&
-          <Menu.Item onClick={this.toggleValidate}>
-            {t('Change validator preferences')}
-          </Menu.Item>
-        }
-        {!isStashNominating &&
-          <Menu.Item onClick={this.toggleSetSessionAccount}>
-            {isSubstrateV2 ? t('Change session keys') : t('Change session account')}
-          </Menu.Item>
-        }
-        {isStashNominating &&
-          <Menu.Item onClick={this.toggleNominate}>
-            {t('Change nominee(s)')}
-          </Menu.Item>
-        }
-        {!isStashNominating && isSubstrateV2 &&
-          <Menu.Item onClick={this.toggleInject}>
-            {t('Inject session keys (advanced)')}
-          </Menu.Item>
-        }
-      </Menu>
-    );
-  }
-
-  private renderSetValidatorPrefs (): React.ReactNode {
-    const { stashId } = this.props;
-    const { controllerId, isValidateOpen, validatorPrefs } = this.state;
-
-    if (!controllerId || !validatorPrefs) {
-      return null;
-    }
-
-    return (
-      <Validate
-        controllerId={controllerId}
-        isOpen={isValidateOpen}
-        onClose={this.toggleValidate}
-        stashId={stashId}
-        validatorPrefs={validatorPrefs}
-      />
-    );
-  }
-
-  private renderSetControllerAccount (): React.ReactNode {
-    const { stashId } = this.props;
-    const { controllerId, isSetControllerAccountOpen, isStashValidating } = this.state;
-
-    if (!isSetControllerAccountOpen) {
-      return null;
-    }
-
-    return (
-      <SetControllerAccount
-        defaultControllerId={controllerId}
-        isValidating={isStashValidating}
-        onClose={this.toggleSetControllerAccount}
-        stashId={stashId}
-      />
-    );
-  }
-
-  private renderSetRewardDestination (): React.ReactNode {
-    const { controllerId, destination, isSetRewardDestinationOpen } = this.state;
-
-    if (!isSetRewardDestinationOpen || !controllerId) {
-      return null;
-    }
-
-    return (
-      <SetRewardDestination
-        controllerId={controllerId}
-        defaultDestination={destination}
-        onClose={this.toggleSetRewardDestination}
-      />
-    );
-  }
-
-  private renderSetSessionAccount (): React.ReactNode {
-    const { stashId } = this.props;
-    const { controllerId, isSetSessionAccountOpen, sessionIds } = this.state;
-
-    if (!controllerId) {
-      return null;
-    }
-
-    return (
-      <SetSessionKey
-        controllerId={controllerId}
-        isOpen={isSetSessionAccountOpen}
-        onClose={this.toggleSetSessionAccount}
-        sessionIds={sessionIds}
-        stashId={stashId}
-      />
-    );
-  }
-
-  private toggleBondExtra = (): void => {
-    this.setState(({ isBondExtraOpen }): Pick<State, never> => ({
-      isBondExtraOpen: !isBondExtraOpen
-    }));
-  }
-
-  private toggleInject = (): void => {
-    this.setState(({ isInjectOpen }): Pick<State, never> => ({
-      isInjectOpen: !isInjectOpen
-    }));
-  }
-
-  private toggleNominate = (): void => {
-    this.setState(({ isNominateOpen }): Pick<State, never> => ({
-      isNominateOpen: !isNominateOpen
-    }));
-  }
-
-  private toggleSetControllerAccount = (): void => {
-    this.setState(({ isSetControllerAccountOpen }): Pick<State, never> => ({
-      isSetControllerAccountOpen: !isSetControllerAccountOpen
-    }));
-  }
-
-  private toggleSetRewardDestination = (): void => {
-    this.setState(({ isSetRewardDestinationOpen }): Pick<State, never> => ({
-      isSetRewardDestinationOpen: !isSetRewardDestinationOpen
-    }));
-  }
-
-  private toggleSetSessionAccount = (): void => {
-    this.setState(({ isSetSessionAccountOpen }): Pick<State, never> => ({
-      isSetSessionAccountOpen: !isSetSessionAccountOpen
-    }));
-  }
-
-  private toggleSettingPopup = (): void => {
-    this.setState(({ isSettingPopupOpen }): Pick<State, never> => ({
-      isSettingPopupOpen: !isSettingPopupOpen
-    }));
-  }
-
-  private toggleUnbond = (): void => {
-    this.setState(({ isUnbondOpen }): Pick<State, never> => ({
-      isUnbondOpen: !isUnbondOpen
-    }));
-  }
-
-  private toggleValidate = (): void => {
-    this.setState(({ isValidateOpen }): Pick<State, never> => ({
-      isValidateOpen: !isValidateOpen
-    }));
-  }
-}
-
-export default withMulti(
-  styled(Account as React.ComponentClass<Props>)`
-    .staking--Card {
-      display: flex;
-    }
-
-    .staking--Accounts {
-      flex: 3;
-      display: flex;
-      flex-direction: column;
-      width: 0px;
-    }
-
-    .staking--Account-detail {
-      text-align: right;
-
-      &.actions{
-        display: inline-block;
-        vertical-align: top;
-        margin-top: .5rem;
-        margin-bottom: 1.5rem;
-
-        &:last-child {
-          margin: 0;
-        }
-      }
-
-      .staking--label {
-        margin: 0 0 -0.75rem 1.75rem;
+        )}
+        <AddressSmall value={stashId} />
+      </td>
+      <td className='top'>
+        <AddressMini
+          label={t('controller')}
+          value={controllerId}
+        />
+      </td>
+      <td className='top'>
+        <AddressInfo
+          address={stashId}
+          withBalance={{
+            available: false,
+            bonded: true,
+            free: false,
+            redeemable: false,
+            unlocking: true
+          }}
+          withRewardDestination
+        />
+      </td>
+      {isStashValidating
+        ? (
+          <td className='top'>
+            <AddressInfo
+              address={stashId}
+              withBalance={{
+                available: false,
+                bonded: false,
+                free: false,
+                redeemable: true,
+                unlocking: true
+              }}
+              withHexSessionId={isSubstrateV2 && hexSessionIdNext !== '0x' && [hexSessionIdQueue, hexSessionIdNext]}
+              withValidatorPrefs
+            />
+          </td>
+        )
+        : (
+          <td>
+            {isStashNominating && nominees && (
+              <details>
+                <summary>{t('Nominating ({{count}})', { replace: { count: nominees.length } })}</summary>
+                {nominees.map((nomineeId, index): React.ReactNode => (
+                  <AddressMini
+                    key={index}
+                    value={nomineeId}
+                    withBalance={false}
+                    withBonded
+                  />
+                ))}
+              </details>
+            )}
+          </td>
+        )
       }
-    }
-
-    .staking--Infos {
-      flex: 1;
-      display: flex;
-      flex-direction: column;
-      margin-top: -2.25rem;
-
-      .staking--balances {
-        div {
-          justify-content: flex-end;
+      <td className='top number together'>
+        {isLoading
+          ? null
+          : (
+            <>
+              {(isStashNominating || isStashValidating)
+                ? (
+                  <TxButton
+                    accountId={controllerId}
+                    isNegative
+                    label={
+                      isStashNominating
+                        ? t('Stop Nominating')
+                        : t('Stop Validating')
+                    }
+                    icon='stop'
+                    key='stop'
+                    tx='staking.chill'
+                  />
+                )
+                : (
+                  <Button.Group>
+                    {(!sessionIds.length || (isSubstrateV2 && hexSessionIdNext === '0x'))
+                      ? (
+                        <Button
+                          isPrimary
+                          key='set'
+                          onClick={toggleSetSession}
+                          label={t('Session Key')}
+                          icon='sign-in'
+                        />
+                      )
+                      : (
+                        <Button
+                          isPrimary
+                          key='validate'
+                          onClick={toggleValidate}
+                          label={t('Validate')}
+                          icon='check circle outline'
+                        />
+                      )
+                    }
+                    <Button.Or key='nominate.or' />
+                    <Button
+                      isPrimary
+                      key='nominate'
+                      onClick={toggleNominate}
+                      label={t('Nominate')}
+                      icon='hand paper outline'
+                    />
+                  </Button.Group>
+                )
+              }
+              <Popup
+                key='settings'
+                onClose={toggleSettings}
+                open={isSettingsOpen}
+                position='bottom right'
+                trigger={
+                  <Button
+                    icon='setting'
+                    onClick={toggleSettings}
+                  />
+                }
+              >
+                <Menu
+                  vertical
+                  text
+                  onClick={toggleSettings}
+                >
+                  {balancesAll?.freeBalance.gtn(0) && (
+                    <Menu.Item
+                      disabled={!isOwnStash}
+                      onClick={toggleBondExtra}
+                    >
+                      {t('Bond more funds')}
+                    </Menu.Item>
+                  )}
+                  <Menu.Item onClick={toggleUnbond}>
+                    {t('Unbond funds')}
+                  </Menu.Item>
+                  <Menu.Item
+                    disabled={!isOwnStash}
+                    onClick={toggleSetController}
+                  >
+                    {t('Change controller account')}
+                  </Menu.Item>
+                  <Menu.Item onClick={toggleRewardDestination}>
+                    {t('Change reward destination')}
+                  </Menu.Item>
+                  {isStashValidating &&
+                    <Menu.Item onClick={toggleValidate}>
+                      {t('Change validator preferences')}
+                    </Menu.Item>
+                  }
+                  {!isStashNominating &&
+                    <Menu.Item onClick={toggleSetSession}>
+                      {isSubstrateV2 ? t('Change session keys') : t('Change session account')}
+                    </Menu.Item>
+                  }
+                  {isStashNominating &&
+                    <Menu.Item onClick={toggleNominate}>
+                      {t('Change nominee(s)')}
+                    </Menu.Item>
+                  }
+                  {!isStashNominating && isSubstrateV2 &&
+                    <Menu.Item onClick={toggleInject}>
+                      {t('Inject session keys (advanced)')}
+                    </Menu.Item>
+                  }
+                </Menu>
+              </Popup>
+            </>
+          )
         }
+      </td>
+    </tr>
+  );
+}
 
-        .column {
-          flex:0;
-        }
-      }
-    }
-
-    .staking--Account-Nominations {
-      margin-top: 1em;
-    }
-
-    .ui--Row-buttons .ui--Button-Group {
-      margin-right: .25rem;
-
-      .ui.tiny.icon.button {
-        visibility: visible;
-      }
+export default translate(
+  styled(Account)`
+    .ui--Button-Group {
+      display: inline-block;
+      margin-right: 0.25rem;
+      vertical-align: inherit;
     }
-  `,
-  translate,
-  withCalls<Props>(
-    ['derive.staking.account', { paramName: 'stashId' }],
-    ['derive.balances.all', { paramName: 'stashId' }]
-  )
+  `
 );

+ 42 - 34
packages/app-staking/src/Actions/index.tsx

@@ -7,8 +7,7 @@ import { I18nProps } from '@polkadot/react-components/types';
 import { AccountId, StakingLedger } from '@polkadot/types/interfaces';
 
 import React, { useEffect, useState } from 'react';
-import styled from 'styled-components';
-import { Button, CardGrid } from '@polkadot/react-components';
+import { Button, Table } from '@polkadot/react-components';
 import { useCall, useApi, useAccounts } from '@polkadot/react-hooks';
 import { Option } from '@polkadot/types';
 
@@ -24,7 +23,7 @@ interface Props extends I18nProps {
   stakingOverview?: DerivedStakingOverview;
 }
 
-function getStashes (allAccounts: string[], queryBonded?: Option<AccountId>[], queryLedger?: Option<StakingLedger>[]): [string, boolean][] | null {
+function getStashes (allAccounts: string[], stashTypes: Record<string, number>, queryBonded?: Option<AccountId>[], queryLedger?: Option<StakingLedger>[]): [string, boolean][] | null {
   const result: [string, boolean][] = [];
 
   if (!queryBonded || !queryLedger) {
@@ -43,7 +42,9 @@ function getStashes (allAccounts: string[], queryBonded?: Option<AccountId>[], q
     }
   });
 
-  return result;
+  return result.sort((a, b): number =>
+    (stashTypes[a[0]] || 99) - (stashTypes[b[0]] || 99)
+  );
 }
 
 function Actions ({ allStashes, className, isVisible, next, recentlyOnline, stakingOverview, t }: Props): React.ReactElement<Props> {
@@ -53,17 +54,26 @@ function Actions ({ allStashes, className, isVisible, next, recentlyOnline, stak
   const queryLedger = useCall<Option<StakingLedger>[]>(api.query.staking.ledger.multi as any, [allAccounts]);
   const [isNewStakeOpen, setIsNewStateOpen] = useState(false);
   const [foundStashes, setFoundStashes] = useState<[string, boolean][] | null>(null);
+  const [stashTypes, setStashTypes] = useState<Record<string, number>>({});
 
   useEffect((): void => {
-    setFoundStashes(getStashes(allAccounts, queryBonded, queryLedger));
-  }, [allAccounts, queryBonded, queryLedger]);
+    setFoundStashes(getStashes(allAccounts, stashTypes, queryBonded, queryLedger));
+  }, [allAccounts, queryBonded, queryLedger, stashTypes]);
 
-  const isEmpty = !isNewStakeOpen && (!foundStashes || foundStashes.length === 0);
   const _toggleNewStake = (): void => setIsNewStateOpen(!isNewStakeOpen);
+  const _onUpdateType = (stashId: string, type: 'validator' | 'nominator' | 'started' | 'other'): void =>
+    setStashTypes({
+      ...stashTypes,
+      [stashId]: type === 'validator'
+        ? 1
+        : type === 'nominator'
+          ? 5
+          : 9
+    });
 
   return (
-    <CardGrid
-      buttons={
+    <div className={`${className} ${!isVisible && 'staking--hidden'}`}>
+      <Button.Group>
         <Button
           isPrimary
           key='new-stake'
@@ -71,35 +81,33 @@ function Actions ({ allStashes, className, isVisible, next, recentlyOnline, stak
           icon='add'
           onClick={_toggleNewStake}
         />
-      }
-      className={`${className} ${!isVisible && 'staking--hidden'}`}
-      emptyText={t('No funds staked yet.')}
-      isEmpty={isEmpty}
-    >
+      </Button.Group>
       {isNewStakeOpen && (
         <StartStaking onClose={_toggleNewStake} />
       )}
-      {foundStashes?.map(([stashId, isOwnStash], index): React.ReactNode => (
-        stashId && (
-          <Account
-            allStashes={allStashes}
-            isOwnStash={isOwnStash}
-            key={index}
-            next={next}
-            recentlyOnline={recentlyOnline}
-            stakingOverview={stakingOverview}
-            stashId={stashId}
-          />
+      {foundStashes?.length
+        ? (
+          <Table>
+            <Table.Body>
+              {foundStashes.map(([stashId, isOwnStash]): React.ReactNode => (
+                <Account
+                  allStashes={allStashes}
+                  isOwnStash={isOwnStash}
+                  key={stashId}
+                  next={next}
+                  onUpdateType={_onUpdateType}
+                  recentlyOnline={recentlyOnline}
+                  stakingOverview={stakingOverview}
+                  stashId={stashId}
+                />
+              ))}
+            </Table.Body>
+          </Table>
         )
-      ))}
-    </CardGrid>
+        : t('No funds staked yet. Bond funds to validate or nominate a validator.')
+      }
+    </div>
   );
 }
 
-export default translate(
-  styled(Actions)`
-    .ui--CardGrid-buttons {
-      text-align: right;
-    }
-  `
-);
+export default translate(Actions);

+ 1 - 1
packages/react-api/package.json

@@ -31,7 +31,7 @@
   "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-reactive#readme",
   "dependencies": {
     "@babel/runtime": "^7.7.5",
-    "@polkadot/api": "^0.99.0-beta.13",
+    "@polkadot/api": "^0.99.0-beta.15",
     "@polkadot/extension-dapp": "^0.14.1",
     "edgeware-node-types": "^1.0.10",
     "rxjs-compat": "^6.5.3"

+ 0 - 64
packages/react-components/src/AddressCard.tsx

@@ -1,64 +0,0 @@
-// Copyright 2017-2019 @polkadot/react-components 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';
-
-// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
-// @ts-ignore This line needed for the styled export... don't ask why
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-import BN from 'bn.js';
-import React from 'react';
-import styled from 'styled-components';
-
-import AddressRow, { Props as AddressProps } from './AddressRow';
-import Card from './Card';
-import LinkPolkascan from './LinkPolkascan';
-
-import translate from './translate';
-
-interface Props extends AddressProps {
-  withExplorer?: boolean;
-}
-
-function AddressCard (props: Props): React.ReactElement<Props> {
-  const { className, value, withExplorer } = props;
-
-  return (
-    <Card className={className}>
-      <AddressRow
-        {...props}
-        className='ui--AddressCard-AddressRow'
-      />
-      {withExplorer && value && (
-        <div className='ui--AddressCard-explorer'>
-          <LinkPolkascan
-            className='ui--AddressCard-exporer-link'
-            data={value.toString()}
-            type='address'
-          />
-        </div>
-      )}
-    </Card>
-  );
-}
-
-export default translate(styled(AddressCard)`
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-
-  .ui--Row-buttons .ui--Button-Group .ui.button {
-    visibility: visible;
-    margin-right: .25rem;
-  }
-
-  .ui--AddressCard-explorer {
-    margin: 0 -0.25rem -0.5rem 0;
-    text-align: right;
-
-    .ui--AddressCard-exporer-link {
-      display: inline-block;
-      margin-top: 0.5rem;
-    }
-  }
-`);

+ 3 - 1
packages/react-components/src/AddressInfo.tsx

@@ -407,7 +407,7 @@ function AddressInfo (props: Props): React.ReactElement<Props> {
         )}
         {withRewardDestination && stakingInfo && stakingInfo.rewardDestination && (
           <>
-            <Label label={t('reward destination')} />
+            <Label label={t('rewards')} />
             <div className='result'>{stakingInfo.rewardDestination.toString().toLowerCase()}</div>
           </>
         )}
@@ -434,6 +434,8 @@ export default withMulti(
     }
 
     .column {
+      justify-content: start;
+
       &.column--expander {
         text-align: left;
         width: 15rem;

+ 1 - 1
packages/react-components/src/AddressMini.tsx

@@ -26,7 +26,7 @@ interface Props extends BareProps {
   isShort?: boolean;
   label?: React.ReactNode;
   type?: KeyringItemType;
-  value?: AccountId | AccountIndex | Address | string;
+  value?: AccountId | AccountIndex | Address | string | null;
   withAddress?: boolean;
   withBalance?: boolean;
   withBonded?: boolean;

+ 0 - 1
packages/react-components/src/index.tsx

@@ -3,7 +3,6 @@
 // of the Apache-2.0 license. See the LICENSE file for details.
 
 export { default as ActionItem } from './ActionItem';
-export { default as AddressCard } from './AddressCard';
 export { default as AddressInfo } from './AddressInfo';
 export { default as AddressMini } from './AddressMini';
 export { default as AddressMulti } from './AddressMulti';

+ 1 - 0
packages/react-hooks/src/index.ts

@@ -11,4 +11,5 @@ export { default as useForm } from './useForm';
 export { default as useDebounce } from './useDebounce';
 export { default as useFavorites } from './useFavorites';
 export { default as usePassword } from './usePassword';
+export { default as useToggle } from './useToggle';
 export { default as useTx } from './useTx';

+ 14 - 0
packages/react-hooks/src/useToggle.ts

@@ -0,0 +1,14 @@
+// Copyright 2017-2019 @polkadot/react-hooks 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 { useState } from 'react';
+
+// Simple wrapper for a true/false toggle
+export default function useToggle (defaultValue = false): [boolean, () => void, (value: boolean) => void] {
+  const [isActive, setActive] = useState(defaultValue);
+
+  const toggle = (): void => setActive(!isActive);
+
+  return [isActive, toggle, setActive];
+}

+ 46 - 46
yarn.lock

@@ -2044,35 +2044,35 @@
   dependencies:
     "@types/node" ">= 8"
 
-"@polkadot/api-contract@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/api-contract/-/api-contract-0.99.0-beta.13.tgz#6020d04034c1f6c309d514aa2a661f26235f2f0b"
-  integrity sha512-eGFJ9LdOzbKmQzbxI3NLrF4Rnwe7P/MImq7MtmKBNOWD9mf+Bxg81+74EHCu4ijizuUjTAmtLIf9C2w+r1vaqQ==
+"@polkadot/api-contract@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/api-contract/-/api-contract-0.99.0-beta.15.tgz#5a500018b0792586248f9810e623a10c6cb0cdd7"
+  integrity sha512-DXdhXsft0iRjXmNBZbvfeucvncWkc6rT0I/rdwj7GRF1+U9KZNsChdVyWhxN37OY1+kgaiNJ3yS79bEhLVyaFw==
   dependencies:
     "@babel/runtime" "^7.7.5"
-    "@polkadot/types" "^0.99.0-beta.13"
+    "@polkadot/types" "^0.99.0-beta.15"
 
-"@polkadot/api-derive@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-0.99.0-beta.13.tgz#347ebc6ad7ca8a5491e2bff8c9045b8709484189"
-  integrity sha512-0D9dr3+KsFPvUZ3uQkL7a4nbfdtI3cmcIqGYXqPxmsiTacn0FQR1rN3j9yQa+rIHLIKPjBlpcQXXFAm2xxrPtA==
+"@polkadot/api-derive@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-0.99.0-beta.15.tgz#c3e50cd254f67133aff7086b7ec50f60b06060b2"
+  integrity sha512-sXALqyyKjJd3BhsSht505Gg13KAM24TyN19NwMwQXerqqR/qjuUZcj+M94KWN988ZdZzNElPNa4P0h3hlEKEAw==
   dependencies:
     "@babel/runtime" "^7.7.5"
-    "@polkadot/api" "^0.99.0-beta.13"
-    "@polkadot/types" "^0.99.0-beta.13"
+    "@polkadot/api" "^0.99.0-beta.15"
+    "@polkadot/types" "^0.99.0-beta.15"
 
-"@polkadot/api@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.99.0-beta.13.tgz#eab4ee8d838f7fa000b14cb279b9aa50dd62f904"
-  integrity sha512-GOrvZvRkoiYIJy8I+9wGHdMsg7A4p5gsPphkTSViy0umeiKbGPg3yxLAm82DJsPoFiXaU1lf+CKQbT40YBYpyQ==
+"@polkadot/api@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.99.0-beta.15.tgz#ce69560fb4a8778bdc05c709bbd0199ae3d9a3b1"
+  integrity sha512-PkQ5r0kUu3HxzB4Bp5cX3VyitMN+3I33Xb4oU8h6G5cnT/W67FOPBRtDQk4yekFJU0cn8kDF/d0xZQGGkXCbww==
   dependencies:
     "@babel/runtime" "^7.7.5"
-    "@polkadot/api-derive" "^0.99.0-beta.13"
+    "@polkadot/api-derive" "^0.99.0-beta.15"
     "@polkadot/keyring" "^1.7.1"
-    "@polkadot/metadata" "^0.99.0-beta.13"
-    "@polkadot/rpc-core" "^0.99.0-beta.13"
-    "@polkadot/rpc-provider" "^0.99.0-beta.13"
-    "@polkadot/types" "^0.99.0-beta.13"
+    "@polkadot/metadata" "^0.99.0-beta.15"
+    "@polkadot/rpc-core" "^0.99.0-beta.15"
+    "@polkadot/rpc-provider" "^0.99.0-beta.15"
+    "@polkadot/types" "^0.99.0-beta.15"
     "@polkadot/util-crypto" "^1.7.1"
 
 "@polkadot/dev-react@^0.32.0-beta.17":
@@ -2177,10 +2177,10 @@
   dependencies:
     "@babel/runtime" "^7.7.5"
 
-"@polkadot/jsonrpc@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.99.0-beta.13.tgz#8c92b9a7b3449601a6175fbfb0ab6aaa652bb580"
-  integrity sha512-mVfSWAeCihBj50IoNGPsPglqtPdE2ahy7dSbOoEhmHhnoOxnbmyd/izt/Nj0OakDWMeXOphXAfX/C9c5HM9KhQ==
+"@polkadot/jsonrpc@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.99.0-beta.15.tgz#f191c063192d4d4f03b7f70d7adc5acb75d93240"
+  integrity sha512-eC1CZwwpCTUF4fxd8buy+n/bbl5aoYlHb/oWN5AJZ+pJ3RXQTg2A72d4fC+Y0rPXIyJ1qo1MnumFCij9dUB9Fg==
   dependencies:
     "@babel/runtime" "^7.7.5"
 
@@ -2193,13 +2193,13 @@
     "@polkadot/util" "^1.7.1"
     "@polkadot/util-crypto" "^1.7.1"
 
-"@polkadot/metadata@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/metadata/-/metadata-0.99.0-beta.13.tgz#1f370a34aac9d1a03367659735ffc11b66a470ae"
-  integrity sha512-tTN3x+wmomq/bKxBEB0sJJCkiqr2nCymDDx9W/UbI+kPBhlWpdJ6OO3AGkDmdYwV8RSSK+71q3A57kTntB3n3Q==
+"@polkadot/metadata@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/metadata/-/metadata-0.99.0-beta.15.tgz#3ed1aab7569dbb15b2e31a05dbaf2e6c218f48cc"
+  integrity sha512-icecwhWzWehiZFxpJ+76IbKxf+11Qh1Qou9l29hcGICw9TewX5VeaWoIp6b9OE7VY7MdlY9Zmc9L87WTLlu9lg==
   dependencies:
     "@babel/runtime" "^7.7.5"
-    "@polkadot/types" "^0.99.0-beta.13"
+    "@polkadot/types" "^0.99.0-beta.15"
     "@polkadot/util" "^1.7.1"
     "@polkadot/util-crypto" "^1.7.1"
 
@@ -2228,25 +2228,25 @@
     qrcode-generator "^1.4.4"
     react-qr-reader "^2.2.1"
 
-"@polkadot/rpc-core@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-0.99.0-beta.13.tgz#d2edcc86f35360c85b782951f5d2e5bcc2fd21cb"
-  integrity sha512-uORlJKZh4PvkLgiWcuZmsVYrSuXF04jd95wFzaB8VIxm60q0c7tu40rs9QyPkggSE/rj6xdhu4syjP58iD18cw==
+"@polkadot/rpc-core@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-0.99.0-beta.15.tgz#4f1fd0d563f3c41b8e66a7d42ace72e4a48eb222"
+  integrity sha512-fm2v0hYXwryYWinT6ZqQIRJgLdcJR02OqojNgG12BNvGgFxFgwBuvGg0ZH/huCGErPPGfSm7auxapxcTRBlEDw==
   dependencies:
     "@babel/runtime" "^7.7.5"
-    "@polkadot/jsonrpc" "^0.99.0-beta.13"
-    "@polkadot/rpc-provider" "^0.99.0-beta.13"
-    "@polkadot/types" "^0.99.0-beta.13"
+    "@polkadot/jsonrpc" "^0.99.0-beta.15"
+    "@polkadot/rpc-provider" "^0.99.0-beta.15"
+    "@polkadot/types" "^0.99.0-beta.15"
     "@polkadot/util" "^1.7.1"
     rxjs "^6.5.3"
 
-"@polkadot/rpc-provider@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-0.99.0-beta.13.tgz#00de8fab8b6c4089a95e9faf299246c9b32e0b35"
-  integrity sha512-yQxPtiLID9N4rjWjKg67UFpUzyZVH7ms3RQ82fVBfNXo7/y3N8e8P8o/6ny+yzMdkQ4Bz1J6jTwYjW2hx+CEGw==
+"@polkadot/rpc-provider@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-0.99.0-beta.15.tgz#602fbbb2e227b0543ae1a865dea6a18763fbdda4"
+  integrity sha512-jBlMrhc/92QxkXsPSLModitEFJ1ZX42xBtwzdinteaRHp55kqQd7T8Vhtu5E+rlpdpyLd7ViyTyI4iqJkCIclw==
   dependencies:
     "@babel/runtime" "^7.7.5"
-    "@polkadot/metadata" "^0.99.0-beta.13"
+    "@polkadot/metadata" "^0.99.0-beta.15"
     "@polkadot/util" "^1.7.1"
     "@polkadot/util-crypto" "^1.7.1"
     eventemitter3 "^4.0.0"
@@ -2260,13 +2260,13 @@
   dependencies:
     "@types/chrome" "^0.0.91"
 
-"@polkadot/types@^0.99.0-beta.13":
-  version "0.99.0-beta.13"
-  resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-0.99.0-beta.13.tgz#a7332127109f3d9b09561622cd79f90519418d26"
-  integrity sha512-8hr6azJuqttDA1InDrTJDgLZbtplE7Fo2Dtfkm+YR7O6QosCKoBRxTZB62whqy9xc4vAv4wcR4ymdcGpIpXhyQ==
+"@polkadot/types@^0.99.0-beta.15":
+  version "0.99.0-beta.15"
+  resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-0.99.0-beta.15.tgz#8d19764b31006f42ab9a5bba6f62635536a7f8dd"
+  integrity sha512-B00a4CaY+qBkhuIo729xauJPp5aLFUJzPIRaDpH//fw9e9Ln8n8Pef9AqrgN9igQpoV3A7QsBq5qKrRgyqTCsA==
   dependencies:
     "@babel/runtime" "^7.7.5"
-    "@polkadot/metadata" "^0.99.0-beta.13"
+    "@polkadot/metadata" "^0.99.0-beta.15"
     "@polkadot/util" "^1.7.1"
     "@polkadot/util-crypto" "^1.7.1"
     "@types/memoizee" "^0.4.3"