diff --git a/packages/page-staking/src/Actions/Account/BondExtra.tsx b/packages/page-staking/src/Actions/Account/BondExtra.tsx
index f28581f308..e951f59e5f 100644
--- a/packages/page-staking/src/Actions/Account/BondExtra.tsx
+++ b/packages/page-staking/src/Actions/Account/BondExtra.tsx
@@ -2,12 +2,13 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
-import { DeriveStakingAccount } from '@polkadot/api-derive/types';
+import { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types';
import { AmountValidateState } from '../types';
import BN from 'bn.js';
-import React, { useMemo, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
+import { useApi, useCall } from '@polkadot/react-hooks';
import { BalanceFree } from '@polkadot/react-query';
import { BN_ZERO } from '@polkadot/util';
@@ -15,21 +16,37 @@ import { useTranslation } from '../../translate';
import ValidateAmount from './InputValidateAmount';
interface Props {
+ controllerId: string | null;
onClose: () => void;
stakingInfo?: DeriveStakingAccount;
stashId: string;
}
-function BondExtra ({ onClose, stakingInfo, stashId }: Props): React.ReactElement {
+function BondExtra ({ controllerId, onClose, stakingInfo, stashId }: Props): React.ReactElement {
const { t } = useTranslation();
+ const { api } = useApi();
const [amountError, setAmountError] = useState(null);
const [maxAdditional, setMaxAdditional] = useState();
const [maxBalance] = useState();
+ const [startBalance, setStartBalance] = useState(null);
+ const stashBalance = useCall(api.derive.balances.all, [stashId]);
const currentAmount = useMemo(
() => stakingInfo && stakingInfo.stakingLedger?.active.unwrap(),
[stakingInfo]
);
+ useEffect((): void => {
+ if (stakingInfo && stakingInfo.stakingLedger && stashBalance) {
+ const available = stashBalance.freeBalance.sub(stakingInfo.stakingLedger.active.unwrap());
+
+ setStartBalance(
+ available.gt(api.consts.balances.existentialDeposit)
+ ? available.sub(api.consts.balances.existentialDeposit)
+ : BN_ZERO
+ );
+ }
+ }, [api, stakingInfo, stashBalance]);
+
return (
{t('Since this transaction deals with funding, the stash account will be used.')}
-
-
- ('Amount to add to the currently bonded funds. This is adjusted using the available funds on the account.')}
- isError={!!amountError?.error || !maxAdditional || maxAdditional.eqn(0)}
- isWarning={!!amountError?.warning}
- label={t('additional bonded funds')}
- labelExtra={
- {t('balance')}}
- params={stashId}
- />
- }
- maxValue={maxBalance}
- onChange={setMaxAdditional}
- />
-
-
-
- {t('The amount placed at-stake should be no more that 95% of your available amount to protect against slashing events.')}
-
-
+ {startBalance && (
+
+
+ ('Amount to add to the currently bonded funds. This is adjusted using the available funds on the account.')}
+ isError={!!amountError?.error || !maxAdditional || maxAdditional.eqn(0)}
+ label={t('additional bonded funds')}
+ labelExtra={
+ {t('balance')}}
+ params={stashId}
+ />
+ }
+ maxValue={maxBalance}
+ onChange={setMaxAdditional}
+ />
+
+
+
+ {t('The amount placed at-stake should allow some free funds for future transactions.')}
+
+
+ )}
void;
+ stashId: string | null;
value?: BN | null;
}
-function ValidateAmount ({ accountId, currentAmount, onError, value }: Props): React.ReactElement | null {
+function formatExistential (value: BN): string {
+ let fmt = (value.mul(BN_THOUSAND).div(BN_TEN.pow(new BN(formatBalance.getDefaults().decimals))).toNumber() / 1000).toFixed(3);
+
+ while (fmt.length !== 1 && ['.', '0'].includes(fmt[fmt.length - 1])) {
+ const isLast = fmt.endsWith('.');
+
+ fmt = fmt.substr(0, fmt.length - 1);
+
+ if (isLast) {
+ break;
+ }
+ }
+
+ return fmt;
+}
+
+function ValidateAmount ({ currentAmount, onError, stashId, value }: Props): React.ReactElement | null {
const { t } = useTranslation();
const { api } = useApi();
- const allBalances = useCall(api.derive.balances.all, [accountId]);
+ const stashBalance = useCall(api.derive.balances.all, [stashId]);
const [{ error, warning }, setResult] = useState({ error: null, warning: null });
useEffect((): void => {
- // don't show an error if the selected controller is the default
- // this applies when changing controller
- if (allBalances && value) {
- let newError: string | null = null;
- let newWarning: string | null = null;
+ if (stashBalance && value) {
+ // also used in bond extra, take check against total of current bonded and new
const check = value.add(currentAmount || BN_ZERO);
+ const existentialDeposit = api.consts.balances.existentialDeposit;
+ const maxBond = stashBalance.freeBalance.sub(existentialDeposit.divn(2));
+ let newError: string | null = null;
- if (check.gte(allBalances.freeBalance)) {
- newError = t('The specified value is greater than your free balance. The node will bond the maximum amount available.');
- } else if (check.gt(allBalances.freeBalance.muln(95).divn(100))) {
- newWarning = t('The specified value is greater than the recommended amount. You may not be adequately protected against slashing events or have funds for future fees.');
+ if (check.gte(maxBond)) {
+ newError = t('The specified value is too large and does not allow funds to pay future transaction fees.');
+ } else if (check.lt(existentialDeposit)) {
+ newError = t('The bonded amount is less than the minimum bond amount of {{existentialDeposit}}', {
+ replace: { existentialDeposit: formatExistential(existentialDeposit) }
+ });
}
setResult((state): AmountValidateState => {
const error = state.error !== newError ? newError : state.error;
- const warning = state.warning !== newWarning ? newWarning : state.warning;
+ const warning = state.warning;
onError((error || warning) ? { error, warning } : null);
return { error, warning };
});
}
- }, [allBalances, currentAmount, onError, t, value]);
+ }, [api, currentAmount, onError, stashBalance, t, value]);
if (error || warning) {
return (
diff --git a/packages/page-staking/src/Actions/Account/index.tsx b/packages/page-staking/src/Actions/Account/index.tsx
index 80ddd42ea7..a2a0482c4e 100644
--- a/packages/page-staking/src/Actions/Account/index.tsx
+++ b/packages/page-staking/src/Actions/Account/index.tsx
@@ -55,6 +55,7 @@ function Account ({ className = '', info: { controllerId, destination, destinati
{isBondExtraOpen && (
{
const [controllerId, setControllerId] = useState(null);
const [destination, setDestination] = useState(0);
const [stashId, setStashId] = useState(null);
+ const [startBalance, setStartBalance] = useState(null);
+ const stashBalance = useCall(api.derive.balances.all, [stashId]);
const bondedBlocks = useUnbondDuration();
const _setError = useCallback(
@@ -39,6 +43,18 @@ function Bond ({ className = '', onChange }: Props): React.ReactElement {
[]
);
+ useEffect((): void => {
+ stashBalance && setStartBalance(
+ stashBalance.freeBalance.gt(api.consts.balances.existentialDeposit)
+ ? stashBalance.freeBalance.sub(api.consts.balances.existentialDeposit)
+ : BN_ZERO
+ );
+ }, [api, stashBalance]);
+
+ useEffect((): void => {
+ setStartBalance(null);
+ }, [stashId]);
+
useEffect((): void => {
onChange(
(amount && amount.gtn(0) && !amountError?.error && !controllerError && controllerId && stashId)
@@ -89,41 +105,44 @@ function Bond ({ className = '', onChange }: Props): React.ReactElement {
{t('To ensure optimal fund security using the same stash/controller is strongly discouraged, but not forbidden.')}
-
-
- ('The total amount of the stash balance that will be at stake in any forthcoming rounds (should be less than the free amount available)')}
- isError={!hasValue || !!amountError?.error}
- isWarning={!!amountError?.warning}
- label={t('value bonded')}
- labelExtra={
- {t('balance')}}
- params={stashId}
- />
- }
- onChange={setAmount}
- />
-
- {bondedBlocks?.gtn(0) && (
- ('The bonding duration for any staked funds. Needs to be unlocked and withdrawn to become available.')}
- label={t('on-chain bonding duration')}
- >
-
-
- )}
-
-
- {t('The amount placed at-stake should be no more that 95% of your available amount to protect against slashing events.')}
- {t('Once bonded, it wil need to be unlocked/withdrawn and will be locked for at least the bonding duration.')}
-
-
+ {startBalance && (
+
+
+ ('The total amount of the stash balance that will be at stake in any forthcoming rounds (should be less than the free amount available)')}
+ isError={!hasValue || !!amountError?.error}
+ label={t('value bonded')}
+ labelExtra={
+ {t('balance')}}
+ params={stashId}
+ />
+ }
+ onChange={setAmount}
+ />
+
+ {bondedBlocks?.gtn(0) && (
+ ('The bonding duration for any staked funds. Needs to be unlocked and withdrawn to become available.')}
+ label={t('on-chain bonding duration')}
+ >
+
+
+ )}
+
+
+ {t('The amount placed at-stake should not be your full available available amount to allow for transaction fees.')}
+ {t('Once bonded, it wil need to be unlocked/withdrawn and will be locked for at least the bonding duration.')}
+
+
+ )}
{
+function InputBalance ({ autoFocus, children, className = '', defaultValue: inDefault, help, isDisabled, isError, isFull, isWarning, isZeroable, label, labelExtra, maxValue, onChange, onEnter, onEscape, placeholder, value, withEllipsis, withLabel, withMax }: Props): React.ReactElement {
const [defaultValue, setDefaultValue] = useState();
useEffect((): void => {
@@ -88,7 +88,9 @@ function InputBalance ({ autoFocus, className = '', defaultValue: inDefault, hel
withEllipsis={withEllipsis}
withLabel={withLabel}
withMax={withMax}
- />
+ >
+ {children}
+
);
}