Redo staking process and overview (#1268)
* remove filtering on actions tab * remove unused balances on actions * remove unused balances on actions * only show stash * show stashed including validators and nominators * some buttons * new stake button * bond edit init * BondEdit button * combined session and validating button * remove comments * remove css hack * phrasing * validator prefs * can edit validator prefs * fix validatorPrefs undifined and nits * validate unstakeThreshold (<11) * with setting button noautoclose * fix nominee styling * change session and process feeling * lint * change nominees without defaultValue * display and change reward destination * change controller * add balances on bond and unbond * available balance, setting button right, nameOrAddress * back to the old flow with set session key, cleanup logs * balance off on chrome * fix comments + a design hickup on chrome * fix conflicts * nits * Various small UI fixes * Types for getAddress defaults * use name * type props for Row * lint
This commit is contained in:
parent
db4d7441d9
commit
d868adae88
@ -60,9 +60,9 @@ class Account extends React.PureComponent<Props> {
|
||||
>
|
||||
{this.renderModals()}
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance
|
||||
withExtended
|
||||
value={address}
|
||||
/>
|
||||
</AddressCard>
|
||||
);
|
||||
|
||||
@ -84,7 +84,6 @@ class Transfer extends React.PureComponent<Props> {
|
||||
componentDidUpdate (prevProps: Props, prevState: State) {
|
||||
const { balances_fees } = this.props;
|
||||
const { extrinsic, recipientId, senderId } = this.state;
|
||||
|
||||
const hasLengthChanged = ((extrinsic && extrinsic.encodedLength) || 0) !== ((prevState.extrinsic && prevState.extrinsic.encodedLength) || 0);
|
||||
|
||||
if ((recipientId && prevState.recipientId !== recipientId) ||
|
||||
|
||||
@ -57,9 +57,9 @@ class Address extends React.PureComponent<Props, State> {
|
||||
>
|
||||
{this.renderModals()}
|
||||
<AddressInfo
|
||||
address={address}
|
||||
withBalance={{ available: true, free: true, total: true }}
|
||||
withExtended={{ nonce: true }}
|
||||
value={address}
|
||||
/>
|
||||
</AddressCard>
|
||||
);
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
// 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 { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@polkadot/ui-app';
|
||||
import { withCalls, withMulti } from '@polkadot/ui-api';
|
||||
import { AccountId, Vector } from '@polkadot/types';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
accountId: string,
|
||||
nominating: AccountId,
|
||||
staking_nominatorsFor: Vector<AccountId>,
|
||||
onClick: (index: number) => void
|
||||
};
|
||||
|
||||
class UnnominateButton extends React.Component<Props> {
|
||||
render () {
|
||||
const { nominating, staking_nominatorsFor, style, t } = this.props;
|
||||
|
||||
if (!nominating || !staking_nominatorsFor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
className='staking--Account-buttons'
|
||||
style={style}
|
||||
isNegative
|
||||
onClick={this.onClick}
|
||||
label={t('Unnominate')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
const { accountId, staking_nominatorsFor, onClick } = this.props;
|
||||
|
||||
onClick(
|
||||
staking_nominatorsFor
|
||||
.map((accountId) => accountId.toString())
|
||||
.indexOf(accountId));
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
UnnominateButton,
|
||||
translate,
|
||||
withCalls<Props>(
|
||||
['query.staking.nominatorsFor', { paramName: 'nominating' }]
|
||||
)
|
||||
);
|
||||
@ -1,467 +0,0 @@
|
||||
// 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 { AccountFilter, RecentlyOfflineMap } from '../types';
|
||||
import { AccountId, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types';
|
||||
import { ApiProps } from '@polkadot/ui-api/types';
|
||||
import { DerivedBalances, DerivedBalancesMap, DerivedStaking } from '@polkadot/api-derive/types';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
|
||||
import React from 'react';
|
||||
import { AddressCard, AddressInfo, AddressMini, Button, RecentlyOffline, TxButton } from '@polkadot/ui-app';
|
||||
import { withCalls } from '@polkadot/ui-api';
|
||||
|
||||
import Bond from './Bond';
|
||||
import BondExtra from './BondExtra';
|
||||
import Nominating from './Nominating';
|
||||
import SessionKey from './SessionKey';
|
||||
import translate from '../translate';
|
||||
import Unbond from './Unbond';
|
||||
import Validating from './Validating';
|
||||
|
||||
type Props = ApiProps & I18nProps & {
|
||||
accountId: string,
|
||||
balances: DerivedBalancesMap,
|
||||
filter: AccountFilter,
|
||||
isValidator: boolean,
|
||||
recentlyOffline: RecentlyOfflineMap,
|
||||
balances_all?: DerivedBalances,
|
||||
staking_info?: DerivedStaking,
|
||||
stashOptions: Array<KeyringSectionOption>
|
||||
};
|
||||
|
||||
type State = {
|
||||
controllerId: string | null,
|
||||
isActiveController: boolean,
|
||||
isActiveSession: boolean,
|
||||
isActiveStash: boolean,
|
||||
isBondOpen: boolean,
|
||||
isBondExtraOpen: boolean,
|
||||
isNominateOpen: boolean,
|
||||
isSessionKeyOpen: boolean,
|
||||
isValidatingOpen: boolean,
|
||||
isUnbondOpen: boolean,
|
||||
nominators?: Array<AccountId>,
|
||||
sessionId: string | null,
|
||||
stakers?: Exposure,
|
||||
stakingLedger?: StakingLedger,
|
||||
stashId: string | null,
|
||||
validatorPrefs?: ValidatorPrefs
|
||||
};
|
||||
|
||||
function toIdString (id?: AccountId | null): string | null {
|
||||
return id
|
||||
? id.toString()
|
||||
: null;
|
||||
}
|
||||
|
||||
class Account extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
isActiveController: false,
|
||||
isActiveSession: false,
|
||||
isActiveStash: false,
|
||||
controllerId: null,
|
||||
isBondOpen: false,
|
||||
isBondExtraOpen: false,
|
||||
isSessionKeyOpen: false,
|
||||
isNominateOpen: false,
|
||||
isValidatingOpen: false,
|
||||
isUnbondOpen: false,
|
||||
sessionId: null,
|
||||
stashId: null
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ staking_info }: Props): State | null {
|
||||
if (!staking_info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { accountId, controllerId, nextSessionId, nominators, stakers, stakingLedger, stashId, validatorPrefs } = staking_info;
|
||||
|
||||
return {
|
||||
controllerId: toIdString(controllerId),
|
||||
isActiveController: accountId.eq(controllerId),
|
||||
isActiveSession: accountId.eq(nextSessionId),
|
||||
isActiveStash: accountId.eq(stashId),
|
||||
nominators,
|
||||
sessionId: toIdString(nextSessionId),
|
||||
stakers,
|
||||
stakingLedger,
|
||||
stashId: toIdString(stashId),
|
||||
validatorPrefs
|
||||
} as State;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountId, filter } = this.props;
|
||||
const { controllerId, isActiveController, isActiveStash, stashId } = this.state;
|
||||
|
||||
if ((filter === 'controller' && isActiveController) || (filter === 'stash' && isActiveStash) || (filter === 'unbonded' && (controllerId || stashId))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Each component is rendered and gets a `is[Component]Openwill` 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()}
|
||||
type='account'
|
||||
value={accountId}
|
||||
>
|
||||
{this.renderBond()}
|
||||
{this.renderBondExtra()}
|
||||
{this.renderNominating()}
|
||||
{this.renderSessionKey()}
|
||||
{this.renderUnbond()}
|
||||
{this.renderValidating()}
|
||||
<AddressInfo
|
||||
withBalance
|
||||
value={accountId}
|
||||
>
|
||||
<div className='staking--Account-links'>
|
||||
{this.renderControllerId()}
|
||||
{this.renderStashId()}
|
||||
{this.renderSessionId()}
|
||||
{this.renderNominee()}
|
||||
</div>
|
||||
</AddressInfo>
|
||||
</AddressCard>
|
||||
);
|
||||
}
|
||||
|
||||
private renderBond () {
|
||||
const { accountId } = this.props;
|
||||
const { controllerId, isBondOpen } = this.state;
|
||||
|
||||
return (
|
||||
<Bond
|
||||
accountId={accountId}
|
||||
controllerId={controllerId}
|
||||
isOpen={isBondOpen}
|
||||
onClose={this.toggleBond}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderBondExtra () {
|
||||
const { accountId } = this.props;
|
||||
const { controllerId, isBondExtraOpen } = this.state;
|
||||
|
||||
return (
|
||||
<BondExtra
|
||||
accountId={accountId}
|
||||
controllerId={controllerId}
|
||||
isOpen={isBondExtraOpen}
|
||||
onClose={this.toggleBondExtra}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderUnbond () {
|
||||
const { controllerId, isUnbondOpen } = this.state;
|
||||
|
||||
return (
|
||||
<Unbond
|
||||
controllerId={controllerId}
|
||||
isOpen={isUnbondOpen}
|
||||
onClose={this.toggleUnbond}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderValidating () {
|
||||
const { accountId } = this.props;
|
||||
const { isValidatingOpen, stashId, validatorPrefs } = this.state;
|
||||
|
||||
if (!validatorPrefs || !isValidatingOpen || !stashId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Validating
|
||||
accountId={accountId}
|
||||
isOpen
|
||||
onClose={this.toggleValidating}
|
||||
stashId={stashId}
|
||||
validatorPrefs={validatorPrefs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSessionKey () {
|
||||
const { accountId } = this.props;
|
||||
const { isSessionKeyOpen, stashId } = this.state;
|
||||
|
||||
if (!stashId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionKey
|
||||
accountId={accountId}
|
||||
isOpen={isSessionKeyOpen}
|
||||
onClose={this.toggleSessionKey}
|
||||
stashId={stashId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominee () {
|
||||
const { t } = this.props;
|
||||
const { nominators } = this.state;
|
||||
|
||||
if (!nominators || !nominators.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail'>
|
||||
<label className='staking--label'>{t('nominating')}</label>
|
||||
{nominators.map((nomineeId, index) => (
|
||||
<AddressMini
|
||||
key={index}
|
||||
iconInfo={this.renderOffline(nomineeId)}
|
||||
value={nomineeId}
|
||||
withBalance={false}
|
||||
withBonded
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderOffline (address: AccountId | string) {
|
||||
const { recentlyOffline } = this.props;
|
||||
|
||||
return (
|
||||
<RecentlyOffline
|
||||
accountId={address}
|
||||
offline={recentlyOffline[address.toString()]}
|
||||
tooltip
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderControllerId () {
|
||||
const { t } = this.props;
|
||||
const { controllerId, isActiveController } = this.state;
|
||||
|
||||
if (!controllerId || isActiveController) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail'>
|
||||
<label className='staking--label'>{t('controller')}</label>
|
||||
<AddressMini
|
||||
iconInfo={this.renderOffline(controllerId)}
|
||||
value={controllerId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSessionId () {
|
||||
const { t } = this.props;
|
||||
const { isActiveSession, sessionId } = this.state;
|
||||
|
||||
if (!sessionId || isActiveSession) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail'>
|
||||
<label className='staking--label'>{t('session')}</label>
|
||||
<AddressMini value={sessionId} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderStashId () {
|
||||
const { t } = this.props;
|
||||
const { isActiveStash, stashId } = this.state;
|
||||
|
||||
if (!stashId || isActiveStash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail'>
|
||||
<label className='staking--label'>{t('stash')}</label>
|
||||
<AddressMini
|
||||
iconInfo={this.renderOffline(stashId)}
|
||||
value={stashId}
|
||||
withBalance={false}
|
||||
withBonded
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominating () {
|
||||
const { accountId, stashOptions } = this.props;
|
||||
const { isNominateOpen, stashId } = this.state;
|
||||
|
||||
if (!stashId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Nominating
|
||||
accountId={accountId}
|
||||
isOpen={isNominateOpen}
|
||||
onClose={this.toggleNominate}
|
||||
stashId={stashId}
|
||||
stashOptions={stashOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderButtons () {
|
||||
const { accountId, balances_all, t } = this.props;
|
||||
const { isActiveStash, isActiveController, nominators, sessionId, stakingLedger, validatorPrefs } = this.state;
|
||||
const buttons = [];
|
||||
|
||||
if (isActiveStash) {
|
||||
// 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)
|
||||
if (balances_all && stakingLedger && stakingLedger.total && (balances_all.freeBalance.gt(stakingLedger.total))) {
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='bond'
|
||||
onClick={this.toggleBondExtra}
|
||||
label={t('Bond Additional')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// don't show the `unbond` button if there's nothing to unbond
|
||||
// staking_ledger.active gives the amount that can be unbonded (total - what's being unlocked).
|
||||
if (stakingLedger && stakingLedger.active && stakingLedger.active.gtn(0)) {
|
||||
buttons.length && buttons.push(<Button.Or key='bondAdditional.or' />);
|
||||
buttons.push(
|
||||
<Button
|
||||
isNegative
|
||||
key='unbond'
|
||||
onClick={this.toggleUnbond}
|
||||
label={t('Unbond')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (isActiveController) {
|
||||
const isNominating = !!nominators && nominators.length;
|
||||
const isValidating = !!validatorPrefs && !validatorPrefs.isEmpty;
|
||||
|
||||
// if we are validating/nominating show stop
|
||||
if (isValidating || isNominating) {
|
||||
buttons.push(
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
isNegative
|
||||
label={
|
||||
isNominating
|
||||
? t('Stop Nominating')
|
||||
: t('Stop Validating')
|
||||
}
|
||||
key='stop'
|
||||
tx='staking.chill'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (!sessionId) {
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='session'
|
||||
onClick={this.toggleSessionKey}
|
||||
label={t('Set Session Key')}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='validate'
|
||||
onClick={this.toggleValidating}
|
||||
label={t('Validate')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(<Button.Or key='nominate.or' />);
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='nominate'
|
||||
onClick={this.toggleNominate}
|
||||
label={t('Nominate')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// we have nothing here, show the bond to get started
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='bond'
|
||||
onClick={this.toggleBond}
|
||||
label={t('Bond Funds')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button.Group>
|
||||
{buttons}
|
||||
</Button.Group>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleBond = () => {
|
||||
this.setState(({ isBondOpen }) => ({
|
||||
isBondOpen: !isBondOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleBondExtra = () => {
|
||||
this.setState(({ isBondExtraOpen }) => ({
|
||||
isBondExtraOpen: !isBondExtraOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleNominate = () => {
|
||||
this.setState(({ isNominateOpen }) => ({
|
||||
isNominateOpen: !isNominateOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleSessionKey = () => {
|
||||
this.setState(({ isSessionKeyOpen }) => ({
|
||||
isSessionKeyOpen: !isSessionKeyOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleUnbond = () => {
|
||||
this.setState(({ isUnbondOpen }) => ({
|
||||
isUnbondOpen: !isUnbondOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleValidating = () => {
|
||||
this.setState(({ isValidatingOpen }) => ({
|
||||
isValidatingOpen: !isValidatingOpen
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(
|
||||
withCalls<Props>(
|
||||
['derive.staking.info', { paramName: 'accountId' }],
|
||||
['derive.balances.all', { paramName: 'accountId' }]
|
||||
)(Account)
|
||||
);
|
||||
@ -1,93 +0,0 @@
|
||||
// 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 { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { AccountFilter, ComponentProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import { CardGrid, Dropdown, FilterOverlay } from '@polkadot/ui-app';
|
||||
import { getAddressName } from '@polkadot/ui-app/util';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import createOption from '@polkadot/ui-keyring/options/item';
|
||||
|
||||
import Account from './Account';
|
||||
import translate from './translate';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
|
||||
type Props = I18nProps & ComponentProps;
|
||||
|
||||
type State = {
|
||||
filter: AccountFilter,
|
||||
filterOptions: Array<{ text: React.ReactNode, value: AccountFilter }>
|
||||
};
|
||||
|
||||
class Accounts extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
const { t } = props;
|
||||
|
||||
this.state = {
|
||||
filter: 'all',
|
||||
filterOptions: [
|
||||
{ text: t('Show all accounts'), value: 'all' },
|
||||
{ text: t('Show all unbonded'), value: 'unbonded' },
|
||||
{ text: t('Show only stashes'), value: 'stash' },
|
||||
{ text: t('Show only controllers'), value: 'controller' }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { balances, recentlyOffline, t, validators } = this.props;
|
||||
const { filter, filterOptions } = this.state;
|
||||
const accounts = keyring.getAccounts();
|
||||
const stashOptions = this.getStashOptions();
|
||||
|
||||
return (
|
||||
<CardGrid>
|
||||
<FilterOverlay>
|
||||
<Dropdown
|
||||
help={t('Select which types of accounts to display, either all, only the stash accounts or the controller accounts.')}
|
||||
label={t('filter')}
|
||||
onChange={this.onChangeFilter}
|
||||
options={filterOptions}
|
||||
value={filter}
|
||||
/>
|
||||
</FilterOverlay>
|
||||
{accounts.map((account) => {
|
||||
const address = account.address();
|
||||
|
||||
return (
|
||||
<Account
|
||||
accountId={address}
|
||||
balances={balances}
|
||||
filter={filter}
|
||||
isValidator={validators.includes(address)}
|
||||
key={address}
|
||||
recentlyOffline={recentlyOffline}
|
||||
stashOptions={stashOptions}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</CardGrid>
|
||||
);
|
||||
}
|
||||
|
||||
private getStashOptions (): Array<KeyringSectionOption> {
|
||||
const { stashes } = this.props;
|
||||
|
||||
return stashes.map((stashId) =>
|
||||
createOption(stashId, getAddressName(stashId, 'account'))
|
||||
);
|
||||
}
|
||||
|
||||
private onChangeFilter = (filter: AccountFilter): void => {
|
||||
this.setState({ filter });
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Accounts);
|
||||
@ -4,35 +4,44 @@
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { ApiProps } from '@polkadot/ui-api/types';
|
||||
import { CalculateBalanceProps } from '../types';
|
||||
import { CalculateBalanceProps } from '../../types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import { Button, InputAddress, InputBalance, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
import { Option, StakingLedger } from '@polkadot/types';
|
||||
import styled from 'styled-components';
|
||||
import { Button, InputAddress, InputBalance, Modal, TxButton, TxComponent, AddressInfo } from '@polkadot/ui-app';
|
||||
import { calcSignatureLength } from '@polkadot/ui-signer/Checks';
|
||||
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
|
||||
import { withCalls, withApi, withMulti } from '@polkadot/ui-api';
|
||||
import { calcSignatureLength } from '@polkadot/ui-signer/Checks';
|
||||
import { ZERO_BALANCE, ZERO_FEES } from '@polkadot/ui-signer/Checks/constants';
|
||||
|
||||
import translate from '../translate';
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & ApiProps & CalculateBalanceProps & {
|
||||
accountId: string,
|
||||
controllerId: string,
|
||||
isOpen: boolean,
|
||||
onClose: () => void,
|
||||
staking_ledger?: Option<StakingLedger>
|
||||
stashId: string
|
||||
};
|
||||
|
||||
type State = {
|
||||
maxAdditional?: BN,
|
||||
extrinsic: SubmittableExtrinsic | null,
|
||||
maxAdditional?: BN,
|
||||
maxBalance?: BN
|
||||
};
|
||||
|
||||
const ZERO = new BN(0);
|
||||
|
||||
const BalanceWrapper = styled.div`
|
||||
& > div {
|
||||
justify-content: flex-end;
|
||||
|
||||
& .column {
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class BondExtra extends TxComponent<Props, State> {
|
||||
state: State = {
|
||||
extrinsic: null
|
||||
@ -52,7 +61,7 @@ class BondExtra extends TxComponent<Props, State> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountId, balances_all = ZERO_BALANCE, isOpen, onClose, t } = this.props;
|
||||
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);
|
||||
|
||||
@ -77,10 +86,10 @@ class BondExtra extends TxComponent<Props, State> {
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
accountId={stashId}
|
||||
isDisabled={!canSubmit}
|
||||
isPrimary
|
||||
label={t('Bond')}
|
||||
label={t('Bond more')}
|
||||
onClick={onClose}
|
||||
extrinsic={extrinsic}
|
||||
ref={this.button}
|
||||
@ -92,26 +101,35 @@ class BondExtra extends TxComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderContent () {
|
||||
const { accountId, t } = this.props;
|
||||
const { stashId, t } = this.props;
|
||||
const { maxBalance } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header>
|
||||
{t('Bond Extra')}
|
||||
{t('Bond more funds')}
|
||||
</Modal.Header>
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={accountId}
|
||||
defaultValue={stashId}
|
||||
isDisabled
|
||||
label={t('stash account')}
|
||||
/>
|
||||
<BalanceWrapper>
|
||||
<AddressInfo
|
||||
address={stashId}
|
||||
withBalance={{
|
||||
available: true,
|
||||
bonded: true
|
||||
}}
|
||||
/>
|
||||
</BalanceWrapper>
|
||||
<InputBalance
|
||||
autoFocus
|
||||
className='medium'
|
||||
help={t('The maximum amount to increase the bonded value, this is adjusted using the available free funds on the account.')}
|
||||
label={t('max additional value')}
|
||||
help={t('Amount to add to the currently bonded funds. This is adjusted using the available funds on the account.')}
|
||||
label={t('additionnal bonded funds')}
|
||||
maxValue={maxBalance}
|
||||
onChange={this.onChangeValue}
|
||||
onEnter={this.sendTx}
|
||||
@ -131,34 +149,29 @@ class BondExtra extends TxComponent<Props, State> {
|
||||
: null;
|
||||
|
||||
return {
|
||||
maxAdditional,
|
||||
extrinsic,
|
||||
maxAdditional,
|
||||
maxBalance
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private setMaxBalance = () => {
|
||||
const { api, system_accountNonce = ZERO, balances_fees = ZERO_FEES, balances_all = ZERO_BALANCE, staking_ledger } = this.props;
|
||||
const { api, system_accountNonce = ZERO, balances_fees = ZERO_FEES, balances_all = ZERO_BALANCE } = this.props;
|
||||
const { maxAdditional } = this.state;
|
||||
|
||||
const { transactionBaseFee, transactionByteFee } = balances_fees;
|
||||
const { freeBalance } = balances_all;
|
||||
const { availableBalance } = balances_all;
|
||||
|
||||
let prevMax = new BN(0);
|
||||
let maxBalance = new BN(1);
|
||||
let extrinsic;
|
||||
|
||||
let bonded = new BN(0);
|
||||
if (staking_ledger && !staking_ledger.isNone) {
|
||||
bonded = staking_ledger.unwrap().active;
|
||||
}
|
||||
|
||||
while (!prevMax.eq(maxBalance)) {
|
||||
prevMax = maxBalance;
|
||||
|
||||
extrinsic = (maxAdditional && maxAdditional.gte(ZERO))
|
||||
? api.tx.staking.bondExtra(maxAdditional.sub(bonded))
|
||||
? api.tx.staking.bondExtra(maxAdditional)
|
||||
: null;
|
||||
|
||||
const txLength = calcSignatureLength(extrinsic, system_accountNonce);
|
||||
@ -166,11 +179,12 @@ class BondExtra extends TxComponent<Props, State> {
|
||||
const fees = transactionBaseFee
|
||||
.add(transactionByteFee.muln(txLength));
|
||||
|
||||
maxBalance = new BN(freeBalance).sub(fees).sub(bonded);
|
||||
maxBalance = availableBalance.sub(fees);
|
||||
}
|
||||
|
||||
this.nextState({
|
||||
extrinsic,
|
||||
maxAdditional,
|
||||
maxBalance
|
||||
});
|
||||
}
|
||||
@ -186,8 +200,7 @@ export default withMulti(
|
||||
withApi,
|
||||
withCalls<Props>(
|
||||
'derive.balances.fees',
|
||||
['derive.balances.all', { paramName: 'accountId' }],
|
||||
['query.system.accountNonce', { paramName: 'accountId' }],
|
||||
['query.staking.ledger', { paramName: 'controllerId' }]
|
||||
['derive.balances.all', { paramName: 'stashId' }],
|
||||
['query.system.accountNonce', { paramName: 'stashId' }]
|
||||
)
|
||||
);
|
||||
@ -9,12 +9,13 @@ import { Icon } from '@polkadot/ui-app';
|
||||
import { AccountId, Option, StakingLedger } from '@polkadot/types';
|
||||
import { withCalls } from '@polkadot/ui-api';
|
||||
|
||||
import translate from '../translate';
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
accountId: string,
|
||||
accountId: string | null,
|
||||
bondedId?: string | null,
|
||||
controllerId: string,
|
||||
controllerId: string | null,
|
||||
defaultController?: string,
|
||||
onError: (error: string | null) => void,
|
||||
stashId?: string | null
|
||||
};
|
||||
@ -28,10 +29,14 @@ class ValidateController extends React.PureComponent<Props, State> {
|
||||
error: null
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ accountId, bondedId, controllerId, onError, stashId, t }: Props, prevState: State): State {
|
||||
static getDerivedStateFromProps ({ accountId, bondedId, controllerId, defaultController, onError, stashId, t }: Props, prevState: State): State {
|
||||
const error = (() => {
|
||||
if (controllerId === accountId) {
|
||||
return t('A controller account which is not the same as your selected account is required');
|
||||
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 t('Please select distinct stash and controller accounts');
|
||||
} 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 } });
|
||||
} else if (stashId) {
|
||||
@ -51,9 +56,10 @@ class ValidateController extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountId } = this.props;
|
||||
const { error } = this.state;
|
||||
|
||||
if (!error) {
|
||||
if (!error || !accountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import React from 'react';
|
||||
import { Icon } from '@polkadot/ui-app';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import translate from '../translate';
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
controllerId: string,
|
||||
@ -0,0 +1,64 @@
|
||||
// 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 { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import { Icon } from '@polkadot/ui-app';
|
||||
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
unstakeThreshold: BN | undefined,
|
||||
onError: (error: string | null) => void
|
||||
};
|
||||
|
||||
type State = {
|
||||
error: string | null
|
||||
};
|
||||
|
||||
class InputValidationUnstakeThreshold extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
error: null
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ onError, t, unstakeThreshold }: Props, prevState: State): State | null {
|
||||
let error = null;
|
||||
|
||||
if (!unstakeThreshold) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (unstakeThreshold.ltn(0)) {
|
||||
error = t('The Threshold must be a positive number');
|
||||
} else if (unstakeThreshold.gtn(10)) {
|
||||
error = t('The Threshold must lower than 11');
|
||||
}
|
||||
|
||||
if (error === prevState.error || !unstakeThreshold) {
|
||||
return null;
|
||||
}
|
||||
|
||||
onError(error);
|
||||
|
||||
return { error };
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error } = this.state;
|
||||
|
||||
if (!error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<article className='warning'>
|
||||
<div><Icon name='warning sign' />{error}</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(InputValidationUnstakeThreshold);
|
||||
@ -8,23 +8,24 @@ import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
import React from 'react';
|
||||
import { Button, InputAddress, Modal, TxButton } from '@polkadot/ui-app';
|
||||
|
||||
import translate from '../translate';
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
accountId: string,
|
||||
controllerId: string,
|
||||
isOpen: boolean,
|
||||
nominees?: Array<string>,
|
||||
onClose: () => void,
|
||||
stashId: string,
|
||||
stashOptions: Array<KeyringSectionOption>
|
||||
};
|
||||
|
||||
type State = {
|
||||
nominees: Array<string>
|
||||
nominees: Array<string> | undefined
|
||||
};
|
||||
|
||||
class Nominating extends React.PureComponent<Props, State> {
|
||||
class Nominate extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
nominees: []
|
||||
nominees: this.props.nominees
|
||||
};
|
||||
|
||||
render () {
|
||||
@ -48,7 +49,7 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderButtons () {
|
||||
const { accountId, onClose, t } = this.props;
|
||||
const { controllerId, onClose, t } = this.props;
|
||||
const { nominees } = this.state;
|
||||
|
||||
return (
|
||||
@ -61,8 +62,8 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
isDisabled={nominees.length === 0}
|
||||
accountId={controllerId}
|
||||
isDisabled={!nominees || nominees.length === 0}
|
||||
isPrimary
|
||||
onClick={onClose}
|
||||
params={[nominees]}
|
||||
@ -75,7 +76,7 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
const { accountId, stashId, stashOptions, t } = this.props;
|
||||
const { controllerId, stashId, stashOptions, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -85,7 +86,7 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={accountId}
|
||||
defaultValue={controllerId}
|
||||
isDisabled
|
||||
label={t('controller account')}
|
||||
/>
|
||||
@ -115,4 +116,4 @@ class Nominating extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Nominating);
|
||||
export default translate(Nominate);
|
||||
@ -0,0 +1,139 @@
|
||||
// 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 { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Icon, InputAddress, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
import { withMulti } from '@polkadot/ui-api';
|
||||
|
||||
import translate from '../../translate';
|
||||
import InputValidationController from '../Account/InputValidationController';
|
||||
|
||||
type Props = I18nProps & {
|
||||
defaultControllerId: string,
|
||||
isValidating?: boolean,
|
||||
onClose: () => void,
|
||||
stashId: string
|
||||
};
|
||||
|
||||
type State = {
|
||||
controllerError: string | null,
|
||||
controllerId: string | null
|
||||
};
|
||||
|
||||
class SetControllerAccount extends TxComponent<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
controllerError: null,
|
||||
controllerId: null
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { defaultControllerId, onClose, stashId, t } = this.props;
|
||||
const { controllerError, controllerId } = this.state;
|
||||
const canSubmit = !controllerError && !!controllerId && (defaultControllerId !== controllerId);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='staking--SetControllerAccount'
|
||||
dimmer='inverted'
|
||||
open
|
||||
size='small'
|
||||
>
|
||||
{this.renderContent()}
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
onClick={onClose}
|
||||
label={t('Cancel')}
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={stashId}
|
||||
isDisabled={!canSubmit}
|
||||
isPrimary
|
||||
label={t('Set controller')}
|
||||
onClick={onClose}
|
||||
params={[controllerId]}
|
||||
tx='staking.setController'
|
||||
ref={this.button}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent () {
|
||||
const { defaultControllerId, stashId, t } = this.props;
|
||||
const { controllerId, controllerError } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header>
|
||||
{t('Change controller account')}
|
||||
</Modal.Header>
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
{this.renderSessionAccountWarning()}
|
||||
<InputAddress
|
||||
className='medium'
|
||||
isDisabled
|
||||
label={t('stash account')}
|
||||
value={stashId}
|
||||
/>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={defaultControllerId}
|
||||
help={t('The controller is the account that will be used to control any nominating or validating actions. Should not match another stash or controller.')}
|
||||
isError={!!controllerError}
|
||||
label={t('controller account')}
|
||||
onChange={this.onChangeController}
|
||||
type='account'
|
||||
value={controllerId}
|
||||
/>
|
||||
<InputValidationController
|
||||
accountId={stashId}
|
||||
defaultController={defaultControllerId}
|
||||
controllerId={controllerId}
|
||||
onError={this.onControllerError}
|
||||
/>
|
||||
</Modal.Content>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSessionAccountWarning () {
|
||||
const { isValidating = false, t } = this.props;
|
||||
|
||||
if (!isValidating) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<article className='warning'>
|
||||
<div className='warning'>
|
||||
<Icon name='warning sign' />
|
||||
{t('Warning - Changing the controller while validating will modify the associated session account. It is advised to stop validating before changing the controller account.')}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
private onChangeController = (controllerId: string) => {
|
||||
this.setState({ controllerId });
|
||||
}
|
||||
|
||||
private onControllerError = (controllerError: string | null) => {
|
||||
this.setState({ controllerError });
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
SetControllerAccount,
|
||||
translate
|
||||
);
|
||||
@ -0,0 +1,107 @@
|
||||
// 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 { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Dropdown, InputAddress, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
import { withMulti } from '@polkadot/ui-api';
|
||||
|
||||
import translate from '../../translate';
|
||||
import { rewardDestinationOptions } from '../constants';
|
||||
|
||||
type Props = I18nProps & {
|
||||
defaultDestination?: number,
|
||||
controllerId: string,
|
||||
onClose: () => void
|
||||
};
|
||||
|
||||
type State = {
|
||||
destination: number
|
||||
};
|
||||
|
||||
class SetRewardDestination extends TxComponent<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
destination: 0
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { controllerId, onClose, t } = this.props;
|
||||
const { destination } = this.state;
|
||||
const canSubmit = !!controllerId;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='staking--Bonding'
|
||||
dimmer='inverted'
|
||||
open
|
||||
size='small'
|
||||
>
|
||||
{this.renderContent()}
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
onClick={onClose}
|
||||
label={t('Cancel')}
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={controllerId}
|
||||
isDisabled={!canSubmit}
|
||||
isPrimary
|
||||
label={t('Set reward destination')}
|
||||
onClick={onClose}
|
||||
params={[destination]}
|
||||
tx={'staking.setPayee'}
|
||||
ref={this.button}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent () {
|
||||
const { controllerId, defaultDestination, t } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header>
|
||||
{t('Bonding Preferences')}
|
||||
</Modal.Header>
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
isDisabled
|
||||
defaultValue={controllerId}
|
||||
help={t('The controller is the account that is be used to control any nominating or validating actions. I will sign this transaction.')}
|
||||
label={t('controller account')}
|
||||
/>
|
||||
<Dropdown
|
||||
className='medium'
|
||||
defaultValue={defaultDestination}
|
||||
help={t('The destination account for any payments as either a nominator or validator')}
|
||||
label={t('payment destination')}
|
||||
onChange={this.onChangeDestination}
|
||||
options={rewardDestinationOptions}
|
||||
/>
|
||||
</Modal.Content>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private onChangeDestination = (destination: number) => {
|
||||
this.setState({ destination });
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
SetRewardDestination,
|
||||
translate
|
||||
);
|
||||
@ -7,13 +7,14 @@ import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import React from 'react';
|
||||
import { Button, InputAddress, Modal, TxButton } from '@polkadot/ui-app';
|
||||
|
||||
import ValidateSession from './ValidateSession';
|
||||
import translate from '../translate';
|
||||
import ValidateSession from './InputValidationSession';
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
accountId: string,
|
||||
controllerId: string,
|
||||
isOpen: boolean,
|
||||
onClose: () => void,
|
||||
sessionId?: string | null,
|
||||
stashId: string
|
||||
};
|
||||
|
||||
@ -22,7 +23,7 @@ type State = {
|
||||
sessionId: string
|
||||
};
|
||||
|
||||
class Key extends React.PureComponent<Props, State> {
|
||||
class SetSessionKey extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
@ -30,12 +31,12 @@ class Key extends React.PureComponent<Props, State> {
|
||||
|
||||
this.state = {
|
||||
sessionError: null,
|
||||
sessionId: props.accountId
|
||||
sessionId: props.sessionId || props.controllerId
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountId, isOpen, onClose, t } = this.props;
|
||||
const { controllerId, isOpen, onClose, t } = this.props;
|
||||
const { sessionError, sessionId } = this.state;
|
||||
|
||||
if (!isOpen) {
|
||||
@ -44,61 +45,61 @@ class Key extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='staking--Stash'
|
||||
className='staking--SetSessionAccount'
|
||||
dimmer='inverted'
|
||||
open
|
||||
size='small'
|
||||
>
|
||||
{this.renderContent()}
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
onClick={onClose}
|
||||
label={t('Cancel')}
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
isDisabled={!sessionId || !!sessionError}
|
||||
isPrimary
|
||||
label={t('Set Session Key')}
|
||||
onClick={onClose}
|
||||
params={[sessionId]}
|
||||
tx='session.setKey'
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
onClick={onClose}
|
||||
label={t('Cancel')}
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={controllerId}
|
||||
isDisabled={!sessionId || !!sessionError}
|
||||
isPrimary
|
||||
label={t('Set Session Key')}
|
||||
onClick={ onClose }
|
||||
params={[sessionId]}
|
||||
tx='session.setKey'
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent () {
|
||||
const { accountId, stashId, t } = this.props;
|
||||
const { controllerId, stashId, t } = this.props;
|
||||
const { sessionId } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header>
|
||||
{t('Session Key')}
|
||||
{t('Set Session Key')}
|
||||
</Modal.Header>
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={accountId}
|
||||
defaultValue={controllerId}
|
||||
isDisabled
|
||||
label={t('controller account')}
|
||||
/>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
help={t('Changing the key only takes effect at the start of the next session. If validating, you should (currently) use an ed25519 key.')}
|
||||
help={t('Changing the key only takes effect at the start of the next session. If validating, it must be an ed25519 key.')}
|
||||
label={t('session key')}
|
||||
onChange={this.onChangeSession}
|
||||
type='account'
|
||||
value={sessionId}
|
||||
/>
|
||||
<ValidateSession
|
||||
controllerId={accountId}
|
||||
controllerId={controllerId}
|
||||
onError={this.onSessionError}
|
||||
sessionId={sessionId}
|
||||
stashId={stashId}
|
||||
@ -117,4 +118,4 @@ class Key extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Key);
|
||||
export default translate(SetSessionKey);
|
||||
@ -7,16 +7,18 @@ import { ApiProps } from '@polkadot/ui-api/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { AccountId, Option, StakingLedger } from '@polkadot/types';
|
||||
import { Button, InputAddress, InputBalance, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
import { AddressInfo, Button, InputAddress, InputBalance, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
import { withCalls, withApi, withMulti } from '@polkadot/ui-api';
|
||||
|
||||
import translate from '../translate';
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & ApiProps & {
|
||||
controllerId?: AccountId | null,
|
||||
isOpen: boolean,
|
||||
onClose: () => void,
|
||||
stashId: string,
|
||||
staking_ledger?: Option<StakingLedger>
|
||||
};
|
||||
|
||||
@ -25,6 +27,16 @@ type State = {
|
||||
maxUnbond?: BN
|
||||
};
|
||||
|
||||
const BalanceWrapper = styled.div`
|
||||
& > div {
|
||||
justify-content: flex-end;
|
||||
|
||||
& .column {
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class Unbond extends TxComponent<Props, State> {
|
||||
state: State = {};
|
||||
|
||||
@ -78,13 +90,13 @@ class Unbond extends TxComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderContent () {
|
||||
const { controllerId, t } = this.props;
|
||||
const { controllerId, stashId, t } = this.props;
|
||||
const { maxBalance } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header>
|
||||
{t('Unbond')}
|
||||
{t('Unbond funds')}
|
||||
</Modal.Header>
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
@ -93,10 +105,18 @@ class Unbond extends TxComponent<Props, State> {
|
||||
isDisabled
|
||||
label={t('controller account')}
|
||||
/>
|
||||
<BalanceWrapper>
|
||||
<AddressInfo
|
||||
accountId={stashId}
|
||||
withBalance={{
|
||||
bonded: true
|
||||
}}
|
||||
/>
|
||||
</BalanceWrapper>
|
||||
<InputBalance
|
||||
autoFocus
|
||||
className='medium'
|
||||
help={t('The maximum amount to unbond, this is adjusted using the bonded funds on the account.')}
|
||||
help={t('The amount of funds to unbond, this is adjusted using the bonded funds on the stash account.')}
|
||||
label={t('unbond amount')}
|
||||
maxValue={maxBalance}
|
||||
onChange={this.onChangeValue}
|
||||
@ -9,39 +9,51 @@ import React from 'react';
|
||||
import { ValidatorPrefs } from '@polkadot/types';
|
||||
import { Button, InputAddress, InputBalance, InputNumber, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
|
||||
import translate from '../translate';
|
||||
import InputValidationUnstakeThreshold from './InputValidationUnstakeThreshold';
|
||||
import translate from '../../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
accountId: string,
|
||||
controllerId: string,
|
||||
isOpen: boolean,
|
||||
onClose: () => void,
|
||||
stashId: string,
|
||||
validatorPrefs: ValidatorPrefs
|
||||
validatorPrefs?: ValidatorPrefs
|
||||
};
|
||||
|
||||
type State = {
|
||||
unstakeThreshold?: BN,
|
||||
unstakeThresholdError: string | null,
|
||||
validatorPayment?: BN
|
||||
};
|
||||
|
||||
class Staking extends TxComponent<Props, State> {
|
||||
class Validate extends TxComponent<Props, State> {
|
||||
state: State = {
|
||||
unstakeThreshold: new BN(3),
|
||||
unstakeThresholdError: null,
|
||||
validatorPayment: new BN(0)
|
||||
};
|
||||
|
||||
// inject the preferences are returned via RPC once into the state (from this
|
||||
// inject the preferences returned via RPC once into the state (from this
|
||||
// point forward it will be entirely managed by the actual inputs)
|
||||
static getDerivedStateFromProps (props: Props, state: State): State | null {
|
||||
if (state.unstakeThreshold) {
|
||||
if (state.unstakeThreshold && state.validatorPayment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { unstakeThreshold, validatorPayment } = props.validatorPrefs;
|
||||
if (props.validatorPrefs) {
|
||||
const { unstakeThreshold, validatorPayment } = props.validatorPrefs;
|
||||
|
||||
return {
|
||||
unstakeThreshold: unstakeThreshold.toBn(),
|
||||
unstakeThresholdError: null,
|
||||
validatorPayment: validatorPayment.toBn()
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
unstakeThreshold: unstakeThreshold.toBn(),
|
||||
validatorPayment: validatorPayment.toBn()
|
||||
unstakeThreshold: undefined,
|
||||
unstakeThresholdError: null,
|
||||
validatorPayment: undefined
|
||||
};
|
||||
}
|
||||
|
||||
@ -66,8 +78,9 @@ class Staking extends TxComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderButtons () {
|
||||
const { accountId, onClose, t } = this.props;
|
||||
const { unstakeThreshold, validatorPayment } = this.state;
|
||||
const { controllerId, onClose, t, validatorPrefs } = this.props;
|
||||
const { unstakeThreshold, unstakeThresholdError, validatorPayment } = this.state;
|
||||
const isChangingPrefs = validatorPrefs && !!validatorPrefs.unstakeThreshold;
|
||||
|
||||
return (
|
||||
<Modal.Actions>
|
||||
@ -79,9 +92,10 @@ class Staking extends TxComponent<Props, State> {
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
accountId={controllerId}
|
||||
isDisabled={!!unstakeThresholdError}
|
||||
isPrimary
|
||||
label={t('Validate')}
|
||||
label={isChangingPrefs ? t('Set validator preferences') : t('Validate')}
|
||||
onClick={onClose}
|
||||
params={[{
|
||||
unstakeThreshold,
|
||||
@ -96,33 +110,36 @@ class Staking extends TxComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderContent () {
|
||||
const { accountId, stashId, t } = this.props;
|
||||
const { unstakeThreshold, validatorPayment } = this.state;
|
||||
const { controllerId, stashId, t, validatorPrefs } = this.props;
|
||||
const { unstakeThreshold, unstakeThresholdError, validatorPayment } = this.state;
|
||||
const defaultValue = validatorPrefs && validatorPrefs.unstakeThreshold && validatorPrefs.unstakeThreshold.toBn();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Header>
|
||||
{t('Validating')}
|
||||
{t('Set validator preferences')}
|
||||
</Modal.Header>
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={accountId}
|
||||
isDisabled
|
||||
label={t('controller account')}
|
||||
/>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={stashId.toString()}
|
||||
isDisabled
|
||||
label={t('stash account')}
|
||||
/>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={controllerId}
|
||||
isDisabled
|
||||
label={t('controller account')}
|
||||
/>
|
||||
<InputNumber
|
||||
autoFocus
|
||||
bitLength={32}
|
||||
className='medium'
|
||||
help={t('The number of allowed slashes for this validator before being automatically unstaked (maximum of 10 allowed)')}
|
||||
label={t('unstake threshold')}
|
||||
defaultValue={defaultValue}
|
||||
help={t('The number of time this validator can get slashed before being automatically unstaked (maximum of 10 allowed)')}
|
||||
isError={!!unstakeThresholdError}
|
||||
label={t('automatic unstake threshold')}
|
||||
onChange={this.onChangeThreshold}
|
||||
onEnter={this.sendTx}
|
||||
value={
|
||||
@ -131,10 +148,15 @@ class Staking extends TxComponent<Props, State> {
|
||||
: '3'
|
||||
}
|
||||
/>
|
||||
<InputValidationUnstakeThreshold
|
||||
onError={this.onUnstakeThresholdError}
|
||||
unstakeThreshold={unstakeThreshold}
|
||||
/>
|
||||
<InputBalance
|
||||
className='medium'
|
||||
help={t('Reward that validator takes up-front, the remainder is split between themselves and nominators')}
|
||||
label={t('payment preferences')}
|
||||
defaultValue={validatorPrefs && validatorPrefs.validatorPayment && validatorPrefs.validatorPayment.toBn()}
|
||||
help={t('Amount taken up-front from the reward by the validator before spliting the remainder between themselves and the nominators')}
|
||||
label={t('reward commission')}
|
||||
onChange={this.onChangePayment}
|
||||
onEnter={this.sendTx}
|
||||
value={
|
||||
@ -159,6 +181,10 @@ class Staking extends TxComponent<Props, State> {
|
||||
this.setState({ unstakeThreshold });
|
||||
}
|
||||
}
|
||||
|
||||
private onUnstakeThresholdError = (unstakeThresholdError: string | null) => {
|
||||
this.setState({ unstakeThresholdError });
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Staking);
|
||||
export default translate(Validate);
|
||||
655
packages/app-staking/src/Actions/Account/index.tsx
Normal file
655
packages/app-staking/src/Actions/Account/index.tsx
Normal file
@ -0,0 +1,655 @@
|
||||
// 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 { RecentlyOfflineMap } from '../../types';
|
||||
import { AccountId, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types';
|
||||
import { ApiProps } from '@polkadot/ui-api/types';
|
||||
import { DerivedBalances, DerivedStaking } from '@polkadot/api-derive/types';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
|
||||
import { Popup } from 'semantic-ui-react';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { AddressInfo, AddressMini, AddressRow, Button, Card, Menu, RecentlyOffline, TxButton } from '@polkadot/ui-app';
|
||||
import { withCalls } from '@polkadot/ui-api';
|
||||
|
||||
import BondExtra from './BondExtra';
|
||||
import Nominate from './Nominate';
|
||||
import SetControllerAccount from './SetControllerAccount';
|
||||
import SetRewardDestination from './SetRewardDestination';
|
||||
import SetSessionAccount from './SetSessionAccount';
|
||||
import translate from '../../translate';
|
||||
import Unbond from './Unbond';
|
||||
import Validate from './Validate';
|
||||
|
||||
type Props = ApiProps & I18nProps & {
|
||||
accountId: string,
|
||||
recentlyOffline: RecentlyOfflineMap,
|
||||
balances_all?: DerivedBalances,
|
||||
staking_info?: DerivedStaking,
|
||||
stashOptions: Array<KeyringSectionOption>
|
||||
};
|
||||
|
||||
type State = {
|
||||
controllerId: string | null,
|
||||
destination: number,
|
||||
isActiveStash: boolean,
|
||||
isBondExtraOpen: boolean,
|
||||
isNominateOpen: boolean,
|
||||
isSetControllerAccountOpen: boolean,
|
||||
isSetRewardDestinationOpen: boolean,
|
||||
isSetSessionAccountOpen: boolean,
|
||||
isSettingPopupOpen: boolean,
|
||||
isStashNominating: boolean,
|
||||
isStashValidating: boolean,
|
||||
isUnbondOpen: boolean,
|
||||
isValidateOpen: boolean,
|
||||
nominees?: Array<string>,
|
||||
sessionId: string | null,
|
||||
stakers?: Exposure,
|
||||
stakingLedger?: StakingLedger,
|
||||
stashId: string | null,
|
||||
validatorPrefs?: ValidatorPrefs
|
||||
};
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
|
||||
.ui.button.tiny {
|
||||
visibility: visible
|
||||
}
|
||||
|
||||
.staking--Accounts {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.staking--Account-detail.actions{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
.staking--label {
|
||||
margin: .5rem 1.75rem -0.5rem 4.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.staking--Actions-Infos {
|
||||
flex: 3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.buttons {
|
||||
margin-bottom: 1rem;
|
||||
flex: 0;
|
||||
|
||||
button {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.staking--balances {
|
||||
div {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex:0;
|
||||
}
|
||||
}
|
||||
|
||||
.staking--Account-Nominee {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1em;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.staking--Account-Nominee {
|
||||
text-align: right;
|
||||
|
||||
.staking--label {
|
||||
margin: 0 2.25rem -.75rem 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function toIdString (id?: AccountId | null): string | null {
|
||||
return id
|
||||
? id.toString()
|
||||
: null;
|
||||
}
|
||||
|
||||
class Account extends React.PureComponent<Props, State> {
|
||||
state: State = {
|
||||
controllerId: null,
|
||||
destination: 0,
|
||||
isActiveStash: false,
|
||||
isBondExtraOpen: false,
|
||||
isNominateOpen: false,
|
||||
isSetControllerAccountOpen: false,
|
||||
isSettingPopupOpen: false,
|
||||
isSetRewardDestinationOpen: false,
|
||||
isSetSessionAccountOpen: false,
|
||||
isStashNominating: false,
|
||||
isStashValidating: false,
|
||||
isUnbondOpen: false,
|
||||
isValidateOpen: false,
|
||||
sessionId: null,
|
||||
stashId: null
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps ({ staking_info }: Props): State | null {
|
||||
if (!staking_info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { accountId, controllerId, nextSessionId, nominators, rewardDestination, stakers, stakingLedger, stashId, validatorPrefs } = staking_info;
|
||||
const isStashNominating = nominators && nominators.length !== 0;
|
||||
const isStashValidating = !!validatorPrefs && !validatorPrefs.isEmpty && !isStashNominating;
|
||||
|
||||
return {
|
||||
controllerId: toIdString(controllerId),
|
||||
destination: rewardDestination && rewardDestination.toNumber(),
|
||||
isActiveStash: accountId.eq(stashId),
|
||||
isStashNominating,
|
||||
isStashValidating,
|
||||
nominees: nominators && nominators.map(toIdString),
|
||||
sessionId: toIdString(nextSessionId),
|
||||
stakers,
|
||||
stakingLedger,
|
||||
stashId: toIdString(stashId),
|
||||
validatorPrefs
|
||||
} as any as State;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { isActiveStash } = this.state;
|
||||
|
||||
if (!isActiveStash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<Card>
|
||||
{this.renderBondExtra()}
|
||||
{this.renderSetValidatorPrefs()}
|
||||
{this.renderNominate()}
|
||||
{this.renderSetControllerAccount()}
|
||||
{this.renderSetRewardDestination()}
|
||||
{this.renderSetSessionAccount()}
|
||||
{this.renderUnbond()}
|
||||
{this.renderValidate()}
|
||||
<Wrapper>
|
||||
<div className='staking--Accounts'>
|
||||
{this.renderStashAccount()}
|
||||
{this.renderControllerAccount()}
|
||||
{this.renderSessionAccount()}
|
||||
</div>
|
||||
<div className='staking--Actions-Infos'>
|
||||
<div className='buttons'>
|
||||
{this.renderButtons()}
|
||||
</div>
|
||||
<div className='staking--balances'>
|
||||
{this.renderInfos()}
|
||||
</div>
|
||||
{this.renderNominee()}
|
||||
</div>
|
||||
</Wrapper>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
private renderBondExtra () {
|
||||
const { controllerId, isBondExtraOpen, stashId } = this.state;
|
||||
|
||||
return (
|
||||
<BondExtra
|
||||
controllerId={controllerId}
|
||||
isOpen={isBondExtraOpen}
|
||||
onClose={this.toggleBondExtra}
|
||||
stashId={stashId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderUnbond () {
|
||||
const { controllerId, isUnbondOpen, stashId } = this.state;
|
||||
|
||||
return (
|
||||
<Unbond
|
||||
controllerId={controllerId}
|
||||
isOpen={isUnbondOpen}
|
||||
onClose={this.toggleUnbond}
|
||||
stashId={stashId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderInfos () {
|
||||
const { stashId } = this.state;
|
||||
|
||||
return (
|
||||
<AddressInfo
|
||||
address={stashId}
|
||||
withBalance={{
|
||||
available: false,
|
||||
bonded: true,
|
||||
free: false,
|
||||
redeemable: true,
|
||||
unlocking: true
|
||||
}}
|
||||
withRewardDestination
|
||||
withValidatorPrefs
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominee () {
|
||||
const { t } = this.props;
|
||||
const { nominees } = this.state;
|
||||
|
||||
if (!nominees || !nominees.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-Nominee'>
|
||||
<label className='staking--label'>{t('nominating')}</label>
|
||||
{nominees.map((nomineeId, index) => (
|
||||
<AddressMini
|
||||
key={index}
|
||||
iconInfo={this.renderOffline(nomineeId)}
|
||||
value={nomineeId}
|
||||
withBalance={false}
|
||||
withBonded
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderOffline (address: AccountId | string) {
|
||||
const { recentlyOffline } = this.props;
|
||||
|
||||
return (
|
||||
<RecentlyOffline
|
||||
accountId={address}
|
||||
offline={recentlyOffline[address.toString()]}
|
||||
tooltip
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderControllerAccount () {
|
||||
const { t } = this.props;
|
||||
const { controllerId } = this.state;
|
||||
|
||||
if (!controllerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail actions'>
|
||||
<label className='staking--label'>{t('controller')}</label>
|
||||
<AddressRow
|
||||
value={controllerId}
|
||||
iconInfo={this.renderOffline(controllerId)}
|
||||
withAddressOrName
|
||||
withBalance={{
|
||||
available: true,
|
||||
bonded: false,
|
||||
free: false,
|
||||
redeemable: false,
|
||||
unlocking: false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
private renderSessionAccount () {
|
||||
const { t } = this.props;
|
||||
const { sessionId } = this.state;
|
||||
|
||||
if (!sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail actions'>
|
||||
<label className='staking--label'>{t('session')}</label>
|
||||
<AddressRow
|
||||
value={sessionId}
|
||||
withAddressOrName
|
||||
withBalance={{
|
||||
available: true,
|
||||
bonded: false,
|
||||
free: false,
|
||||
redeemable: false,
|
||||
unlocking: false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderStashAccount () {
|
||||
const { accountId, t } = this.props;
|
||||
|
||||
return (
|
||||
<div className='staking--Account-detail actions'>
|
||||
<label className='staking--label'>{t('stash')}</label>
|
||||
<AddressRow
|
||||
value={accountId}
|
||||
iconInfo={this.renderOffline(accountId)}
|
||||
withAddressOrName
|
||||
withBalance={{
|
||||
available: true,
|
||||
bonded: false,
|
||||
free: false,
|
||||
redeemable: false,
|
||||
unlocking: false
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominate () {
|
||||
const { stashOptions } = this.props;
|
||||
const { controllerId, isNominateOpen, nominees, stashId } = this.state;
|
||||
|
||||
if (!stashId || !controllerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Nominate
|
||||
controllerId={controllerId}
|
||||
isOpen={isNominateOpen}
|
||||
nominees={nominees}
|
||||
onClose={this.toggleNominate}
|
||||
stashId={stashId}
|
||||
stashOptions={stashOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderValidate () {
|
||||
const { controllerId, isValidateOpen, stashId, validatorPrefs } = this.state;
|
||||
|
||||
if (!stashId || !controllerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Validate
|
||||
controllerId={controllerId}
|
||||
isOpen={isValidateOpen}
|
||||
onClose={this.toggleValidate}
|
||||
stashId={stashId}
|
||||
validatorPrefs={validatorPrefs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderButtons () {
|
||||
const { t } = this.props;
|
||||
const { controllerId, isSettingPopupOpen, isStashNominating, isStashValidating, sessionId } = 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')
|
||||
}
|
||||
key='stop'
|
||||
tx='staking.chill'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (!sessionId) {
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='set'
|
||||
onClick={this.toggleSetSessionAccount}
|
||||
label={t('Set Session Key')}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='validate'
|
||||
onClick={this.toggleValidate}
|
||||
label={t('Validate')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(<Button.Or key='nominate.or' />);
|
||||
buttons.push(
|
||||
<Button
|
||||
isPrimary
|
||||
key='nominate'
|
||||
onClick={this.toggleNominate}
|
||||
label={t('Nominate')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<Popup
|
||||
key='settings'
|
||||
onClose={this.toggleSettingPopup}
|
||||
open={isSettingPopupOpen}
|
||||
position='bottom left'
|
||||
trigger={
|
||||
<Button
|
||||
icon='setting'
|
||||
onClick={this.toggleSettingPopup}
|
||||
size='tiny'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{this.renderPopupMenu()}
|
||||
</Popup>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button.Group>
|
||||
{buttons}
|
||||
</Button.Group>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderPopupMenu () {
|
||||
const { balances_all, t } = this.props;
|
||||
const { isStashNominating, isStashValidating, sessionId } = 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 && balances_all.availableBalance.gtn(0);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
vertical
|
||||
text
|
||||
onClick={this.toggleSettingPopup}
|
||||
>
|
||||
{canBondExtra &&
|
||||
<Menu.Item onClick={this.toggleBondExtra}>
|
||||
{t('Bond more funds')}
|
||||
</Menu.Item>
|
||||
}
|
||||
<Menu.Item onClick={this.toggleUnbond}>
|
||||
{t('Unbond funds')}
|
||||
</Menu.Item>
|
||||
<Menu.Item 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>
|
||||
}
|
||||
{sessionId &&
|
||||
<Menu.Item onClick={this.toggleSetSessionAccount}>
|
||||
{t('Change session account')}
|
||||
</Menu.Item>
|
||||
}
|
||||
{isStashNominating &&
|
||||
<Menu.Item onClick={this.toggleNominate}>
|
||||
{t('Change nominee(s)')}
|
||||
</Menu.Item>
|
||||
}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSetValidatorPrefs () {
|
||||
const { controllerId, isValidateOpen, stashId, validatorPrefs } = this.state;
|
||||
|
||||
if (!controllerId || !validatorPrefs || !stashId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Validate
|
||||
controllerId={controllerId}
|
||||
isOpen={isValidateOpen}
|
||||
onClose={this.toggleValidate}
|
||||
stashId={stashId}
|
||||
validatorPrefs={validatorPrefs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSetControllerAccount () {
|
||||
const { controllerId, isSetControllerAccountOpen, isStashValidating, stashId } = this.state;
|
||||
|
||||
if (!isSetControllerAccountOpen || !stashId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SetControllerAccount
|
||||
defaultControllerId={controllerId}
|
||||
isValidating={isStashValidating}
|
||||
onClose={this.toggleSetControllerAccount}
|
||||
stashId={stashId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSetRewardDestination () {
|
||||
const { controllerId, destination, isSetRewardDestinationOpen } = this.state;
|
||||
|
||||
if (!isSetRewardDestinationOpen || !controllerId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SetRewardDestination
|
||||
controllerId={controllerId}
|
||||
defaultDestination={destination}
|
||||
onClose={this.toggleSetRewardDestination}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSetSessionAccount () {
|
||||
const { controllerId, isSetSessionAccountOpen, stashId, sessionId } = this.state;
|
||||
|
||||
if (!controllerId || !stashId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SetSessionAccount
|
||||
controllerId={controllerId}
|
||||
isOpen={isSetSessionAccountOpen}
|
||||
onClose={this.toggleSetSessionAccount}
|
||||
sessionId={sessionId}
|
||||
stashId={stashId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleBondExtra = () => {
|
||||
this.setState(({ isBondExtraOpen }) => ({
|
||||
isBondExtraOpen: !isBondExtraOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleNominate = () => {
|
||||
this.setState(({ isNominateOpen }) => ({
|
||||
isNominateOpen: !isNominateOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleSetControllerAccount = () => {
|
||||
this.setState(({ isSetControllerAccountOpen }) => ({
|
||||
isSetControllerAccountOpen: !isSetControllerAccountOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleSetRewardDestination = () => {
|
||||
this.setState(({ isSetRewardDestinationOpen }) => ({
|
||||
isSetRewardDestinationOpen: !isSetRewardDestinationOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleSetSessionAccount = () => {
|
||||
this.setState(({ isSetSessionAccountOpen }) => ({
|
||||
isSetSessionAccountOpen: !isSetSessionAccountOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleSettingPopup = () => {
|
||||
this.setState(({ isSettingPopupOpen }) => ({
|
||||
isSettingPopupOpen: !isSettingPopupOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleUnbond = () => {
|
||||
this.setState(({ isUnbondOpen }) => ({
|
||||
isUnbondOpen: !isUnbondOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private toggleValidate = () => {
|
||||
this.setState(({ isValidateOpen }) => ({
|
||||
isValidateOpen: !isValidateOpen
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(
|
||||
withCalls<Props>(
|
||||
['derive.staking.info', { paramName: 'accountId' }],
|
||||
['derive.balances.all', { paramName: 'accountId' }]
|
||||
)(Account)
|
||||
);
|
||||
104
packages/app-staking/src/Actions/Accounts.tsx
Normal file
104
packages/app-staking/src/Actions/Accounts.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
// 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 { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { ComponentProps } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import { Button, CardGrid, Icon } from '@polkadot/ui-app';
|
||||
import createOption from '@polkadot/ui-keyring/options/item';
|
||||
import { getAddressName } from '@polkadot/ui-app/util';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Account from './Account';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
import StartStaking from './NewStake';
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & ComponentProps;
|
||||
|
||||
type State = {
|
||||
isNewStakeOpen: boolean
|
||||
};
|
||||
|
||||
const Wrapper = styled(CardGrid) `
|
||||
.ui--CardGrid-buttons {
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
||||
class Accounts extends React.PureComponent<Props,State> {
|
||||
state: State = {
|
||||
isNewStakeOpen: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { recentlyOffline, t } = this.props;
|
||||
const accounts = keyring.getAccounts();
|
||||
const stashOptions = this.getStashOptions();
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
buttons={
|
||||
<Button
|
||||
isPrimary
|
||||
key='new-stake'
|
||||
label={
|
||||
<>
|
||||
<Icon name='add'/>
|
||||
{t('New stake')}
|
||||
</>
|
||||
}
|
||||
onClick={this.toggleNewStake}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{this.renderNewStake()}
|
||||
{accounts.map((account, index) => {
|
||||
const address = account.address();
|
||||
|
||||
return (
|
||||
<Account
|
||||
accountId={address}
|
||||
key={index}
|
||||
recentlyOffline={recentlyOffline}
|
||||
stashOptions={stashOptions}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNewStake () {
|
||||
const { isNewStakeOpen } = this.state;
|
||||
|
||||
if (!isNewStakeOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StartStaking
|
||||
onClose={this.toggleNewStake}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleNewStake = (): void => {
|
||||
this.setState(({ isNewStakeOpen }) => ({
|
||||
isNewStakeOpen: !isNewStakeOpen
|
||||
}));
|
||||
}
|
||||
|
||||
private getStashOptions (): Array<KeyringSectionOption> {
|
||||
const { stashes } = this.props;
|
||||
|
||||
return stashes.map((stashId) =>
|
||||
createOption(stashId, getAddressName(stashId, 'account'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Accounts);
|
||||
@ -9,79 +9,47 @@ import { CalculateBalanceProps } from '../types';
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
|
||||
import { Button, Dropdown, InputAddress, InputBalance, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
import { withCalls, withApi, withMulti } from '@polkadot/ui-api';
|
||||
import { calcSignatureLength } from '@polkadot/ui-signer/Checks';
|
||||
import { ZERO_BALANCE, ZERO_FEES } from '@polkadot/ui-signer/Checks/constants';
|
||||
import { Button, Dropdown, InputAddress, InputBalanceBonded, Modal, TxButton, TxComponent } from '@polkadot/ui-app';
|
||||
import { withApi, withMulti } from '@polkadot/ui-api';
|
||||
|
||||
import translate from '../translate';
|
||||
import ValidateController from './ValidateController';
|
||||
import { rewardDestinationOptions } from './constants';
|
||||
import InputValidationController from './Account/InputValidationController';
|
||||
|
||||
type Props = I18nProps & ApiProps & CalculateBalanceProps & {
|
||||
accountId: string,
|
||||
controllerId?: string | null,
|
||||
isOpen: boolean,
|
||||
onClose: () => void
|
||||
};
|
||||
|
||||
type State = {
|
||||
bondValue?: BN,
|
||||
controllerError: string | null,
|
||||
controllerId: string,
|
||||
controllerId: string | null,
|
||||
destination: number,
|
||||
extrinsic: SubmittableExtrinsic | null,
|
||||
maxBalance?: BN
|
||||
stashId: string | null
|
||||
};
|
||||
|
||||
const stashOptions = [
|
||||
{ text: 'Stash account (increase the amount at stake)', value: 0 },
|
||||
{ text: 'Stash account (do not increase the amount at stake)', value: 1 },
|
||||
{ text: 'Controller account', value: 2 }
|
||||
];
|
||||
|
||||
const ZERO = new BN(0);
|
||||
|
||||
class Bond extends TxComponent<Props, State> {
|
||||
class NewStake extends TxComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
const { accountId, controllerId } = this.props;
|
||||
|
||||
this.state = {
|
||||
controllerError: null,
|
||||
controllerId: controllerId ? controllerId.toString() : accountId,
|
||||
controllerId: null,
|
||||
destination: 0,
|
||||
extrinsic: null
|
||||
extrinsic: null,
|
||||
stashId: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps: Props, prevState: State) {
|
||||
const { balances_fees } = this.props;
|
||||
const { controllerId, destination, extrinsic } = this.state;
|
||||
|
||||
const hasLengthChanged = ((extrinsic && extrinsic.encodedLength) || 0) !== ((prevState.extrinsic && prevState.extrinsic.encodedLength) || 0);
|
||||
|
||||
if ((controllerId && prevState.controllerId !== controllerId) ||
|
||||
(prevState.destination !== destination) ||
|
||||
(balances_fees !== prevProps.balances_fees) ||
|
||||
hasLengthChanged
|
||||
) {
|
||||
this.setMaxBalance();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountId, isOpen, onClose, t } = this.props;
|
||||
const { bondValue, controllerError, controllerId, extrinsic, maxBalance } = this.state;
|
||||
const hasValue = !!bondValue && bondValue.gtn(0) && (!maxBalance || bondValue.lte(maxBalance));
|
||||
const { onClose, t } = this.props;
|
||||
const { bondValue, controllerError, controllerId, extrinsic, stashId } = this.state;
|
||||
const hasValue = !!bondValue && bondValue.gtn(0);
|
||||
const canSubmit = hasValue && !controllerError && !!controllerId;
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='staking--Bonding'
|
||||
@ -99,7 +67,7 @@ class Bond extends TxComponent<Props, State> {
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
accountId={stashId}
|
||||
isDisabled={!canSubmit}
|
||||
isPrimary
|
||||
label={t('Bond')}
|
||||
@ -114,8 +82,8 @@ class Bond extends TxComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderContent () {
|
||||
const { accountId, t } = this.props;
|
||||
const { controllerId, controllerError, bondValue, destination, maxBalance } = this.state;
|
||||
const { t } = this.props;
|
||||
const { controllerId, controllerError, bondValue, destination, stashId } = this.state;
|
||||
const hasValue = !!bondValue && bondValue.gtn(0);
|
||||
|
||||
return (
|
||||
@ -126,9 +94,10 @@ class Bond extends TxComponent<Props, State> {
|
||||
<Modal.Content className='ui--signer-Signer-Content'>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
defaultValue={accountId}
|
||||
isDisabled
|
||||
label={t('stash account')}
|
||||
onChange={this.onChangeStash}
|
||||
type='account'
|
||||
value={stashId}
|
||||
/>
|
||||
<InputAddress
|
||||
className='medium'
|
||||
@ -139,20 +108,23 @@ class Bond extends TxComponent<Props, State> {
|
||||
type='account'
|
||||
value={controllerId}
|
||||
/>
|
||||
<ValidateController
|
||||
accountId={accountId}
|
||||
<InputValidationController
|
||||
accountId={stashId}
|
||||
controllerId={controllerId}
|
||||
onError={this.onControllerError}
|
||||
/>
|
||||
<InputBalance
|
||||
<InputBalanceBonded
|
||||
autoFocus
|
||||
className='medium'
|
||||
controllerId={controllerId}
|
||||
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}
|
||||
label={t('value bonded')}
|
||||
maxValue={maxBalance}
|
||||
onChange={this.onChangeValue}
|
||||
onEnter={this.sendTx}
|
||||
stashId={stashId}
|
||||
withMax
|
||||
/>
|
||||
<Dropdown
|
||||
@ -161,7 +133,7 @@ class Bond extends TxComponent<Props, State> {
|
||||
help={t('The destination account for any payments as either a nominator or validator')}
|
||||
label={t('payment destination')}
|
||||
onChange={this.onChangeDestination}
|
||||
options={stashOptions}
|
||||
options={rewardDestinationOptions}
|
||||
value={destination}
|
||||
/>
|
||||
</Modal.Content>
|
||||
@ -172,7 +144,7 @@ class Bond 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, maxBalance = prevState.maxBalance } = newState;
|
||||
const { 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;
|
||||
@ -183,43 +155,11 @@ class Bond extends TxComponent<Props, State> {
|
||||
controllerId,
|
||||
destination,
|
||||
extrinsic,
|
||||
maxBalance
|
||||
stashId
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private setMaxBalance = () => {
|
||||
const { api, system_accountNonce = ZERO, balances_fees = ZERO_FEES, balances_all = ZERO_BALANCE } = this.props;
|
||||
const { controllerId, destination } = this.state;
|
||||
|
||||
const { transactionBaseFee, transactionByteFee } = balances_fees;
|
||||
const { freeBalance } = balances_all;
|
||||
|
||||
let prevMax = new BN(0);
|
||||
let maxBalance = new BN(1);
|
||||
let extrinsic;
|
||||
|
||||
while (!prevMax.eq(maxBalance)) {
|
||||
prevMax = maxBalance;
|
||||
|
||||
extrinsic = controllerId && destination
|
||||
? api.tx.staking.bond(controllerId, prevMax, destination)
|
||||
: null;
|
||||
|
||||
const txLength = calcSignatureLength(extrinsic, system_accountNonce);
|
||||
|
||||
const fees = transactionBaseFee
|
||||
.add(transactionByteFee.muln(txLength));
|
||||
|
||||
maxBalance = new BN(freeBalance).sub(fees);
|
||||
}
|
||||
|
||||
this.nextState({
|
||||
extrinsic,
|
||||
maxBalance
|
||||
});
|
||||
}
|
||||
|
||||
private onChangeController = (controllerId: string) => {
|
||||
this.nextState({ controllerId });
|
||||
}
|
||||
@ -228,6 +168,10 @@ class Bond extends TxComponent<Props, State> {
|
||||
this.nextState({ destination });
|
||||
}
|
||||
|
||||
private onChangeStash = (stashId: string) => {
|
||||
this.nextState({ stashId });
|
||||
}
|
||||
|
||||
private onChangeValue = (bondValue?: BN) => {
|
||||
this.nextState({ bondValue });
|
||||
}
|
||||
@ -238,12 +182,7 @@ class Bond extends TxComponent<Props, State> {
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
Bond,
|
||||
NewStake,
|
||||
translate,
|
||||
withApi,
|
||||
withCalls<Props>(
|
||||
'derive.balances.fees',
|
||||
['derive.balances.all', { paramName: 'accountId' }],
|
||||
['query.system.accountNonce', { paramName: 'accountId' }]
|
||||
)
|
||||
withApi
|
||||
);
|
||||
9
packages/app-staking/src/Actions/constants.tsx
Normal file
9
packages/app-staking/src/Actions/constants.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2019 @polkadot/ui-app authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
export const rewardDestinationOptions = [
|
||||
{ text: 'Stash account (increase the amount at stake)', value: 0 },
|
||||
{ text: 'Stash account (do not increase the amount at stake)', value: 1 },
|
||||
{ text: 'Controller account', value: 2 }
|
||||
];
|
||||
@ -13,7 +13,7 @@ import translate from '../translate';
|
||||
import Address from './Address';
|
||||
|
||||
type Props = I18nProps & {
|
||||
balances: DerivedBalancesMap,
|
||||
balances?: DerivedBalancesMap,
|
||||
current: Array<string>,
|
||||
lastAuthor?: string,
|
||||
lastBlock: string,
|
||||
|
||||
@ -62,6 +62,10 @@ class Overview extends React.PureComponent<Props> {
|
||||
private sortByBalance (list: Array<string>): Array<string> {
|
||||
const { balances } = this.props;
|
||||
|
||||
if (!balances) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return list.sort((a, b) => {
|
||||
const balanceA = balances[a] || { freeBalance: ZERO };
|
||||
const balanceB = balances[b] || { freeBalance: ZERO };
|
||||
|
||||
@ -19,7 +19,7 @@ import accountObservable from '@polkadot/ui-keyring/observable/accounts';
|
||||
import './index.css';
|
||||
|
||||
import basicMd from './md/basic.md';
|
||||
import Accounts from './Accounts';
|
||||
import Accounts from './Actions/Accounts';
|
||||
import Overview from './Overview';
|
||||
import translate from './translate';
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ export type Nominators = {
|
||||
};
|
||||
|
||||
export type ComponentProps = {
|
||||
balances: DerivedBalancesMap,
|
||||
balances?: DerivedBalancesMap,
|
||||
controllers: Array<string>,
|
||||
recentlyOffline: RecentlyOfflineMap,
|
||||
stashes: Array<string>,
|
||||
|
||||
@ -12,9 +12,9 @@ import { formatBalance, formatNumber } from '@polkadot/util';
|
||||
import { Icon, Tooltip, TxButton } from '@polkadot/ui-app';
|
||||
import { withCalls, withMulti } from '@polkadot/ui-api';
|
||||
|
||||
import translate from './translate';
|
||||
import CryptoType from './CryptoType';
|
||||
import Label from './Label';
|
||||
import translate from './translate';
|
||||
|
||||
// true to display, or (for bonded) provided values [own, ...all extras]
|
||||
export type BalanceActiveType = {
|
||||
@ -30,31 +30,34 @@ export type CryptoActiveType = {
|
||||
nonce?: boolean
|
||||
};
|
||||
|
||||
export type ValidatorPrefsType = {
|
||||
unstakeThreshold?: boolean,
|
||||
validatorPayment?: boolean
|
||||
};
|
||||
|
||||
type Props = BareProps & I18nProps & {
|
||||
address: string,
|
||||
balances_all?: DerivedBalances,
|
||||
children?: React.ReactNode,
|
||||
staking_info?: DerivedStaking,
|
||||
value: string,
|
||||
withBalance?: boolean | BalanceActiveType,
|
||||
withExtended?: boolean | CryptoActiveType
|
||||
withRewardDestination?: boolean,
|
||||
withExtended?: boolean | CryptoActiveType,
|
||||
withValidatorPrefs?: boolean | ValidatorPrefsType
|
||||
};
|
||||
|
||||
// <AddressInfo
|
||||
// withBalance // default
|
||||
// withExtended={true} // optional
|
||||
// value={address}
|
||||
// >{children></AddressInfo>
|
||||
//
|
||||
// Additionally to tweak the display, i.e. only available
|
||||
//
|
||||
// <AddressInfo withBalance={{ available: true }} />
|
||||
class AddressInfo extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { children, className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{this.renderBalances()}
|
||||
<div className='column'>
|
||||
{this.renderBalances()}
|
||||
{this.renderValidatorPrefs()}
|
||||
{this.renderRewardDestination()}
|
||||
</div>
|
||||
|
||||
{this.renderExtended()}
|
||||
{children && (
|
||||
<div className='column'>
|
||||
@ -78,7 +81,7 @@ class AddressInfo extends React.PureComponent<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='column'>
|
||||
<>
|
||||
{balanceDisplay.free && (
|
||||
<>
|
||||
<Label label={t('total')} />
|
||||
@ -109,17 +112,17 @@ class AddressInfo extends React.PureComponent<Props> {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// either true (filtered above already) or [own, ...all extras]
|
||||
// either true (filtered above already) or [own, ...all extras bonded funds from nominators]
|
||||
private renderBonded (bonded: true | Array<BN>) {
|
||||
const { staking_info, t } = this.props;
|
||||
let value = undefined;
|
||||
|
||||
if (Array.isArray(bonded)) {
|
||||
// Get the sum of all extra values (if available)
|
||||
// Get the sum of funds bonded by any nominator (if available)
|
||||
const extras = bonded.filter((value, index) => index !== 0);
|
||||
const extra = extras.reduce((total, value) => total.add(value), new BN(0)).gtn(0)
|
||||
? `(+${extras.map((bonded) => formatBalance(bonded)).join(', ')})`
|
||||
@ -141,7 +144,7 @@ class AddressInfo extends React.PureComponent<Props> {
|
||||
}
|
||||
|
||||
private renderExtended () {
|
||||
const { balances_all, t, value, withExtended } = this.props;
|
||||
const { balances_all, t, address, withExtended } = this.props;
|
||||
const extendedDisplay = withExtended === true
|
||||
? { crypto: true, nonce: true }
|
||||
: withExtended
|
||||
@ -164,7 +167,7 @@ class AddressInfo extends React.PureComponent<Props> {
|
||||
<>
|
||||
<Label label={t('crypto type')} />
|
||||
<CryptoType
|
||||
accountId={value}
|
||||
accountId={address}
|
||||
className='result'
|
||||
/>
|
||||
</>
|
||||
@ -173,6 +176,23 @@ class AddressInfo extends React.PureComponent<Props> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderRewardDestination () {
|
||||
const { staking_info, withRewardDestination, t } = this.props;
|
||||
|
||||
if (!withRewardDestination) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
staking_info &&
|
||||
staking_info.rewardDestination &&
|
||||
<>
|
||||
<Label label={t('reward destination')} />
|
||||
<div className='result'>{staking_info.rewardDestination.toString().toLowerCase()}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private renderRedeemButton () {
|
||||
const { staking_info, t } = this.props;
|
||||
|
||||
@ -213,6 +233,38 @@ class AddressInfo extends React.PureComponent<Props> {
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
private renderValidatorPrefs () {
|
||||
const { staking_info, t, withValidatorPrefs = false } = this.props;
|
||||
const validatorPrefsDisplay = withValidatorPrefs === true
|
||||
? { unstakeThreshold: true, validatorPayment: true }
|
||||
: withValidatorPrefs;
|
||||
|
||||
if (!validatorPrefsDisplay || !staking_info || !staking_info.validatorPrefs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// start with a spacer
|
||||
return (
|
||||
<>
|
||||
<div className='spacer'></div>
|
||||
{validatorPrefsDisplay.unstakeThreshold && staking_info.validatorPrefs.unstakeThreshold && (
|
||||
<>
|
||||
<Label label={t('unstake threshold')} />
|
||||
<div className='result'>{staking_info.validatorPrefs.unstakeThreshold.toString()}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{validatorPrefsDisplay.validatorPayment && staking_info.validatorPrefs.validatorPayment && (
|
||||
<>
|
||||
<Label label={t('commision')} />
|
||||
<div className='result'>{formatBalance(staking_info.validatorPrefs.validatorPayment.toBn())}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
@ -221,6 +273,7 @@ export default withMulti(
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
|
||||
.column {
|
||||
flex: 1;
|
||||
@ -240,19 +293,25 @@ export default withMulti(
|
||||
.result {
|
||||
grid-column: 2;
|
||||
|
||||
.iconButton {
|
||||
padding-left: 0!important;
|
||||
.icon {
|
||||
margin-left: .3em;
|
||||
margin-right: 0;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
i.info.circle.icon {
|
||||
margin-left: .3em;
|
||||
button.ui.icon.primary.button.iconButton {
|
||||
background: white !important;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
`,
|
||||
translate,
|
||||
withCalls<Props>(
|
||||
['derive.balances.all', { paramName: 'value' }],
|
||||
['derive.staking.info', { paramName: 'value' }]
|
||||
['derive.balances.all', { paramName: 'address' }],
|
||||
['derive.staking.info', { paramName: 'address' }]
|
||||
)
|
||||
);
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { AccountId, AccountIndex, Address } from '@polkadot/types';
|
||||
import { KeyringItemType } from '@polkadot/ui-keyring/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
@ -14,26 +13,23 @@ import BaseIdentityIcon from '@polkadot/ui-identicon';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import AddressInfo, { BalanceActiveType } from './AddressInfo';
|
||||
import { classes, getAddressName, getAddressTags, toShortAddress } from './util';
|
||||
import CopyButton from './CopyButton';
|
||||
import IdentityIcon from './IdentityIcon';
|
||||
import Row, { RowProps, RowState, styles } from './Row';
|
||||
import translate from './translate';
|
||||
import { classes, getAddressName, getAddressTags, toShortAddress } from './util';
|
||||
|
||||
export type Props = I18nProps & RowProps & {
|
||||
accounts_idAndIndex?: [AccountId?, AccountIndex?]
|
||||
bonded?: BN | Array<BN>,
|
||||
isContract?: boolean,
|
||||
isValid?: boolean,
|
||||
type: KeyringItemType,
|
||||
value: AccountId | AccountIndex | Address | string | null,
|
||||
withAddressOrName?: boolean,
|
||||
withBalance?: boolean | BalanceActiveType,
|
||||
withIndex?: boolean
|
||||
};
|
||||
|
||||
type State = RowState & {
|
||||
address: string
|
||||
};
|
||||
type State = RowState;
|
||||
|
||||
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
|
||||
const ICON_SIZE = 48;
|
||||
@ -86,8 +82,7 @@ class AddressRow extends Row<Props, State> {
|
||||
<div className='ui--Row-base'>
|
||||
{this.renderIcon()}
|
||||
<div className='ui--Row-details'>
|
||||
{this.renderName()}
|
||||
{this.renderAddress()}
|
||||
{this.renderAddressAndName()}
|
||||
{this.renderAccountIndex()}
|
||||
{!isContract && this.renderBalances()}
|
||||
{this.renderTags()}
|
||||
@ -117,6 +112,21 @@ class AddressRow extends Row<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
protected renderAddressAndName () {
|
||||
const { withAddressOrName = false } = this.props;
|
||||
|
||||
if (withAddressOrName) {
|
||||
return this.renderName(true);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{this.renderName()}
|
||||
{this.renderAddress()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private renderAddress () {
|
||||
const { address } = this.state;
|
||||
|
||||
@ -158,7 +168,7 @@ class AddressRow extends Row<Props, State> {
|
||||
return (
|
||||
<div className='ui--Row-balances'>
|
||||
<AddressInfo
|
||||
value={accountId}
|
||||
address={accountId}
|
||||
withBalance={withBalance}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -9,10 +9,13 @@ import { CodeStored } from '@polkadot/app-contracts/types';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { withMulti } from '@polkadot/ui-api';
|
||||
import { CopyButton, Icon, Messages } from '@polkadot/ui-app';
|
||||
import { classes, toShortAddress } from '@polkadot/ui-app/util';
|
||||
import contracts from '@polkadot/app-contracts/store';
|
||||
|
||||
import CopyButton from './CopyButton';
|
||||
import Icon from './Icon';
|
||||
import Messages from './Messages';
|
||||
|
||||
import Row, { RowProps, RowState, styles } from './Row';
|
||||
import translate from './translate';
|
||||
|
||||
@ -42,6 +45,8 @@ const CodeIcon = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
|
||||
|
||||
class CodeRow extends Row<Props, State> {
|
||||
state: State;
|
||||
|
||||
@ -51,10 +56,15 @@ class CodeRow extends Row<Props, State> {
|
||||
this.state = this.createState();
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps ({ code: { json } }: Props, prevState: State): State | null {
|
||||
static getDerivedStateFromProps ({ code: { json }, accounts_idAndIndex = [] }: Props, prevState: State): State | null {
|
||||
const codeHash = json.codeHash || DEFAULT_HASH;
|
||||
const name = json.name || DEFAULT_NAME;
|
||||
const tags = json.tags || [];
|
||||
const [_accountId] = accounts_idAndIndex;
|
||||
const accountId = _accountId;
|
||||
const address = accountId
|
||||
? accountId.toString()
|
||||
: DEFAULT_ADDR;
|
||||
|
||||
const state = { tags } as State;
|
||||
let hasChanged = false;
|
||||
@ -69,6 +79,11 @@ class CodeRow extends Row<Props, State> {
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
if (address !== prevState.address) {
|
||||
state.address = address;
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
return hasChanged
|
||||
? state
|
||||
: null;
|
||||
@ -101,6 +116,7 @@ class CodeRow extends Row<Props, State> {
|
||||
const { code: { json: { codeHash = DEFAULT_HASH, name = DEFAULT_NAME, tags = [] } } } = this.props;
|
||||
|
||||
return {
|
||||
address: DEFAULT_ADDR,
|
||||
codeHash,
|
||||
isEditingName: false,
|
||||
isEditingTags: false,
|
||||
|
||||
@ -2,20 +2,20 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringOptions, KeyringSectionOption, KeyringSectionOptions, KeyringOption$Type } from '@polkadot/ui-keyring/options/types';
|
||||
import { BareProps } from './types';
|
||||
import { KeyringOptions, KeyringSectionOption, KeyringSectionOptions, KeyringOption$Type } from '@polkadot/ui-keyring/options/types';
|
||||
|
||||
import React from 'react';
|
||||
import store from 'store';
|
||||
import styled from 'styled-components';
|
||||
import createItem from '@polkadot/ui-keyring/options/item';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import keyringOption from '@polkadot/ui-keyring/options';
|
||||
import createItem from '@polkadot/ui-keyring/options/item';
|
||||
import { withMulti, withObservable } from '@polkadot/ui-api';
|
||||
|
||||
import Dropdown from './Dropdown';
|
||||
import { classes, getAddressName } from './util';
|
||||
import addressToAddress from './util/toAddress';
|
||||
import Dropdown from './Dropdown';
|
||||
|
||||
type Props = BareProps & {
|
||||
defaultValue?: string | null,
|
||||
@ -147,15 +147,20 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
? [createOption(actualValue)]
|
||||
: (optionsAll ? optionsAll[type] : [])
|
||||
);
|
||||
let _defaultValue;
|
||||
|
||||
if (value !== undefined) {
|
||||
_defaultValue = undefined;
|
||||
} else if (isMultiple) {
|
||||
_defaultValue = undefined;
|
||||
} else {
|
||||
_defaultValue = actualValue;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className={classes('ui--InputAddress', hideAddress && 'hideAddress', className)}
|
||||
defaultValue={
|
||||
isMultiple || (value !== undefined)
|
||||
? undefined
|
||||
: actualValue
|
||||
}
|
||||
defaultValue={_defaultValue}
|
||||
help={help}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
@ -263,7 +268,7 @@ class InputAddress extends React.PureComponent<Props, State> {
|
||||
if (accountId) {
|
||||
matches.push(
|
||||
keyring.saveRecent(
|
||||
accountId
|
||||
accountId.toString()
|
||||
).option
|
||||
);
|
||||
}
|
||||
|
||||
161
packages/ui-app/src/InputBalanceBonded.tsx
Normal file
161
packages/ui-app/src/InputBalanceBonded.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2017-2019 @polkadot/ui-app 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 { BareProps, BitLength } from './types';
|
||||
import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import { ApiProps } from '@polkadot/ui-api/types';
|
||||
import { BitLengthOption } from '@polkadot/ui-app/constants';
|
||||
import { calcSignatureLength } from '@polkadot/ui-signer/Checks';
|
||||
import { InputNumber } from '@polkadot/ui-app';
|
||||
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
|
||||
import { withCalls, withMulti, withApi } from '@polkadot/ui-api';
|
||||
import { ZERO_BALANCE, ZERO_FEES } from '@polkadot/ui-signer/Checks/constants';
|
||||
|
||||
type Props = BareProps & ApiProps & {
|
||||
autoFocus?: boolean,
|
||||
balances_fees?: DerivedFees,
|
||||
balances_all?: DerivedBalances,
|
||||
controllerId: string,
|
||||
defaultValue?: BN | string,
|
||||
destination?: number,
|
||||
extrinsicProp: 'staking.bond' | 'staking.bondExtra' | 'staking.unbond',
|
||||
help?: React.ReactNode,
|
||||
isDisabled?: boolean,
|
||||
isError?: boolean,
|
||||
label?: any,
|
||||
onChange?: (value?: BN) => void,
|
||||
onEnter?: () => void,
|
||||
placeholder?: string,
|
||||
stashId: string,
|
||||
system_accountNonce?: BN,
|
||||
value?: BN | string,
|
||||
withEllipsis?: boolean,
|
||||
withLabel?: boolean,
|
||||
withMax?: boolean
|
||||
};
|
||||
|
||||
type State = {
|
||||
maxBalance?: BN,
|
||||
extrinsic: SubmittableExtrinsic | null
|
||||
};
|
||||
|
||||
const ZERO = new BN(0);
|
||||
const DEFAULT_BITLENGTH = BitLengthOption.CHAIN_SPEC as BitLength;
|
||||
|
||||
class InputBalanceBonded extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
extrinsic: null,
|
||||
maxBalance: ZERO
|
||||
};
|
||||
}
|
||||
render () {
|
||||
const { autoFocus, className, defaultValue, help, isDisabled, isError, label, onChange, onEnter, placeholder,style, value, withEllipsis, withLabel, withMax } = this.props;
|
||||
const { maxBalance } = this.state;
|
||||
|
||||
return (
|
||||
<InputNumber
|
||||
autoFocus={autoFocus}
|
||||
className={className}
|
||||
bitLength={DEFAULT_BITLENGTH}
|
||||
defaultValue={defaultValue}
|
||||
help={help}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
isSi
|
||||
label={label}
|
||||
maxValue={maxBalance}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
placeholder={placeholder}
|
||||
style={style}
|
||||
value={value}
|
||||
withEllipsis={withEllipsis}
|
||||
withLabel={withLabel}
|
||||
withMax={withMax}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps: Props, prevState: State) {
|
||||
const { balances_all, balances_fees, controllerId, destination } = this.props;
|
||||
const { extrinsic } = this.state;
|
||||
const hasLengthChanged = ((extrinsic && extrinsic.encodedLength) || 0) !== ((prevState.extrinsic && prevState.extrinsic.encodedLength) || 0);
|
||||
|
||||
if ((controllerId && prevProps.controllerId !== controllerId) ||
|
||||
(prevProps.destination !== destination) ||
|
||||
(balances_fees !== prevProps.balances_fees) ||
|
||||
(balances_all !== prevProps.balances_all) ||
|
||||
hasLengthChanged
|
||||
) {
|
||||
this.setMaxBalance();
|
||||
}
|
||||
}
|
||||
|
||||
private setMaxBalance = () => {
|
||||
const { api, balances_fees = ZERO_FEES, balances_all = ZERO_BALANCE, controllerId, destination, extrinsicProp, system_accountNonce = ZERO } = this.props;
|
||||
const { transactionBaseFee, transactionByteFee } = balances_fees;
|
||||
const { freeBalance } = balances_all;
|
||||
let prevMax = new BN(0);
|
||||
let maxBalance = new BN(1);
|
||||
let extrinsic;
|
||||
|
||||
while (!prevMax.eq(maxBalance)) {
|
||||
prevMax = maxBalance;
|
||||
|
||||
if (extrinsicProp === 'staking.bond') {
|
||||
extrinsic = controllerId && (destination || destination === 0)
|
||||
? api.tx.staking.bond(controllerId, prevMax, destination)
|
||||
: null;
|
||||
} else if (extrinsicProp === 'staking.unbond') {
|
||||
extrinsic = api.tx.staking.unbond(prevMax);
|
||||
} else if (extrinsicProp === 'staking.bondExtra') {
|
||||
extrinsic = api.tx.staking.bonExtra(prevMax);
|
||||
}
|
||||
|
||||
const txLength = calcSignatureLength(extrinsic, system_accountNonce);
|
||||
const fees = transactionBaseFee
|
||||
.add(transactionByteFee.muln(txLength));
|
||||
|
||||
maxBalance = new BN(freeBalance).sub(fees);
|
||||
}
|
||||
|
||||
this.nextState({
|
||||
extrinsic,
|
||||
maxBalance
|
||||
});
|
||||
}
|
||||
|
||||
private nextState (newState: Partial<State>): void {
|
||||
this.setState((prevState: State): State => {
|
||||
const { api, controllerId, destination, value } = this.props;
|
||||
const { maxBalance = prevState.maxBalance } = newState;
|
||||
const extrinsic = (value && controllerId && destination)
|
||||
? api.tx.staking.bond(controllerId, value, destination)
|
||||
: null;
|
||||
|
||||
return {
|
||||
extrinsic,
|
||||
maxBalance
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
InputBalanceBonded,
|
||||
withApi,
|
||||
withCalls<Props>(
|
||||
'derive.balances.fees',
|
||||
['derive.balances.all', { paramName: 'stashId' }],
|
||||
['query.system.accountNonce', { paramName: 'stashId' }]
|
||||
)
|
||||
);
|
||||
@ -2,11 +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 { AccountId, AccountIndex } from '@polkadot/types';
|
||||
import { KeyringItemType } from '@polkadot/ui-keyring/types';
|
||||
|
||||
import { Label } from 'semantic-ui-react';
|
||||
import React from 'react';
|
||||
import { Button, Input, InputTags } from '@polkadot/ui-app';
|
||||
|
||||
import { classes } from './util';
|
||||
import Button from './Button';
|
||||
import { classes, getAddressName } from './util';
|
||||
import CopyButton from './CopyButton';
|
||||
import Input from './Input';
|
||||
import InputTags from './InputTags';
|
||||
|
||||
export const styles = `
|
||||
text-align: left;
|
||||
@ -150,6 +156,7 @@ export const styles = `
|
||||
`;
|
||||
|
||||
export type RowProps = {
|
||||
accounts_idAndIndex?: [AccountId?, AccountIndex?]
|
||||
buttons?: React.ReactNode,
|
||||
children?: React.ReactNode,
|
||||
className?: string,
|
||||
@ -158,11 +165,13 @@ export type RowProps = {
|
||||
iconInfo?: React.ReactNode,
|
||||
isEditable?: boolean,
|
||||
isInline?: boolean,
|
||||
type?: KeyringItemType,
|
||||
withIcon?: boolean,
|
||||
withTags?: boolean
|
||||
};
|
||||
|
||||
export type RowState = {
|
||||
address: string
|
||||
isEditingName: boolean,
|
||||
isEditingTags: boolean,
|
||||
name: string,
|
||||
@ -230,10 +239,11 @@ class Row<P extends RowProps, S extends RowState> extends React.PureComponent<P,
|
||||
);
|
||||
}
|
||||
|
||||
protected renderName () {
|
||||
const { isEditable } = this.props;
|
||||
const { isEditingName, name } = this.state;
|
||||
protected renderName (withCopy: boolean = false) {
|
||||
const { isEditable, type } = this.props;
|
||||
const { address, isEditingName, name } = this.state;
|
||||
|
||||
// can't be both editable and copiable
|
||||
return isEditingName
|
||||
? (
|
||||
<Input
|
||||
@ -251,8 +261,22 @@ class Row<P extends RowProps, S extends RowState> extends React.PureComponent<P,
|
||||
className={classes('ui--Row-name', isEditable && 'editable')}
|
||||
onClick={isEditable ? this.toggleNameEditor : undefined}
|
||||
>
|
||||
{name}
|
||||
{isEditable && this.renderEditIcon()}
|
||||
{withCopy && !isEditable
|
||||
? (
|
||||
<CopyButton
|
||||
isAddress
|
||||
value={address}
|
||||
>
|
||||
{getAddressName(address, type, true)}
|
||||
</CopyButton>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{name}
|
||||
{isEditable && this.renderEditIcon()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,3 +12,9 @@ export enum ScreenSizes {
|
||||
TABLET = 768,
|
||||
PHONE = 576
|
||||
}
|
||||
|
||||
export const rewardDestinationOptions = [
|
||||
{ text: 'Stash account (increase the amount at stake)', value: 0 },
|
||||
{ text: 'Stash account (do not increase the amount at stake)', value: 1 },
|
||||
{ text: 'Controller account', value: 2 }
|
||||
];
|
||||
|
||||
@ -34,6 +34,7 @@ export { default as InfoForInput } from './InfoForInput';
|
||||
export { default as Input } from './Input';
|
||||
export { default as InputAddress } from './InputAddress';
|
||||
export { default as InputBalance } from './InputBalance';
|
||||
export { default as InputBalanceBonded } from './InputBalanceBonded';
|
||||
export { default as InputError } from './InputError';
|
||||
export { default as InputExtrinsic } from './InputExtrinsic';
|
||||
export { default as InputFile } from './InputFile';
|
||||
|
||||
@ -44,13 +44,9 @@ header .ui--Button-Group {
|
||||
}
|
||||
|
||||
button.ui.icon.iconButton {
|
||||
padding: 0em .3em .3em .3em !important;
|
||||
padding: 0em 0em .3em .3em !important;
|
||||
color: #2e86ab !important;
|
||||
background: none !important;
|
||||
/*trick to let the button in the flow but keep the content centered regardless*/
|
||||
margin-left: -2em !important;
|
||||
position: relative !important;
|
||||
right: -2.3em !important;
|
||||
}
|
||||
|
||||
.editable {
|
||||
|
||||
@ -11,8 +11,13 @@
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.ui--signer-Signer-Content .expanded {
|
||||
h3 {
|
||||
.ui--signer-Signer-Content {
|
||||
.modal-Text {
|
||||
margin-bottom: 1em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.expanded h3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user