Superfuse Wizard: an interactive code generator for superchain interoperability.
Build your own cross-chain contracts!! use our toolkit
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
1.0 : Prerequisites
Fork optimism 's monorepo:
git clone --depth 1 --branch v1.9.4 https://github.com/ethereum-optimism/optimism.git
All OPStack's contracts are based on v1.9.4
Add the redprint-forge using your favorite package manager, e.g., with pnpm:
pnpm add redprint-forge
Modify OPStack's foundry.toml to the root directory with following:
[profile.default]
# Compilation settings
src = 'src'
out = 'forge-artifacts'
script = 'scripts'
optimizer = true
optimizer_runs = 999999
remappings = [
'@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts',
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
'@openzeppelin/contracts-v5/=lib/openzeppelin-contracts-v5/contracts',
'@rari-capital/solmate/=lib/solmate',
'@lib-keccak/=lib/lib-keccak/contracts/lib',
'@solady/=lib/solady/src',
'forge-std/=lib/forge-std/src',
'ds-test/=lib/forge-std/lib/ds-test/src',
'safe-contracts/=lib/safe-contracts/contracts',
'kontrol-cheatcodes/=lib/kontrol-cheatcodes/src',
'gelato/=lib/automate/contracts'
'@redprint-core/=src/',
'@redprint-deploy/=node_modules/redprint-forge/script',
'@scripts/=scripts/',
'@redprint-test/=node_modules/redprint-forge/test/',
'@redprint-forge-std/=lib/forge-std/src',
'@redprint-openzeppelin/=lib/openzeppelin-contracts/contracts',
'@redprint-openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts',
'@redprint-safe-contracts/=lib/safe-contracts/contracts',
'@redprint-lib-keccak/=lib/lib-keccak/contracts/lib',
'@redprint-solady/=lib/solady/src',
]
...
We use @redprint-/ as a convention to avoid any naming conflicts with your previously installed libararies ( i.e. @redprint-forge-std/ vs @forge-std/)
Add the deploy config file< network >.json to your desired directory ( i.e script/deploy-config/hardhat.json ) with following:
Copy config!
( e.g. hardhat.json, mainnet.json or optimism.json)
{
"l1StartingBlockTag": "0x28665aedb1e78a65e0878347df30f116e5652ce24ede025a80d603656536074f",
"l1ChainID": 11155111,
"l2ChainID": 42069,
"l2BlockTime": 2,
"l1BlockTime": 12,
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
"p2pSequencerAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"batchInboxAddress": "0xff00000000000000000000000000000000042069",
"batchSenderAddress": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleSubmissionInterval": 120,
"l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": 1709796684,
"l2OutputOracleProposer": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2OutputOracleChallenger": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"finalizationPeriodSeconds": 12,
"proxyAdminOwner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"baseFeeVaultRecipient": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l1FeeVaultRecipient": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"sequencerFeeVaultRecipient": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"finalSystemOwner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"superchainConfigGuardian": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"baseFeeVaultWithdrawalNetwork": 0,
"l1FeeVaultWithdrawalNetwork": 0,
"sequencerFeeVaultWithdrawalNetwork": 0,
"gasPriceOracleOverhead": 2100,
"gasPriceOracleScalar": 1000000,
"enableGovernance": true,
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"governanceTokenOwner": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"l2GenesisRegolithTimeOffset": "0x0",
"eip1559Denominator": 50,
"eip1559DenominatorCanyon": 250,
"eip1559Elasticity": 6,
"l2GenesisDeltaTimeOffset": null,
"l2GenesisCanyonTimeOffset": "0x0",
"systemConfigStartBlock": 0,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameAbsolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000000",
"faultGameMaxDepth": 8,
"faultGameClockExtension": 0,
"faultGameMaxClockDuration": 1200,
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF",
"faultGameSplitDepth": 4,
"faultGameWithdrawalDelay": 604800,
"preimageOracleMinProposalSize": 1800000,
"preimageOracleChallengePeriod": 120,
"preimageOracleCancunActivationTimestamp": 0
}
click on ✕ button to close
Add .envand modify as required.
Copy .env!
This following file uses foundry's default test private keys.
DONT forget to change MNEMONIC, DEPLOYER_PRIVATE_KEY and DEPLOYER_ADDRESS to be yours!
RPC_URL_localhost=http://localhost:8545
#secret management
MNEMONIC="test test test test test test test test test test test junk"
# local network 's default private key so it is still not exposed
DEPLOYER_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
DEPLOYER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
# script/Config.sol
DEPLOYMENT_OUTFILE=deployments/31337/.save.json
DEPLOY_CONFIG_PATH=deploy-config/hardhat.json
CHAIN_ID=
IMPL_SALT=$(openssl rand -hex 32)
STATE_DUMP_PATH=
SIG=
DEPLOY_FILE=
DRIPPIE_OWNER_PRIVATE_KEY=9000
# deploy-Config
GS_ADMIN_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
GS_BATCHER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
GS_PROPOSER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
GS_SEQUENCER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
L1_RPC_URL=http://localhost:8545
click on ✕ button to close
Find out more about our guide on github
One-Click Governance Layer 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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {DeploySafeProxyScript} from "@scripts/101_DeploySafeProxyScript.s.sol";
import {IDeployer, getDeployer} from "@redprint-deploy/deployer/DeployScript.sol";
import {Script} from "@redprint-forge-std/Script.sol";
contract DeployAllScript is Script {
IDeployer deployerProcedue;
function run() public {
deployerProcedue = getDeployer();
deployerProcedue.setAutoSave(true);
DeploySafeProxyScript safeDeployments = new DeploySafeProxyScript();
//1) set up Safe Multisig
safeDeployments.deploy();
}
}
After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.
Example!
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"SafeProxyFactory": "0x41C3c259514f88211c4CA2fd805A93F8F9A57504",
"SafeSingleton": "0x0401911641c4781D93c41f9aa8094B171368E6a9",
"SystemOwnerSafe": "0x31Ce59Df6F742e1C83f00427F09DCAaF0765DF3b"
}
click on ✕ button to close
1.1 : Deploy Governance Contract
In your terminal, copy below contracts' codes and run deployment scripts to your prefered network:
forge script script/101_DeploySafeProxyScript.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"
The default contract is MultiSig.
Contract Settings
Deploy Script Settings
Chains
OpSec Management
Contract Settings
Votes
Timelock
Upgradeability
Info
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title IProxy - Helper interface to access the singleton address of the Proxy on-chain.
* @author Richard Meissner - @rmeissner
*/
interface IProxy {
function masterCopy() external view returns (address);
}
/// @custom:security-contact Consult full code at https://github.com/safe-global/safe-smart-account/blob/a9e3385bb38c29d45b3901ff7180b59fcee86ac9/contracts/proxies/SafeProxy.sol
contract GnosisSafeProxy {
address internal singleton;
constructor(address _singleton) {
require(_singleton != address(0), "Invalid singleton address provided");
singleton = _singleton;
}
fallback() external payable {
// solhint-disable-next-line no-inline-assembly
assembly {
let _singleton := sload(0)
// 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s
if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) {
mstore(0, shr(12, shl(12, _singleton)))
return(0, 0x20)
}
calldatacopy(0, 0, calldatasize())
let success := delegatecall(gas(), _singleton, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
if eq(success, 0) {
revert(0, returndatasize())
}
return(0, returndatasize())
}
}
}
// 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 {GnosisSafe as Safe} from "@redprint-safe-contracts/GnosisSafe.sol";
import {GnosisSafeProxy as SafeProxy} from "@redprint-safe-contracts/proxies/GnosisSafeProxy.sol";
import {GnosisSafeProxyFactory as SafeProxyFactory} from "@redprint-safe-contracts/proxies/GnosisSafeProxyFactory.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 DeploySafeProxyScript is DeployScript {
using DeployerFunctions for IDeployer ;
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 (SafeProxyFactory safeProxyFactory_, Safe safeSingleton_, SafeProxy safeProxy_)
{
console.log("Setup Governance ... ");
address safeProxyFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2;
address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552;
safeProxyFactory.code.length == 0
? safeProxyFactory_ = SafeProxyFactory(deployer.deploy_SafeProxyFactory("SafeProxyFactory"))
: safeProxyFactory_ = SafeProxyFactory(safeProxyFactory);
safeSingleton.code.length == 0
? safeSingleton_ = Safe(deployer.deploy_Safe("SafeSingleton"))
: safeSingleton_ = Safe(payable(safeSingleton));
safeProxy_ = SafeProxy(
deployer.deploy_SystemOwnerSafe("SystemOwnerSafe", "SafeProxyFactory", "SafeSingleton", address(owner), DeployScript.implSalt())
);
}
}
After running the deploy script, the address deployed is saved at deployments/31337/.save.json. Otherwise, as specified in .env.<network>.local.
Example!
Your saved address will be different.
You can change DEPLOYMENT_OUTFILE=deployments/31337/.save.json to reflect yours!
{
"SafeProxyFactory": "<ADDRESS_1>",
"SafeSingleton": "<ADDRESS_2>",
"SystemOwnerSafe": "<ADDRESS_3>"
}
click on ✕ button to close