Build a Besu Supply Chain PoC with Claude Code and ChainLaunch
Written by David Viejo
Last verified: March 2026 | Besu 24.x / Solidity 0.8.x / ChainLaunch latest
Building a blockchain proof-of-concept used to mean days of manual configuration. You'd wrestle with genesis files, fumble through P2P discovery, debug EVM opcode mismatches, and still end up with a fragile testnet nobody wanted to maintain. According to Hyperledger Foundation's 2025 Annual Report, Besu adoption grew 38% year-over-year among enterprise users — yet most PoCs still stall at the infrastructure phase. What if you could go from zero to a working supply chain PoC on Besu in under 10 minutes?
That's what happens when you combine ChainLaunch's automated network provisioning with Claude Code's ability to write, compile, and deploy smart contracts from plain English prompts. The entire workflow — testnet creation, contract development, deployment, and lifecycle demo — collapses into a single conversation.
Most enterprise blockchain PoCs stall at the infrastructure phase. Teams spend so long debugging genesis configs and networking that stakeholders never see a working system. The tooling gap between "I want to test this idea" and "I have a running demo" kills momentum before it starts.
TL;DR: Claude Code + ChainLaunch spins up a 4-node Besu QBFT testnet, verifies it with ethers.js (contract deploy, state read/write, ETH transfers), then deploys a full supply chain contract with lifecycle operations — all from natural language prompts. You get a production-shaped PoC in minutes, not days.
A single command creates your entire 4-node QBFT testnet. ChainLaunch generates validator keys, builds the genesis block, configures P2P discovery, and starts all nodes as managed services. Manually configuring a QBFT network requires at minimum 8 separate configuration files per the Hyperledger Besu documentation — ChainLaunch reduces that to one CLI call.
First, find your key provider ID. On a fresh install the default database provider is typically ID 1, but verify with:
Replace --provider-id 1 with whatever ID the command above returned.
Why 4 nodes? QBFT requires a minimum of 4 validators to tolerate a single Byzantine fault (the formula is n = 3f + 1). Drop below 4 and you lose fault tolerance entirely. For a PoC, 4 is the sweet spot — realistic enough to demo enterprise resilience, lean enough to run on a laptop.
The --initial-balance flag pre-funds an account in the genesis block. That hex value (0x3635C9ADC5DEA00000) equals 1,000 ETH. It's the account you'll use to deploy and call the contract — no faucets, no waiting.
Once the command completes, you'll see output like this:
Creating 4 validator keys...
Creating key for node besu-my-poc-1...
Key created: ID 5
Creating key for node besu-my-poc-2...
Key created: ID 6
Creating key for node besu-my-poc-3...
Key created: ID 7
Creating key for node besu-my-poc-4...
Key created: ID 8
Creating Besu network 'my-poc' with 4 validators...
Besu network created: ID 1
Creating 4 Besu nodes...
Creating Besu node besu-my-poc-1 with key ID 5...
Node created ID: 1
Creating Besu node besu-my-poc-2 with key ID 6...
Node created ID: 2
Creating Besu node besu-my-poc-3 with key ID 7...
Node created ID: 3
Creating Besu node besu-my-poc-4 with key ID 8...
Node created ID: 4
Besu testnet created successfully! Network ID: 1
Important: The RPC port for each node is assigned dynamically. To find the RPC URL for node 1, query the API:
You should see a block number incrementing every 5 seconds. That's your QBFT network running.
ChainLaunch provisions a complete 4-node Hyperledger Besu QBFT testnet — validator key generation, genesis block creation, P2P discovery configuration, and pre-funded accounts — from a single CLI command in approximately 2 minutes. Manual Besu QBFT setup requires generating validator keys, writing genesis.json with RLP-encoded extradata, configuring static-nodes.json per node, and debugging P2P discovery — typically 10-15 minutes for an experienced engineer.
5 QBFT Settings That Make or Break Your Besu Network
Genesis config template + validator key setup guide. Includes the exact block time, epoch length, and gas settings we use for enterprise Besu deployments.
Before building the supply chain contract, let's verify the network is working and learn the fundamentals: connecting, generating wallets, deploying a contract, reading and writing state, and transferring ETH. This step uses Node.js and ethers.js — the most common toolchain for Ethereum development.
Four validators, three peers (each node sees the other three), blocks incrementing every 5 seconds, and 1,000 ETH in the pre-funded account. The network is live.
Now deploy a simple storage contract. This compiles Solidity in-process using the solc npm package and deploys it with a signed transaction. Add solc to your project:
npm install solc
Create deploy-and-use.mjs:
import { ethers } from "ethers";import solc from "solc";const RPC_URL = "http://localhost:8553";const FUNDED_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";function compileSolidity() { const source = `// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract SimpleStorage { uint256 private _value; function set(uint256 x) external { _value = x; } function get() external view returns (uint256) { return _value; }}`; const input = { language: "Solidity", sources: { "SimpleStorage.sol": { content: source } }, settings: { evmVersion: "berlin", // Must match genesis config outputSelection: { "*": { "*": ["abi", "evm.bytecode.object"] } }, }, }; const output = JSON.parse(solc.compile(JSON.stringify(input))); if (output.errors?.some((e) => e.severity === "error")) { throw new Error(output.errors.map((e) => e.message).join("\n")); } const contract = output.contracts["SimpleStorage.sol"]["SimpleStorage"]; return { abi: contract.abi, bytecode: "0x" + contract.evm.bytecode.object, };}async function main() { const provider = new ethers.JsonRpcProvider(RPC_URL); const wallet = new ethers.Wallet(FUNDED_KEY, provider); const blockNumber = await provider.getBlockNumber(); // 1. Compile the contract console.log("Compiling SimpleStorage..."); const { abi, bytecode } = compileSolidity(); console.log("Bytecode:", bytecode.length / 2, "bytes"); // 2. Deploy console.log("\nDeploying..."); const factory = new ethers.ContractFactory(abi, bytecode, wallet); const contract = await factory.deploy(); console.log("Deploy tx:", contract.deploymentTransaction().hash); await contract.waitForDeployment(); const addr = await contract.getAddress(); console.log("Contract deployed at:", addr); // 3. Read initial value const v0 = await contract.get(); console.log("\nget() =", v0.toString(), "(initial, should be 0)"); // 4. Write a value const tx1 = await contract.set(42); console.log("set(42) tx:", tx1.hash); await tx1.wait(); const v1 = await contract.get(); console.log("get() =", v1.toString(), "(should be 42)"); // 5. Write another value const tx2 = await contract.set(1337); await tx2.wait(); const v2 = await contract.get(); console.log("get() =", v2.toString(), "(should be 1337)"); // 6. Transfer ETH to a new wallet console.log("\n--- ETH Transfers ---"); const newWallet = ethers.Wallet.createRandom().connect(provider); console.log("New wallet:", newWallet.address); const sendTx = await wallet.sendTransaction({ to: newWallet.address, value: ethers.parseEther("1.0"), }); await sendTx.wait(); const newBal = await provider.getBalance(newWallet.address); console.log("Sent 1 ETH. New wallet balance:", ethers.formatEther(newBal), "ETH"); // 7. Transfer ETH back const sendBack = await newWallet.sendTransaction({ to: wallet.address, value: ethers.parseEther("0.5"), }); await sendBack.wait(); console.log("New wallet sent 0.5 ETH back."); // Summary const endBlock = await provider.getBlockNumber(); console.log("\nBlocks during test:", endBlock - blockNumber); console.log("\nAll operations verified!");}main().catch(console.error);
Run it:
node deploy-and-use.mjs
Expected output:
Compiling SimpleStorage...
Bytecode: 368 bytes
Deploying...
Deploy tx: 0x4bc25f01784...
Contract deployed at: 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
get() = 0 (initial, should be 0)
set(42) tx: 0x0030808fb7c...
get() = 42 (should be 42)
get() = 1337 (should be 1337)
--- ETH Transfers ---
New wallet: 0xcB1e7c72E843e991a24C29e54C0EA42244311311
Sent 1 ETH. New wallet balance: 1.0 ETH
New wallet sent 0.5 ETH back.
Blocks during test: 5
All operations verified!
In under a minute you've compiled Solidity, deployed a contract, written and read on-chain state, and transferred ETH between wallets — all on the QBFT testnet that ChainLaunch provisioned in Step 1.
Two critical things to note:
evmVersion: "berlin" is required. ChainLaunch's genesis sets berlinBlock: 0. If you compile for shanghai (the default in recent solc versions), the bytecode uses the PUSH0 opcode which Berlin doesn't support. The deploy transaction succeeds but the contract silently fails at runtime. This is the single most common debugging trap with private Besu networks.
Besu requires signed transactions. Unlike Ganache or Hardhat, Besu doesn't support eth_sendTransaction (unsigned). Always use a private key with ethers.js Wallet or the equivalent in your framework. The pre-funded key 0xac0974... is the Hardhat default account #0 — it's only funded because ChainLaunch puts it in the genesis alloc.
The most common Hyperledger Besu deployment failure is an EVM version mismatch: compiling Solidity with the default shanghai target produces PUSH0 opcodes that don't exist in Berlin-genesis private networks. The deploy transaction succeeds but eth_getCode returns empty bytecode. The fix is compiling with --evm-version berlin to match the genesis configuration — a requirement that applies across Hardhat, Foundry, Remix, and raw solc.
The SupplyChain.sol contract is the core of your PoC. It stores item state on-chain, records every status change and ownership transfer as a history entry, and emits events so off-chain systems can react in real time. Claude Code can generate a contract like this from a single prompt: "create a supply chain tracker smart contract in Solidity that records item provenance history."
This is the actual contract we tested during development of this tutorial. String-keyed IDs (instead of auto-incrementing integers) make it easier to demo — you can use meaningful identifiers like "COFFEE-LOT-2240" instead of opaque numbers.
Free resource
5 QBFT Settings That Make or Break Your Besu Network
Genesis config template + validator key setup guide. Includes the exact block time, epoch length, and gas settings we use for enterprise Besu deployments.
Compilation requires one critical flag that most tutorials skip. ChainLaunch's testnet genesis sets berlinBlock: 0, which means the EVM only supports opcodes up to Berlin. If you compile with a newer EVM target (like london or the default shanghai), the resulting bytecode may contain unsupported opcodes — PUSH0 (0x5f) in Shanghai, or BASEFEE in London. The deploy transaction will appear to succeed (status 0x1) but eth_getCode returns empty bytecode. This silent failure can burn hours of debugging.
The fix: compile with --evm-version berlin to match your genesis:
solc --evm-version berlin --bin --abi SupplyChain.sol -o build/
This produces two files in build/: SupplyChain.bin (bytecode) and SupplyChain.abi (ABI). You need both for deployment.
We've hit this --evm-version issue ourselves more than once. ChainLaunch uses berlinBlock: 0 in genesis because Berlin is the most widely tested compatibility target across enterprise Besu deployments. Always match your --evm-version flag to your genesis config.
Here's a Go tool that handles both deployment and contract interaction. Create a directory for it, initialize a Go module, and add the code:
mkdir -p deployer && cd deployergo mod init deployergo get github.com/ethereum/go-ethereum
Now you run the demo that stakeholders actually care about. The scenario: a batch of "Organic Coffee Beans" originates at a farm in Colombia, moves through a warehouse in Miami, and ends at a retailer in New York. Every step is recorded on-chain.
The deployer tool from Step 4 supports all the contract operations. Make sure RPC_URL, DEPLOYER_PRIVATE_KEY, and CONTRACT_ADDRESS are still set from the previous step, then run from the deployer/ directory:
# Create the coffee bean itemgo run main.go create $CONTRACT_ADDRESS \ "COFFEE-LOT-2240" "Organic Coffee Beans" "FincaElParaiso" "Huila, Colombia"# Update status: shipped from farmgo run main.go update $CONTRACT_ADDRESS \ "COFFEE-LOT-2240" "IN_TRANSIT" "Miami Port, FL" "Cleared customs, temp 18C maintained"# Transfer ownership to warehouse operatorgo run main.go transfer $CONTRACT_ADDRESS \ "COFFEE-LOT-2240" "ColdLinkWarehouse" "Miami Port, FL" "Bill of Lading #BL-44892"# Query the item statego run main.go get $CONTRACT_ADDRESS "COFFEE-LOT-2240"# Check history entry 0go run main.go history $CONTRACT_ADDRESS "COFFEE-LOT-2240" 0
The output shows a complete, tamper-evident audit trail. Every status change and ownership transfer is recorded with the identity of the actor who made it. That's the core value proposition for enterprise supply chain — you don't need to trust any single party's records because the blockchain holds the authoritative history.
This is where the PoC speed multiplier comes from. Claude Code doesn't just write code. It calls CLI tools, reads their output, adapts when things go wrong, and chains the entire workflow together. Here's what a real session looks like:
You: Create a 4-node Besu QBFT testnet using ChainLaunch, then write
and deploy a supply chain smart contract. Pre-fund account
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 with 1000 ETH.
Run a full lifecycle demo with "Organic Coffee Beans".
Claude Code:
> Running: chainlaunch testnet besu --name my-poc --nodes 4
--mode docker --initial-balance "0xf39...=0x3635C9ADC5DEA00000"
> Network ready. 4 nodes running. Querying node RPC port...
> Writing SupplyChain.sol...
> Running: solc --evm-version berlin --bin --abi SupplyChain.sol -o build/
> Writing deploy.go...
> Running: go run deploy.go
> Contract deployed at 0x5FbDB2315678afecb367f032d93F642f64180aa3
> Running lifecycle demo...
> Created: "Organic Coffee Beans, Lot #2240" at Huila, Colombia
> Updated: IN_TRANSIT at Miami Port, FL
> Transferred ownership to ColdLinkWarehouse
> Queried history: 3 entries verified
The key insight: Claude Code treats ChainLaunch as a tool it can call. It reads the network output to get RPC endpoints, passes them to the compiler and deployment scripts, and adapts when something goes wrong. For example, Claude Code knows about the --evm-version berlin requirement and applies it automatically.
In our own PoC sprints, we've run this full workflow — testnet to deployed contract to lifecycle demo — in under 8 minutes on a standard MacBook Pro. Without automated tooling, the same workflow took us 2-3 hours, with most of that time spent on genesis config debugging and P2P discovery issues.
Is every PoC this clean? No. Real enterprise PoCs add complexity: private transactions, permissioning, integration with existing systems. But starting from a working baseline in 8 minutes means you spend your time on business logic, not infrastructure plumbing.
Claude Code paired with ChainLaunch's CLI creates a complete AI-assisted Hyperledger Besu workflow: automated QBFT testnet provisioning, Solidity contract compilation with correct EVM version targeting, Go-based deployment, and full supply chain lifecycle demonstration — all from a single natural-language conversation in under 10 minutes.
QBFT is the recommended consensus for enterprise Besu deployments. It provides deterministic finality and doesn't require mining, per the Hyperledger Besu docs. IBFT2 is also supported, though QBFT is preferred for new deployments. Proof of Authority (Clique) is available for simpler test scenarios where BFT guarantees aren't needed. See our QBFT consensus guide for the full comparison.
Use chainlaunch nodes create to add validators or full nodes to an existing network. For QBFT, adding a validator requires a governance vote from existing validators through an on-chain proposal. ChainLaunch automates the voting transaction, but you still need quorum. Non-voting full nodes can be added without governance.
ChainLaunch supports --mode docker for containerized deployments and integrates with Prometheus for monitoring. Production features — automated backups, RBAC, SSO, audit logging — are available in ChainLaunch Pro. The PoC you built here is already production-shaped: same contract, same network topology, just different infrastructure targets.
Step 2 uses Node.js with ethers.js because it's the fastest way to verify your network and prototype contract interactions — most developers already have Node installed and ethers.js is the dominant Ethereum library. Steps 4-5 use Go because it's a natural fit for production backend integrations (common in enterprise Besu deployments) and shows how the same contract works across toolchains. The evmVersion: "berlin" requirement applies to both — it's a Solidity compiler setting, not a runtime concern. Use whatever your team already knows.
Private transactions via Tessera (Besu's privacy manager) are on the ChainLaunch roadmap. For PoC scenarios requiring confidential transfers between known parties, application-layer encryption of sensitive fields before writing to a public contract is a practical workaround most enterprise teams use today.
A Besu supply chain PoC that used to take days now takes minutes. That's the practical result of combining automated network provisioning with AI-assisted contract development.
Here's what you built: real BFT consensus, on-chain provenance, pre-funded accounts, and a full lifecycle demo. Claude Code handled the boilerplate so you could focus on the business logic.
Key things to remember as you build on this:
Always compile with --evm-version berlin to match ChainLaunch's Berlin-genesis config — this is the most common gotcha
David Viejo is the founder of ChainLaunch and a Hyperledger Foundation contributor. He created the Bevel Operator Fabric project and has been building blockchain infrastructure tooling since 2020.
Free resource
5 QBFT Settings That Make or Break Your Besu Network
Genesis config template + validator key setup guide. Includes the exact block time, epoch length, and gas settings we use for enterprise Besu deployments.