// Copyright 2015-2017 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 } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { api } from '../parity';
import styles from './transaction.css';
import IdentityIcon from '../IdentityIcon';
class BaseTransaction extends Component {
shortHash (hash) {
return `${hash.substr(0, 5)}..${hash.substr(hash.length - 3)}`;
}
renderHash (hash) {
return (
{ hash }
);
}
renderFrom (transaction) {
if (!transaction) {
return '-';
}
return (
);
}
renderGasPrice (transaction) {
if (!transaction) {
return '-';
}
return (
{ api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) } shannon
);
}
renderGas (transaction) {
if (!transaction) {
return '-';
}
return (
{ transaction.gas.div(10 ** 6).toFormat(3) } MGas
);
}
renderPropagation (stats) {
const noOfPeers = Object.keys(stats.propagatedTo).length;
const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0);
return (
{ noOfPropagations } ({ noOfPeers } peers)
);
}
}
export class Transaction extends BaseTransaction {
static propTypes = {
idx: PropTypes.number.isRequired,
transaction: PropTypes.object.isRequired,
blockNumber: PropTypes.object.isRequired,
isLocal: PropTypes.bool,
stats: PropTypes.object
};
static defaultProps = {
isLocal: false,
stats: {
firstSeen: 0,
propagatedTo: {}
}
};
static renderHeader () {
return (
Transaction
From
Gas Price
Gas
First propagation
# Propagated
);
}
render () {
const { isLocal, stats, transaction, idx } = this.props;
const blockNo = new BigNumber(stats.firstSeen);
const clazz = classnames(styles.transaction, {
[styles.local]: isLocal
});
return (
{ idx }.
{ this.renderHash(transaction.hash) }
{ this.renderFrom(transaction) }
{ this.renderGasPrice(transaction) }
{ this.renderGas(transaction) }
{ this.renderTime(stats.firstSeen) }
{ this.renderPropagation(stats) }
);
}
renderTime (firstSeen) {
const { blockNumber } = this.props;
if (!firstSeen) {
return 'never';
}
const timeInMinutes = blockNumber.sub(firstSeen).mul(14).div(60).toFormat(1);
return `${timeInMinutes} minutes ago`;
}
}
export class LocalTransaction extends BaseTransaction {
static propTypes = {
hash: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
transaction: PropTypes.object,
isLocal: PropTypes.bool,
stats: PropTypes.object,
details: PropTypes.object
};
static defaultProps = {
stats: {
propagatedTo: {}
}
};
static renderHeader () {
return (
Transaction
From
Gas Price
Gas
Status
);
}
state = {
isSending: false,
isResubmitting: false,
gasPrice: null,
gas: null
};
toggleResubmit = () => {
const { transaction } = this.props;
const { isResubmitting } = this.state;
const nextState = {
isResubmitting: !isResubmitting
};
if (!isResubmitting) {
nextState.gasPrice = api.util.fromWei(transaction.gasPrice, 'shannon').toNumber();
nextState.gas = transaction.gas.div(1000000).toNumber();
}
this.setState(nextState);
};
setGasPrice = el => {
this.setState({
gasPrice: el.target.value
});
};
setGas = el => {
this.setState({
gas: el.target.value
});
};
sendTransaction = () => {
const { transaction, status } = this.props;
const { gasPrice, gas } = this.state;
const newTransaction = {
from: transaction.from,
value: transaction.value,
data: transaction.input,
gasPrice: api.util.toWei(gasPrice, 'shannon'),
gas: new BigNumber(gas).mul(1000000)
};
this.setState({
isResubmitting: false,
isSending: true
});
const closeSending = () => {
this.setState({
isSending: false,
gasPrice: null,
gas: null
});
};
if (transaction.to) {
newTransaction.to = transaction.to;
}
if (!['mined', 'replaced'].includes(status)) {
newTransaction.nonce = transaction.nonce;
}
api.eth.sendTransaction(newTransaction)
.then(closeSending)
.catch(closeSending);
};
render () {
if (this.state.isResubmitting) {
return this.renderResubmit();
}
const { stats, transaction, hash, status } = this.props;
const { isSending } = this.state;
const resubmit = isSending ? (
'sending...'
) : (
resubmit
);
return (
{ !transaction ? null : resubmit }
{ this.renderHash(hash) }
{ this.renderFrom(transaction) }
{ this.renderGasPrice(transaction) }
{ this.renderGas(transaction) }
{ this.renderStatus() }
{ status === 'pending' ? this.renderPropagation(stats) : null }
);
}
renderStatus () {
const { details } = this.props;
let state = {
'pending': () => 'In queue: Pending',
'future': () => 'In queue: Future',
'mined': () => 'Mined',
'dropped': () => 'Dropped because of queue limit',
'invalid': () => 'Transaction is invalid',
'rejected': () => `Rejected: ${details.error}`,
'replaced': () => `Replaced by ${this.shortHash(details.hash)}`
}[this.props.status];
return state ? state() : 'unknown';
}
// TODO [ToDr] Gas Price / Gas selection is not needed
// when signer supports gasPrice/gas tunning.
renderResubmit () {
const { transaction } = this.props;
const { gasPrice, gas } = this.state;
return (
cancel
{ this.renderHash(transaction.hash) }
{ this.renderFrom(transaction) }
shannon
MGas
Send
);
}
}