Merge pull request #25 from status-im/features/backend_tab/show-block-transaction-account

Features/backend tab/show block transaction account
This commit is contained in:
Iuri Matias 2018-08-06 11:32:53 -04:00 committed by GitHub
commit 3ec6ac342e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 582 additions and 196 deletions

View File

@ -1,7 +1,61 @@
// Accounts
export const FETCH_ACCOUNTS = 'FETCH_ACCOUNTS';
export const RECEIVE_ACCOUNTS = 'RECEIVE_ACCOUNTS';
export const RECEIVE_ACCOUNTS_ERROR = 'RECEIVE_ACCOUNTS_ERROR';
export const REQUEST = 'REQUEST';
export const SUCCESS = 'SUCCESS';
export const FAILURE = 'FAILURE';
function createRequestTypes(base) {
return [REQUEST, SUCCESS, FAILURE].reduce((acc, type) => {
acc[type] = `${base}_${type}`;
return acc;
}, {});
}
function action(type, payload = {}) {
return {type, ...payload};
}
export const ACCOUNTS = createRequestTypes('ACCOUNTS');
export const accounts = {
request: () => action(ACCOUNTS[REQUEST]),
success: (accounts) => action(ACCOUNTS[SUCCESS], {accounts}),
failure: (error) => action(ACCOUNTS[FAILURE], {error})
};
export const ACCOUNT = createRequestTypes('ACCOUNT');
export const account = {
request: (address) => action(ACCOUNT[REQUEST], {address}),
success: (account) => action(ACCOUNT[SUCCESS], {account}),
failure: (error) => action(ACCOUNT[FAILURE], {error})
};
export const BLOCKS = createRequestTypes('BLOCKS');
export const blocks = {
request: (from) => action(BLOCKS[REQUEST], {from}),
success: (blocks) => action(BLOCKS[SUCCESS], {blocks}),
failure: (error) => action(BLOCKS[FAILURE], {error})
};
export const BLOCK = createRequestTypes('BLOCK');
export const block = {
request: (blockNumber) => action(BLOCK[REQUEST], {blockNumber}),
success: (block) => action(BLOCK[SUCCESS], {block}),
failure: (error) => action(BLOCK[FAILURE], {error})
};
export const TRANSACTIONS = createRequestTypes('TRANSACTIONS');
export const transactions = {
request: (blockFrom) => action(TRANSACTIONS[REQUEST], {blockFrom}),
success: (transactions) => action(TRANSACTIONS[SUCCESS], {transactions}),
failure: (error) => action(TRANSACTIONS[FAILURE], {error})
};
export const TRANSACTION = createRequestTypes('TRANSACTION');
export const transaction = {
request: (hash) => action(TRANSACTION[REQUEST], {hash}),
success: (transaction) => action(TRANSACTION[SUCCESS], {transaction}),
failure: (error) => action(TRANSACTION[FAILURE], {error})
};
// Processes
export const FETCH_PROCESSES = 'FETCH_PROCESSES';
export const RECEIVE_PROCESSES = 'RECEIVE_PROCESSES';
@ -10,36 +64,9 @@ export const RECEIVE_PROCESSES_ERROR = 'RECEIVE_PROCESSES_ERROR';
export const FETCH_PROCESS_LOGS = 'FETCH_PROCESS_LOGS';
export const RECEIVE_PROCESS_LOGS = 'RECEIVE_PROCESS_LOGS';
export const RECEIVE_PROCESS_LOGS_ERROR = 'RECEIVE_PROCESS_LOGS_ERROR';
// Blocks
export const FETCH_BLOCKS = 'FETCH_BLOCKS';
export const RECEIVE_BLOCKS = 'RECEIVE_BLOCKS';
export const RECEIVE_BLOCKS_ERROR = 'RECEIVE_BLOCKS_ERROR';
// Transactions
export const FETCH_TRANSACTIONS = 'FETCH_TRANSACTIONS';
export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS';
export const RECEIVE_TRANSACTIONS_ERROR = 'RECEIVE_TRANSACTIONS_ERROR';
// BlockHeader
export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER';
export function fetchAccounts() {
return {
type: FETCH_ACCOUNTS
};
}
export function receiveAccounts(accounts) {
return {
type: RECEIVE_ACCOUNTS,
accounts
};
}
export function receiveAccountsError() {
return {
type: RECEIVE_ACCOUNTS_ERROR
};
}
export function fetchProcesses() {
return {
type: FETCH_PROCESSES
@ -82,46 +109,6 @@ export function receiveProcessLogsError(error) {
};
}
export function fetchBlocks(from) {
return {
type: FETCH_BLOCKS,
from
};
}
export function receiveBlocks(blocks) {
return {
type: RECEIVE_BLOCKS,
blocks
};
}
export function receiveBlocksError() {
return {
type: RECEIVE_BLOCKS_ERROR
};
}
export function fetchTransactions(blockFrom) {
return {
type: FETCH_TRANSACTIONS,
blockFrom
};
}
export function receiveTransactions(transactions) {
return {
type: RECEIVE_TRANSACTIONS,
transactions
};
}
export function receiveTransactionsError() {
return {
type: RECEIVE_TRANSACTIONS_ERROR
};
}
export function initBlockHeader(){
return {
type: INIT_BLOCK_HEADER

View File

@ -1,16 +1,38 @@
import axios from "axios";
import constants from '../constants';
function get(path, params) {
return axios.get(constants.httpEndpoint + path, params)
.then((response) => {
return {response};
}).catch((error) => {
return {response: null, error: error.message || 'Something bad happened'};
});
}
export function fetchAccounts() {
return axios.get(`${constants.httpEndpoint}/blockchain/accounts`);
return get('/blockchain/accounts');
}
export function fetchBlocks(from) {
return axios.get(`${constants.httpEndpoint}/blockchain/blocks`, {params: {from}});
export function fetchAccount(payload) {
return get(`/blockchain/accounts/${payload.address}`);
}
export function fetchTransactions(blockFrom) {
return axios.get(`${constants.httpEndpoint}/blockchain/transactions`, {params: {blockFrom}});
export function fetchBlocks(payload) {
return get('/blockchain/blocks', {params: payload});
}
export function fetchBlock(payload) {
return get(`/blockchain/blocks/${payload.blockNumber}`);
}
export function fetchTransactions(payload) {
return get('/blockchain/transactions', {params: payload});
}
export function fetchTransaction(payload) {
return get(`/blockchain/transactions/${payload.hash}`);
}
export function fetchProcesses() {

View File

@ -0,0 +1,18 @@
import React from 'react';
import {
Page
} from "tabler-react";
import PropTypes from 'prop-types';
const Account = ({account}) => (
<Page.Content title={`Account ${account.address}`}>
<p>Balance: {account.balance}</p>
<p>Tx count: {account.transactionCount}</p>
</Page.Content>
);
Account.propTypes = {
account: PropTypes.object
};
export default Account;

View File

@ -5,9 +5,9 @@ import {
Card,
Table
} from "tabler-react";
import {Link} from 'react-router-dom';
import PropTypes from 'prop-types';
const Accounts = ({accounts}) => (
<Page.Content title="Accounts">
<Grid.Row>
@ -25,7 +25,7 @@ const Accounts = ({accounts}) => (
bodyItems={
accounts.map((account) => {
return ([
{content: account.address},
{content: <Link to={`/embark/explorer/accounts/${account.address}`}>{account.address}</Link>},
{content: account.balance},
{content: account.transactionCount},
{content: account.index}

View File

@ -0,0 +1,18 @@
import React from 'react';
import {
Page
} from "tabler-react";
import PropTypes from 'prop-types';
const Block = ({block}) => (
<Page.Content title={`Block ${block.number}`}>
<p>Timestamp: {block.timestamp}</p>
<p>Gas used: {block.gasUsed}</p>
</Page.Content>
);
Block.propTypes = {
block: PropTypes.object
};
export default Block;

View File

@ -1,4 +1,5 @@
import React from 'react';
import {Link} from "react-router-dom";
import {
Page,
Grid,
@ -7,7 +8,6 @@ import {
} from "tabler-react";
import PropTypes from 'prop-types';
const Blocks = ({blocks}) => (
<Page.Content title="Blocks">
<Grid.Row>
@ -20,7 +20,7 @@ const Blocks = ({blocks}) => (
bodyItems={
blocks.map((block) => {
return ([
{content: block.number},
{content: <Link to={`/embark/explorer/blocks/${block.number}`}>{block.number}</Link>},
{content: new Date(block.timestamp * 1000).toLocaleString()},
{content: block.gasUsed},
{content: block.transactions.length}

View File

@ -0,0 +1,19 @@
import PropTypes from "prop-types";
import React from 'react';
import {Grid} from 'tabler-react';
const Error = ({error}) => (
<Grid.Row className="align-items-center h-100 mt-5">
<Grid.Col>
<p className="text-center alert-danger">
{error}
</p>
</Grid.Col>
</Grid.Row>
);
Error.propTypes = {
error: PropTypes.string
};
export default Error;

View File

@ -7,8 +7,11 @@ import {
} from "tabler-react";
import AccountsContainer from '../containers/AccountsContainer';
import AccountContainer from '../containers/AccountContainer';
import BlocksContainer from '../containers/BlocksContainer';
import BlockContainer from '../containers/BlockContainer';
import TransactionsContainer from '../containers/TransactionsContainer';
import TransactionContainer from '../containers/TransactionContainer';
const ExplorerLayout = () => (
<Grid.Row>
@ -46,8 +49,11 @@ const ExplorerLayout = () => (
<Grid.Col md={9}>
<Switch>
<Route exact path="/embark/explorer/accounts" component={AccountsContainer} />
<Route exact path="/embark/explorer/accounts/:address" component={AccountContainer} />
<Route exact path="/embark/explorer/blocks" component={BlocksContainer} />
<Route exact path="/embark/explorer/blocks/:blockNumber" component={BlockContainer} />
<Route exact path="/embark/explorer/transactions" component={TransactionsContainer} />
<Route exact path="/embark/explorer/transactions/:hash" component={TransactionContainer} />
</Switch>
</Grid.Col>
</Grid.Row>

View File

@ -0,0 +1,24 @@
import React from 'react';
import {Link} from 'react-router-dom';
import {
Page
} from "tabler-react";
import PropTypes from 'prop-types';
const Transaction = ({transaction}) => (
<Page.Content title={`Transaction ${transaction.hash}`}>
<p>Block: <Link to={`/embark/explorer/blocks/${transaction.blockNumber}`}>{transaction.blockNumber}</Link></p>
<p>From: {transaction.from}</p>
<p>To: {transaction.to}</p>
<p>Input: {transaction.input}</p>
<p>Gas: {transaction.gas}</p>
<p>Gas Price: {transaction.gasPrice}</p>
<p>Nonce: {transaction.nonce}</p>
</Page.Content>
);
Transaction.propTypes = {
transaction: PropTypes.object
};
export default Transaction;

View File

@ -1,4 +1,5 @@
import React from 'react';
import {Link} from "react-router-dom";
import {
Page,
Grid,
@ -16,20 +17,20 @@ const Transactions = ({transactions}) => (
responsive
className="card-table table-vcenter text-nowrap"
headerItems={[
{content: "Hash"},
{content: "Block Number"},
{content: "From"},
{content: "To"},
{content: "Type"},
{content: "Hash"}
{content: "Type"}
]}
bodyItems={
transactions.map((transaction) => {
return ([
{content: <Link to={`/embark/explorer/transactions/${transaction.hash}`}>{transaction.hash}</Link>},
{content: transaction.blockNumber},
{content: transaction.from},
{content: transaction.to},
{content: transaction.to ? "Contract Call" : "Contract Creation"},
{content: transaction.hash}
{content: transaction.to ? "Contract Call" : "Contract Creation"}
]);
})
}

View File

@ -0,0 +1,58 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router-dom';
import {account as accountAction} from '../actions';
import Account from '../components/Account';
import NoMatch from "../components/NoMatch";
import Transactions from '../components/Transactions';
import Error from '../components/Error';
class AccountContainer extends Component {
componentDidMount() {
this.props.fetchAccount(this.props.match.params.address);
}
render() {
const {account, error} = this.props;
if (error) {
return <Error error={error} />;
}
if (!account) {
return <NoMatch />;
}
return (
<React.Fragment>
<Account account={account} />
<Transactions transactions={account.transactions || []} />
</React.Fragment>
);
}
}
function mapStateToProps(state, props) {
if(state.accounts.error) {
return {error: state.accounts.error};
}
if(state.accounts.data) {
return {account: state.accounts.data.find(account => account.address === props.match.params.address)};
}
return {};
}
AccountContainer.propTypes = {
match: PropTypes.object,
account: PropTypes.object,
fetchAccount: PropTypes.func,
error: PropTypes.string
};
export default withRouter(connect(
mapStateToProps,
{
fetchAccount: accountAction.request
}
)(AccountContainer));

View File

@ -2,9 +2,10 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {fetchAccounts} from '../actions';
import {accounts as accountsAction} from '../actions';
import Accounts from '../components/Accounts';
import Loading from '../components/Loading';
import Error from '../components/Error';
class AccountsContainer extends Component {
componentDidMount() {
@ -13,16 +14,12 @@ class AccountsContainer extends Component {
render() {
const {accounts} = this.props;
if (!accounts.data) {
return <Loading />;
if (accounts.error) {
return <Error error={accounts.error} />;
}
if (accounts.error) {
return (
<h1>
<i>Error API...</i>
</h1>
);
if (!accounts.data) {
return <Loading />;
}
return (
@ -43,6 +40,6 @@ AccountsContainer.propTypes = {
export default connect(
mapStateToProps,
{
fetchAccounts
fetchAccounts: accountsAction.request
},
)(AccountsContainer);

View File

@ -0,0 +1,57 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router-dom';
import {block as blockAction} from '../actions';
import Block from '../components/Block';
import Error from "../components/Error";
import NoMatch from "../components/NoMatch";
import Transactions from '../components/Transactions';
class BlockContainer extends Component {
componentDidMount() {
this.props.fetchBlock(this.props.match.params.blockNumber);
}
render() {
const {block, error} = this.props;
if (error) {
return <Error error={error} />;
}
if (!block) {
return <NoMatch />;
}
return (
<React.Fragment>
<Block block={block} />
<Transactions transactions={block.transactions} />
</React.Fragment>
);
}
}
function mapStateToProps(state, props) {
if(state.blocks.error) {
return {error: state.blocks.error};
}
if(state.blocks.data) {
return {block: state.blocks.data.find(block => block.number.toString() === props.match.params.blockNumber)};
}
return {};
}
BlockContainer.propTypes = {
match: PropTypes.object,
block: PropTypes.object,
fetchBlock: PropTypes.func,
error: PropTypes.string
};
export default withRouter(connect(
mapStateToProps,
{
fetchBlock: blockAction.request
}
)(BlockContainer));

View File

@ -2,10 +2,11 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {fetchBlocks} from '../actions';
import {blocks as blocksAction} from '../actions';
import Blocks from '../components/Blocks';
import Loading from '../components/Loading';
import LoadMore from '../components/LoadMore';
import Error from '../components/Error';
class BlocksContainer extends Component {
componentDidMount() {
@ -23,16 +24,12 @@ class BlocksContainer extends Component {
render() {
const {blocks} = this.props;
if (!blocks.data) {
return <Loading />;
if (blocks.error) {
return <Error error={blocks.error} />;
}
if (blocks.error) {
return (
<h1>
<i>Error API...</i>
</h1>
);
if (!blocks.data) {
return <Loading />;
}
return (
@ -56,6 +53,6 @@ BlocksContainer.propTypes = {
export default connect(
mapStateToProps,
{
fetchBlocks
fetchBlocks: blocksAction.request
},
)(BlocksContainer);

View File

@ -0,0 +1,55 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router-dom';
import {transaction as transactionAction} from '../actions';
import Error from "../components/Error";
import NoMatch from "../components/NoMatch";
import Transaction from '../components/Transaction';
class TransactionContainer extends Component {
componentDidMount() {
this.props.fetchTransaction(this.props.match.params.hash);
}
render() {
const {transaction, error} = this.props;
if (error) {
return <Error error={error} />;
}
if (!transaction) {
return <NoMatch />;
}
return (
<React.Fragment>
<Transaction transaction={transaction} />
</React.Fragment>
);
}
}
function mapStateToProps(state, props) {
if(state.transactions.error) {
return {error: state.transactions.error};
}
if(state.transactions.data) {
return {transaction: state.transactions.data.find(transaction => transaction.hash === props.match.params.hash)};
}
return {};
}
TransactionContainer.propTypes = {
match: PropTypes.object,
transaction: PropTypes.object,
fetchTransaction: PropTypes.func,
error: PropTypes.string
};
export default withRouter(connect(
mapStateToProps,
{
fetchTransaction: transactionAction.request
}
)(TransactionContainer));

View File

@ -2,10 +2,11 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {fetchTransactions} from '../actions';
import {transactions as transactionsAction} from '../actions';
import Transactions from '../components/Transactions';
import Loading from '../components/Loading';
import LoadMore from '../components/LoadMore';
import Error from '../components/Error';
class TransactionsContainer extends Component {
componentDidMount() {
@ -23,16 +24,12 @@ class TransactionsContainer extends Component {
render() {
const {transactions} = this.props;
if (!transactions.data) {
return <Loading />;
if (transactions.error) {
return <Error error={transactions.error} />;
}
if (transactions.error) {
return (
<h1>
<i>Error API...</i>
</h1>
);
if (!transactions.data) {
return <Loading />;
}
return (
@ -56,6 +53,6 @@ TransactionsContainer.propTypes = {
export default connect(
mapStateToProps,
{
fetchTransactions
fetchTransactions: transactionsAction.request
},
)(TransactionsContainer);

View File

@ -1,11 +1,25 @@
import {RECEIVE_ACCOUNTS, RECEIVE_ACCOUNTS_ERROR} from "../actions";
import * as actions from "../actions";
function filterAccount(account, index, self) {
return index === self.findIndex((a) => a.address === account.address);
}
export default function accounts(state = {}, action) {
switch (action.type) {
case RECEIVE_ACCOUNTS:
return Object.assign({}, state, {data: action.accounts.data});
case RECEIVE_ACCOUNTS_ERROR:
return Object.assign({}, state, {error: true});
case actions.ACCOUNTS[actions.SUCCESS]:
return {
...state, error: null, data: [...action.accounts.data, ...state.data || []]
.filter(filterAccount)
};
case actions.ACCOUNTS[actions.FAILURE]:
return Object.assign({}, state, {error: action.error});
case actions.ACCOUNT[actions.SUCCESS]:
return {
...state, error: null, data: [action.account.data, ...state.data || []]
.filter(filterAccount)
};
case actions.ACCOUNT[actions.FAILURE]:
return Object.assign({}, state, {error: action.error});
default:
return state;
}

View File

@ -1,15 +1,31 @@
import {RECEIVE_BLOCKS, RECEIVE_BLOCKS_ERROR} from "../actions";
import * as actions from "../actions";
function sortBlock(a, b) {
return b.number - a.number;
}
function filterBlock(block, index, self) {
return index === self.findIndex((t) => t.number === block.number);
}
export default function blocks(state = {}, action) {
switch (action.type) {
case RECEIVE_BLOCKS:
case actions.BLOCKS[actions.SUCCESS]:
return {
...state, data: [...state.data || [], ...action.blocks.data]
.filter((block, index, self) => index === self.findIndex((t) => t.number === block.number))
.sort((a, b) => b.number - a.number)
...state, error: null, data: [...action.blocks.data, ...state.data || []]
.filter(filterBlock)
.sort(sortBlock)
};
case RECEIVE_BLOCKS_ERROR:
return Object.assign({}, state, {error: true});
case actions.BLOCKS[actions.FAILURE]:
return Object.assign({}, state, {error: action.error});
case actions.BLOCK[actions.SUCCESS]:
return {
...state, error: null, data: [action.block.data, ...state.data || []]
.filter(filterBlock)
.sort(sortBlock)
};
case actions.BLOCK[actions.FAILURE]:
return Object.assign({}, state, {error: action.error});
default:
return state;
}

View File

@ -1,21 +1,35 @@
import {RECEIVE_TRANSACTIONS, RECEIVE_TRANSACTIONS_ERROR} from "../actions";
import * as actions from "../actions";
const BN_FACTOR = 10000;
function sortTransaction(a, b) {
return ((BN_FACTOR * b.blockNumber) + b.transactionIndex) - ((BN_FACTOR * a.blockNumber) + a.transactionIndex);
}
function filterTransaction(tx, index, self) {
return index === self.findIndex((t) => (
t.blockNumber === tx.blockNumber && t.transactionIndex === tx.transactionIndex
));
}
export default function transactions(state = {}, action) {
switch (action.type) {
case RECEIVE_TRANSACTIONS:
case actions.TRANSACTIONS[actions.SUCCESS]:
return {
...state, data: [...state.data || [], ...action.transactions.data]
.filter((tx, index, self) => index === self.findIndex((t) => (
t.blockNumber === tx.blockNumber && t.transactionIndex === tx.transactionIndex
)))
.sort((a, b) => (
((BN_FACTOR * b.blockNumber) + b.transactionIndex) - ((BN_FACTOR * a.blockNumber) + a.transactionIndex))
)
...state, error: null, data: [...action.transactions.data, ...state.data || []]
.filter(filterTransaction)
.sort(sortTransaction)
};
case RECEIVE_TRANSACTIONS_ERROR:
return Object.assign({}, state, {error: true});
case actions.TRANSACTIONS[actions.FAILURE]:
return Object.assign({}, state, {error: action.error});
case actions.TRANSACTION[actions.SUCCESS]:
return {
...state, error: null, data: [action.transaction.data, ...state.data || []]
.filter(filterTransaction)
.sort(sortTransaction)
};
case actions.TRANSACTION[actions.FAILURE]:
return Object.assign({}, state, {error: action.error});
default:
return state;
}

View File

@ -3,43 +3,46 @@ import * as api from '../api';
import {eventChannel} from 'redux-saga';
import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects';
export function *fetchTransactions(payload) {
try {
const transactions = yield call(api.fetchTransactions, payload.blockFrom);
yield put(actions.receiveTransactions(transactions));
} catch (e) {
yield put(actions.receiveTransactionsError());
const {account, accounts, block, blocks, transaction, transactions} = actions;
function *fetchEntity(entity, apiFn, id) {
const {response, error} = yield call(apiFn, id);
if(response) {
yield put(entity.success(response));
} else {
yield put(entity.failure(error));
}
}
export const fetchAccount = fetchEntity.bind(null, account, api.fetchAccount);
export const fetchBlock = fetchEntity.bind(null, block, api.fetchBlock);
export const fetchTransaction = fetchEntity.bind(null, transaction, api.fetchTransaction);
export const fetchAccounts = fetchEntity.bind(null, accounts, api.fetchAccounts);
export const fetchBlocks = fetchEntity.bind(null, blocks, api.fetchBlocks);
export const fetchTransactions = fetchEntity.bind(null, transactions, api.fetchTransactions);
export function *watchFetchTransaction() {
yield takeEvery(actions.TRANSACTION[actions.REQUEST], fetchTransaction);
}
export function *watchFetchTransactions() {
yield takeEvery(actions.FETCH_TRANSACTIONS, fetchTransactions);
yield takeEvery(actions.TRANSACTIONS[actions.REQUEST], fetchTransactions);
}
export function *fetchBlocks(payload) {
try {
const blocks = yield call(api.fetchBlocks, payload.from);
yield put(actions.receiveBlocks(blocks));
} catch (e) {
yield put(actions.receiveBlocksError());
}
export function *watchFetchBlock() {
yield takeEvery(actions.BLOCK[actions.REQUEST], fetchBlock);
}
export function *watchFetchBlocks() {
yield takeEvery(actions.FETCH_BLOCKS, fetchBlocks);
yield takeEvery(actions.BLOCKS[actions.REQUEST], fetchBlocks);
}
export function *fetchAccounts() {
try {
const accounts = yield call(api.fetchAccounts);
yield put(actions.receiveAccounts(accounts));
} catch (e) {
yield put(actions.receiveAccountsError());
}
export function *watchFetchAccount() {
yield takeEvery(actions.ACCOUNT[actions.REQUEST], fetchAccount);
}
export function *watchFetchAccounts() {
yield takeEvery(actions.FETCH_ACCOUNTS, fetchAccounts);
yield takeEvery(actions.ACCOUNTS[actions.REQUEST], fetchAccounts);
}
export function *fetchProcesses() {
@ -84,8 +87,8 @@ export function *initBlockHeader() {
const channel = yield call(createChannel, socket);
while (true) {
yield take(channel);
yield put({type: actions.FETCH_BLOCKS});
yield put({type: actions.FETCH_TRANSACTIONS});
yield put({type: actions.BLOCKS[actions.REQUEST]});
yield put({type: actions.TRANSACTIONS[actions.REQUEST]});
}
}
@ -97,9 +100,12 @@ export default function *root() {
yield all([
fork(watchInitBlockHeader),
fork(watchFetchAccounts),
fork(watchFetchAccount),
fork(watchFetchProcesses),
fork(watchFetchProcessLogs),
fork(watchFetchBlocks),
fork(watchFetchTransactions)
fork(watchFetchBlock),
fork(watchFetchTransactions),
fork(watchFetchTransaction)
]);
}

View File

@ -177,39 +177,15 @@ class BlockchainConnector {
'get',
'/embark-api/blockchain/accounts',
(req, res) => {
let results = [];
self.getAccounts((err, addresses) => {
async.eachOf(addresses, (address, index, eachCb) => {
let result = {address, index};
results.push(result);
async.waterfall([
function(callback) {
self.getTransactionCount(address, (err, count) => {
if (err) {
self.logger.error(err);
result.transactionCount = 0;
} else {
result.transactionCount = count;
self.getAccountsWithTransactionCount(res.send.bind(res));
}
callback();
});
},
function(callback) {
self.getBalance(address, (err, balance) => {
if (err) {
self.logger.error(err);
result.balance = 0;
} else {
result.balance = self.web3.utils.fromWei(balance);
}
callback();
});
}
], eachCb);
}, function () {
res.send(results);
});
});
);
plugin.registerAPICall(
'get',
'/embark-api/blockchain/accounts/:address',
(req, res) => {
self.getAccount(req.params.address, res.send.bind(res));
}
);
@ -223,6 +199,19 @@ class BlockchainConnector {
}
);
plugin.registerAPICall(
'get',
'/embark-api/blockchain/blocks/:blockNumber',
(req, res) => {
self.getBlock(req.params.blockNumber, (err, block) => {
if(err){
self.logger.error(err);
}
res.send(block);
});
}
);
plugin.registerAPICall(
'get',
'/embark-api/blockchain/transactions',
@ -233,6 +222,18 @@ class BlockchainConnector {
}
);
plugin.registerAPICall(
'get',
'/embark-api/blockchain/transactions/:hash',
(req, res) => {
self.getTransaction(req.params.hash, (err, transaction) => {
if(err){
self.logger.error(err);
}
res.send(transaction);
});
}
);
plugin.registerAPICall(
'ws',
@ -245,6 +246,81 @@ class BlockchainConnector {
);
}
getAccountsWithTransactionCount(callback) {
let self = this;
self.getAccounts((err, addresses) => {
let accounts = [];
async.eachOf(addresses, (address, index, eachCb) => {
let account = {address, index};
async.waterfall([
function(callback) {
self.getTransactionCount(address, (err, count) => {
if (err) {
self.logger.error(err);
account.transactionCount = 0;
} else {
account.transactionCount = count;
}
callback(null, account);
});
},
function(account, callback) {
self.getBalance(address, (err, balance) => {
if (err) {
self.logger.error(err);
account.balance = 0;
} else {
account.balance = self.web3.utils.fromWei(balance);
}
callback(null, account);
});
}
], function(_err, account){
accounts.push(account);
eachCb();
});
}, function () {
callback(accounts);
});
});
}
getAccount(address, callback) {
let self = this;
async.waterfall([
function(next) {
self.getAccountsWithTransactionCount((accounts) => {
let account = accounts.find((a) => a.address === address);
if(!account) {
return next("No account found with this address");
}
next(null, account);
});
},
function(account, next) {
self.getBlockNumber((err, blockNumber) => {
if (err) {
self.logger.error(err);
next(err);
} else {
next(null, blockNumber, account);
}
});
},
function(blockNumber, account, next) {
self.getTransactions(blockNumber, blockNumber, (transactions) => {
account.transactions = transactions.filter((transaction) => transaction.from === address);
next(null, account);
});
}
], function (err, result) {
if (err) {
callback();
}
callback(result);
});
}
getTransactions(blockFrom, blockLimit, callback) {
this.getBlocks(blockFrom, blockLimit, true, (blocks) => {
let transactions = blocks.reduce((acc, block) => acc.concat(block.transactions), []);
@ -328,7 +404,11 @@ class BlockchainConnector {
}
getBlock(blockNumber, cb) {
this.web3.eth.getBlock(blockNumber, cb);
this.web3.eth.getBlock(blockNumber, true, cb);
}
getTransaction(hash, cb) {
this.web3.eth.getTransaction(hash, cb);
}
getGasPrice(cb) {