预部署合约(也称为 preinstalls)是指从创世开始就以特定地址存在于链状态中的 EVM 合约。由于地址是固定且预先已知的,因此同一个合约可以在所有包含它的链上都位于相同地址,这使它们非常适合需要在各处都能被可靠访问的基础设施。

默认合约

Cosmos EVM 包含五个默认 preinstall(x/vm/types/preinstall.go):
合约地址用途文档
Create20x4e59b44847b379578588920ca78fbf26c0b4956c使用 CREATE2 进行确定性合约部署EIP-1014
Multicall30xcA11bde05977b3631167028862bE2a173976CA11在一笔交易中批量执行多个合约调用Repo · Site
Permit20x000000000022D473030F116dDEE9F6B43aC78BA3为任意 ERC20 提供基于签名的代币授权Repo · Docs
Safe Singleton Factory0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7在确定性地址部署 Safe 多签钱包Repo · Docs
EIP-29350x0000F90827F1C53a10cb7A02335B175320002935历史区块哈希存储EIP-2935

在创世时启用

Preinstall 通过 genesis.json 中的 app_state.evm.preinstalls 数组进行设置。如何填充该数组,取决于你为链生成创世文件的方式。
  • 使用 local_node.sh:evmd init 不会自动填充 preinstalls。你需要更新 local_node.sh,在节点启动前将默认 preinstalls 写入 genesis.json。每个条目都需要提供合约名称、地址和编译后的字节码:
    local_node.sh
    jq '.app_state["evm"]["preinstalls"]=[
      {
        "name": "Create2",
        "address": "0x4e59b44847b379578588920ca78fbf26c0b4956c",
        "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
      },
      { "name": "Multicall3", "address": "0xcA11bde05977b3631167028862bE2a173976CA11", "code": "0x6080..." },
      { "name": "Permit2", "address": "0x000000000022D473030F116dDEE9F6B43aC78BA3", "code": "0x6040..." },
      { "name": "Safe singleton factory", "address": "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7", "code": "0x7fff..." },
      { "name": "EIP-2935 - Serve historical block hashes from state", "address": "0x0000F90827F1C53a10cb7A02335B175320002935", "code": "0x3373..." }
    ]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
    
    字节码定义见 x/vm/types/preinstall.go。
  • 使用程序化创世生成(例如 evmd testnet):Preinstall 在 evmd/genesis.go 的 NewEVMGenesisState() 中设置。参考链已经在这里设置了 evmGenState.Preinstalls = evmtypes.DefaultPreinstalls,因此会自动包含全部五个默认项。
  • 自定义创世生成:直接在 genesis.json 的 app_state.evm 下包含 preinstalls 数组,并为每个条目指定 name、address 和十六进制编码的 code。示例:
    genesis.json
    {
      "app_state": {
        "evm": {
          "preinstalls": [
            { "name": "MyContract", "address": "0x1234567890123456789012345678901234567890", "code": "0x6001..." }
          ]
        }
      }
    }
    

添加自定义合约

  1. 如果要部署默认项之外的合约,请打开 evmd/genesis.go 并更新 NewEVMGenesisState。下面的示例使用一个最小的 10 字节合约(0x600160005260206000f3),它会在任意调用时返回 1。请将名称、地址和代码替换为你自己的值:
evmd/genesis.go
func NewEVMGenesisState() *evmtypes.GenesisState {
	evmGenState := evmtypes.DefaultGenesisState()
	evmGenState.Params.ActiveStaticPrecompiles = evmtypes.AvailableStaticPrecompiles

	customPreinstall := evmtypes.Preinstall{
		Name:    "MyContract",
		Address: "0x1234567890123456789012345678901234567890",
		Code:    "0x600160005260206000f3",
	}
	evmGenState.Preinstalls = append(evmtypes.DefaultPreinstalls, customPreinstall)

	return evmGenState
}
  1. 在 local_node.sh 中最后一条创世自定义语句(.consensus.params.block.max_gas)之后、# Change proposal periods 注释之前,添加一段 jq patch。使用 += 追加内容,这样不会覆盖之前已设置的任何 preinstall:
local_node.sh
jq '.app_state["evm"]["preinstalls"] += [
  {
    "name": "MyContract",
    "address": "0x1234567890123456789012345678901234567890",
    "code": "0x600160005260206000f3"
  }
]' "$GENESIS" > "$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
有效 preinstall 的要求:
  • 有效的 Ethereum 地址(0x 前缀,40 个十六进制字符)
  • 不能与现有合约或预编译地址(0x1–0x9FF)冲突
  • 非空且有效的 EVM 字节码(十六进制编码)
  1. 重新构建二进制并启动链:
make install && ./local_node.sh -y
-y 标志会清除现有链数据并从创世重新初始化,这是让 preinstall 生效所必需的。
  1. 链运行后,打开一个新终端,并运行以下命令确认合约已安装:
# Check contract code is present
evmd query evm code 0x1234567890123456789012345678901234567890
# Check account exists
evmd query evm account 0x1234567890123456789012345678901234567890
code 的预期输出:
code: YAFgAFJgIGAA8w==
该值是经过 base64 编码的合约字节码。解码后为 0x600160005260206000f3,这正是 genesis.go 和 local_node.sh 中设置的字节码。非空值说明代码已写入状态。 account 的预期输出:
balance: "0"
code_hash: 0xc1d5b4ce3e2a6227293fccce2904121c8647bbe16c1216340b851bf12d12560e
nonce: "0"
  • code_hash:非空哈希说明该地址上存在合约
  • balance: "0":preinstall 部署时不带原生代币余额,这是预期行为
  • nonce: "0":preinstall 不是通过交易部署的,因此 nonce 从 0 开始

在链启动后添加合约

以下方法展示了在链已经运行后部署预部署合约的几种方式。

通过治理提案部署

使用 MsgRegisterPreinstalls 通过治理在运行中的链上部署合约:
proposal.json
{
  "messages": [
    {
      "@type": "/cosmos.evm.vm.v1.MsgRegisterPreinstalls",
      "authority": "<gov module account address>",
      "preinstalls": [
        {
          "name": "Multicall3",
          "address": "0xcA11bde05977b3631167028862bE2a173976CA11",
          "code": "0x..."
        }
      ]
    }
  ],
  "deposit": "10000000<native-denom>",
  "title": "Deploy Multicall3",
  "summary": "Deploy Multicall3 to enable batched contract calls"
}
evmd tx gov submit-proposal proposal.json --from mykey --chain-id <chain-id> --gas auto
evmd tx gov vote 1 yes --from mykey --chain-id <chain-id>

通过链升级处理器部署

在协调的链升级中包含 preinstall:
app/upgrades/v2/upgrades.go
func CreateUpgradeHandler(
    mm *module.Manager,
    configurator module.Configurator,
    evmKeeper *evmkeeper.Keeper,
) upgradetypes.UpgradeHandler {
    return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
        if err := evmKeeper.AddPreinstalls(ctx, evmtypes.DefaultPreinstalls); err != nil {
            return nil, err
        }
        return mm.RunMigrations(ctx, configurator, fromVM)
    }
}

Predeployed contracts (also called preinstalls) are EVM contracts that exist in chain state at a specific address from genesis. Because the address is fixed and known in advance, the same contract can exist at the same address across every chain that includes it, making them useful for infrastructure that needs to be reliably reachable everywhere.

Default contracts

Cosmos EVM includes five default preinstalls (x/vm/types/preinstall.go):
ContractAddressPurposeDocs
Create20x4e59b44847b379578588920ca78fbf26c0b4956cDeterministic contract deployment using CREATE2EIP-1014
Multicall30xcA11bde05977b3631167028862bE2a173976CA11Batch multiple contract calls in one transactionRepo · Site
Permit20x000000000022D473030F116dDEE9F6B43aC78BA3Signature-based token approvals for any ERC20Repo · Docs
Safe Singleton Factory0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7Deploy Safe multisig wallets at deterministic addressesRepo · Docs
EIP-29350x0000F90827F1C53a10cb7A02335B175320002935Historical block hash storageEIP-2935

Enabling at genesis

Preinstalls are set in the app_state.evm.preinstalls array of genesis.json. How you populate that array depends on how you generate genesis for your chain.
  • Using local_node.sh: evmd init does not populate preinstalls automatically. you will need to update local_node.sh to write the default preinstalls into genesis.json before the node starts. Each entry requires the contract name, address, and compiled bytecode:
    local_node.sh
    jq '.app_state["evm"]["preinstalls"]=[
      {
        "name": "Create2",
        "address": "0x4e59b44847b379578588920ca78fbf26c0b4956c",
        "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
      },
      { "name": "Multicall3", "address": "0xcA11bde05977b3631167028862bE2a173976CA11", "code": "0x6080..." },
      { "name": "Permit2", "address": "0x000000000022D473030F116dDEE9F6B43aC78BA3", "code": "0x6040..." },
      { "name": "Safe singleton factory", "address": "0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7", "code": "0x7fff..." },
      { "name": "EIP-2935 - Serve historical block hashes from state", "address": "0x0000F90827F1C53a10cb7A02335B175320002935", "code": "0x3373..." }
    ]' "$GENESIS" >"$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
    
    Bytecodes are defined in x/vm/types/preinstall.go.
  • Using programmatic genesis (e.g., evmd testnet): Preinstalls are set in NewEVMGenesisState() in evmd/genesis.go. The reference chain already sets evmGenState.Preinstalls = evmtypes.DefaultPreinstalls there, so all five defaults are included automatically.
  • Custom genesis generation: Include the preinstalls array directly in your genesis.json under app_state.evm, with each entry specifying name, address, and hex-encoded code. Example:
    genesis.json
    {
      "app_state": {
        "evm": {
          "preinstalls": [
            { "name": "MyContract", "address": "0x1234567890123456789012345678901234567890", "code": "0x6001..." }
          ]
        }
      }
    }
    

Add custom contracts

  1. To deploy a contract beyond the defaults, open evmd/genesis.go and update NewEVMGenesisState. The example below uses a minimal 10-byte contract (0x600160005260206000f3) that returns 1 on any call — replace the name, address, and code with your own:
evmd/genesis.go
func NewEVMGenesisState() *evmtypes.GenesisState {
	evmGenState := evmtypes.DefaultGenesisState()
	evmGenState.Params.ActiveStaticPrecompiles = evmtypes.AvailableStaticPrecompiles

	customPreinstall := evmtypes.Preinstall{
		Name:    "MyContract",
		Address: "0x1234567890123456789012345678901234567890",
		Code:    "0x600160005260206000f3",
	}
	evmGenState.Preinstalls = append(evmtypes.DefaultPreinstalls, customPreinstall)

	return evmGenState
}
  1. Add a jq patch to local_node.sh after the last genesis customization line (.consensus.params.block.max_gas), before the # Change proposal periods comment. Use += to append without overwriting any previously set preinstalls:
local_node.sh
jq '.app_state["evm"]["preinstalls"] += [
  {
    "name": "MyContract",
    "address": "0x1234567890123456789012345678901234567890",
    "code": "0x600160005260206000f3"
  }
]' "$GENESIS" > "$TMP_GENESIS" && mv "$TMP_GENESIS" "$GENESIS"
Requirements for a valid preinstall:
  • Valid Ethereum address (0x prefix, 40 hex characters)
  • Must not conflict with existing contracts or precompile addresses (0x1–0x9FF)
  • Non-empty, valid EVM bytecode (hex encoded)
  1. Rebuild the binary and start the chain:
make install && ./local_node.sh -y
The -y flag wipes any existing chain data and reinitializes from genesis, which is required for the preinstall to take effect.
  1. Once the chain is running, open a new terminal and confirm the contract is installed by running the following commands:
# Check contract code is present
evmd query evm code 0x1234567890123456789012345678901234567890
# Check account exists
evmd query evm account 0x1234567890123456789012345678901234567890
Expected output for code:
code: YAFgAFJgIGAA8w==
The value is the contract bytecode base64-encoded. This decodes to 0x600160005260206000f3, which is the bytecode set in genesis.go and local_node.sh. A non-empty value confirms the code was written to state. Expected output for account:
balance: "0"
code_hash: 0xc1d5b4ce3e2a6227293fccce2904121c8647bbe16c1216340b851bf12d12560e
nonce: "0"
  • code_hash: a non-empty hash confirms the contract exists at this address
  • balance: "0": preinstalls are deployed with no native token balance, which is expected
  • nonce: "0": preinstalls are not deployed via a transaction, so the nonce starts at 0

Add contracts after launch

The following methods are examples of ways to deploy predeployed contracts after the chain is running.

Deploy via governance proposal

Use MsgRegisterPreinstalls to deploy contracts on a running chain via governance:
proposal.json
{
  "messages": [
    {
      "@type": "/cosmos.evm.vm.v1.MsgRegisterPreinstalls",
      "authority": "<gov module account address>",
      "preinstalls": [
        {
          "name": "Multicall3",
          "address": "0xcA11bde05977b3631167028862bE2a173976CA11",
          "code": "0x..."
        }
      ]
    }
  ],
  "deposit": "10000000<native-denom>",
  "title": "Deploy Multicall3",
  "summary": "Deploy Multicall3 to enable batched contract calls"
}
evmd tx gov submit-proposal proposal.json --from mykey --chain-id <chain-id> --gas auto
evmd tx gov vote 1 yes --from mykey --chain-id <chain-id>

Deploy via chain upgrade handler

Include preinstalls in a coordinated chain upgrade:
app/upgrades/v2/upgrades.go
func CreateUpgradeHandler(
    mm *module.Manager,
    configurator module.Configurator,
    evmKeeper *evmkeeper.Keeper,
) upgradetypes.UpgradeHandler {
    return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
        if err := evmKeeper.AddPreinstalls(ctx, evmtypes.DefaultPreinstalls); err != nil {
            return nil, err
        }
        return mm.RunMigrations(ctx, configurator, fromVM)
    }
}