Skip to main content

Handlers (onBlock, onTx)

Registering a handler will be the first thing you do when writing a Bloomr script.

Handlers are called each time something happens on the blockchain, in realtime.

onBlock()

Registers a handler which will be executed each time your script's filter matches a transaction.

It will be given a Block object (see runtime types), containing info about the block and matched transactions.

There is only one important method in onBlock: getTransactions(), which iterates over all top-level transactions matched by your script's filter.

onBlock(block => {
console.log('matched block', block.number);
for (const tx of block.getTransactions()) {
console.log('matched tx', tx.hash);
}
})

onTx()

Registers a handler which will be executed for each transaction that matches your script's filter.

onTx(tx => {
console.log('matched tx', tx.hash);
})
warning

NEVER store a transaction or a block that has been given by a handler in a global variable: Accessing their methods or their children's methods past the end of the handler's execution will throw errors.

For instance:

let previous: TopLevelTx | undefined;
onTx(tx => {
// this will throw an error on the second execution !
previous?.logs()

previous = tx;
})

Both transactions and blocks are memory-heavy objects. This safety has been intentionaly put in place to avoid unintentional high memory usages (which might cause a bad surprise when billing day comes).

info

Your scripts are short lived.

Your handlers might process multiple messages, but they might also be killed then respawned without warning between two runs if Bloomr thinks that you script is leaking memory, or for other internal reasons (load balancing, ...).

👉 Avoid maintaining long-lived internal memory state in global variables if you want to be sure to find it on next execution. Use script databases instead.

Difference between onBlock() and onTx() ?

Suppose you have this handler:

function processTx(tx: TopLevelTx) {
// do something here
}

then, registering the handler:

onTx(processTx)

would be almost the same as registering:

onBlock(block => {
for (const tx of block.getTransactions()) {
processTx(tx);
}
})

But there is a nuance: Suppose that your filter matches multiple transactions on a given block. Then, if processTx() throws an error for a given transaction, then the onBlock() variant will prevent processing the following transactions, thus making you miss transactions.

A more equivalent version would be something like:

onBlock(block => {
let err: any;
for (const tx of block.getTransactions()) {
try {
processTx(tx);
} catch (e) {
err = e;
}
}
if (err) {
throw err;
}
})

which will still process all transactions, but will only display the last thrown error in your script errors.

👉 If you dont need to have a block-level control for your algorithm, you will likely be better off using onTx() for a better error reporting.