gleeth
An Ethereum library for Gleam, targeting the Erlang (BEAM) runtime. Provides JSON-RPC client, transaction signing, ABI encoding/decoding, and wallet management.
Warning: gleeth has not been audited and is in early development. It is recommended for testnet and development use only. Do not use with real funds in production without thorough independent review.
Installation
gleam add gleethQuick start
Read chain state
import gleeth/provider
import gleeth/rpc/methods
pub fn main() {
let assert Ok(p) = provider.new("http://localhost:8545")
// Get the latest block number
let assert Ok(block_number) = methods.get_block_number(p)
// Check an address balance
let assert Ok(balance) = methods.get_balance(
p,
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
)
}Sign and send a transaction (builder API)
The builder API accepts human-readable values - no manual hex conversion needed.
import gleeth/crypto/transaction
import gleeth/crypto/wallet
import gleeth/gas
import gleeth/provider
import gleeth/rpc/methods
pub fn main() {
let assert Ok(p) = provider.new("http://localhost:8545")
let assert Ok(w) = wallet.from_private_key_hex("0xac09...")
// Build and sign in a pipeline
let assert Ok(signed) =
transaction.build_legacy()
|> transaction.legacy_to("0x70997970C51812dc3A010C7d01b50e0d17dc79C8")
|> transaction.legacy_value_ether("1.5")
|> transaction.legacy_gas_limit_int(21_000)
|> transaction.legacy_gas_price_gwei("20.0")
|> transaction.legacy_nonce_int(0)
|> transaction.legacy_chain(1)
|> transaction.sign_legacy(w)
// Broadcast and wait for receipt
let assert Ok(tx_hash) = methods.send_raw_transaction(p, signed.raw_transaction)
let assert Ok(receipt) = methods.wait_for_receipt(p, tx_hash)
}
For lower-level control, use create_legacy_transaction with hex strings directly:
pub fn main() {
let assert Ok(p) = provider.new("http://localhost:8545")
let assert Ok(w) = wallet.from_private_key_hex("0xac09...")
// Gas estimation in one call
let sender = wallet.get_address(w)
let assert Ok(est) = gas.estimate_legacy(p, sender, "0x7099...", "0xde0b6b3a7640000", "0x")
let assert Ok(nonce) = methods.get_transaction_count(p, sender, "pending")
let assert Ok(tx) = transaction.create_legacy_transaction(
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"0xde0b6b3a7640000", // 1 ETH in wei hex
est.gas_limit, // from estimation
est.gas_price, // from estimation
nonce, // from RPC
"0x", // no calldata
1, // mainnet
)
let assert Ok(signed) = transaction.sign_transaction(tx, w)
let assert Ok(tx_hash) = methods.send_raw_transaction(p, signed.raw_transaction)
}Contract interaction
The contract module binds a provider, address, and ABI together so you can
call functions by name without manually encoding calldata:
import gleeth/contract
import gleeth/crypto/wallet
import gleeth/ethereum/abi/json
import gleeth/provider
pub fn main() {
let assert Ok(p) = provider.new("http://localhost:8545")
let assert Ok(w) = wallet.from_private_key_hex("0xac09...")
// Parse the ABI and create a contract handle
let assert Ok(abi) = json.parse_abi(erc20_abi_json)
let usdc = contract.at(p, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", abi)
// Read a balance - just pass strings, types are inferred from the ABI
let assert Ok(values) = contract.call_raw(usdc, "balanceOf", [
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
])
// Send a transfer
let assert Ok(tx_hash) = contract.send_raw(usdc, w, "transfer", [
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"1000000",
], "0x100000", 1)
}Low-level contract call
import gleeth/provider
import gleeth/rpc/methods
pub fn main() {
let assert Ok(p) = provider.new("http://localhost:8545")
// Call balanceOf(address) on an ERC-20 with raw calldata
let assert Ok(result) = methods.call_contract(
p,
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
"0x70a08231000000000000000000000000d8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
)
}ABI encoding
import gleeth/ethereum/abi/encode
import gleeth/ethereum/abi/types.{Uint, Address, AbiUintValue, AbiAddressValue}
pub fn main() {
// Encode a function call: transfer(address, uint256)
let assert Ok(calldata) = encode.encode_call(
"transfer(address,uint256)",
[Uint(256), Address],
[
AbiAddressValue("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"),
AbiUintValue(1000000),
],
)
}Features
- Transaction builder - pipeline API with human-readable values (
value_ether("1.5"),gas_price_gwei("20.0")) - Transaction signing - Legacy (Type 0) and EIP-1559 (Type 2) with EIP-155 replay protection
- Transaction decoding - decode raw transactions, recover sender address
- Gas estimation -
gas.estimate_legacyandgas.estimate_eip1559in a single call - Receipt polling -
wait_for_receiptwith exponential backoff - Nonce manager - local nonce tracking for multi-transaction sequences
- Wei conversions -
wei.from_ether("1.5"),wei.to_gwei(hex),wei.from_int(21000) - JSON-RPC client - block number, balance, call, code, estimate gas, storage, logs, transactions, receipts, fee history
- Provider abstraction - opaque type with URL validation, chain ID caching, and configurable retry
- ABI system - full encoding/decoding for all Solidity types, calldata decoding, revert reason decoding, JSON ABI parsing, event log decoding
- EIP-55 addresses -
address.checksumandaddress.is_valid_checksum - EIP-191 signing -
sign_personal_message,recover_personal_message,verify_personal_message - Wallet management - key generation, message signing, signature recovery
- Crypto primitives - keccak256 (via ex_keccak NIF), secp256k1 (via ex_secp256k1 NIF)
- EIP-712 signing -
eip712.sign_typed_datafor permits, order books, meta-transactions - Batch JSON-RPC - multiple RPC calls in a single HTTP request via
batch.new() |> batch.add(...) |> batch.execute_strings(provider) - Contract deployment -
deploy.deployanddeploy.deploy_with_argshandle signing, broadcasting, and receipt polling - Retry middleware - automatic retry with exponential backoff on 429/503 via
provider.with_retry - Contract type -
contract.at(provider, address, abi)for automatic ABI encoding/decoding oncallandsend, with string-coercedcall_raw/send_raw - Event streaming -
events.get_eventscombines getLogs + ABI decoding;get_events_by_namefilters by event - EIP-2612 permits -
permit.signfor one-liner permit signing with auto domain/nonce fetch - Multicall3 -
multicall.new() |> multicall.add(...) |> multicall.execute(provider)batches contract reads at the EVM level - RLP encoding/decoding - per Ethereum Yellow Paper spec
Requirements
- Gleam >= 1.14.0
- Erlang/OTP >= 27
- Elixir (for ex_keccak and ex_secp256k1 NIF compilation)
Run mix local.hex --force before first build if Elixir is freshly installed.
Development
gleam build # Compile
gleam test # Run all tests
gleam format # Format code
gleam docs build # Generate documentationFurther documentation can be found at https://hexdocs.pm/gleeth.