diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js
index 27c834ead30..b46148cdc4c 100644
--- a/js/src/api/format/input.js
+++ b/js/src/api/format/input.js
@@ -141,3 +141,24 @@ export function inOptions (options) {
return options;
}
+
+export function inTraceFilter (filterObject) {
+ if (filterObject) {
+ Object.keys(filterObject).forEach((key) => {
+ switch (key) {
+ case 'fromAddress':
+ case 'toAddress':
+ filterObject[key] = [].concat(filterObject[key])
+ .map(address => inAddress(address));
+ break;
+
+ case 'toBlock':
+ case 'fromBlock':
+ filterObject[key] = inBlockNumber(filterObject[key]);
+ break;
+ }
+ });
+ }
+
+ return filterObject;
+}
diff --git a/js/src/api/rpc/trace/trace.js b/js/src/api/rpc/trace/trace.js
index 93a35c7f300..95fed42307d 100644
--- a/js/src/api/rpc/trace/trace.js
+++ b/js/src/api/rpc/trace/trace.js
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-import { inBlockNumber, inHex, inNumber16 } from '../../format/input';
+import { inBlockNumber, inHex, inNumber16, inTraceFilter } from '../../format/input';
import { outTrace } from '../../format/output';
export default class Trace {
@@ -24,7 +24,7 @@ export default class Trace {
filter (filterObj) {
return this._transport
- .execute('trace_filter', filterObj)
+ .execute('trace_filter', inTraceFilter(filterObj))
.then(traces => traces.map(trace => outTrace(trace)));
}
diff --git a/js/src/index.js b/js/src/index.js
index db173a07f0d..08b02c2a3c2 100644
--- a/js/src/index.js
+++ b/js/src/index.js
@@ -39,6 +39,8 @@ import { initStore } from './redux';
import { ContextProvider, muiTheme } from './ui';
import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, Dapp, Dapps, Settings, SettingsBackground, SettingsProxy, SettingsViews, Signer, Status } from './views';
+import { setApi } from './redux/providers/apiActions';
+
import './environment';
import '../assets/fonts/Roboto/font.css';
@@ -60,6 +62,7 @@ ContractInstances.create(api);
const store = initStore(api);
store.dispatch({ type: 'initAll', api });
+store.dispatch(setApi(api));
const routerHistory = useRouterHistory(createHashHistory)({});
diff --git a/js/src/redux/middleware.js b/js/src/redux/middleware.js
index 6ba811ea7d1..a54f2fae43d 100644
--- a/js/src/redux/middleware.js
+++ b/js/src/redux/middleware.js
@@ -13,6 +13,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+import thunk from 'redux-thunk';
import ErrorsMiddleware from '../ui/Errors/middleware';
import SettingsMiddleware from '../views/Settings/middleware';
@@ -32,5 +33,5 @@ export default function (api) {
errors.toMiddleware()
];
- return middleware.concat(status);
+ return middleware.concat(status, thunk);
}
diff --git a/js/src/redux/providers/apiActions.js b/js/src/redux/providers/apiActions.js
new file mode 100644
index 00000000000..88e1fa84819
--- /dev/null
+++ b/js/src/redux/providers/apiActions.js
@@ -0,0 +1,22 @@
+// 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 .
+
+export function setApi (api) {
+ return {
+ type: 'setApi',
+ api
+ };
+}
diff --git a/js/src/redux/providers/apiReducer.js b/js/src/redux/providers/apiReducer.js
new file mode 100644
index 00000000000..2d4c96ecdbb
--- /dev/null
+++ b/js/src/redux/providers/apiReducer.js
@@ -0,0 +1,26 @@
+// 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 .
+
+import { handleActions } from 'redux-actions';
+
+const initialState = {};
+
+export default handleActions({
+ setApi (state, action) {
+ const { api } = action;
+ return api;
+ }
+}, initialState);
diff --git a/js/src/redux/providers/blockchainActions.js b/js/src/redux/providers/blockchainActions.js
new file mode 100644
index 00000000000..eac2bf62b6b
--- /dev/null
+++ b/js/src/redux/providers/blockchainActions.js
@@ -0,0 +1,128 @@
+// 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 .
+
+import Contracts from '../../contracts';
+
+export function setBlock (blockNumber, block) {
+ return {
+ type: 'setBlock',
+ blockNumber, block
+ };
+}
+
+export function setTransaction (txHash, info) {
+ return {
+ type: 'setTransaction',
+ txHash, info
+ };
+}
+
+export function setBytecode (address, bytecode) {
+ return {
+ type: 'setBytecode',
+ address, bytecode
+ };
+}
+
+export function setMethod (signature, method) {
+ return {
+ type: 'setMethod',
+ signature, method
+ };
+}
+
+export function fetchBlock (blockNumber) {
+ return (dispatch, getState) => {
+ const { blocks } = getState().blockchain;
+
+ if (blocks[blockNumber.toString()]) {
+ return;
+ }
+
+ const { api } = getState();
+
+ api.eth
+ .getBlockByNumber(blockNumber)
+ .then(block => {
+ dispatch(setBlock(blockNumber, block));
+ })
+ .catch(e => {
+ console.error('blockchain::fetchBlock', e);
+ });
+ };
+}
+
+export function fetchTransaction (txHash) {
+ return (dispatch, getState) => {
+ const { transactions } = getState().blockchain;
+
+ if (transactions[txHash]) {
+ return;
+ }
+
+ const { api } = getState();
+
+ api.eth
+ .getTransactionByHash(txHash)
+ .then(info => {
+ dispatch(setTransaction(txHash, info));
+ })
+ .catch(e => {
+ console.error('blockchain::fetchTransaction', e);
+ });
+ };
+}
+
+export function fetchBytecode (address) {
+ return (dispatch, getState) => {
+ const { bytecodes } = getState().blockchain;
+
+ if (bytecodes[address]) {
+ return;
+ }
+
+ const { api } = getState();
+
+ api.eth
+ .getCode(address)
+ .then(code => {
+ dispatch(setBytecode(address, code));
+ })
+ .catch(e => {
+ console.error('blockchain::fetchBytecode', e);
+ });
+ };
+}
+
+export function fetchMethod (signature) {
+ return (dispatch, getState) => {
+ const { methods } = getState().blockchain;
+
+ if (methods[signature]) {
+ return;
+ }
+
+ Contracts
+ .get()
+ .signatureReg.lookup(signature)
+ .then(method => {
+ dispatch(setMethod(signature, method));
+ })
+ .catch(e => {
+ console.error('blockchain::fetchMethod', e);
+ });
+ };
+}
diff --git a/js/src/redux/providers/blockchainReducer.js b/js/src/redux/providers/blockchainReducer.js
new file mode 100644
index 00000000000..30c7507f10a
--- /dev/null
+++ b/js/src/redux/providers/blockchainReducer.js
@@ -0,0 +1,66 @@
+// 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 .
+
+import { handleActions } from 'redux-actions';
+
+const initialState = {
+ blocks: {},
+ transactions: {},
+ bytecodes: {},
+ methods: {}
+};
+
+export default handleActions({
+ setBlock (state, action) {
+ const { blockNumber, block } = action;
+
+ const blocks = Object.assign({}, state.blocks, {
+ [blockNumber.toString()]: block
+ });
+
+ return Object.assign({}, state, { blocks });
+ },
+
+ setTransaction (state, action) {
+ const { txHash, info } = action;
+
+ const transactions = Object.assign({}, state.transactions, {
+ [txHash]: info
+ });
+
+ return Object.assign({}, state, { transactions });
+ },
+
+ setBytecode (state, action) {
+ const { address, bytecode } = action;
+
+ const bytecodes = Object.assign({}, state.bytecodes, {
+ [address]: bytecode
+ });
+
+ return Object.assign({}, state, { bytecodes });
+ },
+
+ setMethod (state, action) {
+ const { signature, method } = action;
+
+ const methods = Object.assign({}, state.methods, {
+ [signature]: method
+ });
+
+ return Object.assign({}, state, { methods });
+ }
+}, initialState);
diff --git a/js/src/redux/providers/index.js b/js/src/redux/providers/index.js
index 7e5f19f2b22..a83190a10e7 100644
--- a/js/src/redux/providers/index.js
+++ b/js/src/redux/providers/index.js
@@ -19,8 +19,10 @@ export Personal from './personal';
export Signer from './signer';
export Status from './status';
+export apiReducer from './apiReducer';
export balancesReducer from './balancesReducer';
export imagesReducer from './imagesReducer';
export personalReducer from './personalReducer';
export signerReducer from './signerReducer';
export statusReducer from './statusReducer';
+export blockchainReducer from './blockchainReducer';
diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js
index 917a8577e8a..2e039134bcd 100644
--- a/js/src/redux/providers/status.js
+++ b/js/src/redux/providers/status.js
@@ -54,6 +54,16 @@ export default class Status {
.catch(() => dispatch(false));
}
+ _pollTraceMode = () => {
+ return this._api.trace.block()
+ .then(blockTraces => {
+ // Assumes not in Trace Mode if no transactions
+ // in latest block...
+ return blockTraces.length > 0;
+ })
+ .catch(() => false);
+ }
+
_pollStatus = () => {
const { secureToken, isConnected, isConnecting, needsToken } = this._api;
const nextTimeout = (timeout = 1000) => {
@@ -80,9 +90,10 @@ export default class Status {
this._api.ethcore.netPort(),
this._api.ethcore.nodeName(),
this._api.ethcore.rpcSettings(),
- this._api.eth.syncing()
+ this._api.eth.syncing(),
+ this._pollTraceMode()
])
- .then(([clientVersion, coinbase, defaultExtraData, extraData, gasFloorTarget, hashrate, minGasPrice, netChain, netPeers, netPort, nodeName, rpcSettings, syncing]) => {
+ .then(([clientVersion, coinbase, defaultExtraData, extraData, gasFloorTarget, hashrate, minGasPrice, netChain, netPeers, netPort, nodeName, rpcSettings, syncing, traceMode]) => {
const isTest = netChain === 'morden' || netChain === 'testnet';
this._store.dispatch(statusCollection({
@@ -99,7 +110,8 @@ export default class Status {
nodeName,
rpcSettings,
syncing,
- isTest
+ isTest,
+ traceMode
}));
nextTimeout();
})
diff --git a/js/src/redux/providers/statusReducer.js b/js/src/redux/providers/statusReducer.js
index 18a60284bb6..d75f6242952 100644
--- a/js/src/redux/providers/statusReducer.js
+++ b/js/src/redux/providers/statusReducer.js
@@ -41,7 +41,8 @@ const initialState = {
syncing: false,
isApiConnected: true,
isPingConnected: true,
- isTest: true
+ isTest: false,
+ traceMode: undefined
};
export default handleActions({
diff --git a/js/src/redux/reducers.js b/js/src/redux/reducers.js
index 4161807ba4a..ee73b5cdf9e 100644
--- a/js/src/redux/reducers.js
+++ b/js/src/redux/reducers.js
@@ -17,7 +17,7 @@
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
-import { balancesReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer } from './providers';
+import { apiReducer, balancesReducer, blockchainReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer } from './providers';
import { errorReducer } from '../ui/Errors';
import { settingsReducer } from '../views/Settings';
@@ -25,12 +25,14 @@ import { tooltipReducer } from '../ui/Tooltips';
export default function () {
return combineReducers({
+ api: apiReducer,
errors: errorReducer,
tooltip: tooltipReducer,
routing: routerReducer,
settings: settingsReducer,
balances: balancesReducer,
+ blockchain: blockchainReducer,
images: imagesReducer,
nodeStatus: nodeStatusReducer,
personal: personalReducer,
diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js
index 741716428ba..21e7730e8c9 100644
--- a/js/src/ui/MethodDecoding/methodDecoding.js
+++ b/js/src/ui/MethodDecoding/methodDecoding.js
@@ -19,11 +19,12 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import Contracts from '../../contracts';
import IdentityIcon from '../IdentityIcon';
import IdentityName from '../IdentityName';
import { Input, InputAddress } from '../Form';
+import { fetchBytecode, fetchMethod } from '../../redux/providers/blockchainActions';
+
import styles from './methodDecoding.css';
const CONTRACT_CREATE = '0x60606040';
@@ -31,7 +32,7 @@ const TOKEN_METHODS = {
'0xa9059cbb': 'transfer(to,value)'
};
-class Method extends Component {
+class MethodDecoding extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
@@ -40,10 +41,16 @@ class Method extends Component {
address: PropTypes.string.isRequired,
tokens: PropTypes.object,
transaction: PropTypes.object,
- historic: PropTypes.bool
+ historic: PropTypes.bool,
+
+ fetchBytecode: PropTypes.func,
+ fetchMethod: PropTypes.func,
+ bytecodes: PropTypes.object,
+ methods: PropTypes.object
}
state = {
+ contractAddress: null,
method: null,
methodName: null,
methodInputs: null,
@@ -55,20 +62,59 @@ class Method extends Component {
isReceived: false
}
- componentDidMount () {
+ componentWillMount () {
const { transaction } = this.props;
-
this.lookup(transaction);
}
+ componentDidMount () {
+ this.setMethod(this.props);
+ }
+
componentWillReceiveProps (newProps) {
const { transaction } = this.props;
+ this.setMethod(newProps);
- if (newProps.transaction === transaction) {
+ if (newProps.transaction.hash !== transaction.hash) {
+ this.lookup(transaction);
return;
}
+ }
- this.lookup(transaction);
+ setMethod (props) {
+ const { bytecodes, methods } = props;
+ const { contractAddress, methodSignature, methodParams } = this.state;
+
+ if (contractAddress && bytecodes[contractAddress]) {
+ const bytecode = bytecodes[contractAddress];
+
+ if (bytecode && bytecode !== '0x') {
+ this.setState({ isContract: true });
+ }
+ }
+
+ if (methodSignature && methods[methodSignature]) {
+ const method = methods[methodSignature];
+ const { api } = this.context;
+
+ let methodInputs = null;
+ let methodName = null;
+
+ if (method && method.length) {
+ const abi = api.util.methodToAbi(method);
+
+ methodName = abi.name;
+ methodInputs = api.util
+ .decodeMethodInput(abi, methodParams)
+ .map((value, index) => {
+ const type = abi.inputs[index].type;
+
+ return { type, value };
+ });
+ }
+
+ this.setState({ method, methodName, methodInputs });
+ }
}
render () {
@@ -150,7 +196,7 @@ class Method extends Component {
return (
- Deployed a contract at address { this.renderAddressName(transaction.creates, false) }.
+ Deployed a contract at address { this.renderAddressName(transaction.creates, false) }
);
}
@@ -275,18 +321,20 @@ class Method extends Component {
}
lookup (transaction) {
- const { api } = this.context;
- const { address, tokens } = this.props;
-
if (!transaction) {
return;
}
+ const { api } = this.context;
+ const { address, tokens } = this.props;
+
const isReceived = transaction.to === address;
- const token = (tokens || {})[isReceived ? transaction.from : transaction.to];
- this.setState({ token, isReceived });
+ const contractAddress = isReceived ? transaction.from : transaction.to;
+
+ const token = (tokens || {})[contractAddress];
+ this.setState({ token, isReceived, contractAddress });
- if (!transaction.input) {
+ if (!transaction.input || transaction.input === '0x') {
return;
}
@@ -298,52 +346,29 @@ class Method extends Component {
return;
}
- api.eth
- .getCode(isReceived ? transaction.from : transaction.to)
- .then((code) => {
- if (code && code !== '0x') {
- this.setState({ isContract: true });
- }
-
- return Contracts.get().signatureReg.lookup(signature);
- }).then((method) => {
- let methodInputs = null;
- let methodName = null;
-
- if (method && method.length) {
- const abi = api.util.methodToAbi(method);
-
- methodName = abi.name;
- methodInputs = api.util
- .decodeMethodInput(abi, paramdata)
- .map((value, index) => {
- const type = abi.inputs[index].type;
-
- return { type, value };
- });
- }
-
- this.setState({ method, methodName, methodInputs });
- })
- .catch((error) => {
- console.error('lookup', error);
- });
+ const { fetchBytecode, fetchMethod } = this.props;
+
+ fetchBytecode(contractAddress);
+ fetchMethod(signature);
}
}
function mapStateToProps (state) {
const { tokens } = state.balances;
+ const { bytecodes, methods } = state.blockchain;
return {
- tokens
+ tokens, bytecodes, methods
};
}
function mapDispatchToProps (dispatch) {
- return bindActionCreators({}, dispatch);
+ return bindActionCreators({
+ fetchBytecode, fetchMethod
+ }, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
-)(Method);
+)(MethodDecoding);
diff --git a/js/src/util/validation.js b/js/src/util/validation.js
index d6775d237e9..1ec7489f84b 100644
--- a/js/src/util/validation.js
+++ b/js/src/util/validation.js
@@ -38,7 +38,6 @@ export function validateAbi (abi, api) {
abi = JSON.stringify(abiParsed);
}
} catch (error) {
- console.error(error);
abiError = ERRORS.invalidAbi;
}
diff --git a/js/src/views/Account/Transactions/Transaction/transaction.js b/js/src/views/Account/Transactions/Transaction/transaction.js
index 4d8060ecaff..7cf2bce3147 100644
--- a/js/src/views/Account/Transactions/Transaction/transaction.js
+++ b/js/src/views/Account/Transactions/Transaction/transaction.js
@@ -17,12 +17,16 @@
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import moment from 'moment';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import { fetchBlock, fetchTransaction } from '../../../../redux/providers/blockchainActions';
import { IdentityIcon, IdentityName, MethodDecoding } from '../../../../ui';
import styles from '../transactions.css';
-export default class Transaction extends Component {
+class Transaction extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
@@ -30,11 +34,16 @@ export default class Transaction extends Component {
static propTypes = {
transaction: PropTypes.object.isRequired,
address: PropTypes.string.isRequired,
- isTest: PropTypes.bool.isRequired
+ isTest: PropTypes.bool.isRequired,
+
+ fetchBlock: PropTypes.func.isRequired,
+ fetchTransaction: PropTypes.func.isRequired,
+
+ block: PropTypes.object,
+ transactionInfo: PropTypes.object
}
state = {
- info: null,
isContract: false,
isReceived: false
}
@@ -46,8 +55,7 @@ export default class Transaction extends Component {
}
render () {
- const { transaction, isTest } = this.props;
- const { block } = this.state;
+ const { block, transaction, isTest } = this.props;
const prefix = `https://${isTest ? 'testnet.' : ''}etherscan.io/`;
@@ -68,10 +76,9 @@ export default class Transaction extends Component {
}
renderMethod () {
- const { address } = this.props;
- const { info } = this.state;
+ const { address, transactionInfo } = this.props;
- if (!info) {
+ if (!transactionInfo) {
return null;
}
@@ -79,7 +86,7 @@ export default class Transaction extends Component {
+ transaction={ transactionInfo } />
);
}
@@ -129,13 +136,13 @@ export default class Transaction extends Component {
renderEtherValue () {
const { api } = this.context;
- const { info } = this.state;
+ const { transactionInfo } = this.props;
- if (!info) {
+ if (!transactionInfo) {
return null;
}
- const value = api.util.fromWei(info.value);
+ const value = api.util.fromWei(transactionInfo.value);
if (value.eq(0)) {
return { ' ' }
;
@@ -169,25 +176,33 @@ export default class Transaction extends Component {
}
lookup (address, transaction) {
- const { api } = this.context;
- const { info } = this.state;
+ const { transactionInfo } = this.props;
- if (info) {
+ if (transactionInfo) {
return;
}
this.setState({ isReceived: address === transaction.to });
- Promise
- .all([
- api.eth.getBlockByNumber(transaction.blockNumber),
- api.eth.getTransactionByHash(transaction.hash)
- ])
- .then(([block, info]) => {
- this.setState({ block, info });
- })
- .catch((error) => {
- console.error('lookup', error);
- });
+ const { fetchBlock, fetchTransaction } = this.props;
+ const { blockNumber, hash } = transaction;
+
+ fetchBlock(blockNumber);
+ fetchTransaction(hash);
}
}
+
+function mapStateToProps () {
+ return {};
+}
+
+function mapDispatchToProps (dispatch) {
+ return bindActionCreators({
+ fetchBlock, fetchTransaction
+ }, dispatch);
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Transaction);
diff --git a/js/src/views/Account/Transactions/transactions.js b/js/src/views/Account/Transactions/transactions.js
index 1c918766044..77bdf60a3f0 100644
--- a/js/src/views/Account/Transactions/transactions.js
+++ b/js/src/views/Account/Transactions/transactions.js
@@ -37,7 +37,10 @@ class Transactions extends Component {
contacts: PropTypes.object,
contracts: PropTypes.object,
tokens: PropTypes.object,
- isTest: PropTypes.bool
+ isTest: PropTypes.bool,
+ traceMode: PropTypes.bool,
+ blocks: PropTypes.object,
+ transactionsInfo: PropTypes.object
}
state = {
@@ -47,7 +50,24 @@ class Transactions extends Component {
}
componentDidMount () {
- this.getTransactions();
+ if (this.props.traceMode !== undefined) {
+ this.getTransactions(this.props);
+ }
+ }
+
+ componentWillReceiveProps (newProps) {
+ if (this.props.traceMode === undefined && newProps.traceMode !== undefined) {
+ this.getTransactions(newProps);
+ return;
+ }
+
+ const hasChanged = [ 'isTest', 'address' ]
+ .map(key => newProps[key] !== this.props[key])
+ .reduce((truth, keyTruth) => truth || keyTruth, false);
+
+ if (hasChanged) {
+ this.getTransactions(newProps);
+ }
}
render () {
@@ -81,60 +101,127 @@ class Transactions extends Component {
{ this.renderRows() }
-
+ { this.renderEtherscanFooter() }
+
+ );
+ }
+
+ renderEtherscanFooter () {
+ const { traceMode } = this.props;
+
+ if (traceMode) {
+ return null;
+ }
+
+ return (
+
);
}
renderRows () {
- const { address, accounts, contacts, contracts, tokens, isTest } = this.props;
+ const { address, accounts, contacts, contracts, tokens, isTest, blocks, transactionsInfo } = this.props;
const { transactions } = this.state;
- return (transactions || []).map((transaction, index) => {
- return (
-
- );
- });
+ return (transactions || [])
+ .sort((tA, tB) => {
+ return tB.blockNumber.comparedTo(tA.blockNumber);
+ })
+ .slice(0, 25)
+ .map((transaction, index) => {
+ const { blockNumber, hash } = transaction;
+
+ const block = blocks[blockNumber.toString()];
+ const transactionInfo = transactionsInfo[hash];
+
+ return (
+
+ );
+ });
}
- getTransactions = () => {
- const { isTest, address } = this.props;
+ getTransactions = (props) => {
+ const { isTest, address, traceMode } = props;
- return etherscan.account
- .transactions(address, 0, isTest)
- .then((transactions) => {
+ return this
+ .fetchTransactions(isTest, address, traceMode)
+ .then(transactions => {
this.setState({
transactions,
loading: false
});
- })
+ });
+ }
+
+ fetchTransactions = (isTest, address, traceMode) => {
+ if (traceMode) {
+ return this.fetchTraceTransactions(address);
+ }
+
+ return this.fetchEtherscanTransactions(isTest, address);
+ }
+
+ fetchEtherscanTransactions = (isTest, address) => {
+ return etherscan.account
+ .transactions(address, 0, isTest)
.catch((error) => {
console.error('getTransactions', error);
});
}
+
+ fetchTraceTransactions = (address) => {
+ return Promise
+ .all([
+ this.context.api.trace
+ .filter({
+ fromBlock: 0,
+ fromAddress: address
+ }),
+ this.context.api.trace
+ .filter({
+ fromBlock: 0,
+ toAddress: address
+ })
+ ])
+ .then(([fromTransactions, toTransactions]) => {
+ const transactions = [].concat(fromTransactions, toTransactions);
+
+ return transactions.map(transaction => ({
+ from: transaction.action.from,
+ to: transaction.action.to,
+ blockNumber: transaction.blockNumber,
+ hash: transaction.transactionHash
+ }));
+ });
+ }
}
function mapStateToProps (state) {
- const { isTest } = state.nodeStatus;
+ const { isTest, traceMode } = state.nodeStatus;
const { accounts, contacts, contracts } = state.personal;
const { tokens } = state.balances;
+ const { blocks, transactions } = state.blockchain;
return {
isTest,
+ traceMode,
accounts,
contacts,
contracts,
- tokens
+ tokens,
+ blocks,
+ transactionsInfo: transactions
};
}