> ## Documentation Index
> Fetch the complete documentation index at: https://cosmos-docs-sync-security-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Migration: v0.3.0 to v0.4.0

## Process Overview

* Create an upgrade branch and freeze schema-affecting changes
* Export a pre-upgrade state and archive node configs
* Bump `cosmos/evm` to v0.4.0 and align Cosmos SDK/IBC/CometBFT constraints
* Rewire keepers and AppModule (imports, constructors, `RegisterServices`)
* Add client context field and `SetClientCtx` method
* Add pending transaction listener support
* **Migrate ERC20 precompiles if you have existing token pairs** (see \[this section]./(erc20-precompiles-migration))
* Audit and migrate EVM & FeeMarket params (EIP-1559 knobs, denom/decimals)
* Implement store/params migrations in your UpgradeHandler

## Prep

* Create a branch: `git switch -c upgrade/evm-v0.4`
* Ensure a clean build + tests green pre-upgrade
* Snapshot your current params/genesis for comparison later

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
git switch -c upgrade/evm-v0.4
go test ./...
evmd export > pre-upgrade-genesis.json
```

## Dependency bumps

### Pin EVM and tidy

Bump the `cosmos/evm` dependency in `go.mod`:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- github.com/cosmos/evm v0.3.1
+ github.com/cosmos/evm v0.4.0
```

### Transitive bumps

Check for minor dependency bumps (e.g., `google.golang.org/protobuf`, `github.com/gofrs/flock`, `github.com/consensys/gnark-crypto`):

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
go mod tidy
```

Resolve any version conflicts here before moving on.

## App constructor return type & CLI command wiring

Update your app's `newApp` to return an `evmserver.Application` rather than `servertypes.Application`, and CLI commands that still expect an SDK app creator require a wrapper.

### Change the return type

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// cmd/myapp/cmd/root.go
import (
    evmserver "github.com/cosmos/evm/server"
)

func (a appCreator) newApp(
    l log.Logger,
    db dbm.DB,
    traceStore io.Writer,
    appOpts servertypes.AppOptions,
) evmserver.Application { // Changed from servertypes.Application
    // ...
}
```

### Provide a wrapper for commands that expect the SDK type

Create a thin wrapper and use it for `pruning.Cmd` and `snapshot.Cmd`:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// cmd/myapp/cmd/root.go
sdkAppCreatorWrapper := func(l log.Logger, d dbm.DB, w io.Writer, ao servertypes.AppOptions) servertypes.Application {
    return ac.newApp(l, d, w, ao)
}

rootCmd.AddCommand(
    pruning.Cmd(sdkAppCreatorWrapper, myapp.DefaultNodeHome),
    snapshot.Cmd(sdkAppCreatorWrapper),
)
```

### Add clientCtx and SetClientCtx

Add the clientCtx to your app object:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/app.go
import (
    "github.com/cosmos/cosmos-sdk/client"
)

type MyApp struct {
    // ... existing fields
    clientCtx client.Context
}

func (app *MyApp) SetClientCtx(clientCtx client.Context) {
    app.clientCtx = clientCtx
}
```

## Pending-tx listener support

### Imports

Import the EVM ante package and geth common:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/app.go
import (
    "github.com/cosmos/evm/ante"
    "github.com/ethereum/go-ethereum/common"
)
```

### App state: listeners slice

Add a new field for listeners:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/app.go
type MyApp struct {
    // ... existing fields
    pendingTxListeners []ante.PendingTxListener
}
```

### Registration method

Add a public method to register a listener by txHash:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/app.go
func (app *MyApp) RegisterPendingTxListener(listener func(common.Hash)) {
    app.pendingTxListeners = append(app.pendingTxListeners, listener)
}
```

## Precompiles: optionals + codec injection

### New imports

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/keepers/precompiles.go
import (
    "cosmossdk.io/core/address"
    addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
    sdk "github.com/cosmos/cosmos-sdk/types"
)
```

### Define Optionals + defaults + functional options

Create a small options container with sane defaults pulled from the app's bech32 config:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/keepers/precompiles.go
type Optionals struct {
    AddressCodec       address.Codec // used by gov/staking
    ValidatorAddrCodec address.Codec // used by slashing
    ConsensusAddrCodec address.Codec // used by slashing
}

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

type Option func(*Optionals)

func WithAddressCodec(c address.Codec) Option {
    return func(o *Optionals) { o.AddressCodec = c }
}

func WithValidatorAddrCodec(c address.Codec) Option {
    return func(o *Optionals) { o.ValidatorAddrCodec = c }
}

func WithConsensusAddrCodec(c address.Codec) Option {
    return func(o *Optionals) { o.ConsensusAddrCodec = c }
}
```

### 4.3 Update the precompile factory to accept options

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/keepers/precompiles.go
func NewAvailableStaticPrecompiles(
    ctx context.Context,
    // ... other params
    opts ...Option,
) map[common.Address]vm.PrecompiledContract {
    options := defaultOptionals()
    for _, opt := range opts {
        opt(&options)
    }
    // ... rest of implementation
}
```

### 4.4 Modify individual precompile constructors

**ICS-20 precompile** now needs `bankKeeper` first:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- ibcTransferPrecompile, err := ics20precompile.NewPrecompile(
-     stakingKeeper,
+ ibcTransferPrecompile, err := ics20precompile.NewPrecompile(
+     bankKeeper,
+     stakingKeeper,
      transferKeeper,
      &channelKeeper,
      // ...
```

**Gov precompile** now requires an `AddressCodec`:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- govPrecompile, err := govprecompile.NewPrecompile(govKeeper, cdc)
+ govPrecompile, err := govprecompile.NewPrecompile(govKeeper, cdc, options.AddressCodec)
```

## ERC20 Precompiles Migration

<Warning>
  **This migration is required for chains with existing ERC20 token pairs**

  The storage mechanism for ERC20 precompiles has fundamentally changed in v0.4.0. Without proper migration, your ERC20 tokens will become inaccessible via EVM.
</Warning>

Include this migration with your upgrade if your chain has:

* IBC tokens converted to ERC20
* Token factory tokens with ERC20 representations
* Any existing `DynamicPrecompiles` or `NativePrecompiles` in storage

### Implementation

For complete migration instructions, see: **[ERC20 Precompiles Migration Guide](./erc20-precompiles-migration)**

Add this to your upgrade handler:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// In your upgrade handler
store := ctx.KVStore(storeKeys[erc20types.StoreKey])
const addressLength = 42

// Migrate dynamic precompiles
if oldData := store.Get([]byte("DynamicPrecompiles")); len(oldData) > 0 {
    for i := 0; i < len(oldData); i += addressLength {
        address := common.HexToAddress(string(oldData[i : i+addressLength]))
        erc20Keeper.SetDynamicPrecompile(ctx, address)
    }
    store.Delete([]byte("DynamicPrecompiles"))
}

// Migrate native precompiles
if oldData := store.Get([]byte("NativePrecompiles")); len(oldData) > 0 {
    for i := 0; i < len(oldData); i += addressLength {
        address := common.HexToAddress(string(oldData[i : i+addressLength]))
        erc20Keeper.SetNativePrecompile(ctx, address)
    }
    store.Delete([]byte("NativePrecompiles"))
}
```

### Verification

Post-upgrade, verify your migration succeeded:

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
# Check ERC20 balance (should NOT be 0 if tokens existed before)
cast call $TOKEN_ADDRESS "balanceOf(address)" $USER_ADDRESS --rpc-url http://localhost:8545

# Verify precompiles in state
mantrachaind export | jq '.app_state.erc20.dynamic_precompiles'
```

## Build & quick tests

1. **Compile**:
   ```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
   go build ./...
   ```

2. **Smoke tests** (local single-node):
   * Start your node; ensure RPC starts cleanly
   * Deploy a trivial contract; verify events and logs
   * Send a couple 1559 txs and confirm base-fee behavior looks sane
   * (Optional) register a pending-tx listener and log hashes as they enter the mempool

## Rollout checklist

* Package the new binary (and Cosmovisor upgrade folder if you use it)
* Confirm all validators build the same commit (no `replace` lines)
* Share an `app.toml` diff only if you changed defaults; otherwise regenerate the file from the new binary and re-apply customizations
* Post-upgrade: monitor mempool/pending tx logs, base-fee progression, and contract events for the first 20-50 blocks

## Pitfalls & remedies

* **Forgot wrapper for CLI commands** → `pruning`/`snapshot` panic or wrong type:
  * Ensure you pass `sdkAppCreatorWrapper` (not `ac.newApp`) into those commands

* **ICS-20 precompile build error**:
  * You likely didn't pass `bankKeeper` first; update the call site

* **Governance precompile address parsing fails**:
  * Provide the correct `AddressCodec` via defaults or `WithAddressCodec(...)`

* **Listeners never fire**:
  * Register with `RegisterPendingTxListener` during app construction or module init

## Minimal code snippets

**App listeners**

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/app.go
import (
    "github.com/cosmos/evm/ante"
    "github.com/ethereum/go-ethereum/common"
)

type MyApp struct {
    // ...
    pendingTxListeners []ante.PendingTxListener
}

func (app *MyApp) RegisterPendingTxListener(l func(common.Hash)) {
    app.pendingTxListeners = append(app.pendingTxListeners, l)
}
```

**CLI wrapper**

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// cmd/myapp/cmd/root.go
sdkAppCreatorWrapper := func(l log.Logger, d dbm.DB, w io.Writer, ao servertypes.AppOptions) servertypes.Application {
    return ac.newApp(l, d, w, ao)
}

rootCmd.AddCommand(
    pruning.Cmd(sdkAppCreatorWrapper, myapp.DefaultNodeHome),
    snapshot.Cmd(sdkAppCreatorWrapper),
)
```

**Precompile options & usage**

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// app/keepers/precompiles.go
opts := []Option{
    // override defaults only if you use non-standard prefixes/codecs
    WithAddressCodec(myAcctCodec),
    WithValidatorAddrCodec(myValCodec),
    WithConsensusAddrCodec(myConsCodec),
}

pcs := NewAvailableStaticPrecompiles(ctx, /* ... keepers ... */, opts...)
```

## Additional Breaking Changes (v0.3.1 → v0.4.1)

<Note>
  **Credits**: Thanks to the Mantrachain team for identifying these additional migration requirements.
</Note>

### CallEVM Signature Change

The `CallEVM` function now requires a `gasCap` parameter of type `*big.Int`. You can pass `nil` for default behavior.

**Function Signature** (from `x/vm/keeper/call_evm.go:20`):

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
func (k Keeper) CallEVM(
	ctx sdk.Context,
	abi abi.ABI,
	from, contract common.Address,
	commit bool,
	gasCap *big.Int,  // New parameter
	method string,
	args ...interface{},
) (*types.MsgEthereumTxResponse, error)
```

**Migration Example** (from `x/erc20/keeper/evm.go:87`):

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// Before v0.4.x - gasCap parameter didn't exist
- res, err := k.evmKeeper.CallEVM(ctx, erc20, types.ModuleAddress, contract, false, "decimals")

// After v0.4.x - gasCap parameter required (can be nil)
+ res, err := k.evmKeeper.CallEVM(ctx, erc20, types.ModuleAddress, contract, false, nil, "decimals")
```

**With Custom Gas Cap**:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// If you need to specify a gas cap
import "math/big"

gasLimit := big.NewInt(100000)
res, err := k.evmKeeper.CallEVM(ctx, abi, from, contract, false, gasLimit, "balanceOf", account)
```

**Real usage examples**:

* `x/erc20/keeper/evm.go:87` - decimals call
* `x/erc20/keeper/evm.go:143` - balanceOf call
* `x/ibc/callbacks/keeper/keeper.go:193` - approve call with custom gas cap

### Test Keyring Import Path

The test keyring package has been reorganized into its own subdirectory.

**Import Change**:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- testkeyring "github.com/cosmos/evm/testutil"
+ testkeyring "github.com/cosmos/evm/testutil/keyring"
```

**Example from Tests** (from `tests/integration/precompiles/erc20/test_setup.go:10`):

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
package erc20

import (
    "github.com/stretchr/testify/suite"

    testkeyring "github.com/cosmos/evm/testutil/keyring"
)

type PrecompileTestSuite struct {
    suite.Suite
    keyring testkeyring.Keyring
}

func (s *PrecompileTestSuite) SetupTest() {
    // Create keyring with 2 accounts
    keyring := testkeyring.New(2)

    // Get addresses for pre-funding
    options := []network.ConfigOption{
        network.WithPreFundedAccounts(keyring.GetAllAccAddrs()...),
    }
}
```

**Available Functions** (from `testutil/keyring/keyring.go:58`):

* `New(nAccs int) Keyring` - Create keyring with n accounts
* `GetPrivKey(index int)` - Get private key
* `GetAddr(index int)` - Get Ethereum address
* `GetAccAddr(index int)` - Get Cosmos SDK address
* `GetAllAccAddrs()` - Get all SDK addresses

## Verify before tagging

* `go.mod` has no `replace` lines for `github.com/cosmos/evm`
* Node boots with expected RPC namespaces
* Contracts deploy/call; events stream; fee market behaves
* (If applicable) ICS-20 transfers work and precompiles execute
