Manage Complex Smart Contract Interactions with Server-side Listeners
Most decentralized applications (DApps) consist of two parts: the smart contract, running on the Ethereum network, and the UI that a person uses to interact with the contract. When building Tokenpad, we needed to manage a lot of contract logic. It was not efficient to put all of our contract interactions on the client-side, so we built a server-side Ethereum listener to help simplify our client-side logic. In this post, I’ll share our experiences building a server-side smart contract listener.
Smart Contract
Tokenpad is a platform that solves the pain points experienced by blockchain companies and their investors. Our mission with Tokenpad is to democratize access to tokenized companies, to make investing in an initial coin offering as easy as buying a stock. We deployed two types of smart contracts for Tokenpad:
- Pool Factory Contract for creating new a token sale pool. We only need deploy one factory contract, and all the lead investors can use this contract to create their own pool. This contract emits an event when a pool is created. We should listen for the creation event and store the created investment pool address on the server side.
- Pool Contract for contributing ether to a pool and sending tokens to and from the pool. We deploy a new pool contract for each pool. This contract can emit several different events, e.g. “user invested” and “ether withdrawn”, that we need to listen for and handle.
First Version: Subscribe to each contract and event
In the first version we built, we just subscribed to each contract and each event. If we have 100 pools and each pool has 10 events, we need 1000 subscriptions to listen to. It’s not a good idea to keep so many subscriptions in memory, but it’s a good starter to get familiar with Web3j library.
Build smart contract wrappers
The Web3j Command Line Tools can help generate the wrapper class:
$ web3j solidity generate /path/to/<smart-contract>.bin /path/to/<smart-contract>.abi -o /path/to/src/main/java -p com.your.organisation.name
If you are using Truffle’s Contract Schema, the following will also work.
$ web3j truffle generate /path/to/<truffle-smart-contract-output>.json -o /path/to/src/main/java -p com.your.organisation.name
Tip: If you get any error when creating the wrapper, you can remove the functions from .abi file, since we only interested in the events. The tool version should be same with the Web3j version in your project.
I will use the OwnershipTransferred event as an example:
After creating the wrappers, you can then subscribe to the desired contracts through the subscribe.java
example above.
Second Version: Subscribe to all events
By reading the wrapper class, we can find most of the code is rigid, and the useful things are event
and response
definitions. Let's have a concept EventDef
to describe the event; EventDef
is used for getting the event Object, and building a event Response. We then move the real handle event response code to the specific class named XxEventHandler
, each event handler is only able to handle one kind of event, so each handler has a EventDef
object. The Web3j invoke code is written to the Main
class, and we register all the handlers to this class. We can then choose the appropriate handler to handle the incoming event. The diagram below shows this design.
Event Hub Contract
We can then simplify further by abstracting events to a central hub contract. This hub contract will be used to emit all events rather than relying on internal event emitters from each individual contract. After this change, we only need to subscribe to the hub contract.
Final Version: Listen to the network and filter for useful events
The second version seems good enough for listening for smart contract events, but it still has some problems:
- Can’t track failed transactions, since only successful transactions will emit an event
- Other contracts have strong dependencies on the hub contract
- Difficult to upgrade
Therefore we decided to listen to the entire network and filter for our own events. The solution is based on the second version we made. Here, we just need to add a simple filter, to compare the to_address of each transaction with our contract’s address. The Ethereum network can process a maximum of 15 transactions per second (TPS), this solution won’t take too many resources.
We should set the number of confirmations, so we can’t use the web3j.catchUpToLatestAndSubscribeToNewTransactionsObservable
directly.
Instead, I structured my code so that we can efficiently listen for all events including failed transactions so that we can show the appropriate UI state to our users.
57Blocks is a blockchain innovation lab helping companies turn blockchain ideas into reality. If your company is working with smart contracts and want feedback or help, give us a shout at hello@57blocks.io