mirror of
https://github.com/status-im/embark-area-51.git
synced 2025-02-12 23:26:39 +00:00
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:
commit
3ec6ac342e
@ -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
|
||||
|
@ -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() {
|
||||
|
18
embark-ui/src/components/Account.js
Normal file
18
embark-ui/src/components/Account.js
Normal 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;
|
@ -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}
|
||||
|
18
embark-ui/src/components/Block.js
Normal file
18
embark-ui/src/components/Block.js
Normal 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;
|
@ -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}
|
||||
|
19
embark-ui/src/components/Error.js
Normal file
19
embark-ui/src/components/Error.js
Normal 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;
|
@ -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>
|
||||
|
24
embark-ui/src/components/Transaction.js
Normal file
24
embark-ui/src/components/Transaction.js
Normal 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;
|
@ -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"}
|
||||
]);
|
||||
})
|
||||
}
|
||||
|
58
embark-ui/src/containers/AccountContainer.js
Normal file
58
embark-ui/src/containers/AccountContainer.js
Normal 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));
|
@ -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);
|
||||
|
57
embark-ui/src/containers/BlockContainer.js
Normal file
57
embark-ui/src/containers/BlockContainer.js
Normal 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));
|
@ -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);
|
||||
|
55
embark-ui/src/containers/TransactionContainer.js
Normal file
55
embark-ui/src/containers/TransactionContainer.js
Normal 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));
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
]);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
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);
|
||||
});
|
||||
});
|
||||
self.getAccountsWithTransactionCount(res.send.bind(res));
|
||||
}
|
||||
);
|
||||
|
||||
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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user