Load testing for blockchain infrastructure — the way k6 does it for APIs.
Web3Load is an open-source, single-binary load testing framework built for Web3: RPC nodes, smart contracts, and real user flows like swaps, mints, and transfers — not generic HTTP requests wearing a blockchain costume.
Wallets, nonces, gas, and transaction confirmation are first-class concepts in the scenario language and the execution engine, not something you script by hand on top of ethers.js/web3.py every time.
They treat an RPC call as just another HTTP request. Web3Load knows the difference between a transaction being submitted and being confirmed, manages nonces safely across thousands of concurrent wallets without collisions, and reports gas economics and revert rates as first-class metrics — not something you bolt on yourself in every project.
See docs/architecture for the full design rationale.
v0.3 — EVM only, tested against Anvil. All six load models (constant/ramping/spike/stress/soak/arrival-rate), encrypted keystores, structured logs, OpenTelemetry tracing, a Grafana dashboard, HTML reports, fully-enforced dry-run/gas-price safety limits, distributed mode (controller/worker across multiple processes — docs/distributed.md), and a subprocess plugin system (docs/plugins.md). A Solana adapter was deliberately deferred to v1.0 rather than shipped unverified — see the roadmap.
# 1. local EVM node
anvil
# 2. generate + fund test wallets
go run ./cmd/web3load wallets generate --count 20 --out wallets.json
go run ./cmd/web3load wallets fund \
--wallets wallets.json \
--from 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--native 1000000000000000000
# 3. validate and run a scenario
go run ./cmd/web3load validate examples/native_transfer.yaml
go run ./cmd/web3load run examples/native_transfer.yaml \
--wallets wallets.json \
--out results.json
# 4. re-render a saved result
go run ./cmd/web3load report results.json--from above is Anvil's well-known default account #0 — never use it, or
any key committed to a repo, on a network holding real funds.
web3load controller run scenario.yaml --wallets wallets.json --workers 3 --listen :7700
# on each worker:
web3load worker --controller http://<controller-host>:7700Each worker gets a disjoint slice of the wallets and a proportionally
scaled-down load target, runs the same engine run does, and streams
progress back for the controller to aggregate. See
docs/distributed.md — including why this is
HTTP/JSON rather than gRPC, and why it needs a trusted network.
go build -o deadline ./examples/plugins/deadline
web3load run examples/plugin_deadline.yaml --plugin deadline=./deadlineA plugin is a subprocess speaking one line of JSON in, one line of JSON out — any language, no SDK. It returns either a transaction spec (the host builds, signs, and submits it — a plugin never sees a private key) or a plain result. See docs/plugins.md.
load:
type: ramping
stages:
- { duration: 2m, target: 10 }
- { duration: 6m, target: 1000 }
steps:
- action: approve
token: ${usdc}
spender: ${router}
amount: "1000000000"
- action: contract_call
contract: ${router}
abi_file: abis/UniswapV2Router.json
method: swapExactTokensForTokens
args: [100000000, 0, ["${usdc}", "${weth}"], "${wallet.address}", 1893456000]
assertions:
- transaction_success_rate > 99%
- rpc_p95 < 500ms
- confirmation_p95 < 30scontract_call is the general escape hatch: any ABI method on any contract
can be driven from a scenario without a core code change. approve and
transfer are just pre-baked conveniences over the same mechanism. See
docs/dsl-reference.md for the full schema.
web3load validate/run/wallets generate/wallets fund/reportweb3load controller run/worker— shard a scenario and its wallets across N processes; see docs/distributed.md- Load models:
constant,ramping,spike,stress,soak,arrival-rate— see docs/dsl-reference.md - Actions:
get_balance,transfer,erc20_transfer,approve,contract_call, each retryable via a per-stepretrypolicy that's safe against double-broadcast - EVM chain adapter (works against any EVM-compatible RPC, tested on Anvil)
- Nonce management safe under concurrent virtual users, with automatic resync on a nonce mismatch
- Plaintext or encrypted (scrypt + AES-256-GCM) keystores
--dry-runthat never broadcasts, andsafety.max_gas_price_gweithat clamps the fee cap rather than just rejecting- Console, JSON, and self-contained HTML reports (
--html), scenario assertions with pass/fail exit code - Structured logs (
--log-level,--log-format) and periodic progress snapshots (--progress-interval) — see docs/observability.md - Prometheus
/metricsendpoint with an auto-provisioned Grafana dashboard (deploy/grafana), and OpenTelemetry trace export (--otel-endpoint) - Subprocess plugins (
--plugin name=path) for custom step logic beyond whatcontract_callexpresses — see docs/plugins.md
Not yet: non-EVM chains. See the roadmap.
| Version | Focus |
|---|---|
| v0.1 | MVP: constant/ramping load, EVM, wallet+nonce management, core actions |
| v0.2 | ✅ all six load models, encrypted keystores, retry policies, structured logs, OpenTelemetry tracing, Grafana dashboard |
| v0.3 | ✅ HTML reports, full dry-run/gas-price enforcement, distributed mode, plugin system |
| v1.0 | Generalize chain.Adapter off EVM-specific types, Solana adapter (verified against a real devnet/test-validator, not shipped speculatively), stable DSL/schema, adapter conformance suite, docs site |
Private keys never appear in logs or scenario files — only keystore
references. Keystores are plaintext by default; pass --password (or set
WEB3LOAD_KEYSTORE_PASSWORD) to wallets generate to encrypt at rest
(scrypt + AES-256-GCM). Either way, treat wallets.json like a secret.
production environments require an explicit typed confirmation before any
transaction is broadcast. See docs/security.md.
New chains and actions plug in through the chain.Adapter and
action.Action interfaces — no core engine changes required. See
docs/adapters.md and CONTRIBUTING.md.