Skip to main content
This guide is for fresh integration of Cosmos EVM v0.5.0 into new chains. For migration from previous versions, see the specific migration guides.

Prerequisites

  • Cosmos SDK v0.53.x based chain
  • IBC-Go v10.x
  • Go 1.23+
  • Understanding of Cosmos SDK module integration
Critical Interface Requirements: v0.5.0 requires implementing specific interfaces (AppWithPendingTxStream, evmserver.Application) with compile-time enforcement. Missing these will cause build failures.

Step 1: Dependencies & Imports

Update go.mod

require (
    github.com/cosmos/cosmos-sdk v0.53.4
    github.com/cosmos/ibc-go/v10 v10.3.0
    github.com/cosmos/evm v0.5.0
)

replace (
    / Required: Use Cosmos fork of go-ethereum
    github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v1.15.11-cosmos-0
)

Key Imports for app.go

Key Imports for app.go
/ EVM-specific imports
import (
    / EVM ante handlers (updated import path in v0.5.0)
    evmante "github.com/cosmos/evm/ante"
    cosmosevmante "github.com/cosmos/evm/ante/evm"

    / Configuration
    evmconfig "github.com/cosmos/evm/config"
    srvflags "github.com/cosmos/evm/server/flags"

    / Mempool (new in v0.5.0)
    evmmempool "github.com/cosmos/evm/mempool"

    / Modules
    "github.com/cosmos/evm/x/erc20"
    erc20keeper "github.com/cosmos/evm/x/erc20/keeper"
    erc20types "github.com/cosmos/evm/x/erc20/types"
    "github.com/cosmos/evm/x/feemarket"
    feemarketkeeper "github.com/cosmos/evm/x/feemarket/keeper"
    "github.com/cosmos/evm/x/precisebank"
    precisebankkeeper "github.com/cosmos/evm/x/precisebank/keeper"
    precisebanktypes "github.com/cosmos/evm/x/precisebank/types"
    "github.com/cosmos/evm/x/vm"
    evmkeeper "github.com/cosmos/evm/x/vm/keeper"
    evmtypes "github.com/cosmos/evm/x/vm/types"

    / Override IBC transfer for ERC20 support
    "github.com/cosmos/evm/x/ibc/transfer"
    transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper"

    / Server interface
    evmserver "github.com/cosmos/evm/server"

    "github.com/ethereum/go-ethereum/common"
)

Step 2: App Structure Definition

App Struct with Required Fields

App Struct with Required Fields
/ App extends BaseApp with EVM functionality
type App struct {
    *baseapp.BaseApp

    / Encoding
    legacyAmino       *codec.LegacyAmino
    appCodec          codec.Codec
    interfaceRegistry types.InterfaceRegistry
    txConfig          client.TxConfig

    / REQUIRED: Client context for EVM operations
    clientCtx client.Context

    / REQUIRED: Pending transaction listeners for mempool integration
    pendingTxListeners []evmante.PendingTxListener

    / Store keys
    keys    map[string]*storetypes.KVStoreKey
    tkeys   map[string]*storetypes.TransientStoreKey
    memKeys map[string]*storetypes.MemoryStoreKey

    / Standard Cosmos SDK keepers
    AccountKeeper         authkeeper.AccountKeeper
    BankKeeper            bankkeeper.Keeper
    StakingKeeper         *stakingkeeper.Keeper
    SlashingKeeper        slashingkeeper.Keeper
    DistrKeeper           distrkeeper.Keeper
    GovKeeper             govkeeper.Keeper
    / ... other standard keepers

    / REQUIRED: EVM-specific keepers
    FeeMarketKeeper   feemarketkeeper.Keeper
    EVMKeeper         *evmkeeper.Keeper
    Erc20Keeper       erc20keeper.Keeper
    PreciseBankKeeper precisebankkeeper.Keeper  / Critical for 18-decimal precision
    TransferKeeper    transferkeeper.Keeper     / EVM-enhanced IBC transfer

    / REQUIRED: EVM mempool (new in v0.5.0)
    EVMMempool *evmmempool.ExperimentalEVMMempool

    / Module management
    ModuleManager      *module.Manager
    BasicModuleManager module.BasicManager
    sm                 *module.SimulationManager
}

Required Interface Methods

/ REQUIRED: SetClientCtx for EVM operations
func (app *App) SetClientCtx(clientCtx client.Context) {
    app.clientCtx = clientCtx
}

/ REQUIRED: AppWithPendingTxStream interface methods
func (app *App) RegisterPendingTxListener(listener func(common.Hash)) {
    app.pendingTxListeners = append(app.pendingTxListeners, listener)
}

func (app *App) onPendingTx(hash common.Hash) {
    for _, listener := range app.pendingTxListeners {
        listener(hash)
    }
}

App Constructor Return Type

/ CRITICAL: Must return evmserver.Application, not servertypes.Application
func NewApp(
    logger log.Logger,
    db dbm.DB,
    traceStore io.Writer,
    appOpts servertypes.AppOptions,
) evmserver.Application {  / NOT servertypes.Application
    / ... implementation
    return app
}

Step 3: Store Keys & Module Setup

Store Key Registration

Store Key Registration
/ Add EVM-specific store keys to your existing keys
keys := storetypes.NewKVStoreKeys(
    / Standard SDK keys...
    authtypes.StoreKey,
    banktypes.StoreKey,
    / ... other standard keys

    / REQUIRED: EVM module store keys
    evmtypes.StoreKey,
    feemarkettypes.StoreKey,
    erc20types.StoreKey,
    precisebanktypes.StoreKey,  / For 18-decimal precision
    ibctransfertypes.StoreKey,  / EVM-enhanced IBC transfer
)

tkeys := storetypes.NewTransientStoreKeys(
    evmtypes.TransientKey,  / Required for EVM state transitions
)

Module Registration

Module Registration
/ Module manager with EVM modules
app.ModuleManager = module.NewManager(
    / Standard SDK modules...
    auth.NewAppModule(appCodec, app.AccountKeeper, ...),
    bank.NewAppModule(appCodec, app.BankKeeper, ...),
    / ... other standard modules

    / IBC modules (use EVM-enhanced transfer)
    ibc.NewAppModule(app.IBCKeeper),
    transferModule,  / EVM-enhanced, not standard IBC transfer

    / REQUIRED: EVM modules in dependency order
    vm.NewAppModule(app.EVMKeeper, app.AccountKeeper, app.AccountKeeper.AddressCodec()),
    feemarket.NewAppModule(app.FeeMarketKeeper),
    erc20.NewAppModule(app.Erc20Keeper, app.AccountKeeper),
    precisebank.NewAppModule(app.PreciseBankKeeper, app.BankKeeper, app.AccountKeeper),
)

Step 4: Keeper Initialization

Why Initialization Order Matters

The keeper initialization order is critical due to dependencies between modules:
  • PreciseBank must be initialized before EVM keeper as it wraps the bank keeper to provide 18-decimal precision
  • FeeMarket must exist before EVM keeper as EVM uses it for gas pricing
  • EVM keeper must be initialized before ERC20 keeper due to a forward reference pattern
  • ERC20 keeper requires a fully initialized EVM keeper to manage token contracts

Initialization Order (Critical)

Initialization Order (Critical)
/ 1. Standard SDK keepers first
app.AccountKeeper = authkeeper.NewAccountKeeper(...)
app.BankKeeper = bankkeeper.NewKeeper(...)
app.StakingKeeper = stakingkeeper.NewKeeper(...)
/ ... other SDK keepers

/ 2. PreciseBank keeper - REQUIRED for 18-decimal precision
/ Why: Cosmos native tokens use 6 decimals, EVM uses 18. PreciseBank handles conversion.
/ Without this: EVM transactions will lose precision, breaking DeFi protocols
app.PreciseBankKeeper = precisebankkeeper.NewKeeper(
    appCodec,
    keys[precisebanktypes.StoreKey],
    app.BankKeeper,      / Wraps the standard bank keeper
    app.AccountKeeper,
)

/ 3. FeeMarket keeper - REQUIRED for EIP-1559 gas pricing
/ Why: Implements dynamic base fee adjustment based on block utilization
/ Without this: EVM will use static gas prices, vulnerable to spam
app.FeeMarketKeeper = feemarketkeeper.NewKeeper(
    appCodec,
    authtypes.NewModuleAddress(govtypes.ModuleName),  / Gov can update params
    keys[feemarkettypes.StoreKey],
    tkeys[feemarkettypes.TransientKey],  / For temporary base fee calculations
    app.GetSubspace(feemarkettypes.ModuleName),
)

/ 4. EVM keeper - Core EVM functionality
/ CRITICAL: Must be before ERC20 keeper due to forward reference pattern
tracer := cast.ToString(appOpts.Get(srvflags.EVMTracer))
app.EVMKeeper = evmkeeper.NewKeeper(
    appCodec,
    keys[evmtypes.StoreKey],         / Persistent EVM state
    tkeys[evmtypes.TransientKey],    / Temporary execution state
    keys,                             / All store keys for precompile access
    authtypes.NewModuleAddress(govtypes.ModuleName),
    app.AccountKeeper,
    app.PreciseBankKeeper,  / MUST use PreciseBank, not BankKeeper
    app.StakingKeeper,      / For precompile access to staking
    app.FeeMarketKeeper,    / For dynamic gas pricing
    &app.ConsensusParamsKeeper,
    &app.Erc20Keeper,       / Forward reference - will be set below
    tracer,                 / "" for production, "json" for debugging
)

/ 5. ERC20 keeper - Manages ERC20 token registration
/ Why after EVM: Needs EVM keeper to deploy token contracts
/ Why before Transfer: IBC transfer needs ERC20 for token mapping
app.Erc20Keeper = erc20keeper.NewKeeper(
    keys[erc20types.StoreKey],
    appCodec,
    authtypes.NewModuleAddress(govtypes.ModuleName),
    app.AccountKeeper,
    app.PreciseBankKeeper,  / For token balance conversions
    app.EVMKeeper,          / To deploy and interact with contracts
    app.StakingKeeper,      / For staking derivative tokens
)

/ 6. Enhanced IBC Transfer keeper - REPLACES standard IBC transfer
/ Why enhanced: Automatic ERC20 registration for IBC tokens
/ Without this: IBC tokens won't be accessible from EVM
app.TransferKeeper = transferkeeper.NewKeeper(
    appCodec,
    keys[ibctransfertypes.StoreKey],
    app.GetSubspace(ibctransfertypes.ModuleName),
    app.IBCKeeper.ChannelKeeper,     / For IBC channel operations
    app.IBCKeeper.ChannelKeeper,     / Duplicate required by interface
    app.IBCKeeper.PortKeeper,        / For IBC port binding
    app.AccountKeeper,
    app.PreciseBankKeeper,  / MUST use PreciseBank for 18-decimal support
    scopedTransferKeeper,
    app.Erc20Keeper,        / For automatic ERC20 registration of IBC tokens
)

Step 5: Ante Handler Integration

Complete Ante Handler Setup

Complete Ante Handler Setup
func (app *App) setAnteHandler(txConfig client.TxConfig, maxGasWanted uint64) {
    options := evmante.HandlerOptions{
        Cdc:                    app.appCodec,
        AccountKeeper:          app.AccountKeeper,
        BankKeeper:             app.BankKeeper,
        ExtensionOptionChecker: cosmosevmtypes.HasDynamicFeeExtensionOption,
        EvmKeeper:              app.EVMKeeper,
        FeegrantKeeper:         app.FeeGrantKeeper,
        IBCKeeper:              app.IBCKeeper,
        FeeMarketKeeper:        app.FeeMarketKeeper,
        SignModeHandler:        txConfig.SignModeHandler(),
        SigGasConsumer:         evmante.SigVerificationGasConsumer,

        / v0.5.0 new parameters
        MaxTxGasWanted:    maxGasWanted,      / From app.toml evm.max-tx-gas-wanted
        TxFeeChecker:      cosmosevmante.NewDynamicFeeChecker(app.FeeMarketKeeper),
        PendingTxListener: app.onPendingTx,   / Required for mempool integration
    }

    if err := options.Validate(); err != nil {
        panic(fmt.Sprintf("ante handler options validation failed: %v", err))
    }

    / Import path changed from evmd/ante to evm/ante in v0.5.0
    app.SetAnteHandler(evmante.NewAnteHandler(options))
}

Call During App Construction

/ In NewApp constructor, AFTER keeper initialization
maxGasWanted := cast.ToUint64(appOpts.Get(srvflags.EVMMaxTxGasWanted))
app.setAnteHandler(app.txConfig, maxGasWanted)

Step 6: Mempool Integration (v0.5.0 Required)

Why Custom Mempool is Required

The standard Cosmos SDK mempool cannot handle Ethereum transactions because:
  • Nonce ordering: Ethereum requires strict nonce ordering per account
  • Gas price dynamics: EIP-1559 transactions use dynamic base fees
  • Transaction replacement: Ethereum allows replacing pending transactions with higher gas
  • Dual transaction types: Must handle both Cosmos and Ethereum transaction formats
Without the EVM mempool:
  • MetaMask transactions will be rejected
  • Nonce gaps will cause transaction failures
  • Gas estimation will be incorrect
  • Transaction replacement won’t work

Complete Mempool Setup

Complete Mempool Setup
/ In NewApp constructor, AFTER ante handler is set
if cosmosevmtypes.GetChainConfig() != nil {
    / Get configuration from app.toml and genesis.json
    blockGasLimit := evmconfig.GetBlockGasLimit(appOpts, logger)
    minTip := evmconfig.GetMinTip(appOpts, logger)

    / Configure EVM mempool with minimal required settings
    / v0.5.0 uses smart defaults - only configure what you need
    mempoolConfig := &evmmempool.EVMMempoolConfig{
        AnteHandler:   app.GetAnteHandler(),      / Required: validates transactions
        BlockGasLimit: blockGasLimit,             / Default: 100M gas if not set
        MinTip:        minTip,                    / Default: 0 (no minimum tip)
        / LegacyPoolConfig: nil,                 / Uses defaults: 10K tx capacity
        / CosmosPoolConfig: nil,                 / Uses defaults: 5K tx capacity
    }

    / Initialize EVM mempool
    / This replaces the standard SDK mempool entirely
    evmMempool := evmmempool.NewExperimentalEVMMempool(
        app.CreateQueryContext,     / For state queries during validation
        logger,
        app.EVMKeeper,              / Access EVM state
        app.FeeMarketKeeper,        / Get current base fee
        app.txConfig,               / Transaction encoding/decoding
        app.clientCtx,              / Client context for RPC
        mempoolConfig,
    )
    app.EVMMempool = evmMempool

    / CRITICAL: Replace BaseApp mempool
    / Without this: EVM transactions won't enter mempool
    app.SetMempool(evmMempool)

    / CRITICAL: Set custom CheckTx handler
    / Without this: EVM transaction validation fails
    checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
    app.SetCheckTxHandler(checkTxHandler)

    / CRITICAL: Set custom PrepareProposal handler
    / Without this: EVM transactions won't be included in blocks
    abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
    abciProposalHandler.SetSignerExtractionAdapter(
        evmmempool.NewEthSignerExtractionAdapter(
            sdkmempool.NewDefaultSignerExtractionAdapter(),
        ),
    )
    app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}

What Happens Without Each Component

ComponentImpact if Missing
SetMempoolEVM transactions rejected with “unsupported tx type”
CheckTxHandlerTransaction validation uses wrong rules, nonce errors
PrepareProposalEVM transactions in mempool but never included in blocks
SignerExtractorCannot identify transaction sender, auth failures

Step 7: Precompile Registration

What Are Precompiles and When Do You Need Them?

Precompiles are special contracts at fixed addresses that provide native Cosmos SDK functionality to EVM smart contracts. They execute native Go code instead of EVM bytecode, offering:
  • Gas efficiency: 10-100x cheaper than Solidity implementations
  • Native integration: Direct access to Cosmos SDK modules
  • Atomicity: Operations complete in the same transaction

Decision Tree for Precompiles

If You Need…Required PrecompilesOptional Precompiles
Basic EVM onlyNoneBech32, P256
DeFi protocolsBankStaking, Distribution
Liquid stakingBank, StakingDistribution, Governance
Cross-chain assetsBank, ICS20ERC20
Full Cosmos featuresAllCustom precompiles

Precompile Addresses and Functions

PrecompileAddressKey FunctionsGas Cost
Bech320x0000…0100Address conversion~3,000
P2560x0000…0400Secp256r1 signatures~3,500
Bank0x0000…0800Send, balance queries~25,000
Staking0x0000…0801Delegate, undelegate~50,000
Distribution0x0000…0802Withdraw rewards~30,000
ICS200x0000…0804IBC transfers~100,000
Governance0x0000…0805Vote, deposit~30,000
Slashing0x0000…0806Unjail validator~50,000

Interface-Based Precompile Setup

Interface-Based Precompile Setup
/ Create precompile options with address codecs
type Optionals struct {
    AddressCodec       address.Codec
    ValidatorAddrCodec address.Codec
    ConsensusAddrCodec address.Codec
}

func defaultOptionals() Optionals {
    return Optionals{
        AddressCodec:       addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
        ValidatorAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()),
        ConsensusAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()),
    }
}

/ Register static precompiles with interface-based constructors
func NewAvailableStaticPrecompiles(
    stakingKeeper stakingkeeper.Keeper,
    distributionKeeper distributionkeeper.Keeper,
    bankKeeper cmn.BankKeeper,          / Interface, not concrete type
    erc20Keeper erc20keeper.Keeper,
    transferKeeper transferkeeper.Keeper,
    channelKeeper channelkeeper.Keeper,
    govKeeper govkeeper.Keeper,
    slashingKeeper slashingkeeper.Keeper,
    cdc codec.Codec,
    opts ...Option,
) (map[common.Address]vm.PrecompiledContract, error) {
    options := defaultOptionals()
    for _, opt := range opts {
        opt(&options)
    }

    precompiles := make(map[common.Address]vm.PrecompiledContract)

    / Bank precompile
    bankPrecompile, err := bankprecompile.NewPrecompile(bankKeeper, cdc)
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate bank precompile: %w", err)
    }
    precompiles[bankPrecompile.Address()] = bankPrecompile

    / Distribution precompile
    distributionPrecompile, err := distprecompile.NewPrecompile(
        cmn.DistributionKeeper(distributionKeeper),
        cdc,
        options.AddressCodec,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate distribution precompile: %w", err)
    }
    precompiles[distributionPrecompile.Address()] = distributionPrecompile

    / Staking precompile
    stakingPrecompile, err := stakingprecompile.NewPrecompile(
        cmn.StakingKeeper(stakingKeeper),
        cdc,
        options.AddressCodec,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate staking precompile: %w", err)
    }
    precompiles[stakingPrecompile.Address()] = stakingPrecompile

    / ICS20 precompile (parameter order changed in v0.4.0)
    ics20Precompile, err := ics20precompile.NewPrecompile(
        bankKeeper,           / bankKeeper FIRST (changed in v0.4.0)
        stakingKeeper,
        transferKeeper,
        channelKeeper,
        cdc,
        options.AddressCodec,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate ics20 precompile: %w", err)
    }
    precompiles[ics20Precompile.Address()] = ics20Precompile

    / Governance precompile (address codec required in v0.4.0)
    govPrecompile, err := govprecompile.NewPrecompile(
        cmn.GovKeeper(govKeeper),
        cdc,
        options.AddressCodec,  / Required in v0.4.0
    )
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate gov precompile: %w", err)
    }
    precompiles[govPrecompile.Address()] = govPrecompile

    / Slashing precompile
    slashingPrecompile, err := slashingprecompile.NewPrecompile(
        cmn.SlashingKeeper(slashingKeeper),
        cdc,
        options.ValidatorAddrCodec,
        options.ConsensusAddrCodec,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate slashing precompile: %w", err)
    }
    precompiles[slashingPrecompile.Address()] = slashingPrecompile

    / Bech32 precompile
    bech32Precompile, err := bech32.NewPrecompile()
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate bech32 precompile: %w", err)
    }
    precompiles[bech32Precompile.Address()] = bech32Precompile

    / P256 precompile (secp256r1)
    p256Precompile, err := p256.NewPrecompile()
    if err != nil {
        return nil, fmt.Errorf("failed to instantiate p256 precompile: %w", err)
    }
    precompiles[p256Precompile.Address()] = p256Precompile

    return precompiles, nil
}

Step 8: CLI Command Integration

Required CLI Wrapper

/ cmd/myapp/cmd/root.go

/ REQUIRED: Wrapper for SDK commands that expect servertypes.Application
sdkAppCreatorWrapper := func(
    logger log.Logger,
    db dbm.DB,
    traceStore io.Writer,
    appOpts servertypes.AppOptions,
) servertypes.Application {
    return ac.newApp(logger, db, traceStore, appOpts)
}

/ Use wrapper for pruning and snapshot commands
rootCmd.AddCommand(
    pruning.Cmd(sdkAppCreatorWrapper, app.DefaultNodeHome),
    snapshot.Cmd(sdkAppCreatorWrapper),
)

Step 9: Configuration Management

Understanding app.toml EVM Parameters

Each parameter controls specific EVM behavior with direct impact on your chain’s operation:

app.toml EVM Section

app.toml EVM Section
# EVM configuration (v0.5.0)
[evm]
# Minimum priority fee for mempool inclusion (in wei)
# 0 = accept any transaction (permissioned/test chains)
# 1000000000 (1 gwei) = standard for public chains (spam protection)
# Impact: Too low allows spam, too high excludes legitimate users
min-tip = 0

# Maximum gas allowed for a single transaction during CheckTx
# 0 = use block gas limit (default behavior)
# 50000000 = limit individual transactions to 50M gas
# Why limit: Prevent DoS from expensive CheckTx validation
# Impact: Too low blocks complex DeFi operations
max-tx-gas-wanted = 0

# EIP-155 chain ID for transaction signatures
# MUST be unique across all EVM chains to prevent replay attacks
# Calculate: hash(cosmos_chain_id) % 2^32 for uniqueness
# Common values: 1 (mainnet fork), 31337 (local dev), your custom ID
evm-chain-id = 262144

# EVM execution tracer for debugging
# "" = disabled (production - best performance)
# "json" = structured JSON output (debugging)
# "struct" = detailed struct logging (deep debugging)
# "access_list" = track state access (gas optimization)
# Impact: Tracing reduces performance by 20-50%
tracer = ""

# SHA3 preimage tracking (not implemented in v0.5.0)
# Reserved for future Ethereum compatibility
cache-preimage = false

[json-rpc]
# Enable JSON-RPC server
enable = true
address = "127.0.0.1:8545"
ws-address = "127.0.0.1:8546"

# Required for transaction queries
enable-indexer = true

# API namespaces (txpool requires mempool)
api = "eth,net,web3,txpool,debug"

# Performance settings
gas-cap = 25000000
filter-cap = 200
logs-cap = 10000
block-range-cap = 10000

genesis.json EVM Parameters

genesis.json EVM Parameters
{
  "app_state": {
    "evm": {
      "params": {
        # The denomination used for EVM transactions (18 decimal places)
        # Must match your chain's base denomination with 'a' prefix
        # Example: "uatom" "aatom", "stake" "astake"
        "evm_denom": "atoken",

        # Additional EIPs to activate beyond standard set
        # [] = use defaults (sufficient for 99% of chains)
        # [3855] = activate EIP-3855 (PUSH0 opcode) if needed
        # Warning: Only add EIPs you fully understand
        "extra_eips": [],

        # IBC channels for cross-chain EVM calls (advanced feature)
        # [] = no cross-chain EVM (recommended for most chains)
        # ["channel-0"] = allow EVM calls over specified channel
        "evm_channels": [],

        # Access control for contract deployment and calls
        # PERMISSIONLESS = anyone can deploy/call (public chains)
        # RESTRICTED = only allowlisted addresses (private chains)
        # FORBIDDEN = completely disabled (security lockdown)
        "access_control": {
          "create": {"access_type": "ACCESS_TYPE_PERMISSIONLESS"},
          "call": {"access_type": "ACCESS_TYPE_PERMISSIONLESS"}
        },

        # Precompiles to activate (must match registered precompiles)
        # Order doesn't matter, but all addresses must be exact
        # Missing precompile = contract calls fail with "no code"
        "active_static_precompiles": [
          "0x0000000000000000000000000000000000000100",  # Bech32
          "0x0000000000000000000000000000000000000400",  # P256
          "0x0000000000000000000000000000000000000800",  # Bank
          "0x0000000000000000000000000000000000000801",  # Staking
          "0x0000000000000000000000000000000000000802",  # Distribution
          "0x0000000000000000000000000000000000000804",  # ICS20
          "0x0000000000000000000000000000000000000805",  # Governance
          "0x0000000000000000000000000000000000000806",  # Slashing
          "0x0000000000000000000000000000000000000807"   # Unknown/Custom
        ],

        # Number of blocks to serve for eth_getBlockByNumber
        # 8192 = ~13 hours at 6s blocks (standard)
        # 43200 = ~3 days (for archive nodes)
        # Impact: Higher = more disk usage, better for indexers
        "history_serve_window": 8192
      }
    },
    "feemarket": {
      "params": {
        "no_base_fee": false,
        "base_fee_change_denominator": 8,
        "elasticity_multiplier": 2,
        "enable_height": "0",
        "base_fee": "1000000000",
        "min_gas_price": "0",
        "min_gas_multiplier": "0.5"
      }
    },
    "erc20": {
      "params": {
        "enable_erc20": true,
        "permissionless_registration": true
      }
    }
  }
}

Step 10: Account Configuration

18-Decimal Precision Setup

/ Set power reduction for 18-decimal base unit (EVM standard)
func init() {
    sdk.DefaultPowerReduction = sdkmath.NewIntFromBigInt(
        new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil),
    )
}

Coin Type Configuration

/ Use coin type 60 for Ethereum compatibility
const CoinType uint32 = 60

Step 11: Testing & Verification

Build Verification

# Verify clean build
go mod tidy
go build ./...

# Test app interfaces (will fail at compile-time if missing)
go build -v ./cmd/myapp

Functionality Testing

Functionality Testing
# Start node
myevmd start

# Test JSON-RPC connectivity
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
  -H "Content-Type: application/json" http://localhost:8545

# Test new eth_createAccessList method
curl -X POST --data '{
  "jsonrpc":"2.0",
  "method":"eth_createAccessList",
  "params":[{"from":"0x...","to":"0x...","data":"0x..."}, "latest"],
  "id":1
}' -H "Content-Type: application/json" http://localhost:8545

# Test mempool functionality
curl -X POST --data '{"jsonrpc":"2.0","method":"txpool_status","params":[],"id":1}' \
  -H "Content-Type: application/json" http://localhost:8545

# Test precompile functionality
cast call 0x0000000000000000000000000000000000000804 \
  "balanceOf(address)" 0x... --rpc-url http://localhost:8545

Configuration Validation

# Verify configuration is loaded correctly
myevmd start --help | grep "evm\."

# Check genesis parameters
myevmd export | jq '.app_state.evm.params'

# Verify mempool configuration
myevmd export | jq '.app_state.erc20'

Step 12: Production Considerations

Security Settings

Security Settings
[json-rpc]
# Production security settings
allow-insecure-unlock = false
enable-profiling = false
ws-origins = ["yourdomain.com"]

[evm]
# Set minimum tip for spam protection
min-tip = 1000000000  # 1 Gwei

# Limit gas for resource protection
max-tx-gas-wanted = 50000000  # 50M gas

Performance Settings

Performance Settings
[json-rpc]
# Optimize for high load
gas-cap = 25000000
filter-cap = 1000
logs-cap = 50000
http-timeout = "30s"
batch-request-limit = 100

[evm]
# Disable tracing in production
tracer = ""

Integration Checklist

  • Dependencies updated to v0.5.0
  • App implements evmserver.Application interface
  • AppWithPendingTxStream interface methods implemented
  • All EVM keepers initialized in correct order
  • PreciseBank configured for 18-decimal precision
  • Ante handler setup with all v0.5.0 parameters
  • Mempool integration with MinTip configuration
  • Precompiles registered with interface constructors
  • CLI commands use SDK wrapper
  • app.toml EVM section configured
  • genesis.json parameters set correctly
  • Build succeeds without errors
  • JSON-RPC functionality verified
  • Mempool operations tested
  • Precompile calls working

Reference Implementation

The complete reference implementation is available in the evmd chain which demonstrates all integration patterns documented in this guide.
I