Transfer.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /* eslint-disable @typescript-eslint/camelcase */
  2. // Copyright 2017-2019 @polkadot/app-accounts authors & contributors
  3. // This software may be modified and distributed under the terms
  4. // of the Apache-2.0 license. See the LICENSE file for details.
  5. import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
  6. import { Index } from '@polkadot/types/interfaces';
  7. import { ApiProps } from '@polkadot/react-api/types';
  8. import { DerivedFees } from '@polkadot/api-derive/types';
  9. import { I18nProps } from '@polkadot/react-components/types';
  10. import BN from 'bn.js';
  11. import React from 'react';
  12. import styled from 'styled-components';
  13. import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
  14. import { Available } from '@polkadot/react-query';
  15. import Checks, { calcSignatureLength } from '@polkadot/react-signer/Checks';
  16. import { withApi, withCalls, withMulti } from '@polkadot/react-api';
  17. import { ZERO_FEES } from '@polkadot/react-signer/Checks/constants';
  18. import translate from '../translate';
  19. type Props = ApiProps & I18nProps & {
  20. balances_fees?: DerivedFees;
  21. className?: string;
  22. onClose: () => void;
  23. recipientId?: string;
  24. senderId?: string;
  25. system_accountNonce?: BN;
  26. };
  27. interface State {
  28. amount: BN;
  29. extrinsic: SubmittableExtrinsic | null;
  30. hasAvailable: boolean;
  31. maxBalance?: BN;
  32. recipientId?: string | null;
  33. senderId?: string | null;
  34. }
  35. const ZERO = new BN(0);
  36. class Transfer extends React.PureComponent<Props> {
  37. public state: State;
  38. public constructor (props: Props) {
  39. super(props);
  40. this.state = {
  41. amount: ZERO,
  42. extrinsic: null,
  43. hasAvailable: true,
  44. maxBalance: ZERO,
  45. recipientId: props.recipientId || null,
  46. senderId: props.senderId || null
  47. };
  48. }
  49. public componentDidUpdate (prevProps: Props, prevState: State): void {
  50. const { balances_fees } = this.props;
  51. const { extrinsic, recipientId, senderId } = this.state;
  52. const hasLengthChanged = ((extrinsic && extrinsic.encodedLength) || 0) !== ((prevState.extrinsic && prevState.extrinsic.encodedLength) || 0);
  53. if ((recipientId && prevState.recipientId !== recipientId) ||
  54. (balances_fees !== prevProps.balances_fees) || (prevState.senderId !== senderId) ||
  55. hasLengthChanged
  56. ) {
  57. this.setMaxBalance().catch(console.error);
  58. }
  59. }
  60. public render (): React.ReactNode {
  61. const { t } = this.props;
  62. return (
  63. <Modal
  64. className='app--accounts-Modal'
  65. dimmer='inverted'
  66. open
  67. >
  68. <Modal.Header>{t('Send funds')}</Modal.Header>
  69. {this.renderContent()}
  70. {this.renderButtons()}
  71. </Modal>
  72. );
  73. }
  74. private nextState (newState: Partial<State>): void {
  75. this.setState((prevState: State): State => {
  76. const { api } = this.props;
  77. const { amount = prevState.amount, recipientId = prevState.recipientId, hasAvailable = prevState.hasAvailable, maxBalance = prevState.maxBalance, senderId = prevState.senderId } = newState;
  78. const extrinsic = recipientId && senderId
  79. ? api.tx.balances.transfer(recipientId, amount)
  80. : null;
  81. return {
  82. amount,
  83. extrinsic,
  84. hasAvailable,
  85. maxBalance,
  86. recipientId,
  87. senderId
  88. };
  89. });
  90. }
  91. private renderButtons (): React.ReactNode {
  92. const { onClose, t } = this.props;
  93. const { extrinsic, hasAvailable, senderId } = this.state;
  94. return (
  95. <Modal.Actions>
  96. <Button.Group>
  97. <Button
  98. isNegative
  99. label={t('Cancel')}
  100. onClick={onClose}
  101. />
  102. <Button.Or />
  103. <TxButton
  104. accountId={senderId}
  105. extrinsic={extrinsic}
  106. isDisabled={!hasAvailable}
  107. isPrimary
  108. label={t('Make Transfer')}
  109. onStart={onClose}
  110. withSpinner={false}
  111. />
  112. </Button.Group>
  113. </Modal.Actions>
  114. );
  115. }
  116. private renderContent (): React.ReactNode {
  117. const { className, recipientId: propRecipientId, senderId: propSenderId, t } = this.props;
  118. const { extrinsic, hasAvailable, maxBalance, recipientId, senderId } = this.state;
  119. const available = <span className='label'>{t('available ')}</span>;
  120. return (
  121. <Modal.Content>
  122. <div className={className}>
  123. <InputAddress
  124. defaultValue={propSenderId}
  125. help={t('The account you will send funds from.')}
  126. isDisabled={!!propSenderId}
  127. label={t('send from account')}
  128. labelExtra={<Available label={available} params={senderId} />}
  129. onChange={this.onChangeFrom}
  130. type='account'
  131. />
  132. <InputAddress
  133. defaultValue={propRecipientId}
  134. help={t('Select a contact or paste the address you want to send funds to.')}
  135. isDisabled={!!propRecipientId}
  136. label={t('send to address')}
  137. labelExtra={<Available label={available} params={recipientId} />}
  138. onChange={this.onChangeTo}
  139. type='allPlus'
  140. />
  141. <InputBalance
  142. 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.')}
  143. isError={!hasAvailable}
  144. label={t('amount')}
  145. maxValue={maxBalance}
  146. onChange={this.onChangeAmount}
  147. withMax
  148. />
  149. <Checks
  150. accountId={senderId}
  151. extrinsic={extrinsic}
  152. isSendable
  153. onChange={this.onChangeFees}
  154. />
  155. </div>
  156. </Modal.Content>
  157. );
  158. }
  159. private onChangeAmount = (amount: BN = new BN(0)): void => {
  160. this.nextState({ amount });
  161. }
  162. private onChangeFrom = (senderId: string): void => {
  163. this.nextState({ senderId });
  164. }
  165. private onChangeTo = (recipientId: string): void => {
  166. this.nextState({ recipientId });
  167. }
  168. private onChangeFees = (hasAvailable: boolean): void => {
  169. this.setState({ hasAvailable });
  170. }
  171. private setMaxBalance = async (): Promise<void> => {
  172. const { api, balances_fees = ZERO_FEES } = this.props;
  173. const { senderId, recipientId } = this.state;
  174. if (!senderId || !recipientId) {
  175. return;
  176. }
  177. const { transferFee, transactionBaseFee, transactionByteFee, creationFee } = balances_fees;
  178. const accountNonce = await api.query.system.accountNonce<Index>(senderId);
  179. const senderBalance = (await api.derive.balances.all(senderId)).availableBalance;
  180. const recipientBalance = (await api.derive.balances.all(recipientId)).availableBalance;
  181. let prevMax = new BN(0);
  182. let maxBalance = new BN(1);
  183. let extrinsic;
  184. while (!prevMax.eq(maxBalance)) {
  185. prevMax = maxBalance;
  186. extrinsic = api.tx.balances.transfer(recipientId, prevMax);
  187. const txLength = calcSignatureLength(extrinsic, accountNonce);
  188. const fees = transactionBaseFee
  189. .add(transactionByteFee.muln(txLength))
  190. .add(transferFee)
  191. .add(recipientBalance.isZero() ? creationFee : ZERO);
  192. maxBalance = senderBalance.sub(fees);
  193. }
  194. this.nextState({
  195. extrinsic,
  196. maxBalance
  197. });
  198. }
  199. }
  200. export default withMulti(
  201. styled(Transfer)`
  202. article.padded {
  203. box-shadow: none;
  204. margin-left: 2rem;
  205. }
  206. .balance {
  207. margin-bottom: 0.5rem;
  208. text-align: right;
  209. padding-right: 1rem;
  210. .label {
  211. opacity: 0.7;
  212. }
  213. }
  214. label.with-help {
  215. flex-basis: 10rem;
  216. }
  217. `,
  218. translate,
  219. withApi,
  220. withCalls<Props>(
  221. 'derive.balances.fees'
  222. )
  223. );