From a07c19c84b9101a18e18308ad9085ccde19f0aeb Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Fri, 27 Feb 2026 19:51:28 +0100 Subject: [PATCH 1/5] feat: add ev-dev local chain and txpool transaction support in --dev mode Add ev-dev binary providing a one-command local development chain with pre-funded Hardhat accounts, similar to Anvil or Hardhat Node. Includes genesis configuration and Makefile targets for convenience. Fix payload builder to pull pending transactions from the txpool when Engine API attributes are empty, enabling cast send and other RPC-submitted transactions to be included in blocks during --dev mode. Add DebugNode implementation for EvolveNode supporting local payload attributes builder. --- CHANGELOG.md | 2 + Cargo.lock | 18 +++ Cargo.toml | 1 + Makefile | 8 + bin/ev-dev/Cargo.toml | 48 ++++++ bin/ev-dev/assets/devnet-genesis.json | 115 ++++++++++++++ bin/ev-dev/src/main.rs | 215 ++++++++++++++++++++++++++ crates/node/src/node.rs | 23 ++- crates/node/src/payload_service.rs | 36 ++++- 9 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 bin/ev-dev/Cargo.toml create mode 100644 bin/ev-dev/assets/devnet-genesis.json create mode 100644 bin/ev-dev/src/main.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 161b5ca0..1850a278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `ev-dev` binary (`bin/ev-dev`): one-command local development chain with pre-funded Hardhat accounts, similar to Anvil or Hardhat Node - Transaction sponsor service (`bin/sponsor-service`) for signing EvNode transactions on behalf of users via JSON-RPC proxy ([#141](https://github.com/evstack/ev-reth/pull/141)) ### Fixed +- Payload builder now pulls pending transactions from the txpool in `--dev` mode, fixing `cast send` and other RPC-submitted transactions not being included in blocks - Txpool now uses sponsor balance for pending/queued ordering in sponsored EvNode transactions, and validates executor balance separately for call value transfers ([#141](https://github.com/evstack/ev-reth/pull/141)) ## [0.3.0] - 2026-02-23 diff --git a/Cargo.lock b/Cargo.lock index 51b7e5c7..a2251a9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2916,6 +2916,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "ev-dev" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-signer-local", + "clap", + "ev-node", + "evolve-ev-reth", + "eyre", + "reth-cli-util", + "reth-ethereum-cli", + "serde_json", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "ev-node" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 78d2dc2a..30287c89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "bin/ev-dev", "bin/ev-reth", "crates/common", "crates/ev-primitives", diff --git a/Makefile b/Makefile index d8225799..515a1af1 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,14 @@ run: build-dev run-dev: build-dev RUST_LOG=debug ./$(TARGET_DIR)/debug/$(BINARY_NAME) node +## build-ev-dev: Build the ev-dev binary in release mode +build-ev-dev: + $(CARGO) build --release --bin ev-dev + +## dev-chain: Build and run the local dev chain +dev-chain: build-ev-dev + ./$(TARGET_DIR)/release/ev-dev + ## fmt: Format code using rustfmt (nightly) fmt: $(CARGO) +nightly fmt --all diff --git a/bin/ev-dev/Cargo.toml b/bin/ev-dev/Cargo.toml new file mode 100644 index 00000000..99a72d1a --- /dev/null +++ b/bin/ev-dev/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "ev-dev" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "One-command local dev chain for ev-reth" + +[[bin]] +name = "ev-dev" +path = "src/main.rs" + +[dependencies] +# Core evolve crates +ev-node = { path = "../../crates/node" } +evolve-ev-reth = { path = "../../crates/evolve" } + +# Reth CLI and core dependencies +reth-cli-util.workspace = true +reth-ethereum-cli.workspace = true + +# Alloy dependencies +alloy-signer-local.workspace = true +alloy-primitives.workspace = true + +# Core dependencies +eyre.workspace = true +tracing.workspace = true +tokio = { workspace = true, features = ["full"] } +clap = { workspace = true, features = ["derive", "env"] } +tempfile.workspace = true +serde_json.workspace = true + +[lints] +workspace = true + +[features] +default = ["jemalloc"] + +jemalloc = ["reth-cli-util/jemalloc", "reth-ethereum-cli/jemalloc"] +jemalloc-prof = ["reth-cli-util/jemalloc-prof"] +tracy-allocator = ["reth-cli-util/tracy-allocator"] + +asm-keccak = ["reth-ethereum-cli/asm-keccak"] + +dev = ["reth-ethereum-cli/dev"] diff --git a/bin/ev-dev/assets/devnet-genesis.json b/bin/ev-dev/assets/devnet-genesis.json new file mode 100644 index 00000000..967352d3 --- /dev/null +++ b/bin/ev-dev/assets/devnet-genesis.json @@ -0,0 +1,115 @@ +{ + "config": { + "chainId": 1234, + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "terminalTotalDifficulty": "0x0", + "terminalTotalDifficultyPassed": true, + "evolve": { + "baseFeeSink": "0x00000000000000000000000000000000000000fe", + "baseFeeRedirectActivationHeight": 0, + "baseFeeMaxChangeDenominator": 5000, + "baseFeeElasticityMultiplier": 10, + "initialBaseFeePerGas": 1000000000, + "mintAdmin": "0x000000000000000000000000000000000000Ad00", + "mintPrecompileActivationHeight": 0, + "contractSizeLimit": 131072, + "contractSizeLimitActivationHeight": 0 + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "0x3b9aca00", + "alloc": { + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbcd4042de499d14e55001ccbb24a551f3b954096": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x71be63f3384f5fb98995898a86b02fb2426c5788": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xfabb0ac9d68b0b445fb7357272ff202c5651694a": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x1cbd3b2770909d4e10f157cabc84c7264073c9ec": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xcd3b766ccdd6ae721141f452c550ca635964ce71": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x2546bcd3c84621e976d8185a91a922ae77ecec30": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbda5747bfd65f08deb54cb465eb87d40e51b197e": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdd2fd4581271e230360230f9337d5c0430bf44c0": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x000000000000000000000000000000000000Ad00": { + "balance": "0x0", + "code": "0x60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056fea26469706673582212201029704c8e76cc8133cedd39a8adbebfe979b8809644c7f5e9cff417e23119d464736f6c634300081e0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + } + }, + "0x0101010101010101010101010101010101010101": { + "balance": "0x1" + } + }, + "number": "0x0" +} diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs new file mode 100644 index 00000000..333a17a1 --- /dev/null +++ b/bin/ev-dev/src/main.rs @@ -0,0 +1,215 @@ +//! ev-dev: one-command local dev chain for ev-reth. +//! +//! Spins up a fully functional Evolve chain with funded accounts, +//! similar to Hardhat Node or Anvil. + +#![allow(missing_docs, rustdoc::missing_crate_level_docs)] + +use alloy_signer_local::{coins_bip39::English, MnemonicBuilder}; +use clap::Parser; +use evolve_ev_reth::{ + config::EvolveConfig, + rpc::txpool::{EvolveTxpoolApiImpl, EvolveTxpoolApiServer}, +}; +use reth_ethereum_cli::Cli; +use std::io::Write; +use tracing::info; + +use ev_node::{EvolveArgs, EvolveChainSpecParser, EvolveNode}; + +#[global_allocator] +static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); + +const DEVNET_GENESIS: &str = include_str!("../assets/devnet-genesis.json"); +const HARDHAT_MNEMONIC: &str = "test test test test test test test test test test test junk"; + +/// Local dev chain for ev-reth with pre-funded accounts. +#[derive(Parser, Debug)] +#[command(name = "ev-dev", about = "One-command local Evolve dev chain")] +struct EvDevArgs { + /// Host to bind HTTP/WS RPC server + #[arg(long, default_value = "127.0.0.1")] + host: String, + + /// Port for HTTP/WS RPC server + #[arg(long, default_value_t = 8545)] + port: u16, + + /// Block time in seconds (0 = mine on tx) + #[arg(long, default_value_t = 1)] + block_time: u64, + + /// Suppress the startup banner + #[arg(long, default_value_t = false)] + silent: bool, + + /// Number of accounts to display (max 20) + #[arg(long, default_value_t = 10)] + accounts: usize, +} + +fn derive_keys(count: usize) -> Vec<(String, String)> { + (0..count) + .map(|i| { + let signer = MnemonicBuilder::::default() + .phrase(HARDHAT_MNEMONIC) + .index(i as u32) + .expect("valid derivation index") + .build() + .expect("valid key derivation"); + let address = signer.address(); + let key_bytes = signer.credential().to_bytes(); + ( + format!("{address}"), + format!("0x{}", alloy_primitives::hex::encode(key_bytes)), + ) + }) + .collect() +} + +fn chain_id_from_genesis() -> u64 { + let genesis: serde_json::Value = + serde_json::from_str(DEVNET_GENESIS).expect("valid genesis JSON"); + genesis["config"]["chainId"] + .as_u64() + .expect("genesis must have config.chainId") +} + +fn print_banner(args: &EvDevArgs) { + let count = args.accounts.min(20); + let accounts = derive_keys(count); + + println!(); + println!(r" _ "); + println!(r" | | "); + println!(r" _____ _____ __| | _____ __"); + println!(r" / _ \ \ / /___/ / _` |/ _ \ \ / /"); + println!(r" | __/\ V / | (_| | __/\ V / "); + println!(r" \___| \_/ \__,_|\___| \_/ "); + println!(); + println!(" Evolve Local Development Chain"); + println!(" =============================="); + println!(); + println!("Chain ID: {}", chain_id_from_genesis()); + println!("RPC URL: http://{}:{}", args.host, args.port); + println!( + "Block time: {}", + if args.block_time == 0 { + "auto (mine on tx)".to_string() + } else { + format!("{}s", args.block_time) + } + ); + println!(); + println!("Available Accounts"); + println!("=================="); + for (i, (addr, _)) in accounts.iter().enumerate() { + println!("({i}) {addr} (1000 ETH)"); + } + println!(); + println!("Private Keys"); + println!("=================="); + for (i, (_, key)) in accounts.iter().enumerate() { + println!("({i}) {key}"); + } + println!(); + println!("Mnemonic: {HARDHAT_MNEMONIC}"); + println!("Derivation path: m/44'/60'/0'/0/{{index}}"); + println!(); + println!("WARNING: These accounts and keys are publicly known."); + println!("Any funds sent to them on mainnet WILL BE LOST."); + println!(); +} + +fn main() { + reth_cli_util::sigsegv_handler::install(); + + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + let dev_args = EvDevArgs::parse(); + + if !dev_args.silent { + print_banner(&dev_args); + } + + // Write genesis to a temp file that lives for the process duration + let mut genesis_file = + tempfile::NamedTempFile::new().expect("failed to create temp genesis file"); + genesis_file + .write_all(DEVNET_GENESIS.as_bytes()) + .expect("failed to write genesis"); + let genesis_path = genesis_file + .path() + .to_str() + .expect("valid path") + .to_string(); + + // Use a temp data directory so each run starts with clean state + let datadir = tempfile::TempDir::new().expect("failed to create temp data dir"); + let datadir_path = datadir + .path() + .to_str() + .expect("valid path") + .to_string(); + + let mut args = vec![ + "ev-dev".to_string(), + "node".to_string(), + "--dev".to_string(), + "--chain".to_string(), + genesis_path, + "--datadir".to_string(), + datadir_path, + "--http".to_string(), + "--http.addr".to_string(), + dev_args.host.clone(), + "--http.port".to_string(), + dev_args.port.to_string(), + "--http.api".to_string(), + "eth,net,web3,txpool,debug,trace".to_string(), + "--http.corsdomain".to_string(), + "*".to_string(), + "--ws".to_string(), + "--ws.addr".to_string(), + dev_args.host.clone(), + "--ws.port".to_string(), + dev_args.port.to_string(), + "--ws.api".to_string(), + "eth,net,web3,txpool,debug,trace".to_string(), + "--disable-discovery".to_string(), + "--no-persist-peers".to_string(), + "--port".to_string(), + "0".to_string(), + ]; + + if dev_args.block_time > 0 { + args.push("--dev.block-time".to_string()); + args.push(format!("{}s", dev_args.block_time)); + } + + if let Err(err) = Cli::::try_parse_from(args) + .expect("valid CLI args") + .run(|builder, _evolve_args| async move { + info!("=== EV-DEV: Starting local development chain ==="); + let handle = builder + .node(EvolveNode::new()) + .extend_rpc_modules(move |ctx| { + let evolve_cfg = EvolveConfig::default(); + let evolve_txpool = + EvolveTxpoolApiImpl::new(ctx.pool().clone(), evolve_cfg.max_txpool_bytes); + ctx.modules.merge_configured(evolve_txpool.into_rpc())?; + Ok(()) + }) + .launch_with_debug_capabilities() + .await?; + + info!("=== EV-DEV: Local chain running - RPC ready ==="); + handle.node_exit_future.await + }) + { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +} diff --git a/crates/node/src/node.rs b/crates/node/src/node.rs index 4ee48ae6..9f1e5885 100644 --- a/crates/node/src/node.rs +++ b/crates/node/src/node.rs @@ -6,20 +6,23 @@ use alloy_rpc_types::engine::{ ExecutionPayloadV1, }; use ev_primitives::EvPrimitives; +use reth_engine_local::LocalPayloadAttributesBuilder; use reth_ethereum::{ chainspec::ChainSpec, node::{ - api::{EngineTypes, FullNodeTypes, NodeTypes, PayloadTypes}, + api::{EngineTypes, FullNodeComponents, FullNodeTypes, NodeTypes, PayloadTypes}, builder::{ components::{BasicPayloadServiceBuilder, ComponentsBuilder}, rpc::RpcAddOns, - Node, NodeAdapter, + DebugNode, Node, NodeAdapter, }, node::EthereumNetworkBuilder, }, }; +use reth_payload_primitives::PayloadAttributesBuilder; use reth_primitives_traits::SealedBlock; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use tracing::info; use crate::{ @@ -119,6 +122,22 @@ where } } +impl> DebugNode for EvolveNode { + type RpcBlock = alloy_rpc_types::Block; + + fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> ev_primitives::Block { + let eth_block: reth_ethereum_primitives::Block = + rpc_block.into_consensus().convert_transactions(); + eth_block.map_transactions(ev_primitives::EvTxEnvelope::Ethereum) + } + + fn local_payload_attributes_builder( + chain_spec: &Self::ChainSpec, + ) -> impl PayloadAttributesBuilder<::PayloadAttributes> { + LocalPayloadAttributesBuilder::new(Arc::new(chain_spec.clone())) + } +} + /// Helper logging to announce node startup with args. pub fn log_startup() { info!("=== EV-RETH: Evolve node mode enabled ==="); diff --git a/crates/node/src/payload_service.rs b/crates/node/src/payload_service.rs index e12075a9..48bda8bb 100644 --- a/crates/node/src/payload_service.rs +++ b/crates/node/src/payload_service.rs @@ -55,12 +55,13 @@ impl Default for EvolvePayloadBuilderBuilder { /// The evolve engine payload builder that integrates with the evolve payload builder. #[derive(Debug, Clone)] -pub struct EvolveEnginePayloadBuilder +pub struct EvolveEnginePayloadBuilder where Client: Clone, { pub(crate) evolve_builder: Arc>, pub(crate) config: EvolvePayloadBuilderConfig, + pub(crate) pool: Pool, } impl PayloadBuilderBuilder for EvolvePayloadBuilderBuilder @@ -76,12 +77,12 @@ where + Unpin + 'static, { - type PayloadBuilder = EvolveEnginePayloadBuilder; + type PayloadBuilder = EvolveEnginePayloadBuilder; async fn build_payload_builder( self, ctx: &BuilderContext, - _pool: Pool, + pool: Pool, evm_config: EvolveEvmConfig, ) -> eyre::Result { let chain_spec = ctx.chain_spec(); @@ -114,11 +115,12 @@ where Ok(EvolveEnginePayloadBuilder { evolve_builder, config, + pool, }) } } -impl PayloadBuilder for EvolveEnginePayloadBuilder +impl PayloadBuilder for EvolveEnginePayloadBuilder where Client: reth_ethereum::provider::StateProviderFactory + ChainSpecProvider @@ -127,6 +129,9 @@ where + Send + Sync + 'static, + Pool: TransactionPool> + + Unpin + + 'static, { type Attributes = EvolveEnginePayloadBuilderAttributes; type BuiltPayload = EvBuiltPayload; @@ -174,8 +179,25 @@ where } } + // Use transactions from Engine API attributes if provided, otherwise pull from the pool + // (e.g. in --dev mode where LocalMiner sends empty attributes). + let transactions = if attributes.transactions.is_empty() { + let pool_txs: Vec = self + .pool + .pending_transactions() + .into_iter() + .map(|tx| tx.transaction.clone_into_consensus().into_inner()) + .collect(); + if !pool_txs.is_empty() { + info!(pool_tx_count = pool_txs.len(), "pulling transactions from pool"); + } + pool_txs + } else { + attributes.transactions.clone() + }; + let evolve_attrs = EvolvePayloadAttributes::new( - attributes.transactions.clone(), + transactions, Some(effective_gas_limit), attributes.timestamp(), attributes.prev_randao(), @@ -297,6 +319,8 @@ mod tests { use reth_primitives_traits::SealedHeader; use reth_provider::test_utils::MockEthProvider; use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop}; + use crate::txpool::EvPooledTransaction; + use reth_transaction_pool::noop::NoopTransactionPool; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn try_build_span_has_expected_fields() { @@ -348,6 +372,7 @@ mod tests { let engine_builder = EvolveEnginePayloadBuilder { evolve_builder, config, + pool: NoopTransactionPool::::new(), }; let rpc_attrs = RpcPayloadAttributes { @@ -386,4 +411,5 @@ mod tests { "span missing duration_ms field" ); } + } From a296824d0fb66f8ecaebb1288a3a63daab4412b7 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 2 Mar 2026 11:10:59 +0100 Subject: [PATCH 2/5] fix lint --- bin/ev-dev/src/main.rs | 6 +----- crates/node/src/payload_service.rs | 8 +++++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs index 333a17a1..7396652e 100644 --- a/bin/ev-dev/src/main.rs +++ b/bin/ev-dev/src/main.rs @@ -148,11 +148,7 @@ fn main() { // Use a temp data directory so each run starts with clean state let datadir = tempfile::TempDir::new().expect("failed to create temp data dir"); - let datadir_path = datadir - .path() - .to_str() - .expect("valid path") - .to_string(); + let datadir_path = datadir.path().to_str().expect("valid path").to_string(); let mut args = vec![ "ev-dev".to_string(), diff --git a/crates/node/src/payload_service.rs b/crates/node/src/payload_service.rs index 48bda8bb..b49be30c 100644 --- a/crates/node/src/payload_service.rs +++ b/crates/node/src/payload_service.rs @@ -189,7 +189,10 @@ where .map(|tx| tx.transaction.clone_into_consensus().into_inner()) .collect(); if !pool_txs.is_empty() { - info!(pool_tx_count = pool_txs.len(), "pulling transactions from pool"); + info!( + pool_tx_count = pool_txs.len(), + "pulling transactions from pool" + ); } pool_txs } else { @@ -310,6 +313,7 @@ mod tests { use super::*; use crate::{ config::EvolvePayloadBuilderConfig, executor::EvolveEvmConfig, test_utils::SpanCollector, + txpool::EvPooledTransaction, }; use alloy_primitives::B256; use alloy_rpc_types::engine::PayloadAttributes as RpcPayloadAttributes; @@ -319,7 +323,6 @@ mod tests { use reth_primitives_traits::SealedHeader; use reth_provider::test_utils::MockEthProvider; use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop}; - use crate::txpool::EvPooledTransaction; use reth_transaction_pool::noop::NoopTransactionPool; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -411,5 +414,4 @@ mod tests { "span missing duration_ms field" ); } - } From 9f9b7df328a00321d0982494afe3f052e2d14fad Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 2 Mar 2026 17:19:39 +0100 Subject: [PATCH 3/5] fix: address PR review feedback for ev-dev and payload service - Validate --accounts at parse-time (1..=20) instead of silent clamping - Handle CLI parse errors gracefully instead of panicking with .expect() - Fix displayed account balance: 1000000 ETH (matches genesis) - Record tx_count span after pool fallback so telemetry reflects actual count --- bin/ev-dev/src/main.rs | 30 ++++++++++++++++++++++-------- crates/node/src/payload_service.rs | 4 +++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs index 7396652e..e5996bde 100644 --- a/bin/ev-dev/src/main.rs +++ b/bin/ev-dev/src/main.rs @@ -23,6 +23,15 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::ne const DEVNET_GENESIS: &str = include_str!("../assets/devnet-genesis.json"); const HARDHAT_MNEMONIC: &str = "test test test test test test test test test test test junk"; +fn parse_accounts(s: &str) -> Result { + let n: usize = s.parse().map_err(|e| format!("{e}"))?; + if (1..=20).contains(&n) { + Ok(n) + } else { + Err(format!("{n} is not in 1..=20")) + } +} + /// Local dev chain for ev-reth with pre-funded accounts. #[derive(Parser, Debug)] #[command(name = "ev-dev", about = "One-command local Evolve dev chain")] @@ -43,8 +52,8 @@ struct EvDevArgs { #[arg(long, default_value_t = false)] silent: bool, - /// Number of accounts to display (max 20) - #[arg(long, default_value_t = 10)] + /// Number of accounts to display (1..=20) + #[arg(long, default_value_t = 10, value_parser = parse_accounts)] accounts: usize, } @@ -76,8 +85,7 @@ fn chain_id_from_genesis() -> u64 { } fn print_banner(args: &EvDevArgs) { - let count = args.accounts.min(20); - let accounts = derive_keys(count); + let accounts = derive_keys(args.accounts); println!(); println!(r" _ "); @@ -104,7 +112,7 @@ fn print_banner(args: &EvDevArgs) { println!("Available Accounts"); println!("=================="); for (i, (addr, _)) in accounts.iter().enumerate() { - println!("({i}) {addr} (1000 ETH)"); + println!("({i}) {addr} (1000000 ETH)"); } println!(); println!("Private Keys"); @@ -185,9 +193,15 @@ fn main() { args.push(format!("{}s", dev_args.block_time)); } - if let Err(err) = Cli::::try_parse_from(args) - .expect("valid CLI args") - .run(|builder, _evolve_args| async move { + let cli = match Cli::::try_parse_from(args) { + Ok(cli) => cli, + Err(err) => { + eprintln!("{err}"); + std::process::exit(2); + } + }; + + if let Err(err) = cli.run(|builder, _evolve_args| async move { info!("=== EV-DEV: Starting local development chain ==="); let handle = builder .node(EvolveNode::new()) diff --git a/crates/node/src/payload_service.rs b/crates/node/src/payload_service.rs index b49be30c..dc2e8de5 100644 --- a/crates/node/src/payload_service.rs +++ b/crates/node/src/payload_service.rs @@ -137,7 +137,7 @@ where type BuiltPayload = EvBuiltPayload; #[instrument(skip(self, args), fields( - tx_count = args.config.attributes.transactions.len(), + tx_count = tracing::field::Empty, payload_id = %args.config.attributes.payload_id(), duration_ms = tracing::field::Empty, ))] @@ -199,6 +199,8 @@ where attributes.transactions.clone() }; + tracing::Span::current().record("tx_count", transactions.len()); + let evolve_attrs = EvolvePayloadAttributes::new( transactions, Some(effective_gas_limit), From 0429267c9dd1c71569e85778e0c52b426755afea Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Mon, 2 Mar 2026 17:32:01 +0100 Subject: [PATCH 4/5] fix: correct closure indentation for rustfmt --- bin/ev-dev/src/main.rs | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs index e5996bde..7ed4e653 100644 --- a/bin/ev-dev/src/main.rs +++ b/bin/ev-dev/src/main.rs @@ -202,23 +202,22 @@ fn main() { }; if let Err(err) = cli.run(|builder, _evolve_args| async move { - info!("=== EV-DEV: Starting local development chain ==="); - let handle = builder - .node(EvolveNode::new()) - .extend_rpc_modules(move |ctx| { - let evolve_cfg = EvolveConfig::default(); - let evolve_txpool = - EvolveTxpoolApiImpl::new(ctx.pool().clone(), evolve_cfg.max_txpool_bytes); - ctx.modules.merge_configured(evolve_txpool.into_rpc())?; - Ok(()) - }) - .launch_with_debug_capabilities() - .await?; - - info!("=== EV-DEV: Local chain running - RPC ready ==="); - handle.node_exit_future.await - }) - { + info!("=== EV-DEV: Starting local development chain ==="); + let handle = builder + .node(EvolveNode::new()) + .extend_rpc_modules(move |ctx| { + let evolve_cfg = EvolveConfig::default(); + let evolve_txpool = + EvolveTxpoolApiImpl::new(ctx.pool().clone(), evolve_cfg.max_txpool_bytes); + ctx.modules.merge_configured(evolve_txpool.into_rpc())?; + Ok(()) + }) + .launch_with_debug_capabilities() + .await?; + + info!("=== EV-DEV: Local chain running - RPC ready ==="); + handle.node_exit_future.await + }) { eprintln!("Error: {err:?}"); std::process::exit(1); } From 770a6242dae9d0198f6fc285e70ff4f7d6f59a81 Mon Sep 17 00:00:00 2001 From: Randy Grok Date: Tue, 3 Mar 2026 11:14:52 +0100 Subject: [PATCH 5/5] docs: add ev-dev README with usage guide and tool integrations --- README.md | 11 ++- bin/ev-dev/README.md | 212 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 bin/ev-dev/README.md diff --git a/README.md b/README.md index 5f1af9d7..d39ea6f6 100644 --- a/README.md +++ b/README.md @@ -482,10 +482,17 @@ All standard Reth configuration options are supported. Key options for Evolve in ``` ev-reth/ ├── bin/ -│ └── ev-reth/ # Main binary +│ ├── ev-reth/ # Main binary +│ │ ├── Cargo.toml +│ │ └── src/ +│ │ └── main.rs # Binary with Engine API integration +│ └── ev-dev/ # Local dev chain (like Hardhat Node / Anvil) │ ├── Cargo.toml +│ ├── README.md # Usage guide with tool integrations +│ ├── assets/ +│ │ └── devnet-genesis.json │ └── src/ -│ └── main.rs # Binary with Engine API integration +│ └── main.rs ├── crates/ │ ├── common/ # Shared utilities and constants │ │ ├── Cargo.toml diff --git a/bin/ev-dev/README.md b/bin/ev-dev/README.md new file mode 100644 index 00000000..39a615f2 --- /dev/null +++ b/bin/ev-dev/README.md @@ -0,0 +1,212 @@ +# ev-dev + +One-command local development chain for Evolve. Think of it as the Evolve equivalent of [Hardhat Node](https://hardhat.org/hardhat-network/docs/overview) or [Anvil](https://book.getfoundry.sh/reference/anvil/). + +## Quick Start + +```bash +# Build and run +just dev-chain + +# Or build separately +just build-ev-dev +./target/release/ev-dev +``` + +The chain starts immediately with 10 pre-funded accounts, each holding 1,000,000 ETH. + +## CLI Options + +``` +ev-dev [OPTIONS] +``` + +| Flag | Default | Description | +|------|---------|-------------| +| `--host` | `127.0.0.1` | Host to bind HTTP/WS RPC server | +| `--port` | `8545` | Port for HTTP/WS RPC server | +| `--block-time` | `1` | Block time in seconds (`0` = mine on transaction) | +| `--silent` | `false` | Suppress the startup banner | +| `--accounts` | `10` | Number of accounts to display (1-20) | + +### Examples + +```bash +# Mine blocks only when transactions arrive +ev-dev --block-time 0 + +# Listen on all interfaces (useful inside Docker/VMs) +ev-dev --host 0.0.0.0 + +# Custom port, faster blocks +ev-dev --port 9545 --block-time 2 +``` + +## Chain Details + +| Property | Value | +|----------|-------| +| Chain ID | `1234` | +| Gas limit | 30,000,000 | +| Base fee | 1 Gwei | +| Contract size limit | 128 KB | +| Hardforks | All enabled at genesis (through Cancun) | + +## Pre-funded Accounts + +Accounts are derived from the standard Hardhat mnemonic: + +``` +test test test test test test test test test test test junk +``` + +Derivation path: `m/44'/60'/0'/0/{index}` + +| # | Address | Private Key | +|---|---------|-------------| +| 0 | `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` | `0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80` | +| 1 | `0x70997970C51812dc3A010C7d01b50e0d17dc79C8` | `0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d` | +| 2 | `0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC` | `0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a` | +| 3 | `0x90F79bf6EB2c4f870365E785982E1f101E93b906` | `0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6` | +| 4 | `0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65` | `0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a` | +| 5 | `0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc` | `0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba` | +| 6 | `0x976EA74026E726554dB657fA54763abd0C3a0aa9` | `0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e` | +| 7 | `0x14dC79964da2C08dba798bBb5d93A585CAa97F90` | `0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356` | +| 8 | `0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f` | `0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97` | +| 9 | `0xa0Ee7A142d267C1f36714E4a8F75612F20a79720` | `0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6` | + +> **WARNING**: These accounts and their private keys are publicly known. Any funds sent to them on a real network **will be lost**. + +## Using with Common Tools + +### Foundry (cast / forge) + +```bash +# Check balance +cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:8545 + +# Send ETH +cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \ + --value 1ether \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --rpc-url http://127.0.0.1:8545 + +# Deploy a contract +forge create src/MyContract.sol:MyContract \ + --rpc-url http://127.0.0.1:8545 \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +# Run Forge tests against ev-dev +forge test --fork-url http://127.0.0.1:8545 +``` + +### Hardhat + +In `hardhat.config.js`: + +```js +module.exports = { + networks: { + evdev: { + url: "http://127.0.0.1:8545", + chainId: 1234, + accounts: [ + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + ], + }, + }, +}; +``` + +```bash +npx hardhat run scripts/deploy.js --network evdev +``` + +### ethers.js / viem + +```js +// ethers.js v6 +import { JsonRpcProvider, Wallet } from "ethers"; + +const provider = new JsonRpcProvider("http://127.0.0.1:8545"); +const wallet = new Wallet( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + provider +); +``` + +```js +// viem +import { createWalletClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { defineChain } from "viem"; + +const evdev = defineChain({ + id: 1234, + name: "ev-dev", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { + default: { http: ["http://127.0.0.1:8545"] }, + }, +}); + +const account = privateKeyToAccount( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +); +const client = createWalletClient({ account, chain: evdev, transport: http() }); +``` + +### curl (raw JSON-RPC) + +```bash +# Get block number +curl -s http://127.0.0.1:8545 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' + +# Get chain ID +curl -s http://127.0.0.1:8545 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' + +# Get pending transactions from txpool +curl -s http://127.0.0.1:8545 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"txpoolExt_getTxs","params":[],"id":1}' +``` + +## Available RPC Namespaces + +The following namespaces are enabled by default over both HTTP and WebSocket: + +- `eth` — standard Ethereum JSON-RPC +- `net` — network info +- `web3` — client version, SHA3 +- `txpool` — transaction pool inspection +- `debug` — debug namespace (traceCall, traceTransaction, etc.) +- `trace` — OpenEthereum-compatible trace namespace + +Additionally, the custom `txpoolExt` namespace is available: + +- `txpoolExt_getTxs` — returns pending transactions as RLP-encoded bytes + +## Evolve-specific Features + +ev-dev includes all Evolve customizations out of the box: + +- **Base fee redirect**: Base fees are sent to `0x00...00fe` instead of being burned +- **128 KB contract size limit**: Deploy contracts up to 128 KB (vs Ethereum's 24 KB) +- **Mint precompile**: Native minting precompile is active, admin is account `0` (`0xf39F...2266`) +- **EvNode transactions (type 0x76)**: Batch calls and sponsored transactions are supported + +## How It Works + +ev-dev is a thin wrapper around the full `ev-reth` node. On startup it: + +1. Writes the embedded devnet genesis to a temp file +2. Creates a temporary data directory (clean state every run) +3. Launches `ev-reth` in `--dev` mode with networking disabled +4. Exposes HTTP and WebSocket RPC on the configured host/port + +Each run starts from a fresh genesis — there is no persistent state between restarts.