Application Binary Interface (ABI)
Overview
The Application Binary Interface (ABI) is the standardized protocol for interacting with smart contracts in the Ethereum ecosystem. It defines how data is encoded and decoded for Consumer (e.g. Application, Wallet, Server, etc) ↔ Contract communication, as well as Contract → Contract communication.
Encoding and Decoding ABI Parameters
To start, let's take a look at how we can encode and decode primitive ABI types and parameters using Ox. While encoding and decoding ABI parameters might not be directly useful in isolation, they form the foundation of interacting with smart contracts, as listed in the Applications section below.
Encoding
Encoding ABI parameters is the process of converting input data into a bytecode format that can be understood by the Ethereum Virtual Machine (EVM).
You can encode ABI parameters using Ox's AbiParameters.encode
function.
Let's take a trivial example that encodes address
and uint32[]
parameters:
import { AbiParameters } from 'ox';
const encoded = AbiParameters.encode(
[{ type: 'address' }, { type: 'uint32[]' }],
['0xcb98643b8786950F0461f3B0edf99D88F274574D', [1, 2, 3]]
)
console.log(encoded)
'0x000000000000000000000000cb98643b8786950f0461f3b0edf99d88f274574d00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003'
The encoded
variable now contains the ABI-encoded representation of the values we passed with the address
and uint32[]
parameters. We won't go into detail on how the encoding works, but you can read more about it here.
Decoding
Decoding is the process of converting ABI-encoded data back into its values.
You can decode ABI parameters using Ox's AbiParameters.decode
function.
Let's take a look at how we can decode the parameters we encoded above:
import { AbiParameters } from 'ox';
const encoded = AbiParameters.encode(
[{ type: 'address' }, { type: 'uint32[]' }],
['0xcb98643b8786950F0461f3B0edf99D88F274574D', [1, 2, 3]]
)
const decoded = AbiParameters.decode(
[{ type: 'address' }, { type: 'uint32[]' }],
encoded
)
console.log(decoded)
['0xcb98643b8786950f0461f3b0edf99d88f274574d', [1, 2, 3]]
Now that we are aware of how to encode and decode primitive types & values, let's take a look at how ABI coding is applied in real-world scenarios.
Contract Function Calls
When calling a function on a smart contract, an ABI is used to encode the function signature and its parameters into a bytecode format that can be understood by the Ethereum Virtual Machine (EVM). It is equally important for decoding the response (or revert reason) from the contract, hence allowing for two-way communication.
Read-only Functions
A Pure Function or View Function (commonly known as a "contract read" function) does not modify the state of the blockchain. These functions have a stateMutability
of pure
or view
.
They can only read the state of the contract, and cannot make any changes to it. Since pure/view functions do not change the state of the contract, they do not require any gas to be executed. This means that they can be called without the need for a transaction (via an eth_call
JSON-RPC method).
Let's take a look at how we can encode a read-only balanceOf
function call on an ERC20 contract, and then call it using eth_call
.
Define the balanceOf
Function
First, we will define the balanceOf
function using Ox's AbiFunction.from
function:
import { AbiFunction } from 'ox';
const balanceOf = AbiFunction.from(
'function balanceOf(address) returns (uint256)'
)
Encode the Function Call
Next, we will encode the function call using AbiFunction.encodeData
:
In this example, we will encode arguments to get the balance of the 0xcb98643b8786950f0461f3b0edf99d88f274574d
address.
import { AbiFunction, Value } from 'ox';
const balanceOf = AbiFunction.from('function balanceOf(address) returns (uint256)')
const data = AbiFunction.encodeData(
balanceOf,
['0xcb98643b8786950f0461f3b0edf99d88f274574d']
)
Perform the Call
Now, we can perform the call using eth_call
via a JSON-RPC Transport or EIP-1193 Provider. This will invoke the balanceOf
function on the ERC20 contract.
For this example, we will use a RpcTransport.fromHttp
to instantiate a HTTP JSON-RPC Transport.
import { AbiFunction, RpcTransport, TransactionRequest, Value } from 'ox'
const balanceOf = AbiFunction.from('function balanceOf(address) returns (uint256)')
const data = AbiFunction.encodeData(
balanceOf,
['0xcb98643b8786950f0461f3b0edf99d88f274574d']
)
const transport = RpcTransport.fromHttp('https://1.rpc.thirdweb.com')
const result = await transport.request({
method: 'eth_call',
params: [{
data,
to: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
}],
})
Decode the Result
Now, we can decode the result using AbiFunction.decodeResult
:
import { AbiFunction, RpcTransport, TransactionRequest, Value } from 'ox'
const balanceOf = AbiFunction.from('function balanceOf(address) returns (uint256)')
const data = AbiFunction.encodeData(
balanceOf,
['0xcb98643b8786950f0461f3b0edf99d88f274574d']
)
const transport = RpcTransport.fromHttp('https://1.rpc.thirdweb.com')
const result = await transport.request({
method: 'eth_call',
params: [{
data,
to: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
}],
})
const balance = AbiFunction.decodeResult(balanceOf, result)
State-Modifying Functions
A State-Modifying Function (commonly known as a "contract write" function) modifies the state of the blockchain.
These functions have a stateMutability
of nonpayable
or payable
.
These types of functions require gas to be executed, and hence a transaction is needed to be broadcast in order to change the state (via an eth_sendTransaction
JSON-RPC method).
Let's take a look at how we can encode a state-modifying approve
function call on an ERC20 contract, and then broadcast it to the network.
Define the approve
Function
First, we will define the approve
function using Ox's AbiFunction.from
function:
import { AbiFunction } from 'ox';
const approve = AbiFunction.from('function approve(address, uint256) returns (bool)')
Encode the Function Call
Next, we will encode the function call using AbiFunction.encodeData
:
In this example, we will encode arguments to approve 100 USDC to be spent by the 0xcb98643b8786950f0461f3b0edf99d88f274574d
.
import { AbiFunction, Value } from 'ox';
const approve = AbiFunction.from('function approve(address, uint256) returns (bool)')
const data = AbiFunction.encodeData(
approve,
['0xcb98643b8786950f0461f3b0edf99d88f274574d', Value.from('100', 18)]
)
Broadcast the Transaction
Now, we can broadcast the data
to the network using eth_sendTransaction
via a JSON-RPC Transport or EIP-1193 Provider. This will invoke the approve
function on the USDC contract.
import { AbiFunction, RpcTransport, Value } from 'ox'
const approve = AbiFunction.from('function approve(address, uint256) returns (bool)')
const data = AbiFunction.encodeData(
approve,
['0xcb98643b8786950f0461f3b0edf99d88f274574d', Value.fromEther('100')]
)
const transport = RpcTransport.fromHttp('https://1.rpc.thirdweb.com')
const hash = await transport.request({
method: 'eth_sendTransaction',
params: [{
data,
to: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
}],
})
Bonus: Simulate
We can also "simulate" a Transaction by using eth_call
instead of eth_sendTransaction
. Using an eth_call
means we can also retrieve the result of the function call and decode it with AbiFunction.decodeResult
:
const hash = await transport.request({
method: 'eth_sendTransaction',
const result = await transport.request({
method: 'eth_call',
params: [{
data,
to: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
}],
})
const success = AbiFunction.decodeResult(approve, result)
Bonus: Estimate Gas
We can also estimate the gas required to execute a Transaction by using eth_estimateGas
instead of eth_sendTransaction
:
const hash = await transport.request({
method: 'eth_sendTransaction',
const gas = await transport.request({
method: 'eth_estimateGas',
params: [{
data,
to: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
}],
})
Contract Event Filtering
ABIs can also be used to filter contract events by a topic (hashed event signature), and optional indexed parameters. This is useful for applications that need to extract or listen for specific events – such as ERC20 transfers or L2 messages – on Ethereum. We can also ABI-decode extracted event to retrieve the event name and arguments.
Let's take an example of how we can filter for ERC20 Transfer
events, and decode the event arguments.
Define the Transfer
Event
First, we will define the Transfer
event using Ox's AbiEvent.from
function:
import { AbiEvent } from 'ox';
const transfer = AbiEvent.from(
'event Transfer(address indexed from, address indexed to, uint256 value)'
)
Encode to Event Topics
Next, we will encode the event filter using AbiEvent.encode
. We will filter for transfers from 0x9f1fdab6458c5fc642fa0f4c5af7473c46837357
.
import { AbiEvent } from 'ox';
const transfer = AbiEvent.from(
'event Transfer(address indexed from, address indexed to, uint256 value)',
)
const { topics } = AbiEvent.encode(transfer, {
from: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357',
})
Filter Events
Now, we can filter events using eth_getLogs
via a JSON-RPC Transport or EIP-1193 Provider.
For this example, we will use a RpcTransport.fromHttp
to instantiate a HTTP JSON-RPC Transport.
import { AbiEvent, RpcTransport } from 'ox';
const transfer = AbiEvent.from(
'event Transfer(address indexed from, address indexed to, uint256 value)',
)
const { topics } = AbiEvent.encode(transfer, {
from: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357',
})
const transport = RpcTransport.fromHttp('https://1.rpc.thirdweb.com')
const logs = await transport.request({
method: 'eth_getLogs',
params: [{ topics }],
})
Decode Logs
We can decode the logs using AbiEvent.decode
:
import { AbiEvent, RpcTransport } from 'ox';
const transfer = AbiEvent.from(
'event Transfer(address indexed from, address indexed to, uint256 value)',
)
const { topics } = AbiEvent.encode(transfer, {
from: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357',
})
const transport = RpcTransport.fromHttp('https://1.rpc.thirdweb.com')
const logs = await transport.request({
method: 'eth_getLogs',
params: [{ topics }],
})
const decoded = logs.map(log => AbiEvent.decode(transfer, log))
[ { from: '0x9f1fdab6458c5fc642fa0f4c5af7473c46837357', to: '0xcb98643b8786950f0461f3b0edf99d88f274574d', value: 1n, }, ... ]
Contract Deployment
When deploying a smart contract, an ABI can be used to encode constructor arguments to pass to the transaction calldata, along with the deployment bytecode.
Let's take a look at how we can deploy a contract with arguments using Ox.
Define the Constructor
First, we will define the constructor using Ox's AbiConstructor.from
function:
import { AbiConstructor } from 'ox';
const bytecode = '0x...'
const constructor = AbiConstructor.from('constructor(address)')
Encode the Constructor
Next, we will encode the constructor arguments using AbiConstructor.encode
:
import { AbiConstructor } from 'ox';
const bytecode = '0x...'
const constructor = AbiConstructor.from('constructor(address)')
const data = AbiConstructor.encode(constructor, {
bytecode,
args: ['0x9f1fdab6458c5fc642fa0f4c5af7473c46837357'],
})
Broadcast the Transaction
We can now broadcast the deploy transaction to the network using eth_sendTransaction
via a JSON-RPC Transport or EIP-1193 Provider.
import { AbiConstructor, RpcTransport } from 'ox';
const bytecode = '0x...'
const constructor = AbiConstructor.from('constructor(address)')
const data = AbiConstructor.encode(constructor, {
bytecode,
args: ['0x9f1fdab6458c5fc642fa0f4c5af7473c46837357'],
})
const transport = RpcTransport.fromHttp('https://1.rpc.thirdweb.com')
const hash = await transport.request({
method: 'eth_sendTransaction',
params: [{ data }],
})
Related Modules
Module | Description |
---|---|
Abi | Utilities & types for working with Application Binary Interfaces (ABIs) |
AbiConstructor | Utilities & types for working with Constructors on ABIs. |
AbiError | Utilities & types for working with Errors on ABIs. |
AbiEvent | Utilities & types for working with Events on ABIs. |
AbiFunction | Utilities & types for working with Functions on ABIs. |
AbiItem | Utilities & types for working with ABI Items |
AbiParameters | Utilities & types for encoding, decoding, and working with ABI Parameters |