Antler by Autoflux

Examples

End-to-end walkthroughs: run a node, deploy a contract, bridge assets, and drive the stack from a script.

Path: examples

Third-party documentation. This is independently authored analysis of the public Optimism (OP Stack) codebase — not the official docs, and not reviewed or endorsed by the Optimism (OP Stack) team.

Examples

The walkthroughs below are the concrete patterns most OP Stack builders reach for. They assume a running devnet (see ops-bedrock) or public RPCs, and use ethers v6. Each is deliberately focused on one capability.

Deposits, withdrawals, and the three phases

The most common task is moving value across the boundary. The SDK collapses the messy bits — address derivation, approvals, output-root waiting — into a small number of calls, but it is worth understanding what each call does underneath (see the Bridging module).

Running the stack

For local development, the fastest path is the dockerized devnet. It boots L1, op-node, op-geth, op-batcher, and op-proposer together, pre-funds accounts, and exposes RPC endpoints on known ports. Once it is up, ordinary L2 development (deploy, call, query) proceeds exactly as it would on any EVM chain.

Inspecting protocol state

op-node and op-batcher expose optimism_* RPC methods (see the API Reference module) that let operators observe sync status, output roots, and channel state. These are the same endpoints the CLI tools and dashboards poll, and they're a good way to debug "why is my chain stuck".

Examples

Run a local OP Stack devnet with the op-stack CLI

Run a local OP Stack devnet with the op-stack CLI
bash
# clone + build (one-time)
git clone https://github.com/ethereum-optimism/ops-bedrock.git
cd ops-bedrock
docker compose up --build
 
# or, the modern path — launch an entire devnet from a config:
npm install -g @eth-optimism/op-chain
op-chain devnet up
 
# inspect the running services
curl -s http://localhost:8545 \
-X POST -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
STATUSexample

The ops-bedrock docker compose spins up L1 (a local geth node), op-node, op-geth, op-batcher, op-proposer, and the L1 contracts. It's the fastest way to get a full stack locally.

Deploy a contract on the local devnet

Deploy a contract on the local devnet
typescript
import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider("http://localhost:9545"); // op-geth
const deployer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
 
const factory = new ethers.ContractFactory(
["constructor()", "function set(uint256 _v) public", "function get() public view returns (uint256)"],
"6080604052...", // bytecode
deployer
);
 
const c = await factory.deploy();
await c.waitForDeployment();
console.log("deployed at", await c.getAddress());
 
await c.set(42n);
console.log("get() =", (await c.get()).toString());
STATUSexample

Deploying on OP Mainnet is identical to L1 — same JSON-RPC, same ethers API. op-geth behaves like geth for ordinary transactions.

Bridge ETH using the SDK (deposit + withdrawal)

Bridge ETH using the SDK (deposit + withdrawal)
typescript
import { CrossChainMessenger } from "@eth-optimism/sdk";
import { ethers } from "ethers";
 
const l1 = new ethers.JsonRpcProvider(process.env.L1_RPC!);
const l2 = new ethers.JsonRpcProvider(process.env.L2_RPC!);
 
// Deposit direction: signer must control funds on L1.
const depositor = new ethers.Wallet(process.env.PRIVATE_KEY!, l1);
const bridge = new CrossChainMessenger({
l1SignerOrProvider: depositor,
l2SignerOrProvider: l2,
l1ChainId: 1,
l2ChainId: 10,
});
 
const deposit = await bridge.depositETH(ethers.parseEther("1.0"));
await deposit.wait();
await deposit.waitL2();
console.log("Deposit complete on L2");
STATUSexample

The full deposit flow, end to end. For the reverse direction, switch the signer to an L2 provider and call withdrawETH.

Submit a batch manually with the batcher

Submit a batch manually with the batcher
bash
# The op-batcher does this continuously, but you can inspect its state:
# - channels open/closed
# - frames submitted
curl -s http://localhost:8546 \
-X POST -H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","method":"optimism_batcher_channelState","params":[],"id":1}'
STATUSexample

op-batcher exposes an admin RPC on its own port so operators can inspect channel/frame state and force submissions.

Edge Cases

  • If op-batcher stops, L2 keeps producing blocks via P2P, but the chain stops being reconstructible from L1 — blocks that aren't batched are lost when the sequencer restarts.
  • op-geth needs op-node to feed it forkchoice updates; running op-geth standalone gives you an empty chain with no deposits.
  • The sequencer is a single point of ordering (not of validity) — anyone can verify, but ordering is centralized by design in the OP Stack today.