Cancel tx JS (#4958)
* Remove transaction RPC * Bumping multihash and libc * Updating nanomsg * bump nanomsg * cancel tx * cancel-tx-js * cancel-tx-js * cancel-tx-js * cancel-tx-hs * cancel-tx-js * cancel-tx-js * cancel-tx-js * small fixes * edit & time till submit * edit & time till submit * updates * updates * udpates * udpates * grumbles * step 1 * Wonderful updates * ready * small refact * small refact * grumbles 1 * ffx2 * good ol' fashioned updates * latest and greatest * removeHash * removeHash * spec * fix 1 * fix 1 * fix 2 * fix 2 * ff * ff * ff * updates
This commit is contained in:
parent
0768ce3600
commit
f7d5d6c0cd
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,6 +19,9 @@
|
|||||||
# mac stuff
|
# mac stuff
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# npm stuff
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
# gdb files
|
# gdb files
|
||||||
.gdb_history
|
.gdb_history
|
||||||
|
|
||||||
|
@ -164,6 +164,7 @@
|
|||||||
"blockies": "0.0.2",
|
"blockies": "0.0.2",
|
||||||
"brace": "0.9.0",
|
"brace": "0.9.0",
|
||||||
"bytes": "2.4.0",
|
"bytes": "2.4.0",
|
||||||
|
"date-difference": "1.0.0",
|
||||||
"debounce": "1.0.0",
|
"debounce": "1.0.0",
|
||||||
"es6-error": "4.0.0",
|
"es6-error": "4.0.0",
|
||||||
"es6-promise": "4.0.5",
|
"es6-promise": "4.0.5",
|
||||||
|
@ -1833,7 +1833,14 @@ export default {
|
|||||||
example: {
|
example: {
|
||||||
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
|
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
|
||||||
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
|
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
|
||||||
value: fromDecimal(2441406250)
|
gas: fromDecimal(30400),
|
||||||
|
gasPrice: fromDecimal(10000000000000),
|
||||||
|
value: fromDecimal(2441406250),
|
||||||
|
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
|
||||||
|
condition: {
|
||||||
|
block: 354221,
|
||||||
|
time: new Date()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -185,14 +185,16 @@ class MethodDecoding extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.methodDecoding.condition.block'
|
id='ui.methodDecoding.condition.block'
|
||||||
defaultMessage=', {historic, select, true {Submitted} false {Submission}} at block {blockNumber}'
|
defaultMessage='{historic, select, true {Will be submitted} false {To be submitted}} at block {blockNumber}'
|
||||||
values={ {
|
values={ {
|
||||||
historic,
|
historic,
|
||||||
blockNumber
|
blockNumber
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,14 +206,16 @@ class MethodDecoding extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='ui.methodDecoding.condition.time'
|
id='ui.methodDecoding.condition.time'
|
||||||
defaultMessage=', {historic, select, true {Submitted} false {Submission}} at {timestamp}'
|
defaultMessage='{historic, select, true {Will be submitted} false {To be submitted}} {timestamp}'
|
||||||
values={ {
|
values={ {
|
||||||
historic,
|
historic,
|
||||||
timestamp
|
timestamp
|
||||||
} }
|
} }
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import dateDifference from 'date-difference';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
@ -36,12 +38,15 @@ class TxRow extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
accountAddresses: PropTypes.array.isRequired,
|
accountAddresses: PropTypes.array.isRequired,
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
|
blockNumber: PropTypes.object,
|
||||||
contractAddresses: PropTypes.array.isRequired,
|
contractAddresses: PropTypes.array.isRequired,
|
||||||
netVersion: PropTypes.string.isRequired,
|
netVersion: PropTypes.string.isRequired,
|
||||||
tx: PropTypes.object.isRequired,
|
tx: PropTypes.object.isRequired,
|
||||||
|
|
||||||
block: PropTypes.object,
|
block: PropTypes.object,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
cancelTransaction: PropTypes.func,
|
||||||
|
editTransaction: PropTypes.func,
|
||||||
historic: PropTypes.bool
|
historic: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,6 +55,10 @@ class TxRow extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
isCancelOpen: false,
|
||||||
|
isEditOpen: false,
|
||||||
|
canceled: false,
|
||||||
|
editing: false,
|
||||||
isContract: false,
|
isContract: false,
|
||||||
isDeploy: false
|
isDeploy: false
|
||||||
};
|
};
|
||||||
@ -166,11 +175,124 @@ class TxRow extends Component {
|
|||||||
return (
|
return (
|
||||||
<td className={ styles.timestamp }>
|
<td className={ styles.timestamp }>
|
||||||
<div>{ blockNumber && block ? moment(block.timestamp).fromNow() : null }</div>
|
<div>{ blockNumber && block ? moment(block.timestamp).fromNow() : null }</div>
|
||||||
<div>{ blockNumber ? _blockNumber.toFormat() : 'Pending' }</div>
|
<div>{ blockNumber ? _blockNumber.toFormat() : this.renderCancelToggle() }</div>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderCancelToggle () {
|
||||||
|
const { canceled, editing, isCancelOpen, isEditOpen } = this.state;
|
||||||
|
|
||||||
|
if (canceled) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<FormattedMessage
|
||||||
|
lassName={ styles.uppercase }
|
||||||
|
id='ui.txList.txRow.canceled'
|
||||||
|
defaultMessage='Canceled'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.editing'
|
||||||
|
defaultMessage='Editing'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCancelOpen && !isEditOpen) {
|
||||||
|
const pendingStatus = this.getCondition();
|
||||||
|
|
||||||
|
if (pendingStatus === 'submitting') {
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<div />
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.submitting'
|
||||||
|
defaultMessage='Submitting'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<span>
|
||||||
|
{ pendingStatus }
|
||||||
|
</span>
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.scheduled'
|
||||||
|
defaultMessage='Scheduled'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<a onClick={ this.setEdit } className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.edit'
|
||||||
|
defaultMessage='Edit'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<span>{' | '}</span>
|
||||||
|
<a onClick={ this.setCancel } className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.cancel'
|
||||||
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let which;
|
||||||
|
|
||||||
|
if (isCancelOpen) {
|
||||||
|
which = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify.cancelEditCancel'
|
||||||
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
which = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify.cancelEditEdit'
|
||||||
|
defaultMessage='Edit'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.pending }>
|
||||||
|
<div />
|
||||||
|
<div className={ styles.uppercase }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify'
|
||||||
|
defaultMessage='Are you sure?'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<a onClick={ (isCancelOpen) ? this.cancelTx : this.editTx }>
|
||||||
|
{ which }
|
||||||
|
</a>
|
||||||
|
<span>{' | '}</span>
|
||||||
|
<a onClick={ this.revertEditCancel }>
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.verify.nevermind'
|
||||||
|
defaultMessage='Nevermind'
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getIsKnownContract (address) {
|
getIsKnownContract (address) {
|
||||||
const { contractAddresses } = this.props;
|
const { contractAddresses } = this.props;
|
||||||
|
|
||||||
@ -194,6 +316,70 @@ class TxRow extends Component {
|
|||||||
|
|
||||||
return `/addresses/${address}`;
|
return `/addresses/${address}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCondition = () => {
|
||||||
|
const { blockNumber, tx } = this.props;
|
||||||
|
let { time, block } = tx.condition;
|
||||||
|
|
||||||
|
if (time) {
|
||||||
|
if ((time.getTime() - Date.now()) >= 0) {
|
||||||
|
// return `${dateDifference(new Date(), time, { compact: true })} left`;
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.pendingStatus.time'
|
||||||
|
defaultMessage='{time} left'
|
||||||
|
values={ {
|
||||||
|
time: dateDifference(new Date(), time, { compact: true })
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return 'submitting';
|
||||||
|
}
|
||||||
|
} else if (blockNumber) {
|
||||||
|
block = blockNumber.minus(block);
|
||||||
|
// return (block.toNumber() < 0)
|
||||||
|
// ? block.abs().toFormat(0) + ' blocks left'
|
||||||
|
// : 'submitting';
|
||||||
|
if (block.toNumber() < 0) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='ui.txList.txRow.pendingStatus.blocksLeft'
|
||||||
|
defaultMessage='{blockNumber} blocks left'
|
||||||
|
values={ {
|
||||||
|
blockNumber: block.abs().toFormat(0)
|
||||||
|
} }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return 'submitting';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTx = () => {
|
||||||
|
const { cancelTransaction, tx } = this.props;
|
||||||
|
|
||||||
|
cancelTransaction(this, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
editTx = () => {
|
||||||
|
const { editTransaction, tx } = this.props;
|
||||||
|
|
||||||
|
editTransaction(this, tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCancel = () => {
|
||||||
|
this.setState({ isCancelOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
setEdit = () => {
|
||||||
|
this.setState({ isEditOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
revertEditCancel = () => {
|
||||||
|
this.setState({ isCancelOpen: false, isEditOpen: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (initState) {
|
function mapStateToProps (initState) {
|
||||||
|
@ -14,35 +14,51 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { action, observable, transaction } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
import { uniq } from 'lodash';
|
|
||||||
|
|
||||||
export default class Store {
|
export default class Store {
|
||||||
@observable blocks = {};
|
@observable blocks = {};
|
||||||
@observable sortedHashes = [];
|
@observable sortedHashes = [];
|
||||||
@observable transactions = {};
|
@observable transactions = {};
|
||||||
|
|
||||||
constructor (api) {
|
constructor (api, onNewError, hashes) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
this._subscriptionId = 0;
|
this._onNewError = onNewError;
|
||||||
this._pendingHashes = [];
|
this.loadTransactions(hashes);
|
||||||
|
|
||||||
this.subscribe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action addBlocks = (blocks) => {
|
@action addHash = (hash) => {
|
||||||
this.blocks = Object.assign({}, this.blocks, blocks);
|
if (!this.sortedHashes.includes(hash)) {
|
||||||
|
this.sortedHashes.push(hash);
|
||||||
|
this.sortHashes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action addTransactions = (transactions) => {
|
@action removeHash = (hash) => {
|
||||||
transaction(() => {
|
this.sortedHashes.remove(hash);
|
||||||
this.transactions = Object.assign({}, this.transactions, transactions);
|
let tx = this.transactions[hash];
|
||||||
this.sortedHashes = Object
|
|
||||||
.keys(this.transactions)
|
|
||||||
.sort((ahash, bhash) => {
|
|
||||||
const bnA = this.transactions[ahash].blockNumber;
|
|
||||||
const bnB = this.transactions[bhash].blockNumber;
|
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
delete this.transactions[hash];
|
||||||
|
delete this.blocks[tx.blockNumber];
|
||||||
|
}
|
||||||
|
this.sortHashes();
|
||||||
|
}
|
||||||
|
|
||||||
|
containsAll = (arr1, arr2) => {
|
||||||
|
return arr2.every((arr2Item) => arr1.includes(arr2Item));
|
||||||
|
}
|
||||||
|
|
||||||
|
sameHashList = (transactions) => {
|
||||||
|
return this.containsAll(transactions, this.sortedHashes) && this.containsAll(this.sortedHashes, transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
sortHashes = () => {
|
||||||
|
this.sortedHashes = this.sortedHashes.sort((hashA, hashB) => {
|
||||||
|
const bnA = this.transactions[hashA].blockNumber;
|
||||||
|
const bnB = this.transactions[hashB].blockNumber;
|
||||||
|
|
||||||
|
// 0 is a special case (has not been added to the blockchain yet)
|
||||||
if (bnB.eq(0)) {
|
if (bnB.eq(0)) {
|
||||||
return bnB.eq(bnA) ? 0 : 1;
|
return bnB.eq(bnA) ? 0 : 1;
|
||||||
} else if (bnA.eq(0)) {
|
} else if (bnA.eq(0)) {
|
||||||
@ -51,113 +67,82 @@ export default class Store {
|
|||||||
|
|
||||||
return bnB.comparedTo(bnA);
|
return bnB.comparedTo(bnA);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._pendingHashes = this.sortedHashes.filter((hash) => this.transactions[hash].blockNumber.eq(0));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@action clearPending () {
|
loadTransactions (_txhashes) {
|
||||||
this._pendingHashes = [];
|
const { eth } = this._api;
|
||||||
}
|
|
||||||
|
|
||||||
subscribe () {
|
// Ignore special cases and if the contents of _txhashes && this.sortedHashes are the same
|
||||||
this._api
|
if (Array.isArray(_txhashes) || this.sameHashList(_txhashes)) {
|
||||||
.subscribe('eth_blockNumber', (error, blockNumber) => {
|
|
||||||
if (error) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._pendingHashes.length) {
|
// Remove any tx that are edited/cancelled
|
||||||
this.loadTransactions(this._pendingHashes);
|
this.sortedHashes
|
||||||
this.clearPending();
|
.forEach((hash) => {
|
||||||
|
if (!_txhashes.includes(hash)) {
|
||||||
|
this.removeHash(hash);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add any new tx
|
||||||
|
_txhashes
|
||||||
|
.forEach((txhash) => {
|
||||||
|
if (this.sortedHashes.includes(txhash)) { return; }
|
||||||
|
eth.getTransactionByHash(txhash)
|
||||||
|
.then((tx) => {
|
||||||
|
if (!tx) { return; }
|
||||||
|
this.transactions[txhash] = tx;
|
||||||
|
// If the tx has a blockHash, let's get the blockNumber, otherwise it's ready to be added
|
||||||
|
if (tx.blockHash) {
|
||||||
|
eth.getBlockByNumber(tx.blockNumber)
|
||||||
|
.then((block) => {
|
||||||
|
this.blocks[tx.blockNumber] = block;
|
||||||
|
this.addHash(txhash);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.addHash(txhash);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelTransaction = (txComponent, tx) => {
|
||||||
|
const { parity } = this._api;
|
||||||
|
const { hash } = tx;
|
||||||
|
|
||||||
|
parity
|
||||||
|
.removeTransaction(hash)
|
||||||
|
.then(() => {
|
||||||
|
txComponent.setState({ canceled: true });
|
||||||
})
|
})
|
||||||
.then((subscriptionId) => {
|
.catch((err) => {
|
||||||
this._subscriptionId = subscriptionId;
|
this._onNewError({ message: err });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribe () {
|
editTransaction = (txComponent, tx) => {
|
||||||
if (!this._subscriptionId) {
|
const { parity } = this._api;
|
||||||
return;
|
const { hash, gas, gasPrice, to, from, value, input, condition } = tx;
|
||||||
}
|
|
||||||
|
|
||||||
this._api.unsubscribe(this._subscriptionId);
|
parity
|
||||||
this._subscriptionId = 0;
|
.removeTransaction(hash)
|
||||||
}
|
.then(() => {
|
||||||
|
parity.postTransaction({
|
||||||
loadTransactions (_txhashes = []) {
|
from,
|
||||||
const promises = _txhashes
|
to,
|
||||||
.filter((txhash) => !this.transactions[txhash] || this._pendingHashes.includes(txhash))
|
gas,
|
||||||
.map((txhash) => {
|
gasPrice,
|
||||||
return Promise
|
value,
|
||||||
.all([
|
condition,
|
||||||
this._api.eth.getTransactionByHash(txhash),
|
data: input
|
||||||
this._api.eth.getTransactionReceipt(txhash)
|
|
||||||
])
|
|
||||||
.then(([
|
|
||||||
transaction = {},
|
|
||||||
transactionReceipt = {}
|
|
||||||
]) => {
|
|
||||||
return {
|
|
||||||
...transactionReceipt,
|
|
||||||
...transaction
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if (!promises.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise
|
|
||||||
.all(promises)
|
|
||||||
.then((_transactions) => {
|
|
||||||
const blockNumbers = [];
|
|
||||||
const transactions = _transactions
|
|
||||||
.filter((tx) => tx && tx.hash)
|
|
||||||
.reduce((txs, tx) => {
|
|
||||||
txs[tx.hash] = tx;
|
|
||||||
|
|
||||||
if (tx.blockNumber && tx.blockNumber.gt(0)) {
|
|
||||||
blockNumbers.push(tx.blockNumber.toNumber());
|
|
||||||
}
|
|
||||||
|
|
||||||
return txs;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// No need to add transactions if there are none
|
|
||||||
if (Object.keys(transactions).length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addTransactions(transactions);
|
|
||||||
this.loadBlocks(blockNumbers);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.then(() => {
|
||||||
console.warn('loadTransactions', error);
|
txComponent.setState({ editing: true });
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
loadBlocks (_blockNumbers) {
|
|
||||||
const blockNumbers = uniq(_blockNumbers).filter((bn) => !this.blocks[bn]);
|
|
||||||
|
|
||||||
if (!blockNumbers || !blockNumbers.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise
|
|
||||||
.all(blockNumbers.map((blockNumber) => this._api.eth.getBlockByNumber(blockNumber)))
|
|
||||||
.then((blocks) => {
|
|
||||||
this.addBlocks(
|
|
||||||
blocks.reduce((blocks, block, index) => {
|
|
||||||
blocks[blockNumbers[index]] = block;
|
|
||||||
return blocks;
|
|
||||||
}, {})
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((err) => {
|
||||||
console.warn('loadBlocks', error);
|
this._onNewError({ message: err });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ describe('ui/TxList/store', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
store = new Store(api);
|
store = new Store(api, null, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
@ -53,16 +53,14 @@ describe('ui/TxList/store', () => {
|
|||||||
expect(store.sortedHashes.peek()).to.deep.equal([]);
|
expect(store.sortedHashes.peek()).to.deep.equal([]);
|
||||||
expect(store.transactions).to.deep.equal({});
|
expect(store.transactions).to.deep.equal({});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('subscribes to eth_blockNumber', () => {
|
|
||||||
expect(api.subscribe).to.have.been.calledWith('eth_blockNumber');
|
|
||||||
expect(store._subscriptionId).to.equal(SUBID);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('addBlocks', () => {
|
describe('addBlocks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.addBlocks(BLOCKS);
|
Object.keys(BLOCKS)
|
||||||
|
.forEach((blockNumber) => {
|
||||||
|
store.blocks[blockNumber] = BLOCKS[blockNumber];
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds the blocks to the list', () => {
|
it('adds the blocks to the list', () => {
|
||||||
@ -72,7 +70,12 @@ describe('ui/TxList/store', () => {
|
|||||||
|
|
||||||
describe('addTransactions', () => {
|
describe('addTransactions', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store.addTransactions(TRANSACTIONS);
|
Object.keys(TRANSACTIONS)
|
||||||
|
.forEach((hash) => {
|
||||||
|
store.transactions[hash] = TRANSACTIONS[hash];
|
||||||
|
store.addHash(hash);
|
||||||
|
});
|
||||||
|
store.sortHashes();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds all transactions to the list', () => {
|
it('adds all transactions to the list', () => {
|
||||||
@ -82,9 +85,5 @@ describe('ui/TxList/store', () => {
|
|||||||
it('sorts transactions based on blockNumber', () => {
|
it('sorts transactions based on blockNumber', () => {
|
||||||
expect(store.sortedHashes.peek()).to.deep.equal(['0x234', '0x456', '0x345', '0x123']);
|
expect(store.sortedHashes.peek()).to.deep.equal(['0x234', '0x456', '0x345', '0x123']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds pending transactions to the pending queue', () => {
|
|
||||||
expect(store._pendingHashes).to.deep.equal(['0x234', '0x456']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,10 +42,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.timestamp {
|
&.timestamp {
|
||||||
padding-top: 1.5em;
|
max-width: 5em;
|
||||||
text-align: right;
|
padding-top: 0.75em;
|
||||||
|
text-align: center;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
opacity: 0.5;
|
color: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.transaction {
|
&.transaction {
|
||||||
@ -83,4 +84,16 @@
|
|||||||
.left {
|
.left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pending {
|
||||||
|
padding: 0em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending div {
|
||||||
|
padding-bottom: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uppercase {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,17 +35,13 @@ class TxList extends Component {
|
|||||||
PropTypes.array,
|
PropTypes.array,
|
||||||
PropTypes.object
|
PropTypes.object
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
netVersion: PropTypes.string.isRequired
|
blockNumber: PropTypes.object,
|
||||||
|
netVersion: PropTypes.string.isRequired,
|
||||||
|
onNewError: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
store = new Store(this.context.api);
|
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
this.store.loadTransactions(this.props.hashes);
|
this.store = new Store(this.context.api, this.props.onNewError, this.props.hashes);
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
this.store.unsubscribe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (newProps) {
|
componentWillReceiveProps (newProps) {
|
||||||
@ -63,20 +59,24 @@ class TxList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRows () {
|
renderRows () {
|
||||||
const { address, netVersion } = this.props;
|
const { address, netVersion, blockNumber } = this.props;
|
||||||
|
const { editTransaction, cancelTransaction } = this.store;
|
||||||
|
|
||||||
return this.store.sortedHashes.map((txhash) => {
|
return this.store.sortedHashes.map((txhash) => {
|
||||||
const tx = this.store.transactions[txhash];
|
const tx = this.store.transactions[txhash];
|
||||||
const blockNumber = tx.blockNumber.toNumber();
|
const txBlockNumber = tx.blockNumber.toNumber();
|
||||||
const block = this.store.blocks[blockNumber];
|
const block = this.store.blocks[txBlockNumber];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TxRow
|
<TxRow
|
||||||
key={ tx.hash }
|
key={ tx.hash }
|
||||||
tx={ tx }
|
tx={ tx }
|
||||||
block={ block }
|
block={ block }
|
||||||
|
blockNumber={ blockNumber }
|
||||||
address={ address }
|
address={ address }
|
||||||
netVersion={ netVersion }
|
netVersion={ netVersion }
|
||||||
|
editTransaction={ editTransaction }
|
||||||
|
cancelTransaction={ cancelTransaction }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,8 @@ import { connect } from 'react-redux';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import Store from '../../store';
|
import Store from '../../store';
|
||||||
import * as RequestsActions from '~/redux/providers/signerActions';
|
import { newError } from '~/redux/actions';
|
||||||
|
import { startConfirmRequest, startRejectRequest } from '~/redux/providers/signerActions';
|
||||||
import { Container, Page, TxList } from '~/ui';
|
import { Container, Page, TxList } from '~/ui';
|
||||||
|
|
||||||
import RequestPending from '../../components/RequestPending';
|
import RequestPending from '../../components/RequestPending';
|
||||||
@ -36,12 +37,13 @@ class RequestsPage extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
actions: PropTypes.shape({
|
|
||||||
startConfirmRequest: PropTypes.func.isRequired,
|
|
||||||
startRejectRequest: PropTypes.func.isRequired
|
|
||||||
}).isRequired,
|
|
||||||
gasLimit: PropTypes.object.isRequired,
|
gasLimit: PropTypes.object.isRequired,
|
||||||
netVersion: PropTypes.string.isRequired,
|
netVersion: PropTypes.string.isRequired,
|
||||||
|
startConfirmRequest: PropTypes.func.isRequired,
|
||||||
|
startRejectRequest: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
blockNumber: PropTypes.object,
|
||||||
|
newError: PropTypes.func,
|
||||||
signer: PropTypes.shape({
|
signer: PropTypes.shape({
|
||||||
pending: PropTypes.array.isRequired,
|
pending: PropTypes.array.isRequired,
|
||||||
finished: PropTypes.array.isRequired
|
finished: PropTypes.array.isRequired
|
||||||
@ -69,6 +71,7 @@ class RequestsPage extends Component {
|
|||||||
|
|
||||||
renderLocalQueue () {
|
renderLocalQueue () {
|
||||||
const { localHashes } = this.store;
|
const { localHashes } = this.store;
|
||||||
|
const { blockNumber, newError } = this.props;
|
||||||
|
|
||||||
if (!localHashes.length) {
|
if (!localHashes.length) {
|
||||||
return null;
|
return null;
|
||||||
@ -85,7 +88,9 @@ class RequestsPage extends Component {
|
|||||||
>
|
>
|
||||||
<TxList
|
<TxList
|
||||||
address=''
|
address=''
|
||||||
|
blockNumber={ blockNumber }
|
||||||
hashes={ localHashes }
|
hashes={ localHashes }
|
||||||
|
onNewError={ newError }
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
@ -114,7 +119,7 @@ class RequestsPage extends Component {
|
|||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='signer.requestsPage.pendingTitle'
|
id='signer.requestsPage.pendingTitle'
|
||||||
defaultMessage='Pending Requests'
|
defaultMessage='Pending Signature Authorization'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -124,7 +129,7 @@ class RequestsPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderPending = (data, index) => {
|
renderPending = (data, index) => {
|
||||||
const { actions, gasLimit, netVersion } = this.props;
|
const { startConfirmRequest, startRejectRequest, gasLimit, netVersion } = this.props;
|
||||||
const { date, id, isSending, payload, origin } = data;
|
const { date, id, isSending, payload, origin } = data;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -137,8 +142,8 @@ class RequestsPage extends Component {
|
|||||||
isSending={ isSending }
|
isSending={ isSending }
|
||||||
netVersion={ netVersion }
|
netVersion={ netVersion }
|
||||||
key={ id }
|
key={ id }
|
||||||
onConfirm={ actions.startConfirmRequest }
|
onConfirm={ startConfirmRequest }
|
||||||
onReject={ actions.startRejectRequest }
|
onReject={ startRejectRequest }
|
||||||
origin={ origin }
|
origin={ origin }
|
||||||
payload={ payload }
|
payload={ payload }
|
||||||
signerStore={ this.store }
|
signerStore={ this.store }
|
||||||
@ -148,11 +153,11 @@ class RequestsPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { gasLimit, netVersion } = state.nodeStatus;
|
const { gasLimit, netVersion, blockNumber } = state.nodeStatus;
|
||||||
const { actions, signer } = state;
|
const { signer } = state;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actions,
|
blockNumber,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
netVersion,
|
netVersion,
|
||||||
signer
|
signer
|
||||||
@ -160,9 +165,11 @@ function mapStateToProps (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return {
|
return bindActionCreators({
|
||||||
actions: bindActionCreators(RequestsActions, dispatch)
|
newError,
|
||||||
};
|
startConfirmRequest,
|
||||||
|
startRejectRequest
|
||||||
|
}, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
@ -95,7 +95,11 @@ export default class SignerStore {
|
|||||||
this._api.parity
|
this._api.parity
|
||||||
.localTransactions()
|
.localTransactions()
|
||||||
.then((localTransactions) => {
|
.then((localTransactions) => {
|
||||||
this.setLocalHashes(Object.keys(localTransactions));
|
const keys = Object
|
||||||
|
.keys(localTransactions)
|
||||||
|
.filter((key) => localTransactions[key].status !== 'canceled');
|
||||||
|
|
||||||
|
this.setLocalHashes(keys);
|
||||||
})
|
})
|
||||||
.then(nextTimeout)
|
.then(nextTimeout)
|
||||||
.catch(nextTimeout);
|
.catch(nextTimeout);
|
||||||
|
Loading…
Reference in New Issue
Block a user