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

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 (wraps bank keeper for 18-decimal precision)
app.PreciseBankKeeper = precisebankkeeper.NewKeeper(
    appCodec,
    keys[precisebanktypes.StoreKey],
    app.BankKeeper,
    app.AccountKeeper,
)

// 3. FeeMarket keeper (required by EVM)
app.FeeMarketKeeper = feemarketkeeper.NewKeeper(
    appCodec,
    authtypes.NewModuleAddress(govtypes.ModuleName),
    keys[feemarkettypes.StoreKey],
    tkeys[feemarkettypes.TransientKey],
    app.GetSubspace(feemarkettypes.ModuleName),
)

// 4. EVM keeper (MUST be before ERC20 keeper)
tracer := cast.ToString(appOpts.Get(srvflags.EVMTracer))
app.EVMKeeper = evmkeeper.NewKeeper(
    appCodec,
    keys[evmtypes.StoreKey],
    tkeys[evmtypes.TransientKey],
    keys,
    authtypes.NewModuleAddress(govtypes.ModuleName),
    app.AccountKeeper,
    app.PreciseBankKeeper,  // Use PreciseBank for 18-decimal support
    app.StakingKeeper,
    app.FeeMarketKeeper,
    &app.ConsensusParamsKeeper,
    &app.Erc20Keeper,  // Forward reference
    tracer,
)

// 5. ERC20 keeper (after EVM keeper)
app.Erc20Keeper = erc20keeper.NewKeeper(
    keys[erc20types.StoreKey],
    appCodec,
    authtypes.NewModuleAddress(govtypes.ModuleName),
    app.AccountKeeper,
    app.PreciseBankKeeper,
    app.EVMKeeper,
    app.StakingKeeper,
)

// 6. Enhanced IBC Transfer keeper for ERC20 support
app.TransferKeeper = transferkeeper.NewKeeper(
    appCodec,
    keys[ibctransfertypes.StoreKey],
    app.GetSubspace(ibctransfertypes.ModuleName),
    app.IBCKeeper.ChannelKeeper,
    app.IBCKeeper.ChannelKeeper,
    app.IBCKeeper.PortKeeper,
    app.AccountKeeper,
    app.PreciseBankKeeper,  // Use PreciseBank
    scopedTransferKeeper,
    app.Erc20Keeper,  // EVM integration
)

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)

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
    mempoolConfig := &evmmempool.EVMMempoolConfig{
        AnteHandler:   app.GetAnteHandler(),      // Must be set first
        BlockGasLimit: blockGasLimit,             // From genesis consensus params
        MinTip:        minTip,                    // From app.toml evm.min-tip (v0.5.0)
    }

    // Initialize EVM mempool
    evmMempool := evmmempool.NewExperimentalEVMMempool(
        app.CreateQueryContext,
        logger,
        app.EVMKeeper,
        app.FeeMarketKeeper,
        app.txConfig,
        app.clientCtx,
        mempoolConfig,
    )
    app.EVMMempool = evmMempool

    // REQUIRED: Replace BaseApp mempool
    app.SetMempool(evmMempool)

    // REQUIRED: Set custom CheckTx handler
    checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
    app.SetCheckTxHandler(checkTxHandler)

    // REQUIRED: Set custom PrepareProposal handler
    abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
    abciProposalHandler.SetSignerExtractionAdapter(
        evmmempool.NewEthSignerExtractionAdapter(
            sdkmempool.NewDefaultSignerExtractionAdapter(),
        ),
    )
    app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}

Step 7: Precompile Registration

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

app.toml EVM Section

app.toml EVM Section
# EVM configuration (v0.5.0)
[evm]
# Minimum priority fee for mempool (in wei)
min-tip = 0

# Maximum gas for eth transactions in check tx mode
max-tx-gas-wanted = 0

# EIP-155 chain ID (separate from Cosmos chain ID)
evm-chain-id = 262144

# EVM execution tracer (empty = disabled)
tracer = ""

# SHA3 preimage tracking (not implemented yet)
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": {
        "evm_denom": "atoken",
        "extra_eips": [],
        "evm_channels": [],
        "access_control": {
          "create": {"access_type": "ACCESS_TYPE_PERMISSIONLESS"},
          "call": {"access_type": "ACCESS_TYPE_PERMISSIONLESS"}
        },
        "active_static_precompiles": [
          "0x0000000000000000000000000000000000000100",
          "0x0000000000000000000000000000000000000400",
          "0x0000000000000000000000000000000000000800",
          "0x0000000000000000000000000000000000000801",
          "0x0000000000000000000000000000000000000802",
          "0x0000000000000000000000000000000000000804",
          "0x0000000000000000000000000000000000000805",
          "0x0000000000000000000000000000000000000806",
          "0x0000000000000000000000000000000000000807"
        ],
        "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 = ""

Common Integration Issues

Issue: Build Failure - Interface Not Satisfied

error: *App does not implement evmserver.Application
Solution: Ensure app constructor returns evmserver.Application:
- func NewApp(...) servertypes.Application {
+ func NewApp(...) evmserver.Application {

Issue: Runtime Panic - Missing Interface Methods

panic: App must implement AppWithPendingTxStream interface
Solution: Implement required interface methods:
func (app *App) RegisterPendingTxListener(listener func(common.Hash)) {
    app.pendingTxListeners = append(app.pendingTxListeners, listener)
}

Issue: Mempool Configuration Error

error: mempool config must not be nil
Solution: Ensure mempool configuration is properly set:
Mempool Configuration Fix
mempoolConfig := &evmmempool.EVMMempoolConfig{
    AnteHandler:   app.GetAnteHandler(),  // Must be set AFTER ante handler
    BlockGasLimit: blockGasLimit,
    MinTip:        minTip,                // From app.toml evm.min-tip (v0.5.0)
}

Issue: JSON-RPC Methods Not Working

error: eth_createAccessList not found
Solution: Ensure indexer is enabled and mempool is configured:
[json-rpc]
enable-indexer = true
api = "eth,net,web3,txpool"

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.