Nethereum.DevChain.Server
NuGet:
Nethereum.DevChain.Server| Source:src/Nethereum.DevChain.Server/A local Ethereum development node with JSON-RPC server. Similar to Hardhat Network or Anvil but built entirely in .NET. Supports EVM opcodes up to the Amsterdam hardfork.
Overview
- HTTP JSON-RPC server on configurable port
- Pre-funded accounts from HD wallet (default mnemonic compatible with Hardhat/Anvil)
- Instant transaction mining (auto-mine) or configurable block intervals
- Full EVM execution up to Amsterdam hardfork with transaction tracing
- SQLite storage by default with auto-cleanup on exit
- State forking from live networks
- Hardhat and Anvil-compatible RPC methods
- Account impersonation registration (
hardhat_impersonateAccount; no signing support)
Installation
As a .NET Global Tool
dotnet tool install -g Nethereum.DevChain.Server
Then run:
nethereum-devchain
From Source
dotnet run --project src/Nethereum.DevChain.Server
Quick Start
# Start with defaults (port 8545, 10 accounts, SQLite, auto-mine)
nethereum-devchain
# Custom port and more accounts
nethereum-devchain -p 8546 -a 20
# Set account balance to 100 ETH
nethereum-devchain -e 100
# Interval mining (1 block per second)
nethereum-devchain -b 1000
# Fork mainnet
nethereum-devchain -f https://eth.llamarpc.com --fork-block 19000000
# Persist chain data between restarts
nethereum-devchain --persist ./mychain
# Use in-memory storage (no SQLite)
nethereum-devchain --in-memory
Command-Line Options
USAGE: nethereum-devchain [OPTIONS]
SERVER:
-p, --port <PORT> Port to listen on (default: 8545)
--host <HOST> Host to bind to (default: 127.0.0.1)
-v, --verbose Enable verbose RPC logging
ACCOUNTS:
-a, --accounts <NUM> Number of accounts to generate (default: 10)
-m, --mnemonic <MNEMONIC> HD wallet mnemonic phrase
-e, --balance <ETH> Account balance in ETH (default: 10000)
CHAIN:
-c, --chain-id <ID> Chain ID (default: 31337)
-b, --block-time <MS> Block time in ms, 0 = auto-mine (default: 0)
--gas-limit <GAS> Block gas limit (default: 30000000)
--hardfork <NAME> Hardfork to run (default: amsterdam)
FORK:
-f, --fork <URL> Fork from a remote RPC endpoint
--fork-block <NUMBER> Fork at a specific block number
STORAGE:
--persist [DIR] Persist chain data to disk (default: ./chaindata)
--in-memory Use in-memory storage instead of SQLite
--rocksdb Use RocksDB storage with path-based state, same as mainnet
--data-dir <DIR> Data directory for --rocksdb (default: ./chaindata)
Default storage is SQLite with auto-cleanup on exit. Use --persist to keep data between restarts.
The block gas limit defaults to 30000000 (set in appsettings.json). If BlockGasLimit is left unset (no config value and no --gas-limit), it becomes fork-derived (~130,270,080 at the Amsterdam fork).
Storage Modes
| Mode | Flag | Blocks/TX/Receipts/Logs | State/Filters/Trie | Cleanup |
|---|---|---|---|---|
| SQLite (default) | (none) | SQLite (temp file) | SQLite-backed state + trie; filters in-memory | Auto-delete on exit |
| SQLite persistent | --persist | SQLite (./chaindata) | SQLite-backed state + trie; filters in-memory | Kept between restarts |
| In-memory | --in-memory | In-Memory | In-Memory (state, trie, filters) | Lost on exit |
| RocksDB | --rocksdb | RocksDB (./chaindata or --data-dir) | Path-based state + trie, same as mainnet; filters in-memory | Kept between restarts |
SQLite uses WAL journal mode for good read/write concurrency. In SQLite and RocksDB modes, state and trie nodes are persisted to the backing store (not held in memory); snapshot/revert is diff-based through HistoricalStateStore rather than by retaining full state in memory. The filter registry (eth_newFilter etc.) is always in-memory.
Startup Banner
_ _ _ _
| \ | | ___| |_| |__ ___ _ __ ___ _ _ _ __ ___
| \| |/ _ \ __| '_ \ / _ \ '__/ _ \ | | | '_ ` _ \
| |\ | __/ |_| | | | __/ | | __/ |_| | | | | | |
|_| \_|\___|\__|_| |_|\___|_| \___|\__,_|_| |_| |_|
DevChain Server v7.1.0
RPC: http://127.0.0.1:8545
Chain ID: 31337
Hardfork: amsterdam
Gas Limit: 30,000,000
Mining: auto-mine (instant)
Storage: SQLite (auto-cleanup on exit)
Available Accounts
==================
(0) 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000 ETH)
(1) 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000 ETH)
...
Private Keys
============
(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
...
HD Wallet
=========
Mnemonic: test test test test test test test test test test test junk
Derivation Path: m/44'/60'/0'/0/x
Configuration File
Create appsettings.json in the working directory:
{
"DevChain": {
"Port": 8545,
"Host": "127.0.0.1",
"ChainId": 31337,
"BlockGasLimit": 30000000,
"AutoMine": true,
"BlockTime": 0,
"AccountCount": 10,
"Mnemonic": "test test test test test test test test test test test junk",
"Verbose": false,
"Storage": "sqlite",
"Persist": false,
"DataDir": "./chaindata",
"Fork": {
"Url": null,
"BlockNumber": null
}
}
}
Supported RPC Methods
Standard Ethereum
| Method | Description |
|---|---|
web3_clientVersion | Client version |
web3_sha3 | Keccak-256 hash |
net_version | Network ID |
net_listening | Listening status |
net_peerCount | Peer count |
eth_chainId | Chain ID |
eth_blockNumber | Current block number |
eth_gasPrice | Gas price |
eth_maxPriorityFeePerGas | Priority fee |
eth_feeHistory | Fee history |
eth_blobBaseFee | Current blob base fee |
eth_getBalance | Account balance |
eth_getCode | Contract code |
eth_getStorageAt | Storage value |
eth_getTransactionCount | Account nonce |
eth_getBlockByNumber | Block by number |
eth_getBlockByHash | Block by hash |
eth_getBlockReceipts | All receipts in block |
eth_getBlockAccessList | Block-level access list |
eth_getTransactionByHash | Transaction by hash |
eth_getTransactionReceipt | Transaction receipt |
eth_sendRawTransaction | Submit transaction |
eth_call | Execute call |
eth_estimateGas | Estimate gas |
eth_createAccessList | Generate access list |
eth_getLogs | Query logs |
eth_getProof | Merkle proof |
eth_accounts | List funded accounts |
eth_coinbase | Coinbase address |
eth_syncing | Sync status |
eth_mining | Mining status |
eth_getBlockTransactionCountByHash | Transaction count in block by hash |
eth_getBlockTransactionCountByNumber | Transaction count in block by number |
eth_getUncleCountByBlockHash | Uncle count in block by hash |
eth_getUncleCountByBlockNumber | Uncle count in block by number |
eth_getTransactionByBlockHashAndIndex | Transaction by block hash and index |
eth_getTransactionByBlockNumberAndIndex | Transaction by block number and index |
eth_newFilter | Create log filter |
eth_newBlockFilter | Create new block filter |
eth_getFilterChanges | Poll filter for changes |
eth_getFilterLogs | Get all logs for filter |
eth_uninstallFilter | Remove a filter |
eth_simulateV1 | Simulate calls over blocks with state overrides |
eth_config | Active fork configuration |
eth_capabilities | Data-availability capabilities (blocks, logs, receipts, state, tx) |
Transaction Pool
| Method | Description |
|---|---|
txpool_status | Pending/queued transaction counts |
txpool_content | Pending/queued transactions |
txpool_contentFrom | Pending/queued transactions for an address |
Development
| Method | Description |
|---|---|
evm_mine | Mine a block |
evm_snapshot | Create state snapshot |
evm_revert | Revert to snapshot |
evm_increaseTime | Advance block time |
evm_setNextBlockTimestamp | Set next block timestamp |
Testing
| Method | Description |
|---|---|
testing_buildBlockV1 | Build a block on the current head from payload attributes and return it as a getPayload response, without persisting it or moving the head. The given transactions are kept in order and all must apply (else -32000); a null list takes the pool. parentHash must be the current head |
testing_commitBlockV1 | Build a block the same way, insert it, make it the canonical head and return its hash. An unapplicable transaction is an error and the head stays; a null list takes the pool and drains it |
Account Management
| Method | Description |
|---|---|
hardhat_setBalance | Set account balance |
hardhat_setCode | Set contract code |
hardhat_setNonce | Set account nonce |
hardhat_setStorageAt | Set storage slot |
hardhat_impersonateAccount | Record an address as impersonated (no signing support) |
hardhat_stopImpersonatingAccount | Stop impersonating |
Debug
| Method | Description |
|---|---|
debug_traceTransaction | Trace mined transaction |
debug_traceCall | Trace call without mining |
debug_traceBlockByNumber | Trace all transactions in a block by number |
debug_traceBlockByHash | Trace all transactions in a block by hash |
debug_getRawHeader | RLP-encoded block header |
debug_getRawBlock | RLP-encoded block |
debug_getRawReceipts | RLP-encoded receipts for a block |
debug_getRawTransaction | RLP-encoded transaction |
debug_setHead | Rewind the chain to an earlier block number (a future block is refused) |
Anvil Aliases
The following anvil_* aliases are registered for Foundry/Anvil compatibility:
| Anvil Method | Routes To |
|---|---|
anvil_setBalance | hardhat_setBalance |
anvil_setCode | hardhat_setCode |
anvil_setNonce | hardhat_setNonce |
anvil_setStorageAt | hardhat_setStorageAt |
anvil_mine | evm_mine |
anvil_snapshot | evm_snapshot |
anvil_revert | evm_revert |
anvil_impersonateAccount | hardhat_impersonateAccount |
anvil_stopImpersonatingAccount | hardhat_stopImpersonatingAccount |
Usage Examples
Connect with Nethereum (C#)
using Nethereum.Web3;
using Nethereum.Web3.Accounts;
var account = new Account(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
31337);
var web3 = new Web3(account, "http://127.0.0.1:8545");
var balance = await web3.Eth.GetBalance.SendRequestAsync(account.Address);
Console.WriteLine($"Balance: {Web3.Convert.FromWei(balance.Value)} ETH");
Connect with ethers.js
const { ethers } = require("ethers");
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const wallet = new ethers.Wallet(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
provider);
const balance = await provider.getBalance(wallet.address);
console.log(`Balance: ${ethers.formatEther(balance)} ETH`);
Deploy Contract
var receipt = await web3.Eth.DeployContract.SendRequestAndWaitForReceiptAsync(
abi, bytecode, account.Address, new HexBigInteger(3000000));
Console.WriteLine($"Deployed at: {receipt.ContractAddress}");
Fork Mainnet
nethereum-devchain -f https://eth.llamarpc.com --fork-block 19000000
When forking:
- State is fetched on-demand from the fork source
- Local transactions modify local state only
- Original fork state remains accessible
Time Manipulation
// Advance 1 day
await provider.send("evm_increaseTime", [86400]);
await provider.send("evm_mine", []);
// Set specific timestamp
await provider.send("evm_setNextBlockTimestamp", [1700000000]);
await provider.send("evm_mine", []);
Snapshots
const snapshotId = await provider.send("evm_snapshot", []);
// ... make changes ...
await provider.send("evm_revert", [snapshotId]);
Account Impersonation
await provider.send("hardhat_impersonateAccount", ["0xWhaleAddress"]);
await provider.send("hardhat_stopImpersonatingAccount", ["0xWhaleAddress"]);
hardhat_impersonateAccount / hardhat_stopImpersonatingAccount are accepted and record the address; DevChain does not sign or send transactions for impersonated accounts (no eth_sendTransaction handler).
Trace Transaction
const trace = await provider.send("debug_traceTransaction", [
txHash,
{ enableMemory: true, disableStack: false }
]);
console.log(`Gas: ${trace.gas}, Steps: ${trace.structLogs.length}`);
HTTP Endpoints
| Method | Path | Description |
|---|---|---|
| POST | / | JSON-RPC endpoint (single or batch requests) |
| GET | / | Health check: {"status":"ok","service":"Nethereum.DevChain"} |
| GET | /eth/v1/beacon/blob_sidecars/{blockId} | Beacon blob sidecars for a block (EIP-4844) |
Max request body: 10MB. CORS enabled (any origin).
Default Accounts
Using the default mnemonic test test test test test test test test test test test junk:
| Index | Address | Private Key |
|---|---|---|
| 0 | 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 |
| 1 | 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d |
| 2 | 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC | 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a |
| 3 | 0x90F79bf6EB2c4f870365E785982E1f101E93b906 | 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 |
| 4 | 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 | 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a |
Each account is funded with 10,000 ETH by default. The mnemonic and derivation path (m/44'/60'/0'/0/x) are the same as Hardhat and Anvil.
Architecture
┌─────────────────────────────────────────────────────────┐
│ HTTP Server (ASP.NET Minimal) │
├─────────────────────────────────────────────────────────┤
│ RpcDispatcher │
├──────────┬──────────┬──────────┬──────────┬─────────────┤
│ Standard │ Dev │ Debug │ Hardhat │ Anvil │
│ Handlers │ Handlers │ Handlers │ Handlers │ Aliases │
├─────────────────────────────────────────────────────────┤
│ DevChainNode │
├──────────┬──────────────────────┬───────────────────────┤
│ Block │ Transaction │ State │
│ Producer │ Processor │ Store │
├──────────┴──────────────────────┴───────────────────────┤
│ EVMSimulator (Nethereum.EVM) │
├─────────────────────────────────────────────────────────┤
│ Backing store: SQLite (default) / In-Memory / RocksDB │
└─────────────────────────────────────────────────────────┘
Standard Dev Chain Features
Nethereum DevChain implements the standard feature set expected from Ethereum development nodes (Hardhat Network, Anvil, etc.):
- Fork support from live networks
- Transaction tracing (
debug_traceTransaction,debug_traceCall) - Account impersonation registration (no signing support)
- State snapshots and revert
evm_*andhardhat_*RPC methods- Same default mnemonic and derivation path as Hardhat/Anvil
- Pre-funded accounts with configurable balances
Additionally, Nethereum DevChain provides:
- Native .NET integration - embed directly in .NET applications and tests
- SQLite default storage - bounded memory usage for long-running sessions
- Persistent storage - keep chain state between restarts with
--persist - Amsterdam hardfork support - latest EVM opcode support
Aspire Integration
DevChain integrates with .NET Aspire for orchestrated development environments:
// AppHost/Program.cs
var devchain = builder.AddProject<Projects.Nethereum_Aspire_DevChain>("devchain");
var indexer = builder.AddProject<Projects.Nethereum_Aspire_Indexer>("indexer")
.WithReference(devchain)
.WaitFor(devchain);
The Aspire DevChain project uses the reusable hosting extensions from Nethereum.DevChain.Hosting:
// DevChain/Program.cs
using Nethereum.DevChain.Configuration;
using Nethereum.DevChain.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
var config = new DevChainServerConfig();
builder.Configuration.GetSection("DevChain").Bind(config);
builder.AddDevChainServer(config);
var app = builder.Build();
await app.MapDevChainEndpointsAsync();
app.MapDefaultEndpoints();
app.Run();
Install the Aspire template: dotnet new install Nethereum.Aspire.TemplatePack
Embedding in .NET Applications
Use AddDevChainServer and MapDevChainEndpointsAsync directly in any ASP.NET Core application:
using Nethereum.DevChain.Configuration;
using Nethereum.DevChain.Hosting;
var config = new DevChainServerConfig { ChainId = 31337, Storage = "memory" };
builder.AddDevChainServer(config);
// ...
await app.MapDevChainEndpointsAsync();
AddDevChainServer registers DevChainNode, RpcDispatcher, DevAccountManager, CORS, storage providers, and DevChainHostedService as singletons.
Related Packages
- Nethereum.DevChain - Core development chain library
- Nethereum.CoreChain - Blockchain infrastructure and storage interfaces
- Nethereum.EVM - EVM simulator
- Nethereum.Geth - typed clients for the admin, debug and miner RPC namespaces