ethereum-mysql 4.0.0

Ethereum types (Address, U256) wrapper for seamless SQL database integration with SQLx
Documentation

ethereum-mysql

Crates.io Documentation License

Design Purpose:

ethereum-mysql is the missing bridge between Rust Web3 application code and SQL databases. While alloy provides foundational Ethereum types, writing production backend code still involves tedious boilerplate — manual conversions, unreadable arithmetic, and type-unsafe string handling.

This crate solves two real-world pain points:

🔥 Pain Point 1: Intuitive Arithmetic

Without this crate, every calculation requires verbose U256::from() wrappers:

// ❌ Vanilla alloy: verbose, error-prone
let fee = balance * U256::from(25) / U256::from(10000);
let doubled = balance + U256::from(2);

With ethereum-mysql, write natural, readable code:

// ✅ ethereum-mysql: clean, intuitive
let fee = balance * 25u64 / 10000u64;
let doubled = balance + 2u64;

🚀 Pain Point 2: API-Ready Types

Without this crate, your web API structs need manual conversions between Rust primitives and Ethereum types:

// ❌ Without ethereum-mysql: manual parsing in every handler
#[derive(Serialize, Deserialize)]
struct TransferRequest {
    from: String,   // hope it's a valid address...
    amount: String, // hope it's a valid uint256...
}

With ethereum-mysql, use types directly — validation happens automatically:

// ✅ ethereum-mysql: type-safe, validated automatically
#[derive(Serialize, Deserialize, FromRow)]
struct TransferRequest {
    from: SqlAddress,    // compile-time + runtime validation
    amount: SqlU256,     // direct serde + sqlx support
}

Features

  • 🔥 Intuitive Arithmetic: Direct +, -, *, /, % (and +=, -=, *=, /=, %=) between SqlU256/SqlU128 and all Rust unsigned primitives (u8u128, usize). Plus iter().sum() / iter().product() for aggregation.
  • 🚀 API-Ready: Serialize/Deserialize/FromRow all work out of the box — use SqlAddress/SqlU256/SqlU128 directly in your DTOs
  • Binary storage: Big-endian bytes (BINARY/BYTEA/BLOB) following alloy-rs/core#970 and #1020
  • Multi-database: MySQL, PostgreSQL, SQLite via SQLx
  • Type-safe wrappers: SqlAddress, SqlU128, SqlU256, SqlFixedBytes<N>, SqlBytes
  • Unified error type: SqlTypeError enum instead of ad-hoc &'static str — proper std::error::Error impl
  • Compile-time macros: sqladdress!, sqlhash!
  • Constants: SqlAddress::ZERO, SqlU256::ZERO, SqlU256::ETHER
  • Decimal utilities: parse_sether/format_sether for human-readable ETH amounts
  • Serde + SQLx native: Implements Serialize/Deserialize and sqlx_core::Type/Encode/Decode

Database Column Types

All types are stored as raw big-endian bytes:

Type MySQL PostgreSQL SQLite
SqlAddress BINARY(20) BYTEA BLOB
SqlU128 BINARY(16) BYTEA BLOB
SqlU256 BINARY(32) BYTEA BLOB
SqlFixedBytes<N> BINARY(N) BYTEA BLOB
SqlBytes VARBINARY BYTEA BLOB

Binary Encoding

  • All fixed-size types (Address, U128, U256, FixedBytes) are stored as big-endian byte arrays matching their exact byte width.
  • SqlBytes is stored as a variable-length byte array.
  • For predictable sorting/comparison, big-endian encoding ensures binary-order matches numeric-order for Uint types.

Key Advantages

� Intuitive Arithmetic

let mut balance = SqlU256::from_str("1000000000000000000").unwrap(); // 1 ETH
let fee = balance * 25u64 / 10000u64;      // 0.25% fee — clean & readable
balance += 500u64;                          // assign ops with primitives
balance *= 2u64;

// Iterator aggregation
let amounts = vec![SqlU256::from(10u64), SqlU256::from(20u64)];
let total: SqlU256 = amounts.iter().sum();  // 30

Supports +, -, *, /, %, +=, -=, *=, /=, %=, sum(), product() with u8u128, usize.

🚀 API-Ready

#[derive(Serialize, Deserialize, FromRow)]
struct TransferRequest {
    from: SqlAddress,    // auto-validated hex address
    to: SqlAddress,      // EIP-55 checksum format
    amount: SqlU256,     // direct serde + sqlx
}

Works with serde_json, sqlx::FromRow, actix-web, axum, and any serde-compatible framework.

🛡️ Type Safety

Compile-time macros (sqladdress!, sqlhash!) and runtime validation for all types. Unified SqlTypeError for conversion errors (no &'static str).

⚡ Binary Storage

Direct Vec<u8> encoding via SQLx — optimal performance, BINARY/BYTEA/BLOB natively.


Quick Start

Add to your Cargo.toml:

ethereum-mysql = "4.0.0"

Example Usage

Address Creation

use ethereum_mysql::{SqlAddress, sqladdress};
use std::str::FromStr;

let zero = SqlAddress::ZERO;
let addr = sqladdress!("0x742d35Cc6635C0532925a3b8D42cC72b5c2A9A1d");
let addr2 = SqlAddress::from_str("0x742d35Cc6635C0532925a3b8D42cC72b5c2A9A1d").unwrap();
const ADMIN: SqlAddress = sqladdress!("0x742d35Cc6635C0532925a3b8D42cC72b5c2A9A1d");

U256 Usage & Ethereum Constants

use ethereum_mysql::{SqlU256, utils::{parse_sether, format_sether}};
use std::str::FromStr;

// Basic usage
let small = SqlU256::from(42u64);
let large = SqlU256::from(u128::MAX);
let zero = SqlU256::ZERO;
let from_decimal = SqlU256::from_str("123456789").unwrap();
let from_hex = SqlU256::from_str("0x75bcd15").unwrap();
assert_eq!(from_decimal, from_hex);

// Ethereum amount calculations
let one_ether = SqlU256::ETHER;  // 10^18 wei
let half_ether = SqlU256::ETHER / 2u64;
let gas_price = SqlU256::from(20_000_000_000u64); // 20 gwei

// Decimal string parsing and formatting
let amount = parse_sether("1.5").unwrap(); // Parse 1.5 ETH
assert_eq!(format_sether(amount).unwrap(), "1.500000000000000000");

// Arithmetic operations
let a = SqlU256::from(100u64);
let b = SqlU256::from(50u64);
let sum = a + b;
let product = a * b;
let doubled = a * 2u64;
let tripled = 3u64 * a;

U128 Usage

use ethereum_mysql::SqlU128;

// Common for Solidity uint128 values
let amount = SqlU128::from(1_000_000u64);
let supply: SqlU128 = SqlU128::from_str("340282366920938463463374607431768211455").unwrap(); // max uint128
let half = amount / 2u64;

Hash and FixedBytes Creation

use ethereum_mysql::{sqlhash, SqlFixedBytes};

// Create various sized hashes at compile time
const TX_HASH: SqlFixedBytes<32> = sqlhash!(32, "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
const FUNCTION_SELECTOR: SqlFixedBytes<4> = sqlhash!(4, "0xa9059cbb");
const TOPIC: SqlFixedBytes<32> = sqlhash!(32, "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");

SQLx Integration (Binary Mode)

use ethereum_mysql::{SqlAddress, SqlU256, SqlHash};
use sqlx::MySqlPool;
use std::str::FromStr;

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let pool = MySqlPool::connect("mysql://user:pass@localhost/db").await?;
    let user_address = SqlAddress::from_str("0x742d35Cc6635C0532925a3b8D42cC72b5c2A9A1d").unwrap();
    let balance = SqlU256::from_str("1000000000000000000").unwrap();
    let tx_hash = SqlHash::from_str("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef").unwrap();
    sqlx::query(
        "CREATE TABLE IF NOT EXISTS users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            wallet_address BINARY(20) NOT NULL,
            balance BINARY(32),
            tx_hash BINARY(32),
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )",
    )
    .execute(&pool)
    .await?;
    sqlx::query("INSERT INTO users (wallet_address, balance, tx_hash) VALUES (?, ?, ?)")
        .bind(&user_address)
        .bind(&balance)
        .bind(&tx_hash)
        .execute(&pool)
        .await?;
    Ok(())
}

JSON Serialization (with serde)

use ethereum_mysql::{SqlAddress, SqlU256, sqladdress};
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: u64,
    wallet: SqlAddress,
    balance: SqlU256,
    staked_amount: Option<SqlU256>,
}

let user = User {
    id: 1,
    wallet: sqladdress!("0x742d35Cc6635C0532925a3b8D42cC72b5c2A9A1d"),
    balance: SqlU256::from_str("1500000000000000000").unwrap(),
    staked_amount: Some(SqlU256::from_str("1000000000000000000").unwrap()),
};
let json = serde_json::to_string(&user)?;

Migration from v3.x (String Storage)

Starting from v4.0, this crate uses binary storage (BINARY/BYTEA/BLOB) instead of hex string storage (VARCHAR/TEXT).

Migrating your database schema:

Type v3.x Column v4.0 Column
SqlAddress VARCHAR(42) BINARY(20)
SqlU256 VARCHAR(66) BINARY(32)
SqlFixedBytes<N> VARCHAR(2+2*N) BINARY(N)
SqlBytes TEXT VARBINARY

To migrate existing data, use hex-to-binary conversion:

-- MySQL example: migrate VARCHAR(42) address column to BINARY(20)
ALTER TABLE users ADD COLUMN wallet_address_bin BINARY(20);
UPDATE users SET wallet_address_bin = UNHEX(SUBSTRING(wallet_address, 3));
ALTER TABLE users DROP COLUMN wallet_address;
ALTER TABLE users CHANGE wallet_address_bin wallet_address BINARY(20);

License

Licensed under either of:

at your option.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.