Files
pezkuwi-apps/packages/page-staking/src/Actions/Account/BondExtra.tsx
T

110 lines
3.8 KiB
TypeScript

// Copyright 2017-2026 @pezkuwi/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@pezkuwi/api';
import type { DeriveBalancesAll, DeriveStakingAccount } from '@pezkuwi/api-derive/types';
import type { AmountValidateState } from '../types.js';
import React, { useMemo, useState } from 'react';
import { InputAddress, InputBalance, Modal, TxButton } from '@pezkuwi/react-components';
import { useApi, useCall } from '@pezkuwi/react-hooks';
import { BalanceFree } from '@pezkuwi/react-query';
import { BN, BN_ZERO } from '@pezkuwi/util';
import { useTranslation } from '../../translate.js';
import ValidateAmount from './InputValidateAmount.js';
interface Props {
controllerId: string | null;
onClose: () => void;
stakingInfo?: DeriveStakingAccount;
stashId: string;
}
function calcBalance (api: ApiPromise, stakingInfo?: DeriveStakingAccount, stashBalance?: DeriveBalancesAll): BN | null {
if (stakingInfo?.stakingLedger && stashBalance) {
const sumUnlocking = (stakingInfo.unlocking || []).reduce((acc, { value }) => acc.iadd(value), new BN(0));
const redeemable = stakingInfo.redeemable || BN_ZERO;
const available = stashBalance.freeBalance.sub(stakingInfo.stakingLedger.active?.unwrap() || BN_ZERO).sub(sumUnlocking).sub(redeemable);
return available.gt(api.consts.balances.existentialDeposit)
? available.sub(api.consts.balances.existentialDeposit)
: BN_ZERO;
}
return null;
}
function BondExtra ({ controllerId, onClose, stakingInfo, stashId }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const [amountError, setAmountError] = useState<AmountValidateState | null>(null);
const [maxAdditional, setMaxAdditional] = useState<BN | undefined>();
const stashBalance = useCall<DeriveBalancesAll>(api.derive.balances?.all, [stashId]);
const currentAmount = useMemo(
() => stakingInfo?.stakingLedger?.active?.unwrap(),
[stakingInfo]
);
const startBalance = useMemo(
() => calcBalance(api, stakingInfo, stashBalance),
[api, stakingInfo, stashBalance]
);
return (
<Modal
header= {t('Bond more funds')}
onClose={onClose}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t('Since this transaction deals with funding, the stash account will be used.')}>
<InputAddress
defaultValue={stashId}
isDisabled
label={t('stash account')}
/>
</Modal.Columns>
{startBalance && (
<Modal.Columns hint={t('The amount placed at-stake should allow some free funds for future transactions.')}>
<InputBalance
autoFocus
defaultValue={startBalance}
isError={!!amountError?.error || !maxAdditional || maxAdditional.isZero()}
label={t('additional funds to bond')}
labelExtra={
<BalanceFree
label={<span className='label'>{t('balance')}</span>}
params={stashId}
/>
}
onChange={setMaxAdditional}
/>
<ValidateAmount
controllerId={controllerId}
currentAmount={currentAmount}
onError={setAmountError}
stashId={stashId}
value={maxAdditional}
/>
</Modal.Columns>
)}
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={stashId}
icon='sign-in-alt'
isDisabled={!maxAdditional?.gt(BN_ZERO) || !!amountError?.error}
label={t('Bond more')}
onStart={onClose}
params={[maxAdditional]}
tx={api.tx.staking.bondExtra}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(BondExtra);