2016-11-17 10:15:11 +01:00
|
|
|
// Copyright 2015, 2016 Ethcore (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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
2016-11-17 11:01:21 +01:00
|
|
|
import BigNumber from 'bignumber.js';
|
2016-11-17 10:15:11 +01:00
|
|
|
import React, { Component, PropTypes } from 'react';
|
|
|
|
import classnames from 'classnames';
|
|
|
|
|
|
|
|
import { api } from '../parity';
|
|
|
|
|
|
|
|
import styles from './transaction.css';
|
|
|
|
|
|
|
|
import IdentityIcon from '../../githubhint/IdentityIcon';
|
|
|
|
|
|
|
|
class BaseTransaction extends Component {
|
|
|
|
|
|
|
|
shortHash (hash) {
|
2016-11-17 11:01:21 +01:00
|
|
|
return `${hash.substr(0, 5)}..${hash.substr(hash.length - 3)}`;
|
2016-11-17 10:15:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
renderHash (hash) {
|
|
|
|
return (
|
|
|
|
<code title={ hash }>
|
|
|
|
{ this.shortHash(hash) }
|
|
|
|
</code>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderFrom (transaction) {
|
|
|
|
if (!transaction) {
|
|
|
|
return '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div title={ transaction.from } className={ styles.from }>
|
|
|
|
<IdentityIcon
|
|
|
|
address={ transaction.from }
|
|
|
|
/>
|
|
|
|
0x{ transaction.nonce.toString(16) }
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderGasPrice (transaction) {
|
|
|
|
if (!transaction) {
|
|
|
|
return '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2016-11-17 14:00:53 +01:00
|
|
|
<span title={ `${transaction.gasPrice.toFormat(0)} wei` }>
|
2016-11-17 10:15:11 +01:00
|
|
|
{ api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) } shannon
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderGas (transaction) {
|
|
|
|
if (!transaction) {
|
|
|
|
return '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<span title={ `${transaction.gas.toFormat(0)} Gas` }>
|
2016-11-17 14:00:53 +01:00
|
|
|
{ transaction.gas.div(10 ** 6).toFormat(3) } MGas
|
2016-11-17 10:15:11 +01:00
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderPropagation (stats) {
|
|
|
|
const noOfPeers = Object.keys(stats.propagatedTo).length;
|
|
|
|
const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<span className={ styles.nowrap }>
|
|
|
|
{ noOfPropagations } ({ noOfPeers } peers)
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Transaction extends BaseTransaction {
|
|
|
|
|
|
|
|
static propTypes = {
|
|
|
|
idx: PropTypes.number.isRequired,
|
|
|
|
transaction: PropTypes.object.isRequired,
|
2016-11-17 11:01:21 +01:00
|
|
|
blockNumber: PropTypes.object.isRequired,
|
2016-11-17 10:15:11 +01:00
|
|
|
isLocal: PropTypes.bool,
|
|
|
|
stats: PropTypes.object
|
|
|
|
};
|
|
|
|
|
|
|
|
static defaultProps = {
|
|
|
|
isLocal: false,
|
|
|
|
stats: {
|
|
|
|
firstSeen: 0,
|
|
|
|
propagatedTo: {}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static renderHeader () {
|
|
|
|
return (
|
|
|
|
<tr className={ styles.header }>
|
|
|
|
<th></th>
|
|
|
|
<th>
|
|
|
|
Transaction
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
From
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
Gas Price
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
Gas
|
|
|
|
</th>
|
|
|
|
<th>
|
2016-11-17 11:01:21 +01:00
|
|
|
First propagation
|
2016-11-17 10:15:11 +01:00
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
# Propagated
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
</th>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render () {
|
|
|
|
const { isLocal, stats, transaction, idx } = this.props;
|
2016-11-17 14:00:53 +01:00
|
|
|
const blockNo = new BigNumber(stats.firstSeen);
|
2016-11-17 10:15:11 +01:00
|
|
|
|
|
|
|
const clazz = classnames(styles.transaction, {
|
|
|
|
[styles.local]: isLocal
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<tr className={ clazz }>
|
|
|
|
<td>
|
|
|
|
{ idx }.
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderHash(transaction.hash) }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderFrom(transaction) }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderGasPrice(transaction) }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderGas(transaction) }
|
|
|
|
</td>
|
2016-11-17 11:01:21 +01:00
|
|
|
<td title={ blockNo.toFormat(0) }>
|
|
|
|
{ this.renderTime(stats.firstSeen) }
|
2016-11-17 10:15:11 +01:00
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderPropagation(stats) }
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
2016-11-17 11:01:21 +01:00
|
|
|
|
|
|
|
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`;
|
|
|
|
}
|
2016-11-17 10:15:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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 (
|
|
|
|
<tr className={ styles.header }>
|
|
|
|
<th></th>
|
|
|
|
<th>
|
|
|
|
Transaction
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
From
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
Gas Price / Gas
|
|
|
|
</th>
|
|
|
|
<th>
|
|
|
|
Status
|
|
|
|
</th>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
state = {
|
|
|
|
isSending: false,
|
|
|
|
isResubmitting: false,
|
|
|
|
gasPrice: null,
|
|
|
|
gas: null
|
|
|
|
};
|
|
|
|
|
|
|
|
toggleResubmit = () => {
|
|
|
|
const { transaction } = this.props;
|
|
|
|
const { isResubmitting, gasPrice } = this.state;
|
|
|
|
|
|
|
|
this.setState({
|
2016-11-17 14:00:53 +01:00
|
|
|
isResubmitting: !isResubmitting
|
2016-11-17 10:15:11 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
if (gasPrice === null) {
|
|
|
|
this.setState({
|
2016-11-17 14:00:53 +01:00
|
|
|
gasPrice: `0x${transaction.gasPrice.toString(16)}`,
|
|
|
|
gas: `0x${transaction.gas.toString(16)}`
|
2016-11-17 10:15:11 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-17 14:00:53 +01:00
|
|
|
setGasPrice = el => {
|
|
|
|
this.setState({
|
|
|
|
gasPrice: el.target.value
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
setGas = el => {
|
|
|
|
this.setState({
|
|
|
|
gas: el.target.value
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2016-11-17 10:15:11 +01:00
|
|
|
sendTransaction = () => {
|
|
|
|
const { transaction } = this.props;
|
|
|
|
const { gasPrice, gas } = this.state;
|
|
|
|
|
|
|
|
const newTransaction = {
|
|
|
|
from: transaction.from,
|
|
|
|
to: transaction.to,
|
|
|
|
nonce: transaction.nonce,
|
|
|
|
value: transaction.value,
|
|
|
|
data: transaction.data,
|
|
|
|
gasPrice, gas
|
|
|
|
};
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
isResubmitting: false,
|
|
|
|
isSending: true
|
|
|
|
});
|
|
|
|
|
|
|
|
const closeSending = () => this.setState({
|
2016-11-17 11:01:21 +01:00
|
|
|
isSending: false,
|
|
|
|
gasPrice: null,
|
|
|
|
gas: null
|
2016-11-17 10:15:11 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
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...'
|
|
|
|
) : (
|
|
|
|
<a href='javascript:void' onClick={ this.toggleResubmit }>
|
|
|
|
resubmit
|
|
|
|
</a>
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<tr className={ styles.transaction }>
|
|
|
|
<td>
|
|
|
|
{ !transaction ? null : resubmit }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderHash(hash) }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderFrom(transaction) }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderGasPrice(transaction) }
|
|
|
|
<br />
|
|
|
|
{ this.renderGas(transaction) }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderStatus() }
|
2016-11-17 11:01:21 +01:00
|
|
|
<br />
|
|
|
|
{ status === 'pending' ? this.renderPropagation(stats) : null }
|
2016-11-17 10:15:11 +01:00
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderStatus () {
|
|
|
|
const { details } = this.props;
|
|
|
|
|
|
|
|
let state = {
|
2016-11-17 14:00:53 +01:00
|
|
|
'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)}`
|
2016-11-17 10:15:11 +01:00
|
|
|
}[this.props.status];
|
|
|
|
|
|
|
|
return state ? state() : 'unknown';
|
|
|
|
}
|
|
|
|
|
2016-11-17 14:00:53 +01:00
|
|
|
// TODO [ToDr] Gas Price / Gas selection is not needed
|
|
|
|
// when signer supports gasPrice/gas tunning.
|
2016-11-17 10:15:11 +01:00
|
|
|
renderResubmit () {
|
|
|
|
const { transaction } = this.props;
|
|
|
|
const { gasPrice, gas } = this.state;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<tr className={ styles.transaction }>
|
|
|
|
<td>
|
|
|
|
<a href='javascript:void' onClick={ this.toggleResubmit }>
|
|
|
|
cancel
|
|
|
|
</a>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderHash(transaction.hash) }
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{ this.renderFrom(transaction) }
|
|
|
|
</td>
|
|
|
|
<td className={ styles.edit }>
|
|
|
|
<input
|
|
|
|
type='text'
|
|
|
|
value={ gasPrice }
|
2016-11-17 14:00:53 +01:00
|
|
|
onChange={ this.setGasPrice }
|
2016-11-17 10:15:11 +01:00
|
|
|
/>
|
|
|
|
<input
|
|
|
|
type='text'
|
|
|
|
value={ gas }
|
2016-11-17 14:00:53 +01:00
|
|
|
onChange={ this.setGas }
|
2016-11-17 10:15:11 +01:00
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
<td colSpan='2'>
|
|
|
|
<a href='javascript:void' onClick={ this.sendTransaction }>
|
|
|
|
Send
|
|
|
|
</a>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|