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:
parent
573a500cdd
commit
ec7d5a1c5f
@ -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' }]
|
||||
)
|
||||
);
|
||||
|
||||
@ -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' }]
|
||||
)
|
||||
);
|
||||
|
||||
@ -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' }]
|
||||
)
|
||||
);
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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'
|
||||
)
|
||||
);
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
0
packages/ui-signer/src/util.ts
Normal file
0
packages/ui-signer/src/util.ts
Normal file
Loading…
Reference in New Issue
Block a user