ABI & events encoding/decoding
This section is not easy to read.
It just describes meticulously what you can understand intuitively by:
- Browsing examples
- Using the Typescript autocompletion to guide you
👉 Feel free to skip it, and to come back later for details on a specific use case.
This section describes how to decode ABI-encoded data that you might retreive from the blockchain to javascript objects, or encode them to data.
Decoding (or encoding) ABI data is will be useful when you deal with event logs, or to deal with calldatas from payloads.
There are two ways to build an encoder or a decoder:
- Using the
abi
utility described in this section, which exposes decoders/encoders based on a solidity event or type definition. - When using Solidity scripts, functions or events defined in Solidity will be exposed as decoders which work the same way.
This section describes the abi
utility. It works the same way, but prefer using it over solidity if you dont actually need to make Solidity calls: It will be cheaper.
Define an ABI​
Let's say you know the signature of a Solidity event MyEvent
, and you want to decode it from transaction logs.
Bloomr exposes a tag abi
, that can be used like this:
const { MyData, MyEvent, namedReturnFn, anonReturnFn } = abi<MyAbi>`
struct MyData {
address field1;
uint256 field2;
uint32[] field3;
}
event MyEvent(address owner, MyData myEvtData);
function anonReturnFn(address arg1) returns (address, MyData);
function namedReturnFn(address arg1) returns (address addr, MyData myData);
`;
... the event in this example is completely made up, of course. But you see the gist.
In this example, you might notice the presence of a <MyAbi>
type parameter: Just type any non existing Typescript type in this place: Our compiler will build this type automatically in order to infer the type of the MyData
and MyEvent
typescript values, and give you a nice autocompletion experience.
abi
definitions must be declared outside handlers.
// OK !
const myAbi = abi<MyAbi>`...`;
onTx(tx => { ... });
// 💥 this will NOT work (it will throw an error)
onTx(tx => {
const myAbi = abi<MyAbi>`...`;
});
Let the types guide you, but the main idea is:
Call an interface​
You can define interfaces using the abi
tag, and then call it from you handlers.
For instance, this script prints some balances:
const { IERC20 } = abi<MyAbi>`
interface IERC20 {
function balanceOf(address owner) external view returns (uint256)
}
`;
// let's assume that you have filtered on tx.to = some ERC20s
onTx(tx => {
const token = IERC20.at(tx.to);
console.log('balance of sender', token.balanceOf(tx.from));
})
See our [pricing model](../Pricing Model) to know the cost of Solidity calls
Event decoders​
The main usage of event decoders will to parse logs using the tx.logs()
or tx.localLogs()
function, like so:
const {Transfer} = abi<MyAbi>`event Transfer(address indexed from, address indexed to, uint256 value);`
onTx(tx => {
// iterate all emitted logs that match the abi definition
for (const {from, to, value} of tx.logs(MyEvent)) {
// use those values
}
})
But you can use it manually. In the above example, Transfer
will be an event decoder, which will have:
- 1 property
topic0: HexString
, which will be the topic0 of your event.
If you are unfamiliar with what topic0
means, read the required EVM knowledge or this for advanced details
- 1 method
decode()
, which takes a log (see logs) as argument, and returns the decoded value, ornull
if the given log data does not match your event definition.
With the example above, MyEvent.decode(myLog)
will give you a javascript object (automatically typed by Typescript) that might look like this:
const result = {
owner: '0x4242424242424242424242424242424242424242',
myEvtData: {
field1: '0x4242424242424242424242424242424242424242',
field2: 42n,
field3: [1, 2, 3],
},
};
...which data will be extracted from the event data and topics.
Struct decoders​
Still with the first example, MyData
is an ABI/Solidity struct decoder, which has:
-
a method
decode()
which takes data as input (i.e. a0x
string), and returns the decoded value ornull
, similar to what is explained above. -
a method
encode()
which is the inverse operation: Provide data as input matching the struct specification, and you will get data (as a0x
string) as output.
Calldata decoders​
Every interface or contract defined in your ABI exposes a decodeCall()
method. Pass it some calldatas, and it will return:
null
if no method defined in the ABI matched your calldata- Some result containing the method
name
,signature
andargs
For instance:
const {MyInterface: { decodeCall } } = abi<MyAbi>`
interface MyInterface {
function myFn(uint256 fnArg) external returns (uint256);
}
`;
console.log(decodeCall('0xc92e77d2000000000000000000000000000000000000000000000000000000000000002a'))
would print the object:
({
name: "myFn",
signature: "myFn(uint256)",
args: { fnArg : 42n },
})
If you have defined multiple functions, you will note that Typescript type the result of decodeCall()
with the possible values that you could encounter.
Function decoders​
They can be used to decode inputs (calldatas sent to a contract) or outputs (call results) of a smartcontracts call that is implemented in Solidity.
Both namedReturnFn
and anonReturnFn
of the first example are function decoders... they expose two properties arguments
and result
, which are both two decoders which work the same as struct decoders described above.
- use
arguments
to encode & decode calldata to a contract - use
result
to encode & decode return data of a contract
The result decoding of functions that do not name their return arguments like anonReturnFn
will return an array of those data.
Decode arguments of a function​
Use the .arguments.decode()
function with calldata that you might encounter as transaction inputs to decode the given arguments to JS objects.
The decode function will return you either the decoded arguments, or null if the data and/or selector found in data do not match your definition.
For instance, namedReturnFn.arguments.decode(someData)
might return something like:
const result = {
arg1: '0x4242424242424242424242424242424242424242',
}
Calldata encoding​
This is the inverse operation of above, where you will have to pass a javscript object matching the structure, and the arguments.encode()
function will return you a 0x data string with arguments encoded, along with the corresponding function selector as prefix (see Required EVM knowledge)
Result decoding, with anonymous return values​
For instance, anonReturnFn.result.decode(someData)
might return something like:
const result = [
'0x4242424242424242424242424242424242424242',
{
field1: '0x4242424242424242424242424242424242424242',
field2: 42n,
field3: [1, 2, 3],
},
];
Similarily, you will have to send it the same structure to use the .encode()
function.
Result decoding, with named return values​
When having named return arguments, like with namedReturnFn
, result decoding will return an object with properties corresponding to those names.
For instance, namedReturnFn.result.decode(someData)
might return something like:
const result = {
addr: '0x4242424242424242424242424242424242424242',
myData: {
field1: '0x4242424242424242424242424242424242424242',
field2: 42n,
field3: [1, 2, 3],
},
};
Similarily, you will have to send it the same structure to use the .encode()
function.
Real life example​
Assuming you've not assigned any Filter, this script will track all ERC20 token transfers, on all blockchains, and send them to you in realtime.
It goes a bit ahead by using the .logs()
method described in the next section:
// define event's the ABI that interests us (ERC20 Transfer event)
const { Transfer } = abi<Erc20>`
event Transfer(address indexed from, address indexed to, uint256 value);
`
onTx(tx => {
// decode all transfer events emitted by the transaction
// and emit a message for each one !
for (const log of tx.logs(Transfer)) {
emit({
network: tx.network,
tx: tx.hash,
...log,
})
}
})
For instance, here is one of the messages that it has emitted (emittedBy
is the token address):
{
"network": "polygon",
"from": "0x69460570c93f9de5e2edbc3052bf10125f0ca22d",
"to": "0x207a8eb7216fa4a5e5bc4db6fe0f041604c8cd9b",
"value": "74727",
"emittedBy": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
"tx": "0xa91e3e77b10920c0330665ad8f579ef62fde0abb795b22f4d0eabb8a3b5014ef"
}
We'll see later, in the Solidity section how to build on this example to enrich results with info about the transfered token.
This example script emits A LOT of messages when ran without filter.
We would suggest not to leave it running on your account in the background to avoid unnecessary costs !
JS/Solidity type mapping​
Our compiler will generate types for you everywhere it is needed so you scripts should always be typechecked, but for reference find below the correspondance between ABI-encoded values and their JS mapped values:
Solidity type | JS type |
---|---|
bool | bool |
[u]int[8-32] | number |
[u]int[>32] | bigint |
string | string |
bytes | HexString |
bytes32 | HexString |
address | HexString |
T[] (arrays) | T[] |
struct (named fields) | a js object |
tuple(...args) | [...args] (javascript array) |
nb: HexString
is just a Typescript type for an hexadecimal string with the 0x
prefix.