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
Dependency bumps
Pin EVM and tidy
Bump the cosmos/evm dependency in go.mod:
Transitive bumps
Check for minor dependency bumps (e.g., google.golang.org/protobuf, github.com/gofrs/flock, github.com/consensys/gnark-crypto):
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
Provide a wrapper for commands that expect the SDK type
Create a thin wrapper and use it for pruning.Cmd and snapshot.Cmd:
Add clientCtx and SetClientCtx
Add the clientCtx to your app object:
Pending-tx listener support
Imports
Import the EVM ante package and geth common:
App state: listeners slice
Add a new field for listeners:
Registration method
Add a public method to register a listener by txHash:
Precompiles: optionals + codec injection
New imports
Define Optionals + defaults + functional options
Create a small options container with sane defaults pulled from the app’s bech32 config:
4.3 Update the precompile factory to accept options
4.4 Modify individual precompile constructors
ICS-20 precompile now needs bankKeeper first:
Gov precompile now requires an AddressCodec:
ERC20 Precompiles Migration
This migration is required for chains with existing ERC20 token pairsThe storage mechanism for ERC20 precompiles has fundamentally changed in v0.4.0. Without proper migration, your ERC20 tokens will become inaccessible via EVM.
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
Add this to your upgrade handler:
Verification
Post-upgrade, verify your migration succeeded:
Build & quick tests
-
Compile:
-
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
CLI wrapper
Precompile options & usage
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