Introduction

OP Deployer is a CLI tool that simplifies deploying and upgrading smart contracts for OP Stack chains. It also exposes a suite of libraries that allow developers to easily manage smart contracts from their applications.

Goals

Declarative

With OP Deployer, developers define their chain's desired configuration in a declarative configuration file. The tool then makes the minimum number of smart contract calls required to make the deployment match the configuration. This ensures that the implementation details of the deployment are abstracted away, and allows complex configurations to be expressed cleanly without concern for the underlying deployment process.

Portable

OP Deployer is designed to be small, portable, and easily installed. As such it is distributed as a standalone binary with no additional dependencies. This allows it to be used in a variety of contexts, including as a CLI tool, in CI pipelines, and as part of local development environments like Kurtosis.

Standard, But Extensible

OP Deployer aims to make doing the right thing easy, and doing dangerous things hard. As such its configuration and API are optimized for deploying and upgrading Standard OP Chains. However, it also exposes a lower-level set of primitives and configuration directives which users can use to deploy more complex configurations if the need arises.

Development Status

OP Deployer is undergoing active development and has been used for several mainnet deployments. It is considered production-ready. However, please keep in mind that OP Deployer has not been audited and that any chains deployed using OP Deployer should be checked thoroughly for correctness prior to launch.

Installation

OP Deployer can be installed both from pre-built binaries and from source. This guide will walk you through both methods.

Install From Binaries

Installing OP Deployer from pre-built binaries is the easiest and most preferred way to get started. To install from binaries, download the latest release from the releases page and extract the binary to a directory in your $PATH.

Install From Source

To install from source, you will need Go, just, and git. Then, run the following:

git clone git@github.com:ethereum-optimism/ethereum-optimism.git # you can skip this if you already have the repo
cd ethereum-optimism/op-deployer
just build
cp ./bin/op-deployer /usr/local/bin/op-deployer # or any other directory in your $PATH

Usage

The OP Deployer CLI tool is used to deploy and manage your smart contracts. After installing OP Deployer, you can use op-deployer help to view the available commands.

This following sections provide in-depth information on the different commands available.

The Bootstrap Commands

Bootstrap commands are used to deploy global singletons and implementation contracts for use with future invocations of apply. Most users won't need to use these commands, since op-deployer apply will automatically use predeployed contracts if they are available. However, you may need to use bootstrap commands if you're deploying chains to an L1 that isn't natively supported by op-deployer.

There are several bootstrap commands available, which you can view by running op-deployer bootstrap --help. We'll focus on the most important ones below.

Implementations

You can bootstrap implementations by running a command like this:

op-deployer bootstrap implementations \
  --artifacts-locator <locator> \ 
  --l1-contracts-release op-contracts/<your-release> \
  --l1-rpc-url <rpc url> \
  --mips-version <1 or 2, for MIPS32 or MIPS64> \
  --private-key <some private key> \ 
  --protocol-versions-proxy <protocol versions proxy address> \
  --superchain-config-proxy <superchain config proxy address> \
  --upgrade-controller <upgrade controller address>

This command will deploy implementations, blueprints, and the OPCM. Deployments are (for the most part) deterministic, so contracts will only be deployed once per chain as long as the implementation and constructor args remain the same. This applies to the op-deployer apply pipeline - that is, if someone else ran op-deployer boostrap implementations at some point on a given L1 chain, then the apply pipeline will re-use those implementations.

The command will output a JSON like the one below:

{
  "Opcm": "0x4eeb114aaf812e21285e5b076030110e7e18fed9",
  "DelayedWETHImpl": "0x5e40b9231b86984b5150507046e354dbfbed3d9e",
  "OptimismPortalImpl": "0x2d7e764a0d9919e16983a46595cfa81fc34fa7cd",
  "PreimageOracleSingleton": "0x1fb8cdfc6831fc866ed9c51af8817da5c287add3",
  "MipsSingleton": "0xf027f4a985560fb13324e943edf55ad6f1d15dc1",
  "SystemConfigImpl": "0x760c48c62a85045a6b69f07f4a9f22868659cbcc",
  "L1CrossDomainMessengerImpl": "0x3ea6084748ed1b2a9b5d4426181f1ad8c93f6231",
  "L1ERC721BridgeImpl": "0x276d3730f219f7ec22274f7263180b8452b46d47",
  "L1StandardBridgeImpl": "0x78972e88ab8bbb517a36caea23b931bab58ad3c6",
  "OptimismMintableERC20FactoryImpl": "0x5493f4677a186f64805fe7317d6993ba4863988f",
  "DisputeGameFactoryImpl": "0x4bba758f006ef09402ef31724203f316ab74e4a0",
  "AnchorStateRegistryImpl": "0x7b465370bb7a333f99edd19599eb7fb1c2d3f8d2",
  "SuperchainConfigImpl": "0x4da82a327773965b8d4d85fa3db8249b387458e7",
  "ProtocolVersionsImpl": "0x37e15e4d6dffa9e5e320ee1ec036922e563cb76c"
}

It is safe to call this command from a hot wallet. None of the contracts deployed by this command are "ownable," so the deployment address has no further control over the system.

The Init Command

The init command is used to create a new intent and state file in the specified directory. This command is the starting point of each new deployment.

The init command is used like this:

op-deployer init \
  --l1-chain-id <chain ID of your L1> \
  --l2-chain-ids <comman separated list of chain IDs for your L2s> \
  --outdir <directory to write the intent and state files> \
  --intent-type <standard/custom/standard-overrides>

You should then see the following files appear in your output directory:

outdir
├── intent.toml
└── state.json

The intent.toml file is where you specify the configuration for your deployment. The state.json file is where OP Deployer will output the current state of the deployment after each stage of the deployment.

Your intent should look something like this:

configType = "standard"
l1ChainID = 11155420
fundDevAccounts = false
useInterop = false
l1ContractsLocator = "tag://op-contracts/v1.8.0-rc.4"
l2ContractsLocator = "op-contracts/v1.7.0-beta.1+l2-contracts"

[superchainRoles]
  proxyAdminOwner = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"
  protocolVersionsOwner = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"
  guardian = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"

[[chains]]
  id = "0x0000000000000000000000000000000000000000000000000000000000002390"
  baseFeeVaultRecipient = "0x0000000000000000000000000000000000000000"
  l1FeeVaultRecipient = "0x0000000000000000000000000000000000000000"
  sequencerFeeVaultRecipient = "0x0000000000000000000000000000000000000000"
  eip1559DenominatorCanyon = 250
  eip1559Denominator = 50
  eip1559Elasticity = 6
  [chains.roles]
    l1ProxyAdminOwner = "0x0000000000000000000000000000000000000000"
    l2ProxyAdminOwner = "0x0000000000000000000000000000000000000000"
    systemConfigOwner = "0x0000000000000000000000000000000000000000"
    unsafeBlockSigner = "0x0000000000000000000000000000000000000000"
    batcher = "0x0000000000000000000000000000000000000000"
    proposer = "0x0000000000000000000000000000000000000000"
    challenger = "0x0000000000000000000000000000000000000000"

Before you can use your intent file for a deployment, you will need to update all zero values to whatever is appropriate for your chain.

The Apply Command

Once you have initialized your intent and state files, you can use the apply command to perform the deployment.

You can call the apply command like this:


op-deployer apply \
  --workdir <directory containing the intent and state files> \
  <... additional arguments ...>

You will need to specify additional arguments depending on what you're trying to do. See below for a reference of each supported CLI arg.

--deployment-target

Default: live

--deployment-target specifies where each chain should be deployed to. It can be one of the following values:

  • live: Deploys to a live L1. Concretely, this means that OP Deployer will send transactions identified by vm.broadcast calls to L1. --l1-rpc-url and --private-key must be specified when using this target.
  • genesis: Deploys to an L1 genesis file. This is useful for testing or local development purposes. You do not need to specify any additional arguments when using this target.
  • calldata: Deploys to a calldata file. This is useful for generating inputs to multisig wallets for future execution.
  • noop: Doesn't deploy anything. This is useful for performing a dry-run of the deployment process prior to another deployment target.

--l1-rpc-url

Defines the RPC URL of the L1 chain to deploy to.

--private-key

Defines the private key to use for signing transactions. This is only required for deployment targets that involve sending live transactions. Note that ownership over each L2 is transferred to the proxy admin owner specified in the intent after the deployment completes, so it's OK to use a hot key for this purpose.

The Verify Command

Once you have deployed contracts via bootstrap, you can use the verify command to verify the source code on Etherscan. Constructor args used in the verification request are extracted automatically from contract initcode via the tx that created the contract.

You can call the verify command like this:

op-deployer verify \
  --l1-rpc-url <l1 rpc url> \
  --input-file <filepath to input .json file> \
  --etherscan-api-key <your free etherscan api key> \
  --artifacts-locator <l1 forge-artifacts locator>

CLI Args

--l1-rpc-url

Defines the RPC URL of the L1 chain to deploy to (currently only supports mainnet and sepolia).

--input-file

The full filepath to the input .json file. This file should be a key/value store where the key is a contract name and the value is the contract address. The output of the bootstrap superchain|implementations commands is a good example of this format, and those output files can be fed directly into verify. Unless the --contract-name flag is passed, all contracts in the input file will be verified.

Example:

{
  "opcmAddress": "0x437d303c20ea12e0edba02478127b12cbad54626",
  "opcmContractsContainerAddress": "0xf89d7ce62fc3a18354b37b045017d585f7e332ab",
  "opcmGameTypeAdderAddress": "0x9aa4b6c0575e978dbe6d6bc31b7e4403ea8bd81d",
  "opcmDeployerAddress": "0x535388c15294dc77a287430926aba5ba5fe6016a",
  "opcmUpgraderAddress": "0x68a7a93750eb56dd043f5baa41022306e6cd50fa",
  "delayedWETHImplAddress": "0x33ddc90167c923651e5aef8b14bc197f3e8e7b56",
  "optimismPortalImplAddress": "0x54b75cb6f44e36768912e070cd9cb995fc887e6c",
  "ethLockboxImplAddress": "0x05484deeb3067a5332960ca77a5f5603df878ced",
  "preimageOracleSingletonAddress": "0xfbcd4b365f97cb020208b5875ceaf6de76ec068b",
  "mipsSingletonAddress": "0xcc50288ad0d79278397785607ed675292dce37b1",
  "systemConfigImplAddress": "0xfb24aa6d99824b2c526768e97b23694aa3fe31d6",
  "l1CrossDomainMessengerImplAddress": "0x957c0bf84fe541efe46b020a6797fb1fb2eaa6ac",
  "l1ERC721BridgeImplAddress": "0x62786d16978436f5d85404735a28b9eb237e63d0",
  "l1StandardBridgeImplAddress": "0x6c9b377c00ec7e6755aec402cd1cfff34fa75728",
  "optimismMintableERC20FactoryImplAddress": "0x3842175f3af499c27593c772c0765f862b909b93",
  "disputeGameFactoryImplAddress": "0x70ed1725abb48e96be9f610811e33ed8a0fa97f9",
  "anchorStateRegistryImplAddress": "0xce2206af314e5ed99b48239559bdf8a47b7524d4",
  "superchainConfigImplAddress": "0x77008cdc99fb1cf559ac33ca3a67a4a2f04cc5ef",
  "protocolVersionsImplAddress": "0x32e07ddb36833cae3ca1ec5f73ca348a7e9467f4"
}

--contract-name (optional)

Specifies a single contract name, matching a contract key within the input file, to verify. If not provided, all contracts in the input file will be verified.

--artifacts-locator

The locator to forge-artifacts containing the output of the forge build command (i.e. compiled bytecode and solidity source code). This can be a local path (with a file:// prefix), remote URL (with a http:// or https:// prefix), or standard contracts tag (with a tag://op-contracts/v prefix).

Output

Output logs will be printed to the console and look something like the following. If the final results show numFailed=0, all contracts were verified successfully.

INFO [03-05|15:56:55.900] Formatting etherscan verify request      name=superchainConfigProxyAddress            address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:56:55.900] Opening artifact                         path=Proxy.sol/Proxy.json           name=superchainConfigProxyAddress
INFO [03-05|15:56:55.905] contractName                             name=src/universal/Proxy.sol:Proxy
INFO [03-05|15:56:55.905] Extracting constructor args from initcode address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A argSlots=1
INFO [03-05|15:56:56.087] Contract creation tx hash                txHash=0x71b377ccc11304afc32e1016c4828a34010a0d3d81701c7164fb19525ba4fbc4
INFO [03-05|15:56:56.494] Successfully extracted constructor args  address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:56:56.683] Verification request submitted           name=superchainConfigProxyAddress            address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:57:02.035] Verification complete                    name=superchainConfigProxyAddress            address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:57:02.208] Formatting etherscan verify request      name=protocolVersionsImplAddress             address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768
INFO [03-05|15:57:02.208] Opening artifact                         path=ProtocolVersions.sol/ProtocolVersions.json name=protocolVersionsImplAddress
INFO [03-05|15:57:02.215] contractName                             name=src/L1/ProtocolVersions.sol:ProtocolVersions
INFO [03-05|15:57:02.418] Verification request submitted           name=protocolVersionsImplAddress             address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768
INFO [03-05|15:57:07.789] Verification complete                    name=protocolVersionsImplAddress             address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768
INFO [03-05|15:57:07.971] Contract is already verified             name=protocolVersionsProxyAddress            address=0x17C64430Fa08475D41801Dfe36bAFeE9667c6fA7
INFO [03-05|15:57:07.971] --- COMPLETE ---
INFO [03-05|15:57:07.971] final results                            numVerified=4 numSkipped=1 numFailed=0

Known Limitations

  • Does not currently work for contracts in the opchain bundle (deployed via op-deployer apply) that have constructor args. Those constructors args cannot be extracted from the deployment tx.Data() since OPContractsManager.deploy() uses factory pattern with CREATE2 to deploy those contracts.

  • Currently only supports etherscan block explorers. Blockscout support is planned but not yet implemented.

Known Limitations

OP Deployer is subject to some known limitations which we're working on addressing in future releases.

Tagged Releases on New Chains

Fixed in all versions after v0.0.11.

It is not currently possible to deploy chains using tagged contract locators (i.e., those starting with tag://) anywhere except Sepolia and Ethereum mainnet. If you try to, you'll see an error like this:

####################### WARNING! WARNING WARNING! #######################

You are deploying a tagged release to a chain with no pre-deployed OPCM.
Due to a quirk of our contract version system, this can lead to deploying
contracts containing unaudited or untested code. As a result, this 
functionality is currently disabled.

We will fix this in an upcoming release.

This process will now exit.

####################### WARNING! WARNING WARNING! #######################

Like the error says, this is due to a quirk of how we version our smart contracts. We currently follow a process like this:

  1. We tag a release, like op-contracts/v1.8.0.
  2. We update the release notes to reference which contracts are updated in that release.
  3. We manually deploy the updated contract implementations.
  4. We manually deploy a new OPCM to reference the newly-deployed implementations, as well as existing implementations for any contracts that have not been updated.

There's a flaw in this strategy, however. The release only includes the contracts that explicitly changed during that release. This means that any contract not referenced as "updated" in the release notes is "in-development," and has not been audited or approved by governance. Deploying all contracts from the release tag will therefore deploy a combination of prod-ready and in-development code. To get the version of the contract that will actually run in prod, OP Deployer would have to reference all previous releases to get the correct combination of contracts.

For example, to deploy on Holesky you will need to deploy contracts from versions op-contracts/v1.8.0, op-contracts/v1.6.0, and op-contracts/v1.3.0. On Sepolia and mainnet, we've been incrementally deploying implementation contracts so we just use the existing ¬implementations to work around this issue.

We plan on addressing this in our next release. In the meantime, as a workaround you can use a non-tagged locator for development chains, or use Sepolia or Ethereum mainnet as your L1.

Architecture

This section details OP Deployer's architecture and internals. Unless you're contributing directly to OP Deployer, you don't need to read this.

architecture-diagram Full architecture diagram (source)

Deployment Pipeline

OP Deployer is architected as a pipeline where each stage is responsible for a single piece of the deployment process. The pipeline consumes a configuration called an intent which describes the desired state of the chain, and produces a file called the state which describes the current state of the chain during and after the deployment. The steps of the pipeline are:

  1. Initialization
  2. Superchain Deployment
  3. Implementations Deployment
  4. OP Chain Deployment
  5. Alt-DA Deployment
  6. Dispute Game Deployment
  7. L2 Genesis Generation
  8. Setting Start Block

State is written to disk after each state. This allows the pipeline to be restarted from any point in the event of a recoverable error.

We'll cover each of these stages in more detail below.

Initialization

During this step, OP Deployer sets initial values for the pipeline based on the user's intent. These values will be used by downstream stages. For example, if the user is deploying using an existing set of Superchain contracts, those contracts will be inserted into the state during this step.

Superchain/Implementations Deployment

Next, the base contracts for the chain are deployed. This includes Superchain-wide contracts like the SuperchainConfig and ProtocolVersions, as well as implementation contracts that will be used for the OP Chain deployment in the future like the OP Contracts Manager (OPCM).

Most chains will be configured to use existing implementations. In this case, these steps will be skipped.

OP Chain Deployment

The OP Chain itself is deployed during this step. Multiple chains will be deployed if they are specified in the intent. The deployment works by calling into the OPCM, which will emit an event for each successfully-deployed chain.

Customizations Deployment

The next two steps (Alt-DA and Dispute Game) deploy customizations. As their names imply, they deploy Alt-DA and additional dispute game contracts. Typically, these steps will be skipped as they are mostly useful in testing.

L2 Genesis Generation

This step generates the L2 Genesis file which is used to initialize the chain. This file is generated by calling into L2Genesis.sol, and dumping the outputted state.

Setting Start Block

Lastly, the start block is set to the current block number on L1. This is done last to ensure that the start block is relatively recent, since the deployment process can take arbitrarily long.

Scripting Engine

One of OP Deployer's most powerful features is its in-memory EVM scripting engine. The scripting engine provides similar capabilities to Forge:

  • It runs all on-chain calls in a simulated environment first, which allows the effects of on-chain calls to be validated before they cost gas.
  • It exposes Foundry cheatcodes, which allow for deep instrumentation and customization of the EVM environment. These cheatcodes in turn allow OP Deployer to call into Solidity scripts.

The scripting engine is really the heart of OP Deployer. Without it, OP Deployer would be nothing more than a thin wrapper over Forge. The scripting engine enables:

  • Easy integration with existing Solidity-based tooling.
  • Detailed stack traces when deployments fail.
  • Fast feedback loops that prevent sending on-chain transactions that may fail.
  • Live chain forking.

For these reasons and more, the scripting engine is a critical part of OP Deployer's architecture. You will see that almost all on-chain interactions initiated by OP Deployer use the scripting engine to call into a Solidity script. The script then uses vm.broadcast to signal a transaction that should be sent on-chain.

Aside: Why Use Solidity Scripts?

Solidity scripts are much more ergonomic than Go code for complex on-chain interactions. They allow for:

  • Easy integration with existing Solidity-based tooling and libraries.
  • Simple ABI encoding/decoding.
  • Clear separation of concerns between inter-contract calls, and the underlying RPC calls that drive them.

The alternative is to encode all on-chain interactions in Go code. This is possible, but it is much more verbose and requires writing bindings between Go and the Solidity ABI. These bindings are error-prone and difficult to maintain.

Engine Implementation

The scripting engine is implemented in the op-chain-ops/script package. It extends Geth's EVM implementation with Forge cheatcodes, and defines some tools that allow Go structs to be etched into the EVM's memory. Geth exposes hooks that drive most of the engine's behavior. The best way to understand these further is to read the code.

Using the Engine

OP Deployer uses the etching tooling described above to communicate between OP Deployer and the scripting engine. Most Solidity scripts define an input contract, an output contract, and the script itself. The script reads data from fields on the input contract, then sets fields on the output contract as it runs. OP Deployer defines the input and output contracts as Go structs, like this:

package foo_script

type FooInput struct {
  Number uint64
  Bytes []byte
}

type FooOutput struct {
  Result uint64
  Bytes []byte
}

The input and output contracts are then "etched" into the EVM's memory, like this:

package foo_script

// ... struct defs elided

func Run(host *script.Host, input FooInput) (FooOutput, error) {
	// Create a variable to hold our output
	var output FooOutput
	
	// Make new addresses for our input/output contracts
	inputAddr := host.NewScriptAddress()
	outputAddr := host.NewScriptAddress()

	// Inject the input/output contracts into the EVM as precompiles
	cleanupInput, err := script.WithPrecompileAtAddress[*FooInput](host, inputAddr, &input)
	if err != nil {
		return output, fmt.Errorf("failed to insert input precompile: %w", err)
	}
	defer cleanupInput()

	cleanupOutput, err := script.WithPrecompileAtAddress[*FooOutput](host, outputAddr, &output,
		script.WithFieldSetter[*FooOutput])
	if err != nil {
		return output, fmt.Errorf("failed to insert output precompile: %w", err)
	}
	defer cleanupOutput()
	
	// ... do stuff with the input/output contracts ...
}

The script engine will automatically generate getters and setters for the fields on the input and output contracts. You can use the evm: struct tag to customize the behavior of these getters and setters.

Finally, the script itself gets etched into the EVM's memory and executed, like this:

package foo_script

type FooScript struct {
	Run func(input, output common.Address) error
}

func Run(host *script.Host, input FooInput) (FooOutput, error) {
	// .. see implementation above...

	deployScript, cleanupDeploy, err := script.WithScript[FooScript](host, "FooScript.s.sol", "FooScript")
	if err != nil {
		return output, fmt.Errorf("failed to load %s script: %w", scriptFile, err)
	}
	defer cleanupDeploy()

	if err := deployScript.Run(inputAddr, outputAddr); err != nil {
		return output, fmt.Errorf("failed to run %s script: %w", scriptFile, err)
	}

	return output, nil
}

You may notice that the script is loaded from a file. To run the scripting engine, contract artifacts (not source code) must exist somewhere on disk for the scripting engine to use. For more information on that, see the chapter on artifacts locators.

Artifacts Locators

OP Deployer calls into precompiled contract artifacts. To make this work, OP Deployer uses artifacts locators to point to the location of contract artifacts. While locators are nothing more than URLs, they do encode some additional behaviors which are described here.

Locator Types

Locators can be one of three types:

  • tag:// locators, which point to a versioned contracts release. These resolve to a known URL. Artifacts downloaded using a tagged locator are validated against a hardcoded checksum in the OP Deployer implementation. This prevents tampering with the contract artifacts once they have been tagged. Additionally, tagged locators are cached on disk to avoid repeated downloads.
  • https:// locators, which point to a tarball of contract artifacts somewhere on the web. HTTP locators are cached just like tagged locators are, but they are not validated against a checksum.
  • file:// locators, which point to a directory on disk containing the artifacts.

Releases

Versioning

For all releases after v0.0.11, each minor version of OP Deployer will support a single release of the governance-approved smart contracts. If you want to deploy an earlier version of the contracts (which may be dangerous!), you should use an earlier version of OP Deployer. This setup allows our smart contract developers to make breaking changes on develop, while still allowing new chains to be deployed and upgraded using production-ready smart contracts.

If you deploy from an HTTPS or file locator, the deployment behavior will match the contract's tag. For example, if version v0.2.0 supports v2.0.0 then the deployment will work as if you were deploying op-contracts/v2.0.0. Typically, errors like unknown selector: <some hex> imply that you're using the wrong version of OP Deployer for your contract artifacts. If this happens, we recommend trying different versions until you get one that works. Note that this workflow is not recommended for production chains.

Version Backports

From time to time, we may backport bugfixes from develop onto earlier versions of OP Deployer. The process for this is as follows:

  1. If one doesn't exist already, make a new branch for the version lineage you're patching (e.g. v0.2.x). This branch should be based on the latest release of that lineage. The branch should be named as follows: backports/op-deployer/<lineage, i.e. v0.2.0>.
  2. Open a PR with the backport against that branch. Be sure to reference the original commit in the backport.
  3. Make and push a new tag on that lineage.

Adding Support for New Contract Versions

Adding support for a new contract version is a multi-step process. Here's a high-level overview. For the sake of simplicity we will assume you are adding support for a new rc release.

Step 1: Add Support on develop

This section is designed for people developing OP Deployer itself.

First, you need to add support for the new contract version on the develop branch. This means ensuring that the deployment pipeline supports whatever changes are required for the new version. Typically, this means passing in new deployment variables, and responding to ABI changes in the Solidity scripts/OPCM.

Step 2: Add the Published Artifacts

Run the following from the root of the monorepo:

cd packages/contracts-bedrock
just clean
just build
bash scripts/ops/calculate-checksum.sh
# copy the outputted checksum
cd ../../op-deployer
just calculate-artifacts-hash <checksum>

This will calculate the checksum of your artifacts as well as the hash of the artifacts tarball. OP Deployer uses these values to download and verify tagged contract locators.

Now, update standard/standard.go with these values so that the new artifacts tarball can be downloaded:

// Add a new const for your release

const ContractsVXTag = "op-contracts/vX.Y.Z"

var taggedReleases = map[string]TaggedRelease{
    // Other releases...
    ContractsVXTag: {
		ArtifactsHash: common.HexToHash("<the artifacts hash>"), 
		ContentHash:   common.HexToHash("<the checksum>"),
	},
}

// Update the L1/L2 versions accordingly
func IsSupportedL1Version(tag string) bool {
	return tag == ContractsVXTag
}

Step 3: Update the SR With the New Release

Add the new RC to the standard versions in the Superchain Registry.

Step 4: Update the validation Package

The SR is pulled into OP Deployer via the validation package. Update it by running the following command from the root of the monorepo:

go get -u github.com/ethereum-optimism/superchain-registry/validation@<SR commit SHA>

That should be it!