Build on BB84
BB84 is an experimental OP Stack L2 (chain ID 42069422) that eliminates ECDSA from all Layer 2 transactions. Instead of signing, users prove ownership by revealing hash-chain preimages — providing 128-bit post-quantum security against Grover's algorithm.
This document covers everything you need to interact with the network programmatically: the hash-scan CLI, the L1 ↔ L2 bridge flow, and the on-chain contract interfaces.
Quick Start
The fastest path to interacting with BB84 is through MetaMask + the faucet. No private keys needed on L2.
Add BB84 to MetaMask
Visit bb84.com and click "Add to MetaMask". The network is added automatically with chain ID 42069422.
Or add manually:
Network Name: BB84 (Onyx)
RPC URL: https://bb84.com/rpc
Chain ID: 42069422
Currency: ETH
Explorer: https://bb84.com/explorer
Get test ETH from the faucet
Visit bb84.com/faucet, enter your L2 address, and receive 0.01 ETH. The faucet drips via an L1 deposit transaction — no ECDSA signing required on L2.
Rate limit: one drip per address and IP per 24 hours.
Explore transactions
View blocks, transaction types, and stealth withdrawal details in the Block Explorer. Hash commitment (COMMIT) and reveal (REVEAL) transactions are decoded inline.
Use the CLI for full OTA access
Download hash-scan to create One-Time-Address (OTA) wallets and interact with the chain without ECDSA signing. See Installation.
Installing hash-scan
hash-scan is a standalone CLI for BB84. It manages OTA wallets, scans for incoming stealth payments, and submits hash-chain transactions — no Ethereum private key required on L2.
Download pre-built binaries
Grab the latest release from GitHub:
# Linux amd64
curl -L https://github.com/HadamardIO/optimism/releases/latest/download/hash-scan-linux-amd64 -o hash-scan
chmod +x hash-scan
# macOS (arm64 / Apple Silicon)
curl -L https://github.com/HadamardIO/optimism/releases/latest/download/hash-scan-darwin-arm64 -o hash-scan
chmod +x hash-scan
Build from source
git clone https://github.com/HadamardIO/optimism
cd optimism/cmd/hash-scan
go build -o hash-scan .
# Requires Go 1.22+. Only dependency: golang.org/x/crypto
Verify
./hash-scan version
# hash-scan v0.1.0 — BB84 OTA wallet & bridge CLI
generate — Create an OTA Wallet
An OTA (One-Time-Address) wallet contains a spend key and a scan key. For each incoming payment, a fresh stealth address is derived deterministically. Spend keys never leave your machine.
./hash-scan generate \
--rpc https://bb84.com/rpc \
--chain-id 42069422 \
--wallet wallet.json
This creates wallet.json with 10 pre-generated OTAs and a 10-step hash chain per OTA. Keep this file safe — it contains your spend keys.
Wallet file format
{
"chain": 42069422,
"otas": [
{
"ota_addr": "0xabcd...", // stealth address for this OTA slot
"spend_sk": "0x1234...", // SHA-256 chain seed
"chain_depth": 10, // remaining spend steps
"withdraw_count":0 // number of withdrawals initiated
},
...
]
}
scan — Find Incoming Payments
Scan the chain for stealth payments directed to your OTA wallet. The scanner checks each OTA address for incoming deposit transactions and hash commitments.
./hash-scan scan \
--rpc https://bb84.com/rpc \
--wallet wallet.json
Output example:
OTA 0 0xabcd... balance: 0.010 ETH chain: 10/10 commits: 0
OTA 1 0xef12... balance: 0.000 ETH chain: 10/10 commits: 0
spend — Send ETH
Spend ETH from an OTA by revealing the next hash-chain preimage. Each spend consumes one chain step. The transaction is a HashRevealTx (0x7F) on L2.
./hash-scan spend \
--rpc https://bb84.com/rpc \
--wallet wallet.json \
--to 0xRecipientAddress \
--amount 0.005ether \
--ota 0 # optional: select OTA index (default: first with balance)
Under the hood
A spend submits two transactions in sequence:
- HashCommitTx (
0x7C) — commits the hash of the preimage - HashRevealTx (
0x7F) — reveals the preimage, executing the transfer
The two-step commit/reveal prevents front-running of preimage revelation.
status — Check Balance and Chain Depth
./hash-scan status \
--rpc https://bb84.com/rpc \
--wallet wallet.json
Output:
Chain ID: 42069422
OTAs: 10 total, 2 with balance
#0 0xabcd... 0.010 ETH chain 8/10 (2 spends used)
#1 0xef12... 0.001 ETH chain 10/10
...
Total: 0.011 ETH
withdraw — Bridge ETH to L1
Withdrawals are a multi-step process taking ~60 seconds end-to-end on the testnet (12s proof maturity delay). You need a Sepolia EOA with ETH for gas.
Step 1 — Initiate withdrawal on L2
./hash-scan withdraw initiate \
--rpc https://bb84.com/rpc \
--wallet wallet.json \
--ota 0 \
--amount 0.001ether \
--l1-target 0xYourSepoliaAddress
This submits a HashRevealTx calling initiateWithdrawalHash on the L2ToL1MessagePasserHash predeploy. Note the L2 block number in the output.
Step 2 — Propose output root
The auto-proposer runs every 10 minutes, but you can also propose manually for a specific block:
./hash-scan propose-output \
--l2-rpc https://bb84.com/rpc \
--factory 0xBfD39d047516d0Ec022A8F8b90E5ace26F60fc30
# Then submit with cast:
cast send 0xBfD39d... "create(uint32,bytes32,bytes)" \
1000 <OutputRoot> <ExtraData> \
--private-key $PROPOSER_KEY --rpc-url $L1_RPC
Step 3 — Prove the withdrawal on L1
./hash-scan withdraw prove \
--l2-rpc https://bb84.com/rpc \
--l1-rpc $SEPOLIA_RPC \
--factory 0xBfD39d047516d0Ec022A8F8b90E5ace26F60fc30 \
--portal 0x74b350258195aA8B8DbcA8f74d1b00d2EF652039 \
--wallet wallet.json \
--ota 0 \
--private-key $SEPOLIA_KEY
This constructs the Merkle proof for the withdrawal and calls proveWithdrawalTransactionHash on OptimismPortalHash.
Step 4 — Wait for maturity delay
After proving, wait 12 seconds (testnet setting; mainnet is 7 days) for the proofMaturityDelaySeconds.
Step 5 — Finalize on L1
./hash-scan withdraw finalize \
--l2-rpc https://bb84.com/rpc \
--l1-rpc $SEPOLIA_RPC \
--portal 0x74b350258195aA8B8DbcA8f74d1b00d2EF652039 \
--wallet wallet.json \
--ota 0 \
--private-key $SEPOLIA_KEY
ETH is released from OptimismPortalHash to --l1-target.
L1 → L2 Deposits
BB84 uses a custom OptimismPortalHash contract on Sepolia. The Onyx hardfork disables direct L2 ECDSA transactions, so the only way to fund an L2 address is via a deposit transaction from L1.
Deposit ETH
Call depositTransaction on the portal. The L2 gas limit must be at least 21000.
cast send 0x74b350258195aA8B8DbcA8f74d1b00d2EF652039 \
"depositTransaction(address,uint256,uint64,bool,bytes)" \
<l2_recipient> 0 21000 false 0x \
--value 0.01ether \
--private-key $SEPOLIA_KEY \
--rpc-url $SEPOLIA_RPC
The deposit is included on L2 within ~20 seconds (2-second block time, 3-block finalization confirmation).
Stealth deposit via OTA
To deposit directly to an OTA stealth address so the recipient can scan for it:
cast send 0x74b350258195aA8B8DbcA8f74d1b00d2EF652039 \
"depositWithStealth(address,bytes32,uint256,uint64,bool,bytes)" \
<ota_addr> <view_tag> 0 21000 false 0x \
--value 0.01ether \
--private-key $SEPOLIA_KEY \
--rpc-url $SEPOLIA_RPC
view_tag is a 32-byte scan hint. Use the hash-scan CLI to generate it for a recipient's OTA wallet.ERC-20 deposits
Approve the portal first, then call depositWithStealthToken. An ERC-20 bridge tutorial is on the roadmap.
L2 → L1 Withdrawals
Withdrawals follow the standard OP Stack fault-proof flow, adapted for hash-chain authentication.
| Step | Action | Chain | Delay |
|---|---|---|---|
| 1 | Call initiateWithdrawalHash on L2ToL1MessagePasserHash | L2 | — |
| 2 | Submit output root to DisputeGameFactory (game type 1000) | L1 | auto-proposer ~10 min |
| 3 | Call proveWithdrawalTransactionHash on OptimismPortalHash | L1 | after step 2 |
| 4 | Wait proofMaturityDelaySeconds | — | 12 seconds (testnet) |
| 5 | Call finalizeWithdrawalTransactionHash | L1 | after step 4 |
initiateWithdrawalHash parameters
The L2 predeploy is at 0x4200000000000000000000000000000000000043. The function signature:
function initiateWithdrawalHash(
bytes32 l2Commitment, // hash commitment of the preimage
bytes32 withdrawNullifier, // unique nonce preventing replay
address l1Target, // Sepolia address to receive ETH
uint256 gasLimit, // L1 execution gas limit
bytes extraData // optional calldata for l1Target
)
Output root format
The output root committed to DisputeGameFactory is:
outputRoot = keccak256(
version // bytes32(0)
‖ stateRoot // L2 block stateRoot
‖ storageRoot // L2ToL1MessagePasserHash storage root
‖ blockHash // L2 block hash
)
Use hash-scan propose-output to compute this automatically from the L2 RPC.
Transaction Types
BB84 introduces two new EIP-2718 transaction types alongside the standard OP Stack deposit type:
| Type | Hex | Name | Description |
|---|---|---|---|
| COMMIT | 0x7C |
HashCommitTx | Commits keccak256(preimage) to a nullifier tree slot. Required before revealing. |
| REVEAL | 0x7F |
HashRevealTx | Reveals the preimage, executing the encoded L2 action (transfer, withdrawal, etc.). |
| DEPOSIT | 0x7E |
DepositTx | Standard OP Stack deposit transaction derived from OptimismPortalHash events on L1. |
Why two steps?
The commit/reveal pattern prevents front-running: a sequencer cannot steal a withdrawal by racing to reveal the preimage because the commitment is already included before the preimage is known.
HashRevealTx calldata encoding
For a withdrawal initiation, the reveal calldata encodes the initiateWithdrawalHash ABI call:
// selector: keccak256("initiateWithdrawalHash(bytes32,bytes32,address,uint256,bytes)")[:4]
0x4cc0963c
l2Commitment // bytes32
withdrawNullifier // bytes32
l1Target // address (right-padded to 32 bytes)
gasLimit // uint256
extraData offset // uint256 (= 0xa0)
extraData length // uint256
extraData bytes // padded to 32-byte boundary
Contract Addresses
L1 (Sepolia)
L2 Predeploys (chainId 42069422)
Network Parameters
| Parameter | Value |
|---|---|
| Chain ID | 42069422 |
| L2 Block Time | 2 seconds |
| L1 (Sepolia) Chain ID | 11155111 |
| Proof Maturity Delay | 12 seconds (testnet) |
| Dispute Game Finality Delay | 12 seconds (testnet) |
| Output Proposal Interval | ~10 minutes (auto-proposer) |
| Sequencer Drift | 600 seconds |
| Channel Timeout | 300 seconds |
| Max Sequencer Drift | 600 seconds |
| Game Type (HashOutputOracle) | 1000 |
| Currency Symbol | ETH |
| RPC | https://bb84.com/rpc |
| Block Explorer | https://bb84.com/explorer |
| Faucet | https://bb84.com/faucet |
Hardfork activation
| Fork | Activation |
|---|---|
| Regolith | Genesis (t=0) |
| Canyon | Genesis (t=0) |
| Delta | Genesis (t=0) |
| Ecotone | Genesis (t=0) |
| Fjord | Genesis (t=0) |
| Granite | Genesis (t=0) |
| Holocene | Genesis (t=0) |
| Onyx (ECDSA-free) | Genesis (t=0) |