TuT: Simple Ethereum Blockchain Explorer [Part 2]

The second part of a tutorial to build a simple ethereum blockchain explorer. [ Previous ] In this part we will build a component to display the most recent transactions, build a small dropdown menu and a reactive input search to filter blocks.

Phase 4: Transactions

Our Transactions component will be build similar to the previous Blocks . It will extend the ReactiveComponent from oo7-react and have the updating bonds as constructor argument super(['bonds']); . Additionally we will need to pull transactions (TXNs) because the block component only gives us an array with TXN-Hashes. The oo7-parity library gives us again a function to pull a transaction either by hash or by blockNumber/blockHash inc. index in the array. bonds.transactions(x) .

So in the constructor we create a new array this.transactions = []; which will be initialized when this.state.bonds is loaded (bond returned promise resolved).

We could call the init function in Reacts componentWillReceiveProps() method. However we would need to do the check if this.state.bonds is undefined. And as in our Blocks component before we also need this check in render() already so we can move the init() call right there.

This makes the setup from our component very similar to the Blocks . We use the Transaction component of parity-reactive-ui with the default properties enabled: TXN-Hash, From, To, Ether.

Note: If the Transaction component is not included in parity-reactive-ui you can copy it locally from here.

We also initialize a TimeBond again to use later to display how long the transaction is already confirmed. Still missing is the init(5); method which takes a length parameter of how many transactions should be pulled.

Since we already have the some blocks we can iterate over their transactions array and pull transactions until the asked amount is reached.

init(length) {
this.state.bonds.map(b => {
for (let i=0;i<b.transactions.length;i++) {
if (this.transactions.length<length)
this.transactions
.push(bonds.transaction(b.transactions[i]));
}
})
}

However this could still be quite inconsistent because sometimes all blocks our app loaded might not include enough transactions as we want to display. Thus we could additionally pull blocks and repeat the transaction pulling if the amount is not reached. We will skip this here because it would get quite complex.

For dealing with transactions I would recommend looking at paritys feature trace-API, which allows detailed tracking and tracing of transactions but is not enabled by default due to high power usage.

Finally we need to reset the transactions array each time init is called. We can do this just at the beginning of init : this.transactions = []; .

Let’s give the Transaction a summary again by showing the TX-Hash and how long it is already confirmed. Again we will use Bond.all() to join the bonds and then compute the time when both are ready. For the TX-Hash to display we can use the Hash component from oo7-react which shorts the Hash to make it more readable.

const computeTimeDiff = ([t1,t2]) => 
Math.floor((t1 - t2.getTime()) / 1000);
...
<a>TX# <Hash value={txn.hash}></Hash></a>
<Feed.Date>
<Rspan>
{Bond.all([this.time,bonds.blocks[txn.blockHash].timestamp])
.map(computeTimeDiff)}s ago
</Rspan>
</Feed.Date>

Our content part will now look something like this with constantly updating transactions and blocks:

Phase 5: NavBar extension

So far our Navbar is very basic. Lets add some functionality to it to be able to go further. Our dropdown menu will for now only be a semantic-ui-react component. E.g.

<Menu>
<Menu.Item>
Home
</Menu.Item>
<Dropdown text='Blockchain' pointing className='link item'>
<Dropdown.Menu>
<Dropdown.Header>Blockchain</Dropdown.Header>
<Dropdown.Item>View Txns</Dropdown.Item>
<Dropdown.Item>View Pending Txns</Dropdown.Item>
<Dropdown.Item>View Contract Internal Txns</Dropdown.Item>
<Dropdown.Item>Cancellations</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item>
View Blocks
<Dropdown.Menu>
<Dropdown.Item>
FORKED Blocks (Reorgs)
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown.Item>
<Dropdown.Item>
View Uncles
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>

Feel free to customize your own here. All information for customization can be found here.

Our search will be reactive and use a bond to directly filter our list of blocks when searching for an address. We can use InputBond from parity-reactive-ui for it. We create an additional searchBond in our app and pass it to our Navbar component.

this.searchBond = new Bond();
...
<Navbar bond={this.searchBond}></Navbar>

The Navbar will now use a simple input tied to the given bond:

const Search = () => (
<InputBond
bond={this.props.bond}
placeholder="Search by Address"
fluid
/>
);

We can now use the value directly in our app component. However it might be better to filter the blocks when the promise is already resolved. so we pass the same bond as filter down to our Blocks component.

<Blocks bonds={this.bonds} filter={this.searchBond}></Blocks>

Now we modify our map function to only iterate over the blocks which pass the filter in our render function.

const filterBlock = b => 
b.hash.startsWith(this.state.filter) ||
b.hash.startsWith(this.state.filter,2);
...
this.state.bonds.filter(filterBlock).map((block,i) => {

If we now start typing the address of a block we can filter our list. We see that the seconds in transactions processed is messed up. So lets add the filter to our Meta data too:

<a>{block.transactions.length} txns</a> in 
<Rspan>
{i === (this.state.bonds.filter(filterBlock).length-1)
? Bond.all([block.timestamp,getParent(block).timestamp])
.map(computeTime)
: Bond.all([block.timestamp,
this.state.bonds.filter(filterBlock[i+1].timestamp])
.map(computeTime)} sec
</Rspan>

The code gets very messy so lets refractor and declare the filtered blocks at the beginning of our render method else-branch when bonds is defined:

const blocks = this.state.bonds.filter(filterBlocks);

Now it is pretty annoying having the blocks update while we investigate one. Lets disable the updating when our search is not empty. We subscribed our page to always update and have the newest information so from a design point of view it would’nt be good if we somehow disable our bonds to keep up to date. However we can introduce a simple this.update = true; boolean variable in our constructor which determines if we display a static snapshot of our blocks or the recent ones.

Now we tie a function to the search bond in our app. This function will be called each time the search changes. Lets call it setUpdate(bool) .

this.searchBond.tie(search => this.update 
? this.setUpdate(search === '')
: '');

In setUpdate we will set this.update to true if the search is empty. Also we will make a snapshot of our current blocks. Since we only have the bonds we will use Bond.all() to combine all promises and then when triggered set our staticBlocks inc. update.

setUpdate(bool) {
if (!bool) {
Bond.all(this.bonds).then(blocks => {
this.update = bool;
this.staticBlocks = blocks;
})
} else {
this.update = bool;
}
}

Now we only need to change our render function to use this.staticBlocks if update is not true:

render() {
return (<div className={'ui stackable'}>
<Grid>
<Grid.Row>
<Grid.Column>
<Navbar bond={this.searchBond} checked={this.checked}></Navbar>
</Grid.Column>
</Grid.Row>
{this.update ?
<Grid.Row centered columns={2}>
<Grid.Column mobile={16} tablet={16} computer={7}>
<Blocks bonds={this.bonds} filter={this.searchBond} />
</Grid.Column>
<Grid.Column mobile={16} tablet={16} computer={7}>
<Transactions bonds={this.bonds}></Transactions>
</Grid.Column>
</Grid.Row> :
<Grid.Row centered columns={2}>
<Grid.Column mobile={16} tablet={16} computer={7}>
<Blocks bonds={this.staticBlocks}
filter={this.searchBond}></Blocks>
</Grid.Column>
<Grid.Column mobile={16} tablet={16} computer={7}>
<Transactions bonds={this.staticBlocks}></Transactions>
</Grid.Column>
</Grid.Row> }
</Grid>
</div>);
}

And there we go we can search one of the blocks from the list without being permanently updated.

We could add several more features here but I think you got a first idea of how the oo7-Bonds API works and how it can help you to create perfectly updating dApps in the fly! It is definitely worth trying then to use only the low level API’s such as parity-js or web3.js.