Try it Live
Run ABI examples in the interactive playground
Usage Patterns
Practical patterns for working with ABI encoding and decoding in production code.Contract Interaction
Function Call Encoding
import { Function, Abi } from 'tevm';
// Define function
const transferFn = {
type: "function",
name: "transfer",
stateMutability: "nonpayable",
inputs: [
{ type: "address", name: "to" },
{ type: "uint256", name: "amount" }
],
outputs: [{ type: "bool" }]
};
// Encode function call
const calldata = Function.encodeParams(transferFn, [
"0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e",
1000n
]);
// Get selector
const selector = Function.getSelector(transferFn); // 4 bytes
// Full calldata = selector + encoded params
const fullCalldata = new Uint8Array([...selector, ...calldata]);
// Send transaction
await provider.sendTransaction({
to: contractAddress,
data: fullCalldata
});
Return Value Decoding
// Execute call and decode result
const returnData = await provider.call({
to: contractAddress,
data: fullCalldata
});
// Decode return value
const [success] = Function.decodeResult(transferFn, returnData);
console.log(`Transfer ${success ? 'succeeded' : 'failed'}`);
Multi-call Pattern
interface Call {
target: string;
callData: Uint8Array;
decoder: (data: Uint8Array) => any;
}
// Prepare multiple calls
const calls: Call[] = [
{
target: token1,
callData: Function.encodeParams(balanceOfFn, [userAddress]),
decoder: (data) => Function.decodeResult(balanceOfFn, data)
},
{
target: token2,
callData: Function.encodeParams(balanceOfFn, [userAddress]),
decoder: (data) => Function.decodeResult(balanceOfFn, data)
}
];
// Execute multicall
const results = await Promise.all(
calls.map(call =>
provider.call({ to: call.target, data: call.callData })
)
);
// Decode results
const balances = results.map((data, i) => calls[i].decoder(data));
Event Processing
Log Parsing
import { Event } from 'tevm';
// Define Transfer event
const transferEvent = {
type: "event",
name: "Transfer",
inputs: [
{ type: "address", name: "from", indexed: true },
{ type: "address", name: "to", indexed: true },
{ type: "uint256", name: "value", indexed: false }
]
};
// Get logs from provider
const logs = await provider.getLogs({
address: tokenAddress,
fromBlock: startBlock,
toBlock: endBlock,
topics: [Event.getSelector(transferEvent)]
});
// Parse all logs
const transfers = logs.map(log =>
Event.decodeLog(transferEvent, log.data, log.topics)
);
// Process transfers
transfers.forEach(({ from, to, value }) => {
console.log(`${from} → ${to}: ${value}`);
});
Event Filtering
// Filter by specific sender
const senderTopic = Event.encodeIndexed(
{ type: "address" },
senderAddress
);
const logs = await provider.getLogs({
address: tokenAddress,
topics: [
Event.getSelector(transferEvent),
senderTopic // Filter by 'from' address
]
});
Multi-event Parsing
const eventDefinitions = {
Transfer: transferEvent,
Approval: approvalEvent,
Mint: mintEvent
};
// Get topic0 → event mapping
const topicMap = new Map(
Object.entries(eventDefinitions).map(([name, def]) => [
Event.getSelector(def).toString(),
{ name, definition: def }
])
);
// Parse mixed event logs
const parsed = logs.map(log => {
const topic0 = log.topics[0];
const event = topicMap.get(topic0.toString());
if (!event) return null;
return {
name: event.name,
args: Event.decodeLog(event.definition, log.data, log.topics)
};
}).filter(Boolean);
Error Handling
Custom Error Decoding
import { AbiError } from 'tevm';
// Define custom errors
const errors = {
InsufficientBalance: {
type: "error",
name: "InsufficientBalance",
inputs: [
{ type: "uint256", name: "balance" },
{ type: "uint256", name: "required" }
]
},
InvalidRecipient: {
type: "error",
name: "InvalidRecipient",
inputs: [{ type: "address", name: "recipient" }]
}
};
// Try transaction, catch revert
try {
await contract.transfer(recipient, amount);
} catch (err: any) {
const revertData = err.data;
// Try to decode with each error definition
for (const [name, errorDef] of Object.entries(errors)) {
const selector = AbiError.getSelector(errorDef);
if (revertData.startsWith(selector)) {
const args = AbiError.decodeParams(errorDef, revertData.slice(4));
console.error(`${name}:`, args);
break;
}
}
}
Error Encoding
// Encode custom error for testing
const errorData = AbiError.encodeParams(
errors.InsufficientBalance,
[100n, 1000n]
);
// Simulate revert in tests
const fullError = new Uint8Array([
...AbiError.getSelector(errors.InsufficientBalance),
...errorData
]);
Type-safe ABI Loading
From JSON
import { Abi } from 'tevm';
// Load ABI from contract artifact
const artifact = require('./artifacts/Token.json');
const abi = Abi(artifact.abi);
// Get specific items
const transferFn = abi.getFunction("transfer");
const transferEvent = abi.getEvent("Transfer");
const balanceError = abi.getError("InsufficientBalance");
Dynamic ABI Construction
// Build ABI programmatically
const items = [
{
type: "function",
name: "transfer",
inputs: [
{ type: "address", name: "to" },
{ type: "uint256", name: "amount" }
],
outputs: [{ type: "bool" }]
},
{
type: "event",
name: "Transfer",
inputs: [
{ type: "address", name: "from", indexed: true },
{ type: "address", name: "to", indexed: true },
{ type: "uint256", name: "value" }
]
}
];
const abi = Abi(items);
Complex Type Handling
Struct Encoding
// Define struct as tuple
const positionType = {
type: "tuple",
components: [
{ type: "uint256", name: "amount" },
{ type: "uint256", name: "shares" },
{ type: "uint256", name: "timestamp" }
]
};
// Encode struct
const encoded = Abi.encode([positionType], [[1000n, 500n, 1234567890n]]);
Array Handling
// Dynamic array
const dynamicArrayType = { type: "uint256[]" };
const values = [1n, 2n, 3n, 4n, 5n];
const encoded = Abi.encode([dynamicArrayType], [values]);
// Fixed array
const fixedArrayType = { type: "uint256[5]" };
const encoded = Abi.encode([fixedArrayType], [values]);
// Multi-dimensional
const matrixType = { type: "uint256[][]" };
const matrix = [[1n, 2n], [3n, 4n]];
const encoded = Abi.encode([matrixType], [matrix]);
String and Bytes
// String
const stringType = { type: "string" };
const encoded = Abi.encode([stringType], ["Hello, Ethereum!"]);
// Dynamic bytes
const bytesType = { type: "bytes" };
const data = new Uint8Array([1, 2, 3, 4]);
const encoded = Abi.encode([bytesType], [data]);
// Fixed bytes
const bytes32Type = { type: "bytes32" };
const hash = Bytes32();
const encoded = Abi.encode([bytes32Type], [hash]);
Optimization Patterns
Selector Caching
// Cache selectors for frequent use
const selectorCache = new Map<string, Uint8Array>();
function getCachedSelector(fn: Function): Uint8Array {
const key = `${fn.name}(${fn.inputs.map(i => i.type).join(',')})`;
if (!selectorCache.has(key)) {
selectorCache.set(key, Function.getSelector(fn));
}
return selectorCache.get(key)!;
}
Reusable Encoders
class ContractInterface {
constructor(private abi: Abi) {}
// Pre-bind common functions
private transfer = this.abi.getFunction("transfer");
private balanceOf = this.abi.getFunction("balanceOf");
encodeTransfer(to: string, amount: bigint): Uint8Array {
return Function.encodeParams(this.transfer, [to, amount]);
}
encodeBalanceOf(account: string): Uint8Array {
return Function.encodeParams(this.balanceOf, [account]);
}
decodeBalance(data: Uint8Array): bigint {
const [balance] = Function.decodeResult(this.balanceOf, data);
return balance;
}
}
Testing Patterns
Mock Contract Responses
// Encode expected return values
function mockBalanceOf(balance: bigint): Uint8Array {
return Abi.encode([{ type: "uint256" }], [balance]);
}
// Use in tests
test("handles zero balance", async () => {
mock.returns(mockBalanceOf(0n));
const balance = await contract.balanceOf(user);
expect(balance).toBe(0n);
});
Event Testing
// Encode expected event
function mockTransferEvent(from: string, to: string, value: bigint) {
return {
topics: [
Event.getSelector(transferEvent),
Event.encodeIndexed({ type: "address" }, from),
Event.encodeIndexed({ type: "address" }, to)
],
data: Abi.encode([{ type: "uint256" }], [value])
};
}
Related
- Decode - ABI decoding
- Encode - ABI encoding
- Parse Logs - Event log parsing
- Fundamentals - ABI basics

