Add maximum balance button to app-transfer and app-staking (#1076)

* Add maximum balance button to app-transfer and app-staking

* Promise handling

* cleanup

* Use balances.all for max transfer amount

* update max amts with balances.all

* travis ci

* fixes

* merge
This commit is contained in:
kwingram25 2019-05-13 16:49:55 +02:00 committed by GitHub
parent 573a500cdd
commit ec7d5a1c5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 499 additions and 64 deletions

View File

@ -3,15 +3,21 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import { CalculateBalanceProps } from '../types';
import BN from 'bn.js';
import React from 'react';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { Button, InputAddress, InputBalance, Modal, TxButton, Dropdown } 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 translate from '../translate';
import ValidateController from './ValidateController';
type Props = I18nProps & {
type Props = I18nProps & ApiProps & CalculateBalanceProps & {
accountId: string,
controllerId?: string | null,
isOpen: boolean,
@ -22,7 +28,9 @@ type State = {
bondValue?: BN,
controllerError: string | null,
controllerId: string,
destination: number
destination: number,
extrinsic: SubmittableExtrinsic | null,
maxBalance?: BN
};
const stashOptions = [
@ -31,6 +39,8 @@ const stashOptions = [
{ text: 'Controller account', value: 2 }
];
const ZERO = new BN(0);
class Bond extends React.PureComponent<Props, State> {
state: State;
@ -42,15 +52,31 @@ class Bond extends React.PureComponent<Props, State> {
this.state = {
controllerError: null,
controllerId: controllerId ? controllerId.toString() : accountId,
destination: 0
destination: 0,
extrinsic: 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, destination } = this.state;
const hasValue = !!bondValue && bondValue.gtn(0);
const canSubmit = hasValue && !controllerError;
const { bondValue, controllerError, controllerId, extrinsic, maxBalance } = this.state;
const hasValue = !!bondValue && bondValue.gtn(0) && (!maxBalance || bondValue.lte(maxBalance));
const canSubmit = hasValue && !controllerError && !!controllerId;
if (!isOpen) {
return null;
@ -78,8 +104,7 @@ class Bond extends React.PureComponent<Props, State> {
isPrimary
label={t('Bond')}
onClick={onClose}
params={[controllerId, bondValue, destination]}
tx='staking.bond'
extrinsic={extrinsic}
/>
</Button.Group>
</Modal.Actions>
@ -89,7 +114,7 @@ class Bond extends React.PureComponent<Props, State> {
private renderContent () {
const { accountId, t } = this.props;
const { controllerId, controllerError, bondValue, destination } = this.state;
const { controllerId, controllerError, bondValue, destination, maxBalance } = this.state;
const hasValue = !!bondValue && bondValue.gtn(0);
return (
@ -124,7 +149,9 @@ class Bond extends React.PureComponent<Props, State> {
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}
withMax
/>
<Dropdown
className='medium'
@ -140,16 +167,77 @@ class Bond extends React.PureComponent<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 extrinsic = (bondValue && controllerId)
? api.tx.staking.bond(controllerId, bondValue, destination)
: null;
return {
bondValue,
controllerError,
controllerId,
destination,
extrinsic,
maxBalance
};
});
}
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;
console.log(Object.entries(balances_all)
.reduce((obj, [key, bn]) => {
return {
...obj,
[key]: bn.toString()
};
}, {}));
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));
console.log(fees.toString());
maxBalance = new BN(freeBalance).sub(fees);
}
this.nextState({
extrinsic,
maxBalance
});
}
private onChangeController = (controllerId: string) => {
this.setState({ controllerId });
this.nextState({ controllerId });
}
private onChangeDestination = (destination: number) => {
this.setState({ destination });
this.nextState({ destination });
}
private onChangeValue = (bondValue?: BN) => {
this.setState({ bondValue });
this.nextState({ bondValue });
}
private onControllerError = (controllerError: string | null) => {
@ -157,4 +245,13 @@ class Bond extends React.PureComponent<Props, State> {
}
}
export default translate(Bond);
export default withMulti(
Bond,
translate,
withApi,
withCalls<Props>(
'derive.balances.fees',
['derive.balances.all', { paramName: 'accountId' }],
['query.system.accountNonce', { paramName: 'accountId' }]
)
);

View File

@ -3,30 +3,58 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import { CalculateBalanceProps } from '../types';
import BN from 'bn.js';
import React from 'react';
import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/ui-app';
import { Option, StakingLedger } from '@polkadot/types';
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';
type Props = I18nProps & {
type Props = I18nProps & ApiProps & CalculateBalanceProps & {
accountId: string,
controllerId: string,
isOpen: boolean,
onClose: () => void
onClose: () => void,
staking_ledger?: Option<StakingLedger>
};
type State = {
maxAdditional?: BN
maxAdditional?: BN,
extrinsic: SubmittableExtrinsic | null,
maxBalance?: BN
};
const ZERO = new BN(0);
class BondExtra extends React.PureComponent<Props, State> {
state: State = {};
state: State = {
extrinsic: null
};
componentDidUpdate (prevProps: Props, prevState: State) {
const { balances_fees } = this.props;
const { extrinsic } = this.state;
const hasLengthChanged = ((extrinsic && extrinsic.encodedLength) || 0) !== ((prevState.extrinsic && prevState.extrinsic.encodedLength) || 0);
if ((balances_fees !== prevProps.balances_fees) ||
hasLengthChanged
) {
this.setMaxBalance();
}
}
render () {
const { accountId, isOpen, onClose, t } = this.props;
const { maxAdditional } = this.state;
const canSubmit = !!maxAdditional && maxAdditional.gtn(0);
const { accountId, balances_all = ZERO_BALANCE, isOpen, onClose, t } = this.props;
const { extrinsic, maxAdditional, maxBalance = balances_all.availableBalance } = this.state;
const canSubmit = !!maxAdditional && maxAdditional.gtn(0) && maxAdditional.lte(maxBalance);
if (!isOpen) {
return null;
@ -54,8 +82,7 @@ class BondExtra extends React.PureComponent<Props, State> {
isPrimary
label={t('Bond')}
onClick={onClose}
params={[maxAdditional]}
tx='staking.bondExtra'
extrinsic={extrinsic}
/>
</Button.Group>
</Modal.Actions>
@ -65,6 +92,7 @@ class BondExtra extends React.PureComponent<Props, State> {
private renderContent () {
const { accountId, t } = this.props;
const { maxBalance } = this.state;
return (
<>
@ -83,16 +111,81 @@ class BondExtra extends React.PureComponent<Props, State> {
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')}
maxValue={maxBalance}
onChange={this.onChangeValue}
withMax
/>
</Modal.Content>
</>
);
}
private nextState (newState: Partial<State>): void {
this.setState((prevState: State): State => {
const { api } = this.props;
const { maxAdditional = prevState.maxAdditional, maxBalance = prevState.maxBalance } = newState;
const extrinsic = (maxAdditional && maxAdditional.gte(ZERO))
? api.tx.staking.bondExtra(maxAdditional)
: null;
return {
maxAdditional,
extrinsic,
maxBalance
};
});
}
private setMaxBalance = () => {
const { api, system_accountNonce = ZERO, balances_fees = ZERO_FEES, balances_all = ZERO_BALANCE, staking_ledger } = this.props;
const { maxAdditional } = this.state;
const { transactionBaseFee, transactionByteFee } = balances_fees;
const { freeBalance } = 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))
: null;
const txLength = calcSignatureLength(extrinsic, system_accountNonce);
const fees = transactionBaseFee
.add(transactionByteFee.muln(txLength));
maxBalance = new BN(freeBalance).sub(fees).sub(bonded);
}
this.nextState({
extrinsic,
maxBalance
});
}
private onChangeValue = (maxAdditional?: BN) => {
this.setState({ maxAdditional });
this.nextState({ maxAdditional });
}
}
export default translate(BondExtra);
export default withMulti(
BondExtra,
translate,
withApi,
withCalls<Props>(
'derive.balances.fees',
['derive.balances.all', { paramName: 'accountId' }],
['query.system.accountNonce', { paramName: 'accountId' }],
['query.staking.ledger', { paramName: 'controllerId' }]
)
);

View File

@ -2,26 +2,40 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import BN from 'bn.js';
import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/ui-app';
import { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import BN from 'bn.js';
import React from 'react';
import { AccountId, Option, StakingLedger } from '@polkadot/types';
import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/ui-app';
import { withCalls, withApi, withMulti } from '@polkadot/ui-api';
import translate from '../translate';
type Props = I18nProps & {
controllerId?: string | null,
type Props = I18nProps & ApiProps & {
controllerId?: AccountId | null,
isOpen: boolean,
onClose: () => void
onClose: () => void,
staking_ledger?: Option<StakingLedger>
};
type State = {
maxBalance?: BN
maxUnbond?: BN
};
class Unbond extends React.PureComponent<Props, State> {
state: State = {};
componentDidUpdate (prevProps: Props) {
const { staking_ledger } = this.props;
if (staking_ledger !== prevProps.staking_ledger) {
this.setMaxBalance();
}
}
render () {
const { controllerId, isOpen, onClose, t } = this.props;
const { maxUnbond } = this.state;
@ -64,6 +78,7 @@ class Unbond extends React.PureComponent<Props, State> {
private renderContent () {
const { controllerId, t } = this.props;
const { maxBalance } = this.state;
return (
<>
@ -75,23 +90,57 @@ class Unbond extends React.PureComponent<Props, State> {
className='medium'
defaultValue={controllerId}
isDisabled
label={t('controler account')}
label={t('controller account')}
/>
<InputBalance
autoFocus
className='medium'
help={t('The maximum amount to unbond, this is adjusted using the bonded funds on the account.')}
label={t('unbond amount')}
maxValue={maxBalance}
onChange={this.onChangeValue}
withMax
/>
</Modal.Content>
</>
);
}
private nextState (newState: Partial<State>): void {
this.setState((prevState: State): State => {
const { maxUnbond = prevState.maxUnbond, maxBalance = prevState.maxBalance } = newState;
return {
maxUnbond,
maxBalance
};
});
}
private setMaxBalance = () => {
const { staking_ledger } = this.props;
if (!staking_ledger || staking_ledger.isNone) {
return;
}
const { active: maxBalance } = staking_ledger.unwrap();
this.nextState({
maxBalance
});
}
private onChangeValue = (maxUnbond?: BN) => {
this.setState({ maxUnbond });
this.nextState({ maxUnbond });
}
}
export default translate(Unbond);
export default withMulti(
Unbond,
translate,
withApi,
withCalls<Props>(
['query.staking.ledger', { paramName: 'controllerId' }]
)
);

View File

@ -113,12 +113,13 @@ class Account extends React.PureComponent<Props, State> {
}
private renderBondExtra () {
const { accountId } = this.props;
const { accountId, controllerId } = this.props;
const { isBondExtraOpen } = this.state;
return (
<BondExtra
accountId={accountId}
controllerId={controllerId}
isOpen={isBondExtraOpen}
onClose={this.toggleBondExtra}
/>

View File

@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import BN from 'bn.js';
import { DerivedBalancesMap } from '@polkadot/api-derive/types';
import { DerivedFees, DerivedBalances, DerivedBalancesMap } from '@polkadot/api-derive/types';
import { AccountId, BlockNumber } from '@polkadot/types';
export type Nominators = {
@ -19,6 +19,12 @@ export type ComponentProps = {
validators: Array<string>
};
export type CalculateBalanceProps = {
balances_fees?: DerivedFees,
balances_all?: DerivedBalances,
system_accountNonce?: BN
};
export type RecentlyOffline = Array<[AccountId, BlockNumber, BN]>;
export type RecentlyOfflineMap = {

View File

@ -4,26 +4,33 @@
import { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
import BN from 'bn.js';
import React from 'react';
import styled from 'styled-components';
import { IExtrinsic } from '@polkadot/types/types';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { AddressSummary, InputAddress, InputBalance } from '@polkadot/ui-app';
import { withApi, withMulti } from '@polkadot/ui-api';
import { withApi, withCalls, withMulti } from '@polkadot/ui-api';
import keyring from '@polkadot/ui-keyring';
import Checks from '@polkadot/ui-signer/Checks';
import Checks, { calcSignatureLength } from '@polkadot/ui-signer/Checks';
import { ZERO_FEES } from '@polkadot/ui-signer/Checks/constants';
import Submit from './Submit';
import translate from './translate';
type Props = I18nProps & ApiProps & {};
type Props = I18nProps & ApiProps & {
balances_fees?: DerivedFees,
balances_votingBalance?: DerivedBalances,
system_accountNonce?: BN
};
type State = {
accountId: string | null,
amount: BN,
extrinsic: IExtrinsic | null,
extrinsic: SubmittableExtrinsic | null,
hasAvailable: boolean,
maxBalance?: BN,
recipientId: string | null
};
@ -55,12 +62,28 @@ class Transfer extends React.PureComponent<Props, State> {
amount: ZERO,
extrinsic: null,
hasAvailable: true,
maxBalance: ZERO,
recipientId: null
};
componentDidUpdate (prevProps: Props, prevState: State) {
const { balances_fees } = this.props;
const { accountId, extrinsic, recipientId } = this.state;
const hasLengthChanged = ((extrinsic && extrinsic.encodedLength) || 0) !== ((prevState.extrinsic && prevState.extrinsic.encodedLength) || 0);
if ((accountId && prevState.accountId !== accountId) ||
(recipientId && prevState.recipientId !== recipientId) ||
(balances_fees !== prevProps.balances_fees) ||
hasLengthChanged
) {
this.setMaxBalance();
}
}
render () {
const { t } = this.props;
const { accountId, extrinsic, recipientId, hasAvailable } = this.state;
const { accountId, extrinsic, recipientId, hasAvailable, maxBalance } = this.state;
return (
<Wrapper>
@ -84,7 +107,9 @@ class Transfer extends React.PureComponent<Props, State> {
help={t('Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 mili is equivalent to sending 0.001.')}
isError={!hasAvailable}
label={t('amount')}
maxValue={maxBalance}
onChange={this.onChangeAmount}
withMax
/>
<Checks
accountId={accountId}
@ -128,7 +153,7 @@ class Transfer extends React.PureComponent<Props, State> {
private nextState (newState: Partial<State>): void {
this.setState((prevState: State): State => {
const { api } = this.props;
const { accountId = prevState.accountId, amount = prevState.amount, recipientId = prevState.recipientId, hasAvailable = prevState.hasAvailable } = newState;
const { accountId = prevState.accountId, amount = prevState.amount, recipientId = prevState.recipientId, hasAvailable = prevState.hasAvailable, maxBalance = prevState.maxBalance } = newState;
const extrinsic = accountId && recipientId
? api.tx.balances.transfer(recipientId, amount)
: null;
@ -138,11 +163,58 @@ class Transfer extends React.PureComponent<Props, State> {
amount,
extrinsic,
hasAvailable,
maxBalance,
recipientId
};
});
}
private setMaxBalance = () => {
const { api, balances_fees = ZERO_FEES } = this.props;
const { accountId, recipientId } = this.state;
if (!accountId || !recipientId) {
return;
}
void api.query.system.accountNonce(accountId, (accountNonce) => {
const { transferFee, transactionBaseFee, transactionByteFee, creationFee } = balances_fees;
void api.derive.balances.all(accountId, ({ availableBalance: senderBalance }) => {
void api.derive.balances.all(recipientId, ({ availableBalance: recipientBalance }) => {
let prevMax = new BN(0);
let maxBalance = new BN(1);
let extrinsic;
while (!prevMax.eq(maxBalance)) {
prevMax = maxBalance;
extrinsic = accountId && recipientId
? api.tx.balances.transfer(recipientId, prevMax)
: null;
const txLength = calcSignatureLength(extrinsic, accountNonce);
const fees = transactionBaseFee
.add(transactionByteFee.muln(txLength))
.add(transferFee)
.add(senderBalance.isZero() ? creationFee : ZERO)
.add(recipientBalance.isZero() ? creationFee : ZERO);
maxBalance = new BN(senderBalance).sub(fees);
}
this.nextState({
extrinsic,
maxBalance
});
});
});
});
}
private onChangeFrom = (accountId: string) => {
this.nextState({ accountId });
}
@ -163,5 +235,8 @@ class Transfer extends React.PureComponent<Props, State> {
export default withMulti(
Transfer,
translate,
withApi
withApi,
withCalls<Props>(
'derive.balances.fees'
)
);

View File

@ -14,6 +14,7 @@ import Labelled from './Labelled';
type Props<Option> = BareProps & {
defaultValue?: any,
dropdownClassName?: string,
help?: React.ReactNode,
isButton?: boolean,
isDisabled?: boolean,
@ -57,9 +58,10 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
}
render () {
const { className, defaultValue, help, isButton, isDisabled, isError, isMultiple, label, onSearch, options, placeholder, renderLabel, style, withLabel, value } = this.props;
const { className, defaultValue, dropdownClassName, help, isButton, isDisabled, isError, isMultiple, label, onSearch, options, placeholder, renderLabel, style, withLabel, value } = this.props;
const dropdown = (
<SUIDropdown
className={dropdownClassName}
button={isButton}
compact={isButton}
disabled={isDisabled}
@ -99,7 +101,7 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
);
}
private onChange = (event: React.SyntheticEvent<HTMLElement>, { value }: DropdownProps): void => {
private onChange = (_: React.SyntheticEvent<HTMLElement>, { value }: DropdownProps): void => {
const { onChange, transform } = this.props;
onChange && onChange(

View File

@ -16,17 +16,19 @@ type Props = BareProps & {
isDisabled?: boolean,
isError?: boolean,
label?: any,
maxValue?: BN,
onChange?: (value?: BN) => void,
placeholder?: string,
value?: BN | string,
withLabel?: boolean
withLabel?: boolean,
withMax?: boolean
};
const DEFAULT_BITLENGTH = BitLengthOption.CHAIN_SPEC as BitLength;
export default class InputBalance extends React.PureComponent<Props> {
render () {
const { autoFocus, className, defaultValue, help, isDisabled, isError, label, onChange, placeholder, style, value, withLabel } = this.props;
const { autoFocus, className, defaultValue, help, isDisabled, isError, label, maxValue, onChange, placeholder, style, value, withLabel, withMax } = this.props;
return (
<InputNumber
@ -39,11 +41,13 @@ export default class InputBalance extends React.PureComponent<Props> {
isError={isError}
isSi
label={label}
maxValue={maxValue}
onChange={onChange}
placeholder={placeholder}
style={style}
value={value}
withLabel={withLabel}
withMax={withMax}
/>
);
}

View File

@ -10,6 +10,7 @@ import { formatBalance, isUndefined } from '@polkadot/util';
import { classes } from './util';
import { BitLengthOption } from './constants';
import Button from './Button';
import Dropdown from './Dropdown';
import Input, { KEYS, KEYS_PRE } from './Input';
import translate from './translate';
@ -25,10 +26,12 @@ type Props = BareProps & I18nProps & {
isDecimal?: boolean,
label?: any,
maxLength?: number,
maxValue?: BN,
onChange?: (value?: BN) => void,
placeholder?: string,
value?: BN | string,
withLabel?: boolean
withLabel?: boolean,
withMax?: boolean
};
type State = {
@ -41,6 +44,8 @@ type State = {
};
const DEFAULT_BITLENGTH = BitLengthOption.NORMAL_NUMBERS as BitLength;
const ZERO = new BN(0);
const TEN = new BN(10);
class InputNumber extends React.PureComponent<Props, State> {
constructor (props: Props) {
@ -84,7 +89,7 @@ class InputNumber extends React.PureComponent<Props, State> {
}
render () {
const { bitLength = DEFAULT_BITLENGTH, className, help, isSi, isDisabled, isError = false, maxLength, style, t } = this.props;
const { bitLength = DEFAULT_BITLENGTH, className, help, isSi, isDisabled, isError = false, maxLength, maxValue, style, withMax, t } = this.props;
const { isValid, value } = this.state;
const maxValueLength = this.maxValue(bitLength).toString().length - 1;
@ -106,6 +111,7 @@ class InputNumber extends React.PureComponent<Props, State> {
value={value}
type='text'
>
{(withMax && !!maxValue) && this.renderMaxButton()}
{isSi && this.renderSiDropdown()}
</Input>
);
@ -116,6 +122,7 @@ class InputNumber extends React.PureComponent<Props, State> {
return (
<Dropdown
dropdownClassName='ui--SiDropdown'
isButton
defaultValue={siUnit}
onChange={this.selectSiUnit}
@ -124,6 +131,24 @@ class InputNumber extends React.PureComponent<Props, State> {
);
}
private renderMaxButton () {
const { valueBN } = this.state;
const { maxValue } = this.props;
if (!maxValue || valueBN.eq(maxValue)) {
return;
}
return (
<Button
className='ui--MaxButton'
onClick={this.setToMaxValue}
>
Max
</Button>
);
}
private maxValue (bitLength?: number): BN {
return new BN(2).pow(new BN(bitLength || DEFAULT_BITLENGTH)).subn(1);
}
@ -133,8 +158,15 @@ class InputNumber extends React.PureComponent<Props, State> {
}
private isValidNumber (input: BN, bitLength: number = DEFAULT_BITLENGTH): boolean {
const { maxValue, withMax } = this.props;
const maxBN = this.maxValue(bitLength);
if (input.lt(new BN(0)) || !input.lt(maxBN) || !this.isValidBitLength(input, bitLength)) {
if (
input.lt(new BN(0)) ||
!input.lt(maxBN) ||
!this.isValidBitLength(input, bitLength) ||
(withMax && maxValue !== undefined && maxValue.gtn(0) && input.gt(maxValue))
) {
return false;
}
@ -229,15 +261,38 @@ class InputNumber extends React.PureComponent<Props, State> {
});
}
private inputValueToBn = (value: string, siUnit: string): BN => {
const { isSi } = this.props;
const basePower = isSi ? formatBalance.getDefaults().decimals : 0;
const siPower = isSi ? formatBalance.findSi(siUnit).power : 0;
private setToMaxValue = () => {
this.setState((prevState: State) => {
const { bitLength, maxValue, onChange } = this.props;
const { siUnit = prevState.siUnit } = this.state;
if (!maxValue) {
return {} as State;
}
const isValid = this.isValidNumber(maxValue, bitLength);
onChange && onChange(
isValid
? maxValue
: undefined
);
return {
value: this.bnToInputValue(maxValue, siUnit),
valueBN: maxValue,
isValid
};
});
}
private inputValueToBn = (value: string, siUnit?: string): BN => {
const [siPower, basePower, siUnitPower] = this.getSiPowers(siUnit);
const isDecimalValue = value.match(/^(\d+)\.(\d+)$/);
if (isDecimalValue) {
if (siPower - isDecimalValue[2].length < -basePower) {
if (siUnitPower - isDecimalValue[2].length < -basePower) {
return new BN(-1);
}
@ -245,13 +300,45 @@ class InputNumber extends React.PureComponent<Props, State> {
const mod = new BN(value.replace(/^\d+\./, ''));
return div
.mul(new BN(10).pow(new BN(basePower + siPower)))
.add(mod.mul(new BN(10).pow((new BN(basePower + siPower - mod.toString().length)))));
.mul(TEN.pow(siPower))
.add(mod.mul(TEN.pow(new BN(basePower + siUnitPower - mod.toString().length))));
} else {
return new BN(value.replace(/[^\d]/g, ''))
.mul(new BN(10).pow(new BN(basePower + siPower)));
.mul(TEN.pow(siPower));
}
}
private bnToInputValue = (bn: BN, siUnit?: string): string => {
const [siPower] = this.getSiPowers(siUnit);
const base = TEN.pow(siPower);
const div = bn.div(base);
const mod = bn.mod(base);
return `${
div.gt(ZERO) ? div.toString() : '0'
}${
mod.gt(ZERO) ?
(() => {
const padding = Math.max(
mod.toString().length,
base.toString().length - div.toString().length,
bn.toString().length - div.toString().length
);
return `.${mod.toString(10, padding).replace(/0*$/, '')}`;
})() :
''
}`;
}
private getSiPowers = (siUnit = this.state.siUnit): [BN, number, number] => {
const { isSi } = this.props;
const basePower = isSi ? formatBalance.getDefaults().decimals : 0;
const siUnitPower = isSi ? formatBalance.findSi(siUnit).power : 0;
return [new BN(basePower + siUnitPower), basePower, siUnitPower];
}
}
export {

View File

@ -257,6 +257,21 @@ header .ui--Button-Group {
white-space: nowrap;
}
}
.ui--SiDropdown {
width: 6rem;
text-align: center;
}
.ui--MaxButton {
position: absolute;
right: 6.5rem;
top: 20%;
height: 60%;
text-transform: uppercase;
padding: 0 0.5rem !important;
border-radius: 0.3rem !important;
}
}
.ui--InputFile {

View File

@ -7,8 +7,11 @@ import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
import BN from 'bn.js';
export const ZERO_BALANCE = {
availableBalance: new BN(0),
lockedBalance: new BN(0),
freeBalance: new BN(0),
reservedBalance: new BN(0),
vestedBalance: new BN(0),
votingBalance: new BN(0)
} as DerivedBalances;

View File

@ -37,14 +37,20 @@ type Props = I18nProps & {
accountId?: string | null,
extrinsic?: IExtrinsic | null,
isSendable: boolean,
onChange?: (hasAvailble: boolean) => void,
onChange?: (hasAvailable: boolean) => void,
system_accountNonce?: BN
};
const LENGTH_PUBLICKEY = 32 + 1; // publicKey + prefix
const LENGTH_SIGNATURE = 64;
const LENGTH_ERA = 1;
const SIGNATURE_SIZE = LENGTH_PUBLICKEY + LENGTH_SIGNATURE + LENGTH_ERA;
export const SIGNATURE_SIZE = LENGTH_PUBLICKEY + LENGTH_SIGNATURE + LENGTH_ERA;
export const calcSignatureLength = (extrinsic?: IExtrinsic | null, accountNonce?: BN): number => {
return SIGNATURE_SIZE +
(accountNonce ? compactToU8a(accountNonce).length : 0) +
(extrinsic ? extrinsic.encodedLength : 0);
};
export class FeeDisplay extends React.PureComponent<Props, State> {
state: State = {
@ -68,11 +74,8 @@ export class FeeDisplay extends React.PureComponent<Props, State> {
const fn = Method.findFunction(extrinsic.callIndex);
const extMethod = fn.method;
const extSection = fn.section;
const txLength = SIGNATURE_SIZE + compactToU8a(system_accountNonce).length + (
extrinsic
? extrinsic.encodedLength
: 0
);
const txLength = calcSignatureLength(extrinsic, system_accountNonce);
const isSameExtrinsic = prevState.extMethod === extMethod && prevState.extSection === extSection;
const extraAmount = isSameExtrinsic
? prevState.extraAmount

View File