# Cheatsheet for running OP devnet ### Dependencies Install golang and rust Install docker and docker-compose `wget -qO- https://get.pnpm.io/install.sh | sh - ` ``` curl -L https://foundry.paradigm.xyz | bash foundryup ``` download jq from https://jqlang.github.io/jq/ `sudo mv jq-linux-amd64 /usr/bin/jq` `sudo apt install direnv` `cargo install just` ### 1. Geth & prism devnet for L1 ``` git clone https://github.com/uprendis/eth-pos-devnet.git docker compose up -d # use docker-compose if running an older version docker logs --follow eth-pos-devnet_geth_1 # read logs sudo ./clean.sh # stop and erase the datadirs ``` It'll listen on 8545 for http web3 API Here is the original instruction: https://docs.prylabs.network/docs/advanced/proof-of-stake-devnet The genesis will pre-fund all dev accounts ### 2. Deploy contracts and generate genesis for L2 ``` git clone https://github.com/ethereum-optimism/optimism.git cd optimism git checkout v1.9.3 # I tested this version but it probably works with the latest one too make build mkdir build cd packages/contracts-bedrock ``` Create file `deploy-config/devnetL1.json` with this content: ``` { "l1ChainID": 32382, "l2ChainID": 901, "l2BlockTime": 2, "maxSequencerDrift": 300, "sequencerWindowSize": 200, "channelTimeout": 120, "p2pSequencerAddress": "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", "batchInboxAddress": "0xff00000000000000000000000000000000000901", "batchSenderAddress": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", "l1StartingBlockTag": "earliest", "l2OutputOracleSubmissionInterval": 10, "l2OutputOracleStartingTimestamp": 0, "l2OutputOracleStartingBlockNumber": 0, "l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "l2OutputOracleChallenger": "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", "l2GenesisBlockGasLimit": "0x1c9c380", "l1BlockTime": 12, "baseFeeVaultRecipient": "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", "l1FeeVaultRecipient": "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", "sequencerFeeVaultRecipient": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", "baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000", "baseFeeVaultWithdrawalNetwork": 0, "l1FeeVaultWithdrawalNetwork": 0, "sequencerFeeVaultWithdrawalNetwork": 0, "proxyAdminOwner": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", "finalSystemOwner": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", "superchainConfigGuardian": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", "finalizationPeriodSeconds": 2, "fundDevAccounts": true, "l2GenesisBlockBaseFeePerGas": "0x1", "gasPriceOracleOverhead": 2100, "gasPriceOracleScalar": 1000000, "gasPriceOracleBaseFeeScalar": 1368, "gasPriceOracleBlobBaseFeeScalar": 810949, "enableGovernance": true, "governanceTokenSymbol": "NB", "governanceTokenName": "Nebra", "governanceTokenOwner": "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", "eip1559Denominator": 50, "eip1559DenominatorCanyon": 250, "eip1559Elasticity": 6, "l1GenesisBlockTimestamp": "0x123", "l2GenesisRegolithTimeOffset": "0x0", "l2GenesisCanyonTimeOffset": "0x0", "l2GenesisDeltaTimeOffset": "0x0", "l2GenesisEcotoneTimeOffset": "0x0", "l2GenesisFjordTimeOffset": "0x0", "l1CancunTimeOffset": "0x0", "systemConfigStartBlock": 0, "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "faultGameAbsolutePrestate": "0x03c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98", "faultGameMaxDepth": 50, "faultGameClockExtension": 0, "faultGameMaxClockDuration": 1200, "faultGameGenesisBlock": 0, "faultGameGenesisOutputRoot": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", "faultGameSplitDepth": 14, "faultGameWithdrawalDelay": 604800, "preimageOracleMinProposalSize": 10000, "preimageOracleChallengePeriod": 120, "proofMaturityDelaySeconds": 12, "disputeGameFinalityDelaySeconds": 6, "respectedGameType": 254, "useFaultProofs": false, "useAltDA": false, "daCommitmentType": "KeccakCommitment", "daChallengeWindow": 16, "daResolveWindow": 16, "daBondSize": 1000000, "daResolverRefundPercentage": 0 } ``` ``` # deploy the L1 contracts # This step has to be ran against a clean network without prior deployments! Clean your L1 network if you've already deployed before # the private key is for a standard dev account, it's prefunded. It's from mnemonic 'test test test test test test test test test test test junk' DEPLOYMENT_OUTFILE=deployments/artifact.json DEPLOY_CONFIG_PATH=deploy-config/devnetL1.json forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --rpc-url 127.0.0.1:8545 --broadcast --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # intermediate files for genesis FORK=latest STATE_DUMP_PATH=deployments/state_dump.json DEPLOY_CONFIG_PATH=deploy-config/devnetL1.json CONTRACT_ADDRESSES_PATH=deployments/artifact.json forge script scripts/L2Genesis.s.sol:L2Genesis --sig 'runWithStateDump()' cp deployments/artifact.json ../../build/artifact.json # generate the genesis files for op-geth and op-node go run ../../op-node/cmd/main.go genesis l2 \ --deploy-config=deploy-config/devnetL1.json \ --l1-deployments=deployments/artifact.json \ --l2-allocs=deployments/state_dump.json \ --outfile.l2=../../build/genesis.json \ --outfile.rollup=../../build/rollup.json \ --l1-rpc=http://127.0.0.1:8545 ``` ### 3. Run op-geth ``` openssl rand -hex 32 > /tmp/jwt.txt git clone https://github.com/ethereum-optimism/op-geth.git git checkout v1.101408.0 make all # Prime the datadir with our genesis ./build/bin/geth --datadir=~/.op-geth/ --gcmode=archive init --state.scheme=hash ../optimism/build/genesis.json # Run the node ./build/bin/geth \ --datadir ~/.op-geth \ --http \ --http.corsdomain="*" \ --http.vhosts="*" \ --http.port=7547 \ --http.addr=0.0.0.0 \ --http.api=web3,debug,eth,txpool,net,engine \ --ws \ --ws.addr=0.0.0.0 \ --ws.port=7546 \ --ws.origins="*" \ --ws.api=debug,eth,txpool,net,engine \ --syncmode=full \ --gcmode=archive \ --nodiscover \ --maxpeers=0 \ --networkid=901 \ --authrpc.vhosts="*" \ --authrpc.addr=0.0.0.0 \ --authrpc.port=8552 \ --authrpc.jwtsecret=/tmp/jwt.txt \ --rollup.disabletxpoolgossip=true # erase ~/.op-geth and re-prime it with new genesis for a clean restart ``` ### 4. Run op-node Must be launched after op-geth ``` cd optimism ./op-node/bin/op-node --l2=http://localhost:8552 --l2.jwt-secret=/tmp/jwt.txt --sequencer.enabled --sequencer.l1-confs=5 --verifier.l1-confs=4 --rollup.config=build/rollup.json --rpc.addr=0.0.0.0 --p2p.disable --rpc.enable-admin --p2p.sequencer.key=0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba --l1=http://127.0.0.1:8545 --l1.beacon=http://127.0.0.1:3500 --l1.rpckind=standard ``` ### 4. Run op-batcher Must be launched after op-node ``` cd optimism ./op-batcher/bin/op-batcher \ --l2-eth-rpc=http://localhost:7547 \ --rollup-rpc=http://localhost:9545 \ --poll-interval=1s \ --sub-safety-margin=6 \ --num-confirmations=1 \ --safe-abort-nonce-too-low-count=3 \ --resubmission-timeout=30s \ --rpc.addr=0.0.0.0 \ --rpc.port=8548 \ --rpc.enable-admin \ --max-channel-duration=25 \ --l1-eth-rpc=http://127.0.0.1:8545 \ --private-key=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a ``` 9545 is a default port for op-node's RPC ### 5. Run op-proposer Must be launched after op-node ``` cd optimism ./op-proposer/bin/op-proposer \ --poll-interval=12s \ --rpc.port=8560 \ --rollup-rpc=http://localhost:9545 \ --l2oo-address=$(cat ./build/artifact.json | jq -r .L2OutputOracleProxy) \ --private-key=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d \ --l1-eth-rpc=http://127.0.0.1:8545 ``` ### 6. Send a transaction to L2 ``` mkdir viem cd viem npm install viem ``` You may create this JS script which uses `viem` library: ```js import {createPublicClient, http, createWalletClient, defineChain} from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; export const mychain = /*#__PURE__*/ defineChain({ id: 901, name: 'Devnet', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: { default: { http: ['http://localhost:7547'], }, }, }) const client = createPublicClient({ chain: mychain, transport: http('http://localhost:7547'), }); const acc = privateKeyToAccount('0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e') const wallet = createWalletClient({ account: acc, chain: mychain, transport: http('http://localhost:7547'), }); async function sendEth() { const balance = await client.getBalance({ address: acc.address }); console.log(`Balance: ${balance} wei`); const txHash = await wallet.sendTransaction({ to: '0x7626f6940e2eB28930efB4cEF49B2d1F2C9C1199', value: 1n, }); console.log('Transaction hash:', txHash); } sendEth(); ``` And run it with `node script.js` You can also use `node` in REPL mode by typing `node` and pasting the code in there, but it'll require a different syntax