Here are a few boiled down tips & tricks you can use when working with Web3 events.

Unique Event Ids

When saving events to a database you will want your events to have unique ids to prevent adding the same event twice. You can do this by combining the transaction hash and the log index of the event you are processing.

const eventId = web3.sha3(transaction.transactionHash + myEvent.logIndex);

Event Timestamps

You may want to display your events in the UI and accurately order them in the sequence in which they we fired off. To do this you can use the timestamp of the block the transaction was mined in and the log index of an event.

const timestamp = block.timestamp + myEvent.logIndex;

Consistent Events

Right now it is hard to consistently get all events from your smart contract. Often you will find that current subscription based methods lead to dropped events and an out of sync database. This will improve as the space matures but for now the most consistent approach is polling each block for your events.

const poll = async (fn, time) => {

await fn();

setTimeout(() => poll(fn), time);

}; const watch = async () => {

poll(async () => {

// After we finish executing, call again in 10 seconds

}, 10000);

};

Use your database to store previous block numbers:

const watch = async () => {

let fromBlock = await db.getLastBlock();

let toBlock;

poll(async () => {

toBlock = await utils.getBlockNumber();

if (fromBlock !== toBlock) {

// Make sure infura has fully synced this block

await utils.awaitBlock(toBlock);

fromBlock = toBlock;

await db.setLastBlock(toBlock);

}

}, 10000);

};

And finally, process the events for your contract:

const watch = async () => {

let fromBlock = await db.getLastBlock();

let toBlock;

poll(async () => {

toBlock = await utils.getBlockNumber();

if (fromBlock !== toBlock) {

// Make sure infura has fully synced this block

await utils.awaitBlock(toBlock);

const filter = myContract.allEvents({ fromBlock, toBlock });

await new Promise((resolve, reject) => {

listener.get(async (err, result) => {

if (err) {

return reject(err);

}

if (result.length) {

for (let i = 0; i < result.length; i++) {

// handle logs sequentially

}

}

resolve();

});

});

fromBlock = toBlock;

await db.setLastBlock(toBlock);

}

}, 10000);

};

You can poll for events from multiple contracts by registering callbacks.

let callbacks = []; const registerCallback = cb => {

callbacks = [...callbacks, cb];

};

Registering a callback:

registerCallback(

async (fromBlock, toBlock) => {

const filter = myContract.allEvents({ fromBlock, toBlock });

await new Promise((resolve, reject) => {

filter.get(async (err, result) => {

if (err) {

reject(err);

} else {

if (result.length) {

for (let i = 0; i < result.length; i++) {

// handle logs sequentially

}

}

resolve();

}

});

});

}

);

Call the registered functions when processing a block:

// Make sure infura has fully synced this block

await utils.awaitBlock(toBlock);

for (let i = 0; i < callbacks.length; i++) {

await callbacks[i](fromBlock, toBlock);

}

Conclusion

Hopefully these brief tips & tricks find a safe home in your web3 toolbox and are improved upon in your next project.

Thanks for reading, please like and share this article if you found it useful!