Examples
Runnable code examples for eth.zig -- address derivation, signing, ERC-20, HD wallets, and more.
The examples/ directory in the repo contains self-contained programs for each major feature.
| Example | Description | Requires RPC |
|---|---|---|
01_derive_address | Derive address from private key | No |
02_check_balance | Query ETH balance via JSON-RPC | Yes |
03_sign_message | EIP-191 personal message signing | No |
04_send_transaction | Send ETH with Wallet | Yes (Anvil) |
05_read_erc20 | ERC-20 module API showcase | Yes |
06_hd_wallet | BIP-44 HD wallet derivation | No |
07_comptime_selectors | Comptime function selectors | No |
08_mev_share_backrunner | MEV-Share backrunner bot (SSE stream + bundle) | No (dry-run) |
Run any example:
cd examples && zig build && ./zig-out/bin/01_derive_addressDerive an Address
const eth = @import("eth");
// WARNING: This is the default Anvil/Hardhat test key. Never use it for real funds.
const private_key = try eth.hex.hexToBytesFixed(
32,
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
);
const signer = eth.signer.Signer.init(private_key);
const addr = try signer.address();
const checksum = eth.primitives.addressToChecksum(&addr);
// "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"Sign and Send a Transaction
const eth = @import("eth");
var transport = eth.http_transport.HttpTransport.init(allocator, "https://rpc.example.com", eth.runtime.blockingIo());
defer transport.deinit();
var provider = eth.provider.Provider.init(allocator, &transport);
var wallet = eth.wallet.Wallet.init(allocator, private_key, &provider);
const tx_hash = try wallet.sendTransaction(.{
.to = recipient_address,
.value = eth.units.parseEther(1.0),
});Read an ERC-20 Token
const eth = @import("eth");
// Comptime selectors -- zero runtime cost
const balance_sel = eth.erc20.selectors.balanceOf;
// Or use the typed wrapper
var token = eth.erc20.ERC20.init(allocator, token_addr, &provider);
const balance = try token.balanceOf(holder_addr);
const name = try token.name();
defer allocator.free(name);Function Selectors
const eth = @import("eth");
// Computed at compile time -- zero runtime cost
const transfer_sel = comptime eth.keccak.selector("transfer(address,uint256)");
// transfer_sel == [4]u8{ 0xa9, 0x05, 0x9c, 0xbb }
// Same function works at runtime too
const runtime_sel = eth.keccak.selector(some_signature);
const transfer_topic = comptime eth.keccak.hash("Transfer(address,address,uint256)");
// transfer_topic == keccak256("Transfer(address,address,uint256)")HD Wallet from Mnemonic
const eth = @import("eth");
const words = [_][]const u8{
"abandon", "abandon", "abandon", "abandon",
"abandon", "abandon", "abandon", "abandon",
"abandon", "abandon", "abandon", "about",
};
const seed = try eth.mnemonic.toSeed(&words, "");
const key = try eth.hd_wallet.deriveEthAccount(seed, 0);
const addr = key.toAddress();Multicall3 Batch Reads
const eth = @import("eth");
var mc = eth.multicall.Multicall3.init(allocator, &provider);
try mc.addCall(token_addr, eth.erc20.selectors.balanceOf, .{holder_addr});
try mc.addCall(token_addr, eth.erc20.selectors.totalSupply, .{});
const results = try mc.execute();MEV-Share Backrunner
08_mev_share_backrunner is a teaching example that ties together comptime
selectors, the MEV-Share SSE stream, EIP-1559 signing, and bundle composition.
It subscribes to the pending-transaction event stream, matches hints against a
comptime-computed table of Uniswap swap selectors, and builds a backrun bundle
(the user's tx hash followed by a signed backrun tx). It defaults to a safe
DRY_RUN mode that prints the mev_sendBundle params it would submit, and
points at Sepolia by default so the subscription flow can be exercised with no
mainnet funds.
const eth = @import("eth");
// Comptime: the swap selector is hashed by the compiler.
const v2_swap = comptime eth.keccak.selector(
"swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
);
var client = eth.mev_share.MevShareClient.init(allocator, relay_url, stream_url, auth_key, eth.runtime.blockingIo());
defer client.deinit();
// Subscribe to the SSE stream; the callback fires per pending-tx hint.
try client.on(&onEvent);
// Inside onEvent: match the revealed selector, then compose the bundle.
const body = [_]eth.flashbots.MevBundleBody{
.{ .hash = user_tx_hash }, // the user's pending swap
.{ .tx = .{ .data = signed_backrun_tx } }, // our backrun
};
_ = try client.relay.mevSendBundle(.{
.body = &body,
.inclusion = .{ .block = target_block, .max_block = target_block + 2 },
});Run it (dry-run, against the Sepolia matchmaker):
cd examples && zig build && DRY_RUN=1 ./zig-out/bin/08_mev_share_backrunner