Developer mode (--dev) starts Geth as a private single-node network optimized for local development. It provides:
- A pre-funded developer account with no password
- Mining on demand — blocks are only produced when there are pending transactions
- An ephemeral chain that starts fresh on every restart (unless
--datadir is set)
- A low chain ID (
1337) that MetaMask and most tooling recognize
Starting dev mode
Geth will print the address of the pre-funded account and the IPC endpoint:
INFO [..] Using developer account address=0x...
INFO [..] IPC endpoint opened url=/tmp/geth.ipc
Enabling the HTTP-RPC server
geth --dev --http --http.api eth,web3,net,debug
The JSON-RPC server listens on http://127.0.0.1:8545 by default.
Enabling the WebSocket server
geth --dev --ws --ws.api eth,web3,net
Persistent dev node
By default the dev chain is in-memory and disappears when Geth exits. To keep state across restarts, set a data directory:
geth --dev --datadir /tmp/geth-dev
The same pre-funded account is restored on every restart because the genesis is deterministic.
| Setting | Value |
|---|
| Network name | Localhost 8545 |
| RPC URL | http://127.0.0.1:8545 |
| Chain ID | 1337 |
| Currency symbol | ETH |
Start Geth with --http and --http.corsdomain "*" (development only) before connecting MetaMask.
geth --dev \
--http \
--http.api eth,web3,net \
--http.corsdomain "*"
Setting --http.corsdomain "*" allows any website to make RPC calls to your node. Only use it for local development.
Point any tool that accepts an RPC endpoint to http://127.0.0.1:8545. The dev account private key can be retrieved via the IPC console:
geth attach /tmp/geth.ipc
// Inside the Geth console:
eth.accounts
admin.nodeInfo.enode
Simulated backend for Go tests
The ethclient/simulated package provides an in-process simulated blockchain that is suitable for unit and integration tests in Go. It always uses chain ID 1337 and mines blocks only when you call Commit().
Setting up the backend
package mypackage_test
import (
"context"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient/simulated"
)
func TestSimulated(t *testing.T) {
// Generate a key for the funded account.
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
// Pre-fund the account in the genesis block.
alloc := types.GenesisAlloc{
addr: {Balance: big.NewInt(1e18)},
}
// Create the backend.
backend := simulated.NewBackend(alloc)
defer backend.Close()
client := backend.Client()
// Check the balance.
balance, err := client.BalanceAt(context.Background(), addr, nil)
if err != nil {
t.Fatal(err)
}
t.Logf("balance: %s", balance)
// Mine a block.
committedHash := backend.Commit()
t.Logf("committed block hash: %s", committedHash)
}
Backend API
| Method | Description |
|---|
NewBackend(alloc, ...options) | Create a new simulated blockchain with the given genesis allocation |
backend.Client() | Return an ethclient-compatible client for the simulated chain |
backend.Commit() | Seal a block and advance the chain. Returns the new block hash. |
backend.Rollback() | Discard all pending transactions and revert to the last committed state |
backend.Fork(parentHash) | Start a side chain from the given block (for reorg simulation) |
backend.AdjustTime(d) | Advance the block timestamp by d on the next empty block |
backend.Close() | Shut down the backend. Must be called when the test is done. |
Deploying and calling a contract in tests
package mypackage_test
import (
"context"
"math/big"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient/simulated"
"myproject/token" // generated by abigen
)
func TestDeploy(t *testing.T) {
key, _ := crypto.GenerateKey()
addr := crypto.PubkeyToAddress(key.PublicKey)
backend := simulated.NewBackend(types.GenesisAlloc{
addr: {Balance: big.NewInt(1e18)},
})
defer backend.Close()
auth, err := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337))
if err != nil {
t.Fatal(err)
}
auth.GasLimit = 3_000_000
// Deploy using abigen-generated bindings.
_, _, instance, err := token.DeployToken(
auth,
backend.Client(),
"TestToken",
big.NewInt(1000),
)
if err != nil {
t.Fatal(err)
}
// Mine the deployment transaction.
backend.Commit()
// Call a view function.
name, err := instance.Name(&bind.CallOpts{Context: context.Background()})
if err != nil {
t.Fatal(err)
}
if name != "TestToken" {
t.Fatalf("expected TestToken, got %s", name)
}
}
The simulated backend is faster and more deterministic than running a live dev node. Prefer it for unit tests and reserve geth --dev for end-to-end or manual testing scenarios.