123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- /* eslint-disable @typescript-eslint/camelcase */
- // Copyright 2017-2019 @polkadot/app-accounts 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 { SubmittableExtrinsic } from '@polkadot/api/promise/types';
- import { Index } from '@polkadot/types/interfaces';
- import { ApiProps } from '@polkadot/react-api/types';
- import { DerivedFees } from '@polkadot/api-derive/types';
- import { I18nProps } from '@polkadot/react-components/types';
- import BN from 'bn.js';
- import React from 'react';
- import styled from 'styled-components';
- import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
- import { Available } from '@polkadot/react-query';
- import Checks, { calcSignatureLength } from '@polkadot/react-signer/Checks';
- import { withApi, withCalls, withMulti } from '@polkadot/react-api';
- import { ZERO_FEES } from '@polkadot/react-signer/Checks/constants';
- import translate from '../translate';
- type Props = ApiProps & I18nProps & {
- balances_fees?: DerivedFees;
- className?: string;
- onClose: () => void;
- recipientId?: string;
- senderId?: string;
- system_accountNonce?: BN;
- };
- interface State {
- amount: BN;
- extrinsic: SubmittableExtrinsic | null;
- hasAvailable: boolean;
- maxBalance?: BN;
- recipientId?: string | null;
- senderId?: string | null;
- }
- const ZERO = new BN(0);
- class Transfer extends React.PureComponent<Props> {
- public state: State;
- public constructor (props: Props) {
- super(props);
- this.state = {
- amount: ZERO,
- extrinsic: null,
- hasAvailable: true,
- maxBalance: ZERO,
- recipientId: props.recipientId || null,
- senderId: props.senderId || null
- };
- }
- public componentDidUpdate (prevProps: Props, prevState: State): void {
- 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) ||
- (balances_fees !== prevProps.balances_fees) || (prevState.senderId !== senderId) ||
- hasLengthChanged
- ) {
- this.setMaxBalance().catch(console.error);
- }
- }
- public render (): React.ReactNode {
- const { t } = this.props;
- return (
- <Modal
- className='app--accounts-Modal'
- dimmer='inverted'
- open
- >
- <Modal.Header>{t('Send funds')}</Modal.Header>
- {this.renderContent()}
- {this.renderButtons()}
- </Modal>
- );
- }
- private nextState (newState: Partial<State>): void {
- this.setState((prevState: State): State => {
- const { api } = this.props;
- const { amount = prevState.amount, recipientId = prevState.recipientId, hasAvailable = prevState.hasAvailable, maxBalance = prevState.maxBalance, senderId = prevState.senderId } = newState;
- const extrinsic = recipientId && senderId
- ? api.tx.balances.transfer(recipientId, amount)
- : null;
- return {
- amount,
- extrinsic,
- hasAvailable,
- maxBalance,
- recipientId,
- senderId
- };
- });
- }
- private renderButtons (): React.ReactNode {
- const { onClose, t } = this.props;
- const { extrinsic, hasAvailable, senderId } = this.state;
- return (
- <Modal.Actions>
- <Button.Group>
- <Button
- isNegative
- label={t('Cancel')}
- onClick={onClose}
- />
- <Button.Or />
- <TxButton
- accountId={senderId}
- extrinsic={extrinsic}
- isDisabled={!hasAvailable}
- isPrimary
- label={t('Make Transfer')}
- onStart={onClose}
- withSpinner={false}
- />
- </Button.Group>
- </Modal.Actions>
- );
- }
- private renderContent (): React.ReactNode {
- const { className, recipientId: propRecipientId, senderId: propSenderId, t } = this.props;
- const { extrinsic, hasAvailable, maxBalance, recipientId, senderId } = this.state;
- const available = <span className='label'>{t('available ')}</span>;
- return (
- <Modal.Content>
- <div className={className}>
- <InputAddress
- defaultValue={propSenderId}
- help={t('The account you will send funds from.')}
- isDisabled={!!propSenderId}
- label={t('send from account')}
- labelExtra={<Available label={available} params={senderId} />}
- onChange={this.onChangeFrom}
- type='account'
- />
- <InputAddress
- defaultValue={propRecipientId}
- help={t('Select a contact or paste the address you want to send funds to.')}
- isDisabled={!!propRecipientId}
- label={t('send to address')}
- labelExtra={<Available label={available} params={recipientId} />}
- onChange={this.onChangeTo}
- type='allPlus'
- />
- <InputBalance
- 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={senderId}
- extrinsic={extrinsic}
- isSendable
- onChange={this.onChangeFees}
- />
- </div>
- </Modal.Content>
- );
- }
- private onChangeAmount = (amount: BN = new BN(0)): void => {
- this.nextState({ amount });
- }
- private onChangeFrom = (senderId: string): void => {
- this.nextState({ senderId });
- }
- private onChangeTo = (recipientId: string): void => {
- this.nextState({ recipientId });
- }
- private onChangeFees = (hasAvailable: boolean): void => {
- this.setState({ hasAvailable });
- }
- private setMaxBalance = async (): Promise<void> => {
- const { api, balances_fees = ZERO_FEES } = this.props;
- const { senderId, recipientId } = this.state;
- if (!senderId || !recipientId) {
- return;
- }
- const { transferFee, transactionBaseFee, transactionByteFee, creationFee } = balances_fees;
- const accountNonce = await api.query.system.accountNonce<Index>(senderId);
- const senderBalance = (await api.derive.balances.all(senderId)).availableBalance;
- const recipientBalance = (await api.derive.balances.all(recipientId)).availableBalance;
- let prevMax = new BN(0);
- let maxBalance = new BN(1);
- let extrinsic;
- while (!prevMax.eq(maxBalance)) {
- prevMax = maxBalance;
- extrinsic = api.tx.balances.transfer(recipientId, prevMax);
- const txLength = calcSignatureLength(extrinsic, accountNonce);
- const fees = transactionBaseFee
- .add(transactionByteFee.muln(txLength))
- .add(transferFee)
- .add(recipientBalance.isZero() ? creationFee : ZERO);
- maxBalance = senderBalance.sub(fees);
- }
- this.nextState({
- extrinsic,
- maxBalance
- });
- }
- }
- export default withMulti(
- styled(Transfer)`
- article.padded {
- box-shadow: none;
- margin-left: 2rem;
- }
- .balance {
- margin-bottom: 0.5rem;
- text-align: right;
- padding-right: 1rem;
- .label {
- opacity: 0.7;
- }
- }
- label.with-help {
- flex-basis: 10rem;
- }
- `,
- translate,
- withApi,
- withCalls<Props>(
- 'derive.balances.fees'
- )
- );
|