// Copyright 2015, 2016 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see . import BigNumber from 'bignumber.js'; import React, { Component, PropTypes } from 'react'; import { eip20 } from '~/contracts/abi'; import { api } from '../../parity'; import { loadBalances } from '../../services'; import AddressSelect from '../../AddressSelect'; import Container from '../../Container'; import styles from './send.css'; export default class Send extends Component { static contextTypes = { accounts: PropTypes.object.isRequired } state = { loading: true, tokens: null, selectedToken: null, availableBalances: [], fromAddress: null, fromBalance: null, toAddress: null, toKnown: true, amount: 0, amountError: null, sendBusy: false, sendError: null, sendState: null, sendDone: false, signerRequestId: null, txHash: null, txReceipt: null } componentDidMount () { this.loadBalances(); this.onAmountChange({ target: { value: '0' } }); } render () { const { loading } = this.state; return loading ? this.renderLoading() : this.renderBody(); } renderBody () { const { sendBusy } = this.state; return sendBusy ? this.renderSending() : this.renderForm(); } renderSending () { const { sendDone, sendError, sendState } = this.state; if (sendDone) { return (
Your token value transfer has been completed
); } if (sendError) { return (
Your deployment has encountered an error
{ sendError }
); } return (
Your token value is being transferred
{ sendState }
); } renderLoading () { return (
Loading available tokens
); } renderForm () { const { accounts } = this.context; const { availableBalances, fromAddress, amount, amountError, toKnown, toAddress } = this.state; const fromBalance = availableBalances.find((balance) => balance.address === fromAddress); const fromAddresses = availableBalances.map((balance) => balance.address); const toAddresses = Object.keys(accounts); const toInput = toKnown ? : ; const hasError = amountError; const error = `${styles.input} ${styles.error}`; const maxAmountHint = `Value to transfer (max: ${fromBalance ? fromBalance.balance.div(1000000).toFormat(6) : '1'})`; return (
type of token to transfer
account to transfer from
the type of address input
{ amountError || maxAmountHint }
); } renderTokens () { const { tokens } = this.state; return tokens.map((token) => ( )); } onSelectFrom = (event) => { const fromAddress = event.target.value; this.setState({ fromAddress }); } onChangeTo = (event) => { const toAddress = event.target.value; this.setState({ toAddress }); } onChangeToType = (event) => { const toKnown = event.target.value === 'known'; this.setState({ toKnown }); } onSelectToken = (event) => { const { tokens } = this.state; const address = event.target.value; const selectedToken = tokens.find((_token) => _token.address === address); const availableBalances = selectedToken.balances.filter((balance) => balance.balance.gt(0)); this.setState({ selectedToken, availableBalances }); this.onSelectFrom({ target: { value: availableBalances[0].address } }); } onAmountChange = (event) => { const amount = parseFloat(event.target.value); const amountError = !isFinite(amount) || amount <= 0 ? 'amount needs to be > 0' : null; this.setState({ amount, amountError }); } onSend = () => { const { amount, fromAddress, toAddress, amountError, selectedToken, sendBusy } = this.state; const hasError = amountError; if (hasError || sendBusy) { return; } const values = [toAddress, new BigNumber(amount).mul(1000000).toFixed(0)]; const options = { from: fromAddress }; const instance = api.newContract(eip20, selectedToken.address).instance; this.setState({ sendBusy: true, sendState: 'Estimating gas for the transaction' }); instance .transfer.estimateGas(options, values) .then((gas) => { this.setState({ sendState: 'Gas estimated, Posting transaction to the network' }); const gasPassed = gas.mul(1.2); options.gas = gasPassed.toFixed(0); console.log(`gas estimated at ${gas.toFormat(0)}, passing ${gasPassed.toFormat(0)}`); return instance.transfer.postTransaction(options, values); }) .then((signerRequestId) => { this.setState({ signerRequestId, sendState: 'Transaction posted, Waiting for transaction authorization' }); return api.pollMethod('parity_checkRequest', signerRequestId); }) .then((txHash) => { this.setState({ txHash, sendState: 'Transaction authorized, Waiting for network confirmations' }); return api.pollMethod('eth_getTransactionReceipt', txHash, (receipt) => { if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) { return false; } return true; }); }) .then((txReceipt) => { this.setState({ txReceipt, sendDone: true, sendState: 'Network confirmed, Received transaction receipt' }); }) .catch((error) => { console.error('onSend', error); this.setState({ sendError: error.message }); }); } loadBalances () { const { accounts } = this.context; const myAccounts = Object .values(accounts) .filter((account) => account.uuid) .map((account) => account.address); loadBalances(myAccounts) .then((_tokens) => { const tokens = _tokens.filter((token) => { for (let index = 0; index < token.balances.length; index++) { if (token.balances[index].balance.gt(0)) { return true; } } return false; }); this.setState({ tokens, loading: false }); this.onSelectToken({ target: { value: tokens[0].address } }); }); } }