Skip to content

feat(popkins): Local Devnet with Anvil + popsigner-lite for offline development #2

@Bidon15

Description

@Bidon15

📋 Summary

Implement a zero-dependency local development environment for both OP Stack and Nitro rollups. Developers can run a complete rollup on their local machine without connecting to Ethereum, Celestia, or POPSigner Cloud.


🎯 Goals

  • docker compose up experience for local rollup development
  • Works completely offline (airgapped)
  • Same binaries as production
  • Clear upgrade path from local → testnet → mainnet
  • Applies to both OP Stack and Nitro (same architecture, different rollup configs)

🏗️ Components to Build

Build in this order - each step depends on the previous:

# Component Description
1 popsigner-lite Minimal signing service with Anvil's 10 deterministic keys. Same JSON-RPC interface as production POPSigner (eth_signTransaction, health_status, opsigner_signBlockPayload). No auth, no DB, no OpenBao. ~200 lines Go. This unblocks everything else.
2 mock-celestia / localestia Mock Celestia node with gRPC (9090) + JSON-RPC (26658). In-memory blob storage, instant finality, state dump/load. ~500 lines Go. Required for OP Stack with Alt-DA.
3 Anvil State Generator (OP Stack) Backend code at control-plane/internal/bootstrap/opstack/anvil_deployer.go. Spins up ephemeral Anvil, deploys OP Stack contracts using existing op-deployer integration, dumps state via anvil_dumpState.
4 Anvil State Generator (Nitro) Backend code at control-plane/internal/bootstrap/nitro/anvil_deployer.go. Same pattern - deploy Nitro contracts (RollupCreator, etc.) to Anvil, dump state. Mirror the working OP Stack implementation.
5 Local Docker Compose Templates Generate docker-compose.yml for local devnet. Two templates: one for OP Stack (Anvil + mock-celestia + popsigner-lite + op-* binaries), one for Nitro (Anvil + popsigner-lite + nitro-node + sequencer).
6 Bundle Generator & Download Package everything into downloadable .tar.gz artifact with pre-filled .env, state dumps, genesis, and scripts.
7 Dashboard UI Add "Local Devnet" option in chain creation wizard. Show bundle download when ready.

🔁 Nitro Parity

The Nitro implementation should mirror OP Stack's architecture since both are already working in production:

Concern OP Stack (Reference) Nitro (To Implement)
Contract Deployer opstack/anvil_deployer.go nitro/anvil_deployer.go
Signing popsigner-lite popsigner-lite (same)
L1 Mock Anvil Anvil (same)
DA Mock mock-celestia N/A (Nitro uses L1 calldata or AnyTrust - not in scope for local)
State Dump anvil_dumpState anvil_dumpState (same)

🔑 Key Assignments (Anvil's Deterministic Keys)

Role Address Private Key
Deployer 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 0xac09...ff80
Batcher 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 0x59c6...690d
Proposer 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC 0x5de4...365a
Admin 0x90F79bf6EB2c4f870365E785982E1f101E93b906 0x7c85...07a6

Use publicly known keys


📦 Bundle Structure

my-local-rollup-devnet/
├── docker-compose.yml
├── .env                    # Pre-filled (not a template!)
├── state/
│   ├── anvil-state.json    # L1 with deployed contracts
│   └── celestia-state.json # OP Stack only
├── genesis/
│   └── genesis.json
├── config/
│   ├── rollup.json         # OP Stack: rollup.json, Nitro: nodeConfig.json
│   ├── addresses.json
│   └── jwt.txt
├── scripts/
│   ├── start.sh
│   ├── stop.sh
│   └── reset.sh
└── README.md

🌐 API Changes

New deployment option:

POST /api/v1/deployments
{
  "chainType": "opstack",  // or "nitro"
  "targetEnvironment": "local",  // NEW
  "name": "my-local-rollup",
  "l2ChainId": 42069
}

CLI support:

popctl bootstrap create --name my-rollup --chain-type opstack --target local --download ./my-rollup/
popctl bootstrap create --name my-rollup --chain-type nitro --target local --download ./my-rollup/

✅ Success Metrics

Metric Target
Bundle generation time < 60 seconds
Bundle size < 20 MB compressed
Time to first L2 block < 30 seconds after docker compose up
Works offline 100%

✅ In Scope (from PRD)

Backend:

  • popsigner-lite Docker image with Anvil keys pre-loaded
  • mock-celestia Docker image with blob storage + state dump/load
  • anvil_deployer.go for OP Stack - ephemeral Anvil + contract deployment + state dump
  • anvil_deployer.go for Nitro - same pattern
  • Local Docker Compose template generation (different from production templates)
  • Bundle packaging: .tar.gz with pre-filled .env, state dumps, genesis, scripts
  • New API: POST /api/v1/deployments with targetEnvironment: "local"
  • New API: GET /api/v1/deployments/{id}/bundle to download .tar.gz

Dashboard:

  • "Local Devnet (Anvil)" option in target environment selector
  • Bundle download button when generation completes
  • Quick start instructions in deployment details page
  • Clear "No funding required" messaging for local devnet

CLI (popctl):

  • --target local flag for bootstrap create
  • --download <path> option to save bundle directly

Bundle Contents:

  • docker-compose.yml - ready to run, no edits needed
  • .env - pre-filled with Anvil addresses and contract addresses
  • state/anvil-state.json - L1 with deployed contracts
  • state/celestia-state.json - empty initial state (OP Stack only)
  • genesis/genesis.json - L2 genesis
  • config/rollup.json or nodeConfig.json - rollup configuration
  • config/addresses.json - deployed contract addresses
  • config/jwt.txt - JWT secret for auth RPC
  • scripts/start.sh, stop.sh, reset.sh - convenience scripts
  • README.md - quick start guide with endpoints and accounts

🚫 Out of Scope (v1)

  • Block explorer
  • L2 faucet (Anvil accounts have unlimited ETH)
  • Multiple L2 chains
  • Custom key configuration

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions