Quick Guide !!

We have prepared a set of scripts to deploy all contracts in one click. Otherwise, you can look into and customize each contract as you like.

we reference to OPStack 's version of v1.9.4

2.0 : Prerequisites

Make sure you have run the deploy script for governance layer:

You should have following file with below fields saved at the deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

{
"SafeProxyFactory": "<ADDRESS_1>",
"SafeSingleton": "<ADDRESS_2>",
"SystemOwnerSafe": "<ADDRESS_3>"
}

Without this artifact file, the next deployment scripts can not be run.

One-Click L1 Superchain Deployment

Note: This script is not completed yet. It is for one layer only. Check our final script for full deployment.

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/000_DeployAllScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Step 1

Owner

Step 2

Deploy Script:
000_DeployAllScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeploySafeProxyScript} from "@scripts/101_DeploySafeProxyScript.s.sol";
import {Script} from "@redprint-forge-std/Script.sol";
import {SetupSuperchainScript} from "@scripts/200_SetupSuperchain.s.sol";

contract DeployAllScript is Script {
    function run() public {
        DeploySafeProxyScript safeDeployments = new DeploySafeProxyScript();
        //1) set up Safe Multisig
        safeDeployments.deploy();
        SetupSuperchainScript superchainSetups = new SetupSuperchainScript();
        superchainSetups.run();
    }
}

        
      

Contract Settings

Step 2

AddressManager ProxyAdmin SuperchainConfigProxy SuperchainConfig ProtocolVersionsProxy ProtocolVersions
Deploy Script:
200_SetupSuperchainScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeployAddressManagerScript} from "@scripts/201A_DeployAddressManagerScript.s.sol";
import {DeployAndInitializeProtocolVersionsScript} from "@scripts/203B_DeployAndInitializeProtocolVersionsScript.s.sol";
import {DeployAndInitializeSuperchainConfigScript} from "@scripts/202B_DeployAndInitializeSuperchainConfigScript.s.sol";
import {DeployAndSetupProxyAdminScript} from "@scripts/201B_DeployAndSetupProxyAdminScript.s.sol";
import {DeployProtocolVersionsProxyScript} from "@scripts/203A_DeployProtocolVersionsProxyScript.s.sol";
import {DeploySuperchainConfigProxyScript} from "@scripts/202A_DeploySuperchainConfigProxyScript.s.sol";
import {IDeployer, getDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {Script} from "@redprint-forge-std/Script.sol";
import {console} from "@redprint-forge-std/console.sol";

contract SetupSuperchainScript is Script {
    IDeployer deployerProcedue;

    function run() public {
        deployerProcedue = getDeployer();
        deployerProcedue.setAutoSave(true);
        console.log("Setup Superchain ... ");
      
        DeployAddressManagerScript addressManagerDeployments = new DeployAddressManagerScript();
        DeployAndSetupProxyAdminScript proxyAdminDeployments = new DeployAndSetupProxyAdminScript();

        DeploySuperchainConfigProxyScript superchainConfigProxyDeployments = new DeploySuperchainConfigProxyScript();
        DeployAndInitializeSuperchainConfigScript superchainConfigDeployments = new DeployAndInitializeSuperchainConfigScript();

        DeployProtocolVersionsProxyScript protocolVersionsProxyDeployments = new DeployProtocolVersionsProxyScript();
        DeployAndInitializeProtocolVersionsScript protocolVersionsDeployments = new DeployAndInitializeProtocolVersionsScript();

        // Deploy a new ProxyAdmin and AddressManager
        addressManagerDeployments.deploy();
        proxyAdminDeployments.deploy();
        proxyAdminDeployments.initialize();

        // Deploy the SuperchainConfigProxy
        superchainConfigProxyDeployments.deploy();
        superchainConfigDeployments.deploy();
        superchainConfigDeployments.initialize();

        // Deploy the ProtocolVersionsProxy
        protocolVersionsProxyDeployments.deploy();
        protocolVersionsDeployments.deploy();
        protocolVersionsDeployments.initialize();

        console.log("AddressManager at: ", deployerProcedue.getAddress("AddressManager"));
        console.log("ProxyAdmin at: ", deployerProcedue.getAddress("ProxyAdmin"));
        console.log("SuperchainConfigProxy at: ", deployerProcedue.getAddress("SuperchainConfigProxy"));
        console.log("SuperchainConfig at: ", deployerProcedue.getAddress("SuperchainConfig"));
        console.log("ProtocolVersionsProxy at: ", deployerProcedue.getAddress("ProtocolVersionsProxy"));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.1A : Deploy AddressManager Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/201A_DeployAddressManagerScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
AddressManager.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Ownable} from "@redprint-openzeppelin/access/Ownable.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/legacy/AddressManager.sol
contract AddressManager is Ownable {
    mapping(bytes32 => address) private addresses;
    event AddressSet(string indexed name, address newAddress, address oldAddress);

    function setAddress(string memory _name, address _address) external onlyOwner {
        bytes32 nameHash = _getNameHash(_name);
        address oldAddress = addresses[nameHash];
        addresses[nameHash] = _address;
        emit AddressSet(_name, _address, oldAddress);
    }

    function getAddress(string memory _name) external view returns (address) {
        return addresses[_getNameHash(_name)];
    }

    function _getNameHash(string memory _name) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(_name));
    }
}

        
      
Deploy Script:
201A_DeployAddressManagerScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AddressManager} from "@redprint-core/legacy/AddressManager.sol";
import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAddressManagerScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    function deploy() external returns (AddressManager) {
        return AddressManager(deployer.deploy_AddressManager("AddressManager"));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.1B : Deploy And Setup ProxyAdmin Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/201B_DeployAndSetupProxyAdminScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

OpSec Management

Owner

Smart Contract:
ProxyAdmin.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Constants} from "@redprint-core/libraries/Constants.sol";
import {IAddressManager} from "@redprint-core/legacy/interfaces/IAddressManager.sol";
import {IL1ChugSplashProxy, IStaticL1ChugSplashProxy} from "@redprint-core/legacy/interfaces/IL1ChugSplashProxy.sol";
import {IProxy} from "@redprint-core/universal/interfaces/IProxy.sol";
import {IStaticERC1967Proxy} from "@redprint-core/universal/interfaces/IStaticERC1967Proxy.sol";
import {Ownable} from "@redprint-openzeppelin/access/Ownable.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/universal/ProxyAdmin.sol
contract ProxyAdmin is Ownable {
    enum ProxyType {
        ERC1967,
        CHUGSPLASH,
        RESOLVED
    }
     /// @notice A mapping of proxy types, used for backwards compatibility.
    mapping(address => ProxyType) public proxyType;
    /// @notice A reverse mapping of addresses to names held in the AddressManager. This must be
    ///         manually kept up to date with changes in the AddressManager for this contract
    ///         to be able to work as an admin for the ResolvedDelegateProxy type.
    mapping(address => string) public implementationName;
    /// @notice The address of the address manager, this is required to manage the
    ///         ResolvedDelegateProxy type.
    IAddressManager public addressManager;
    bool internal upgrading;

    constructor(address _owner) Ownable() {
        _transferOwnership(_owner);
    }

    function setProxyType(address _address, ProxyType _type) external onlyOwner {
        proxyType[_address] = _type;
    }

    function setImplementationName(address _address, string memory _name)
        external
        onlyOwner
    {
        implementationName[_address] = _name;
    }

    function setAddressManager(IAddressManager _address) external onlyOwner {
        addressManager = _address;
    }

    function setAddress(string memory _name, address _address) external onlyOwner {
        addressManager.setAddress(_name, _address);
    }

    function setUpgrading(bool _upgrading) external onlyOwner {
        upgrading = _upgrading;
    }

    function isUpgrading() external view returns (bool) {
        return upgrading;
    }

    function getProxyImplementation(address _proxy)
        external
        view
        returns (address)
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            return IStaticERC1967Proxy(_proxy).implementation();
        } else if (ptype == ProxyType.CHUGSPLASH) {
            return IStaticL1ChugSplashProxy(_proxy).getImplementation();
        } else if (ptype == ProxyType.RESOLVED) {
            return addressManager.getAddress(implementationName[_proxy]);
        } else {
            revert("ProxyAdmin: unknown proxy type");
        }
    }

    function getProxyAdmin(address payable _proxy)
        external
        view
        returns (address)
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            return IStaticERC1967Proxy(_proxy).admin();
        } else if (ptype == ProxyType.CHUGSPLASH) {
            return IStaticL1ChugSplashProxy(_proxy).getOwner();
        } else if (ptype == ProxyType.RESOLVED) {
            return addressManager.owner();
        } else {
            revert("ProxyAdmin: unknown proxy type");
        }
    }

    function changeProxyAdmin(address payable _proxy, address _newAdmin)
        external
        onlyOwner
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            IProxy(_proxy).changeAdmin(_newAdmin);
        } else if (ptype == ProxyType.CHUGSPLASH) {
            IL1ChugSplashProxy(_proxy).setOwner(_newAdmin);
        } else if (ptype == ProxyType.RESOLVED) {
            addressManager.transferOwnership(_newAdmin);
        } else {
            revert("ProxyAdmin: unknown proxy type");
        }
    }

    function upgrade(address payable _proxy, address _implementation)
        public
        onlyOwner
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            IProxy(_proxy).upgradeTo(_implementation);
        } else if (ptype == ProxyType.CHUGSPLASH) {
            IL1ChugSplashProxy(_proxy).setStorage(
                Constants.PROXY_IMPLEMENTATION_ADDRESS, bytes32(uint256(uint160(_implementation)))
            );
        } else if (ptype == ProxyType.RESOLVED) {
            string memory name = implementationName[_proxy];
            addressManager.setAddress(name, _implementation);
        } else {
            // It should not be possible to retrieve a ProxyType value which is not matched by
            // one of the previous conditions.
            assert(false);
        }
    }

    function upgradeAndCall(address payable _proxy, address _implementation, bytes memory _data)
        external payable
        onlyOwner
    {
        ProxyType ptype = proxyType[_proxy];
        if (ptype == ProxyType.ERC1967) {
            IProxy(_proxy).upgradeToAndCall{ value: msg.value }(_implementation, _data);
        } else {
            // reverts if proxy type is unknown
            upgrade(_proxy, _implementation);
            (bool success,) = _proxy.call{ value: msg.value }(_data);
            require(success, "ProxyAdmin: call to proxy after upgrade failed");
        }
    }
}

        
      
Deploy Script:
201B_DeployAndSetupProxyAdminScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {AddressManager} from "@redprint-core/legacy/AddressManager.sol";
import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {IAddressManager} from "@redprint-core/legacy/interfaces/IAddressManager.sol";
import {ProxyAdmin} from "@redprint-core/universal/ProxyAdmin.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {console} from "@redprint-forge-std/console.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAndSetupProxyAdminScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    ProxyAdmin proxyAdmin;
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function deploy() external returns (ProxyAdmin) {
        proxyAdmin = deployer.deploy_ProxyAdmin("ProxyAdmin", address(owner));
        require(proxyAdmin.owner() == address(owner));
        return proxyAdmin;
    }

    function initialize() external {
        AddressManager addressManager = AddressManager(deployer.mustGetAddress("AddressManager"));
        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if ( address(proxyAdmin.addressManager()) != address(addressManager)) {
             if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
                console.log("Pranking owner ...");
                vm.prank(owner);
             } else {
                console.log("Broadcasting ...");
                vm.broadcast(owner);
             }
            proxyAdmin.setAddressManager( IAddressManager(address(addressManager)));
            console.log("AddressManager setted to : %s", address(addressManager));
        }
        address safe = deployer.mustGetAddress("SystemOwnerSafe");
        if (proxyAdmin.owner() != safe) {
            if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
                console.log("Pranking owner ...");
                vm.prank(owner);
             } else {
                console.log("Broadcasting ...");
                vm.broadcast(owner);
             }

            proxyAdmin.transferOwnership(safe);
            console.log("ProxyAdmin ownership transferred to Safe at: %s", safe);
        }
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.2A : Deploy SuperchainConfigProxy Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/202A_DeploySuperchainConfigProxyScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
Proxy.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Constants} from "@redprint-core/libraries/Constants.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/4/packages/contracts-bedrock/src/universal/Proxy.sol
contract Proxy {
    /**
     * @notice An event that is emitted each time the implementation is changed. This event is part
     *         of the EIP-1967 specification.
     *
     * @param implementation The address of the implementation contract
     */
    event Upgraded(address indexed implementation);
    /**
     * @notice An event that is emitted each time the owner is upgraded. This event is part of the
     *         EIP-1967 specification.
     *
     * @param previousAdmin The previous owner of the contract
     * @param newAdmin      The new owner of the contract
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    modifier proxyCallIfNotAdmin() {
        if (msg.sender == _getAdmin() || msg.sender == address(0)) {
            _;
        } else {
            // This WILL halt the call frame on completion.
            _doProxyCall();
        }
    }

    constructor(address _admin) {
        _changeAdmin(_admin);
    }

    receive() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    fallback() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    function upgradeTo(address _implementation)
        public
        virtual
        returns (proxyCallIfNotAdmin)
    {
        _setImplementation(_implementation);
    }

    function upgradeToAndCall(address _implementation, bytes calldata _data)
        public
        payable virtual proxyCallIfNotAdmin
        returns (bytes memory)
    {
        _setImplementation(_implementation);
        (bool success, bytes memory returndata) = _implementation.delegatecall(_data);
        require(success, "Proxy: delegatecall to new implementation contract failed");
        return returndata;
    }

    function changeAdmin(address _admin) public virtual proxyCallIfNotAdmin {
        _changeAdmin(_admin);
    }

    function admin() public virtual proxyCallIfNotAdmin returns (address) {
        return _getAdmin();
    }

    function implementation()
        public
        virtual proxyCallIfNotAdmin
        returns (address)
    {
        return _getImplementation();
    }

    function _setImplementation(address _implementation) internal {
        bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
        assembly {
            sstore(proxyImplementation, _implementation)
        }
        emit Upgraded(_implementation);
    }

    function _changeAdmin(address _admin) internal {
        address previous = _getAdmin();
        bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
        assembly {
            sstore(proxyOwner, _admin)
        }
        emit AdminChanged(previous, _admin);
    }

    function _doProxyCall() internal {
        address impl = _getImplementation();
        require(impl != address(0), "Proxy: implementation not initialized");
  
        assembly {
            // Copy calldata into memory at 0x0....calldatasize.
            calldatacopy(0x0, 0x0, calldatasize())
  
            // Perform the delegatecall, make sure to pass all available gas.
            let success := delegatecall(gas(), impl, 0x0, calldatasize(), 0x0, 0x0)
  
            // Copy returndata into memory at 0x0....returndatasize. Note that this *will*
            // overwrite the calldata that we just copied into memory but that doesn't really
            // matter because we'll be returning in a second anyway.
            returndatacopy(0x0, 0x0, returndatasize())
  
            // Success == 0 means a revert. We'll revert too and pass the data up.
            if iszero(success) { revert(0x0, returndatasize()) }
  
            // Otherwise we'll just return and pass the data up.
            return(0x0, returndatasize())
        }
    }

    function _getImplementation() internal view returns (address) {
        address impl;
        bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
        assembly {
            impl := sload(proxyImplementation)
        }
        return impl;
    }

    function _getAdmin() internal view returns (address) {
        address owner;
        bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
        assembly {
            owner := sload(proxyOwner)
        }
        return owner;
    }
}

        
      
Deploy Script:
202A_DeploySuperchainConfigProxyScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {Proxy} from "@redprint-core/universal/Proxy.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeploySuperchainConfigProxyScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    function deploy() external returns (Proxy) {
        address proxyOwner = deployer.mustGetAddress("ProxyAdmin");

        return Proxy(deployer.deploy_ERC1967Proxy("SuperchainConfigProxy", proxyOwner));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.2B : DeployAndInitializeSuperchainConfig Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/202B_DeployAndInitializeSuperchainConfigScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

OpSec Management

Owner

Smart Contract:
SuperchainConfig.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {Initializable} from "@redprint-openzeppelin/proxy/utils/Initializable.sol";
import {Storage} from "@redprint-core/libraries/Storage.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/SuperchainConfig.sol
contract SuperchainConfig is Initializable, ISemver {
    /// @notice Enum representing different types of updates.
    /// @custom:value GUARDIAN            Represents an update to the guardian.
    enum UpdateType {
        GUARDIAN
    }
    /// @notice Whether or not the Superchain is paused.
    bytes32 public constant PAUSED_SLOT = bytes32(uint256(keccak256("superchainConfig.paused")) - 1);
    /// @notice The address of the guardian, which can pause withdrawals from the System.
    ///         It can only be modified by an upgrade.
    bytes32 public constant GUARDIAN_SLOT = bytes32(uint256(keccak256("superchainConfig.guardian")) - 1);
    /// @notice Emitted when the pause is triggered.
    /// @param identifier A string helping to identify provenance of the pause transaction.
    event Paused(string identifier);
    /// @notice Emitted when the pause is lifted.
    event Unpaused();
    event ConfigUpdate(UpdateType indexed updateType, bytes data);
    /// @notice Semantic version.
    /// @custom:semver 1.1.1-beta.1
    string public constant version = "1.1.1-beta.1";

    constructor() {
        initialize({ _guardian: address(0), _paused: false });
    }

    function initialize(address _guardian, bool _paused) public initializer {
        _setGuardian(_guardian);
        if (_paused) {
            _pause("Initializer paused");
        }
    }

    function guardian() public view returns (address guardian_) {
        guardian_ = Storage.getAddress(GUARDIAN_SLOT);
    }

    function paused() public view returns (bool paused_) {
        paused_ = Storage.getBool(PAUSED_SLOT);
    }

    function pause(string memory _identifier) external {
        require(msg.sender == guardian(), "SuperchainConfig: only guardian can pause");
        _pause(_identifier);
    }

    function _pause(string memory _identifier) internal returns (address) {
        Storage.setBool(PAUSED_SLOT, true);
        emit Paused(_identifier);
    }

    function unpause() external {
        require(msg.sender == guardian(), "SuperchainConfig: only guardian can unpause");
        Storage.setBool(PAUSED_SLOT, false);
        emit Unpaused();
    }

    function _setGuardian(address _guardian) internal {
        Storage.setAddress(GUARDIAN_SLOT, _guardian);
        emit ConfigUpdate(UpdateType.GUARDIAN, abi.encode(_guardian));
    }
}

        
      
Deploy Script:
202B_DeployAndInitializeSuperchainConfigScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {Proxy} from "@redprint-core/universal/Proxy.sol";
import {SafeScript} from "@redprint-deploy/safe-management/SafeScript.sol";
import {SuperchainConfig} from "@redprint-core/L1/SuperchainConfig.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {console} from "@redprint-forge-std/console.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAndInitializeSuperchainConfigScript is DeployScript, SafeScript {
    using DeployerFunctions for IDeployer ;
    SuperchainConfig superchainConfig;
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function deploy() external returns (SuperchainConfig) {
        bytes32 _salt = DeployScript.implSalt();

        DeployOptions memory options = DeployOptions({salt:_salt});

        superchainConfig = deployer.deploy_SuperchainConfig("SuperchainConfig", options);
        return superchainConfig;
    }

    function initialize() external {
        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
            console.log("Pranking owner ...");
            vm.startPrank(owner);
            initializeSuperchainConfig();
            vm.stopPrank();
        } else {
            console.log("Broadcasting ...");
            vm.startBroadcast(owner);

            initializeSuperchainConfig();
            console.log("SuperchainConfig setted to : %s", address(superchainConfig));

            vm.stopBroadcast();
        }
    }

    function initializeSuperchainConfig() public {
        console.log("Upgrading and initializing SuperchainConfig");

        address payable superchainConfigProxy = deployer.mustGetAddress("SuperchainConfigProxy");
        address proxyAdmin = deployer.mustGetAddress("ProxyAdmin");
        address safe = deployer.mustGetAddress("SystemOwnerSafe");

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: superchainConfigProxy,
            _implementation:  address(superchainConfig),
            _innerCallData: abi.encodeCall(SuperchainConfig.initialize, ( deployer.getConfig().superchainConfigGuardian(), false))
        });

        ChainAssertions.checkSuperchainConfig({ _contracts: deployer.getProxiesUnstrict(), _cfg: deployer.getConfig(), _isPaused: false, _isProxy: true  });
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.3A : Deploy ProtocolVersionsProxy Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/203A_DeployProtocolVersionsProxyScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

Smart Contract:
Proxy.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Constants} from "@redprint-core/libraries/Constants.sol";

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/universal/Proxy.sol
contract Proxy {
    /**
     * @notice An event that is emitted each time the implementation is changed. This event is part
     *         of the EIP-1967 specification.
     *
     * @param implementation The address of the implementation contract
     */
    event Upgraded(address indexed implementation);
    /**
     * @notice An event that is emitted each time the owner is upgraded. This event is part of the
     *         EIP-1967 specification.
     *
     * @param previousAdmin The previous owner of the contract
     * @param newAdmin      The new owner of the contract
     */
    event AdminChanged(address previousAdmin, address newAdmin);

    modifier proxyCallIfNotAdmin() {
        if (msg.sender == _getAdmin() || msg.sender == address(0)) {
            _;
        } else {
            // This WILL halt the call frame on completion.
            _doProxyCall();
        }
    }

    constructor(address _admin) {
        _changeAdmin(_admin);
    }

    receive() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    fallback() external payable {
        // Proxy call by default.
        _doProxyCall();
    }

    function upgradeTo(address _implementation)
        public
        virtual
        returns (proxyCallIfNotAdmin)
    {
        _setImplementation(_implementation);
    }

    function upgradeToAndCall(address _implementation, bytes calldata _data)
        public
        payable virtual proxyCallIfNotAdmin
        returns (bytes memory)
    {
        _setImplementation(_implementation);
        (bool success, bytes memory returndata) = _implementation.delegatecall(_data);
        require(success, "Proxy: delegatecall to new implementation contract failed");
        return returndata;
    }

    function changeAdmin(address _admin) public virtual proxyCallIfNotAdmin {
        _changeAdmin(_admin);
    }

    function admin() public virtual proxyCallIfNotAdmin returns (address) {
        return _getAdmin();
    }

    function implementation()
        public
        virtual proxyCallIfNotAdmin
        returns (address)
    {
        return _getImplementation();
    }

    function _setImplementation(address _implementation) internal {
        bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
        assembly {
            sstore(proxyImplementation, _implementation)
        }
        emit Upgraded(_implementation);
    }

    function _changeAdmin(address _admin) internal {
        address previous = _getAdmin();
        bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
        assembly {
            sstore(proxyOwner, _admin)
        }
        emit AdminChanged(previous, _admin);
    }

    function _doProxyCall() internal {
        address impl = _getImplementation();
        require(impl != address(0), "Proxy: implementation not initialized");
  
        assembly {
            // Copy calldata into memory at 0x0....calldatasize.
            calldatacopy(0x0, 0x0, calldatasize())
  
            // Perform the delegatecall, make sure to pass all available gas.
            let success := delegatecall(gas(), impl, 0x0, calldatasize(), 0x0, 0x0)
  
            // Copy returndata into memory at 0x0....returndatasize. Note that this *will*
            // overwrite the calldata that we just copied into memory but that doesn't really
            // matter because we'll be returning in a second anyway.
            returndatacopy(0x0, 0x0, returndatasize())
  
            // Success == 0 means a revert. We'll revert too and pass the data up.
            if iszero(success) { revert(0x0, returndatasize()) }
  
            // Otherwise we'll just return and pass the data up.
            return(0x0, returndatasize())
        }
    }

    function _getImplementation() internal view returns (address) {
        address impl;
        bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
        assembly {
            impl := sload(proxyImplementation)
        }
        return impl;
    }

    function _getAdmin() internal view returns (address) {
        address owner;
        bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
        assembly {
            owner := sload(proxyOwner)
        }
        return owner;
    }
}

        
      
Deploy Script:
203A_DeployProtocolVersionsProxyScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DeployScript} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, IDeployer} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {Proxy} from "@redprint-core/universal/Proxy.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployProtocolVersionsProxyScript is DeployScript {
    using DeployerFunctions for IDeployer ;
    function deploy() external returns (Proxy) {
        address proxyOwner = deployer.mustGetAddress("ProxyAdmin");

        return Proxy(deployer.deploy_ERC1967Proxy("ProtocolVersionsProxy", proxyOwner));
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.

2.3B : DeployAndInitializeProtocolVersions Contract

In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:

forge script script/203B_DeployAndInitializeProtocolVersionsScript.s.sol --trezor --sender <DEPLOYER_ADDRESS> --rpc-url <RPC_URL> --broadcast

(Optional), you can specify your derivation path:

--mnemonic-derivation-paths "m/44'/60'/0'/0/0"

Contract Settings

OpSec Management

Owner

Smart Contract:
ProtocolVersions.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ISemver} from "@redprint-core/universal/interfaces/ISemver.sol";
import {OwnableUpgradeable} from "@redprint-openzeppelin-upgradeable/access/OwnableUpgradeable.sol";
import {Storage} from "@redprint-core/libraries/Storage.sol";

type ProtocolVersion is uint256;

/// @custom:security-contact Consult full code at https://github.com/ethereum-optimism/optimism/blob/v1.9.4/packages/contracts-bedrock/src/L1/ProtocolVersions.sol
contract ProtocolVersions is OwnableUpgradeable, ISemver {
    /// @notice Enum representing different types of updates.
    /// @custom:value REQUIRED_PROTOCOL_VERSION              Represents an update to the required protocol version.
    /// @custom:value RECOMMENDED_PROTOCOL_VERSION           Represents an update to the recommended protocol version.
    enum UpdateType {
        REQUIRED_PROTOCOL_VERSION,
        RECOMMENDED_PROTOCOL_VERSION
    }
    /// @notice Version identifier, used for upgrades.
    uint256 public constant VERSION = 0;
    /// @notice Storage slot that the required protocol version is stored at.
    bytes32 public constant REQUIRED_SLOT = bytes32(uint256(keccak256("protocolversion.required")) - 1);
    /// @notice Storage slot that the recommended protocol version is stored at.
    bytes32 public constant RECOMMENDED_SLOT = bytes32(uint256(keccak256("protocolversion.recommended")) - 1);
    /// @notice Emitted when configuration is updated.
    /// @param version    ProtocolVersion version.
    /// @param updateType Type of update.
    /// @param data       Encoded update data.
    event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
    /// @notice Semantic version.
    /// @custom:semver 1.0.1-beta.3
    string public constant version = "1.0.1-beta.3";

    constructor() {
        initialize({
            _owner: address(0xdEaD),
            _required: ProtocolVersion.wrap(uint256(0)),
            _recommended: ProtocolVersion.wrap(uint256(0))
        });
    }

    function initialize(address _owner, ProtocolVersion _required, ProtocolVersion _recommended)
        public
        initializer
    {
        __Ownable_init();
        transferOwnership(_owner);
        _setRequired(_required);
        _setRecommended(_recommended);
    }

    function required() external view returns (ProtocolVersion out_) {
        out_ = ProtocolVersion.wrap(Storage.getUint(REQUIRED_SLOT));
    }

    function setRequired(ProtocolVersion _required) external onlyOwner {
        _setRequired(_required);
    }

    function _setRequired(ProtocolVersion _required) internal {
        Storage.setUint(REQUIRED_SLOT, ProtocolVersion.unwrap(_required));
        bytes memory data = abi.encode(_required);
        emit ConfigUpdate(VERSION, UpdateType.REQUIRED_PROTOCOL_VERSION, data);
    }

    function recommended() external view returns (ProtocolVersion out_) {
        out_ = ProtocolVersion.wrap(Storage.getUint(RECOMMENDED_SLOT));
    }

    function setRecommended(ProtocolVersion _recommended) external onlyOwner {
        _setRecommended(_recommended);
    }

    function _setRecommended(ProtocolVersion _recommended) internal {
        Storage.setUint(RECOMMENDED_SLOT, ProtocolVersion.unwrap(_recommended));

        bytes memory data = abi.encode(_recommended);
        emit ConfigUpdate(VERSION, UpdateType.RECOMMENDED_PROTOCOL_VERSION, data);
    }
}

        
      
Deploy Script:
203B_DeployAndInitializeProtocolVersionsScript.s.sol
        
          // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ChainAssertions} from "@redprint-deploy/optimism/ChainAssertions.sol";
import {DeployScript, IDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {DeployerFunctions, DeployOptions} from "@redprint-deploy/deployer/DeployerFunctions.sol";
import {ProtocolVersions, ProtocolVersion} from "@redprint-core/L1/ProtocolVersions.sol";
import {SafeScript} from "@redprint-deploy/safe-management/SafeScript.sol";
import {Types} from "@redprint-deploy/optimism/Types.sol";
import {Vm, VmSafe} from "@redprint-forge-std/Vm.sol";
import {console} from "@redprint-forge-std/console.sol";

/// @custom:security-contact Consult full internal deploy script at https://github.com/Ratimon/redprint-forge
contract DeployAndInitializeProtocolVersionsScript is DeployScript, SafeScript {
    using DeployerFunctions for IDeployer ;
    ProtocolVersions versions;
    string mnemonic = vm.envString("MNEMONIC");
    uint256 ownerPrivateKey = vm.deriveKey(mnemonic, "m/44'/60'/0'/0/", 1);
    address owner = vm.envOr("DEPLOYER_ADDRESS", vm.addr(ownerPrivateKey));

    function deploy() external returns (ProtocolVersions) {
        bytes32 _salt = DeployScript.implSalt();

        DeployOptions memory options = DeployOptions({salt:_salt});

        versions = deployer.deploy_ProtocolVersions("ProtocolVersions", options);

        Types.ContractSet memory contracts = deployer.getProxiesUnstrict();
        contracts.ProtocolVersions = address(versions);
        ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: deployer.getConfig(), _isProxy: false });

        return versions;
    }

    function initialize() external {
        (VmSafe.CallerMode mode ,address msgSender, ) = vm.readCallers();
        if(mode != VmSafe.CallerMode.Broadcast && msgSender != owner) {
            console.log("Pranking owner ...");
            vm.startPrank(owner);
            initializeProtocolVersions();
            vm.stopPrank();
        } else {
            console.log("Broadcasting ...");
            vm.startBroadcast(owner);

            initializeProtocolVersions();
            console.log("ProtocolVersions setted to : %s", address(versions));

            vm.stopBroadcast();
        }
    }

    function initializeProtocolVersions() public {
        console.log("Upgrading and initializing ProtocolVersions proxy");

        address proxyAdmin = deployer.mustGetAddress("ProxyAdmin");
        address safe = deployer.mustGetAddress("SystemOwnerSafe");

        address protocolVersionsProxy = deployer.mustGetAddress("ProtocolVersionsProxy");
        address protocolVersions = deployer.mustGetAddress("ProtocolVersions");

        address finalSystemOwner = deployer.getConfig().finalSystemOwner();
        uint256 requiredProtocolVersion = deployer.getConfig().requiredProtocolVersion();
        uint256 recommendedProtocolVersion = deployer.getConfig().recommendedProtocolVersion();

        _upgradeAndCallViaSafe({
            _proxyAdmin: proxyAdmin,
            _safe: safe,
            _owner: owner,
            _proxy: payable(protocolVersionsProxy),
            _implementation: protocolVersions,
            _innerCallData: abi.encodeCall(
                ProtocolVersions.initialize,
                (
                    finalSystemOwner,
                    ProtocolVersion.wrap(requiredProtocolVersion),
                    ProtocolVersion.wrap(recommendedProtocolVersion)
                )
            )
        });

        ProtocolVersions _versions = ProtocolVersions(protocolVersionsProxy);
        string memory version = _versions.version();
        console.log("ProtocolVersions version: %s", version);

        ChainAssertions.checkProtocolVersions({ _contracts: deployer.getProxiesUnstrict(), _cfg: deployer.getConfig(), _isProxy: true });
    }
}

        
      

After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.