> ## 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.6.0 to v0.7.0

v0.7.0 ships two new headline features — the **Krakatoa application-layer mempool** and **BlockSTM parallel execution** (with virtual fee collection) — plus a large dependency-stack bump (cosmos-sdk v0.54, cometbft v0.39, ibc-go v11, geth 1.17, Go 1.25). The two features are independent of each other. BlockSTM is always opt-in; Krakatoa is required for forks that already wired the v0.6 `ExperimentalEVMMempool` (the type is gone in v0.7) and optional for forks on CometBFT's stock mempool. The unconditional migration work is the dependency bump and the keeper-signature shuffle that comes with it.

Optional headline features (each enables a distinct path; see Step 5):

* **Krakatoa mempool** (#1030, #1053, #1112) — moves the EVM mempool out of CometBFT and into the application layer. The chain gets full control over `CheckTx` / `RecheckTx`, sudo-rechecking on promote/demote, reapable-list filtering, and pool-level lifecycle hooks. Krakatoa is the only app-side EVM mempool in v0.7; the v0.6 `ExperimentalEVMMempool` type is gone. **If your v0.6 fork wired `ExperimentalEVMMempool`, migrating to Krakatoa is required** — see Step 5a. **If your v0.6 fork ran on CometBFT's stock mempool**, Krakatoa is optional and you can stay on stock CometBFT.
* **BlockSTM parallel execution** (#589, #1082, #1132) — parallel state-transition function under a software-transactional-memory scheduler, with per-tx object stores for bloom / log indexing / gas accounting and an incarnation cache for signature/auth verification. **Includes virtual fee collection** (`EndBlock` fee settlement via per-tx bank object store, 18-decimal chains only) — virtual fees are bundled with BlockSTM, not a standalone feature. Chains that don't wire the STM runner keep sequential execution.

Other features in this release (unconditional):

* **ICS-02 client-router precompile** (#768) — Solidity contracts can now manage IBC light clients.
* **Authority params** (#1130) — `x/vm`, `x/erc20`, `x/feemarket` `MsgServer` handlers consult `AuthorityParams` from the consensus module before falling back to per-module gov authority.
* **OpenTelemetry tracing** (#871, #863) — spans across `x/vm` and the JSON-RPC layer.
* **JSON-RPC filter lifecycle overhaul** (#1008) — idle-timeout reclamation replacing the old global filter cap.

Secondary breaking changes — IBC callback hardening, EIP-712 verification, the `x/precisebank` deprecation, the `abi.json` strict-ABI requirement (#758) — round out the upgrade.

The reference `evmd` also enables [optimistic execution](https://docs.cosmos.network/main/build/building-apps/optimistic-execution) (`baseapp.SetOptimisticExecution()`). That's an SDK-level feature, not new in v0.7, but if your fork hasn't turned it on yet the upgrade is a convenient time — see Step 5c for the wiring.

For deeper background on the new features, see:

* [Krakatoa overview](https://docs.cosmos.network/evm/latest/documentation/concepts/mempool)
* [Krakatoa configuration](https://docs.cosmos.network/evm/latest/documentation/getting-started/build-a-chain/additional-configuration/mempool-integration)
* [BlockSTM and parallel execution](https://docs.cosmos.network/sdk/latest/experimental/blockstm#block-stm-parallel-transaction-execution)

If you're skipping a release, read the [v0.5.x → v0.6.0 guide](/evm/next/documentation/migrations/migration-v0.5-to-v0.6) first — its StateDB / `callFromPrecompile` API break still applies.

***

## Table of contents

The migration steps split into three groups. Required steps apply to every fork; the Krakatoa and BlockSTM groups are independent and either may be skipped. (Step 5c covers optimistic execution — an SDK feature unrelated to v0.7 — for forks that haven't wired it.)

**Required (every fork)**

* [Step 1: Bump dependencies](#step-1-bump-dependencies)
* [Step 1.5: Migrate import paths](#step-15-migrate-import-paths)
* [Step 2: Update store keys in `app.go`](#step-2-update-store-keys-in-appgo)
* [Step 3: Update `EVMD` struct fields](#step-3-update-evmd-struct-fields)
* [Step 4: Update `NewExampleApp` and keeper constructors in `app.go`](#step-4-update-newexampleapp-and-keeper-constructors-in-appgo)
* [Step 6: Drop `x/precisebank` (18-decimal chains)](#step-6-drop-xprecisebank-18-decimal-chains)
* [Step 7: Update custom code](#step-7-update-custom-code)
* [Step 8: Coordinate the upgrade](#step-8-coordinate-the-upgrade)
* [Step 9: Verify](#step-9-verify)

**Krakatoa application-layer mempool (required if you previously used `ExperimentalEVMMempool`; otherwise optional)**

* [Step 5a: Krakatoa application-layer mempool](#5a-krakatoa-application-layer-mempool)

**BlockSTM parallel execution (optional, bundled with virtual fee collection)**

* [Step 5b: BlockSTM parallel execution and virtual fee collection](#5b-optional-blockstm-parallel-execution-and-virtual-fee-collection)

**Optimistic execution (SDK feature, not v0.7-specific — opt-in)**

* [Step 5c: Optimistic execution](#5c-optional-optimistic-execution)

***

## Step 1: Bump dependencies

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
go get github.com/cosmos/evm@v0.7.0
go mod tidy
```

> **Audit every `go.mod` in your tree.** Forks with nested submodules (e.g., a separate `tests/systemtests/go.mod`) need the dependency bumps applied independently and `go mod tidy` run separately in each — root-only bumps leave the nested modules with stale `cosmossdk.io/log`, `cosmossdk.io/store` resolutions that fail to compile.

v0.7.0 transitively bumps the entire stack. The minimum versions you need:

| Dependency    | v0.6.x  | v0.7.0                    | Upstream guide      |
| ------------- | ------- | ------------------------- | ------------------- |
| Go toolchain  | 1.23    | **1.25**                  | —                   |
| `cosmos-sdk`  | v0.53.x | **v0.54.2**               | [SDK][sdk-upgrade]  |
| `cometbft`    | v0.38.x | **v0.39.3**               | [release notes][ct] |
| `ibc-go`      | v10     | **v11**                   | [v10→v11][ibc-v11]  |
| `go-ethereum` | v1.15   | **v1.17** via Cosmos fork | [release notes][gh] |

[sdk-upgrade]: https://github.com/cosmos/cosmos-sdk/blob/main/UPGRADING.md

[ibc-v11]: https://github.com/cosmos/ibc-go/blob/main/docs/docs/05-migrations/14-v10-to-v11.md

[gh]: https://github.com/ethereum/go-ethereum/releases

[ct]: https://github.com/cometbft/cometbft/releases

Read the SDK and ibc-go guides end-to-end before starting — many of the keeper-signature edits in Step 4 are direct consequences of those upstream changes, and the upstream docs cover edge cases (capability migration, store/v2 nuances, gov vote-tally extension points) that this guide only summarizes.

The geth bump uses a fork pin. Add (or update) the replace directive in your `go.mod`:

```
replace github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v1.17.2-cosmos-0
```

If you depend on geth APIs directly (custom tracers, custom precompiles importing `core/vm`), expect compile errors and follow the geth 1.16 → 1.17 changelog for shim updates.

## Step 1.5: Migrate import paths

A large block of `cosmossdk.io/...` packages moved into `github.com/cosmos/cosmos-sdk/...` in SDK v0.54, and `store` is now `store/v2`. Run these find-and-replaces across your fork:

| Old import                                  | New import                                              |
| ------------------------------------------- | ------------------------------------------------------- |
| `cosmossdk.io/store`                        | `github.com/cosmos/cosmos-sdk/store/v2`                 |
| `cosmossdk.io/store/types`                  | `github.com/cosmos/cosmos-sdk/store/v2/types`           |
| `cosmossdk.io/store/snapshots/types`        | `github.com/cosmos/cosmos-sdk/store/v2/snapshots/types` |
| `cosmossdk.io/store/prefix`                 | `github.com/cosmos/cosmos-sdk/store/v2/prefix`          |
| `cosmossdk.io/log`                          | `cosmossdk.io/log/v2`                                   |
| `cosmossdk.io/x/upgrade{,/keeper,/types}`   | `github.com/cosmos/cosmos-sdk/x/upgrade/...`            |
| `cosmossdk.io/x/evidence{,/keeper,/types}`  | `github.com/cosmos/cosmos-sdk/x/evidence/...`           |
| `cosmossdk.io/x/feegrant{,/keeper,/module}` | `github.com/cosmos/cosmos-sdk/x/feegrant/...`           |
| `cosmossdk.io/x/tx/signing`                 | `github.com/cosmos/cosmos-sdk/x/tx/signing`             |
| `cosmossdk.io/systemtests`                  | `github.com/cosmos/cosmos-sdk/tools/systemtests`        |
| `github.com/cosmos/ibc-go/v10/...`          | `github.com/cosmos/ibc-go/v11/...`                      |

If your fork has a separate `tests/systemtests/go.mod` (recommended pattern), apply the rename there too and run `go mod tidy` in that submodule independently of the main module.

Internal Cosmos EVM relocations — the top-level `github.com/cosmos/evm/config` package was deleted and its symbols redistributed:

| v0.6 symbol                       | v0.7 location                                           |
| --------------------------------- | ------------------------------------------------------- |
| `config.MustGetDefaultNodeHome`   | `evmd/config.MustGetDefaultNodeHome`                    |
| `config.InitAppConfig`            | `evmd/config.InitAppConfig`                             |
| `config.BlockedAddresses`         | `evmd/config.BlockedAddresses`                          |
| `config.GetMaccPerms`             | `evmd/config.GetMaccPerms`                              |
| `config.SetBip44CoinType`         | `evmd/config.SetBip44CoinType`                          |
| `config.GetChainIDFromHome`       | `utils.GetChainIDFromHome`                              |
| `config.EVMChainID` (constant)    | removed — use `evmtypes.DefaultEVMChainID`              |
| `config.GetCosmosPoolMaxTx`       | `server.GetCosmosPoolMaxTx`                             |
| `config.GetLegacyPoolConfig` etc. | folded into `server.ResolveMempoolConfig` (see Step 5a) |

The example-app helpers (`MustGetDefaultNodeHome`, `InitAppConfig`, `BlockedAddresses`, `GetMaccPerms`) moved from `github.com/cosmos/evm/config` to `github.com/cosmos/evm/evmd/config`. Mempool / chain-id helpers moved to `github.com/cosmos/evm/server` and `github.com/cosmos/evm/utils`. Update every import in `evmd/cmd/evmd/cmd/root.go`, `evmd/cmd/evmd/main.go`, and `evmd/cmd/evmd/cmd/testnet.go` accordingly.

## Step 2: Update store keys in `app.go`

The transient stores `evmtypes.TransientKey` and `feemarkettypes.TransientKey` were both removed. The EVM keeper still needs a per-tx scratch store for tx bloom and gas accounting, so it now consumes `evmtypes.ObjectKey` (an object store) instead. This change is **unconditional** — it applies even on the sequential path.

Replace the transient-store declaration, mount the new object store, and build a `nonTransientKeys` slice for the EVM keeper:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  keys := storetypes.NewKVStoreKeys(/* ... */)
- tkeys := storetypes.NewTransientStoreKeys(evmtypes.TransientKey, feemarkettypes.TransientKey)
+ oKeys := storetypes.NewObjectStoreKeys(evmtypes.ObjectKey)
+
+ var nonTransientKeys []storetypes.StoreKey
+ for _, k := range keys {
+     nonTransientKeys = append(nonTransientKeys, k)
+ }
+ for _, k := range oKeys {
+     nonTransientKeys = append(nonTransientKeys, k)
+ }

- app.MountTransientStores(tkeys)
+ app.MountObjectStores(oKeys)
```

`nonTransientKeys` is the 4th argument to `evmkeeper.NewKeeper` (Step 4) and the EVM keeper uses it for cross-module store access. Step 5 reuses the same slice if you wire the BlockSTM runner.

> **Step 5 (parallel path) extends `oKeys`** with `banktypes.ObjectStoreKey` and wires it into the bank keeper. Skip if you're not adopting virtual fee collection.

(The matching `EVMD` struct field rename is in Step 3.)

## Step 3: Update `EVMD` struct fields

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  type EVMD struct {
      // ...
-     keys    map[string]*storetypes.KVStoreKey
-     tkeys   map[string]*storetypes.TransientStoreKey
-     memKeys map[string]*storetypes.MemoryStoreKey
+     keys  map[string]*storetypes.KVStoreKey
+     oKeys map[string]*storetypes.ObjectStoreKey

-     clientCtx client.Context
-
      // ...
-     TransferKeeper    transferkeeper.Keeper
+     TransferKeeper    *transferkeeper.Keeper

-     EVMMempool        *evmmempool.ExperimentalEVMMempool
+     EVMMempool        sdkmempool.ExtMempool
-
-     PreciseBankKeeper precisebankkeeper.Keeper
  }
```

`TransferKeeper` is now stored as a pointer (ibc-go v11). `EVMMempool`'s field type was widened to the `ExtMempool` interface to permit Krakatoa or any future custom subpool. If you have getters/setters keyed on these field types (e.g. `GetTransferKeeper`, `SetTransferKeeper`), update their signatures too.

The `Close()` method on `EVMD` does a type assertion against the old mempool type — update it to the new one (or remove if you're not wiring an EVM mempool):

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  func (app *EVMD) Close() error {
      var err error
-     if m, ok := app.GetMempool().(*evmmempool.ExperimentalEVMMempool); ok {
+     if m, ok := app.EVMMempool.(*evmmempool.Mempool); ok && m != nil {
          app.Logger().Info("Shutting down mempool")
          err = m.Close()
      }
      // ...
  }
```

## Step 4: Update `NewExampleApp` and keeper constructors in `app.go`

### `NewExampleApp` signature — drop `traceStore`

The `traceStore io.Writer` parameter is gone. Update the function signature **and every call site of your app constructor** — CLI commands (`newApp`, `appExport`, the `appCreator` callback in your root cmd), test-network fixtures (e.g., `NewTestNetworkFixture`), and any custom integration-test bootstrapping. `nil`-passing call sites need to drop the `nil` argument too.

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  func NewExampleApp(
      logger log.Logger,
      db dbm.DB,
-     traceStore io.Writer,
      loadLatest bool,
      appOpts servertypes.AppOptions,
      baseAppOptions ...func(*baseapp.BaseApp),
  ) *EVMD {
```

Inside, drop the corresponding tracer wiring:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- bApp.SetCommitMultiStoreTracer(traceStore)
```

### `feemarketkeeper.NewKeeper` — drop transient key

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  app.FeeMarketKeeper = feemarketkeeper.NewKeeper(
      appCodec, authtypes.NewModuleAddress(govtypes.ModuleName),
      keys[feemarkettypes.StoreKey],
-     tkeys[feemarkettypes.TransientKey],
  )
```

### `ibckeeper.NewKeeper` — drop capability keeper

ibc-go v11 removed the capability-keeper argument. Existing chains have nothing to migrate — the parameter was already nil — but the call must lose it:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  app.IBCKeeper = ibckeeper.NewKeeper(
      appCodec,
      runtime.NewKVStoreService(keys[ibcexported.StoreKey]),
-     nil,
      app.UpgradeKeeper,
      authAddr,
  )
```

### `govkeeper.NewKeeper` — reordered args, new vote-results function

Cosmos SDK v0.54 reordered the constructor and added a final pluggable vote-tally function:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  govKeeper := govkeeper.NewKeeper(
      appCodec,
      runtime.NewKVStoreService(keys[govtypes.StoreKey]),
      app.AccountKeeper,
      app.BankKeeper,
-     app.StakingKeeper,
      app.DistrKeeper,
      app.MsgServiceRouter(),
      govConfig,
      authAddr,
+     govkeeper.NewDefaultCalculateVoteResultsAndVotingPower(app.StakingKeeper),
  )
```

### `transferkeeper.NewKeeper` — pointer return, inline address codec, drop ICS4Wrapper / duplicate ChannelKeeper

ibc-go v11 returns `*transferkeeper.Keeper`. The constructor also takes the EVM address codec inline (no more `SetAddressCodec`) and drops the duplicate ICS4Wrapper / ChannelKeeper params:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  app.TransferKeeper = transferkeeper.NewKeeper(
      appCodec,
+     evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
      runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]),
-     nil, // ICS4Wrapper param
-     app.IBCKeeper.ChannelKeeper,
      app.IBCKeeper.ChannelKeeper,
      app.MsgServiceRouter(),
      app.AccountKeeper,
      app.BankKeeper,
      authAddr,
  )
- app.TransferKeeper.SetAddressCodec(evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix()))
```

> ⚠️ The reference `evmd` instantiates `TransferKeeper` **before** `EVMKeeper` so static precompiles receive a non-nil reference. v0.6 constructs them in the opposite order (EVMKeeper, then Erc20Keeper consuming `&app.TransferKeeper`, then TransferKeeper). To swap, you'll also need to move `Erc20Keeper` so it's constructed *after* `TransferKeeper` — and `Erc20Keeper` itself now takes the pointer-typed `app.TransferKeeper` directly, no `&`. Walk the three keeper constructions as a unit, not individually.

### IBC callbacks middleware — wrap with setter calls

ibc-go v11 split the constructor and the wrapping. Replace the single-line `NewIBCMiddleware` with the three-step setter form:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCKeeper.ChannelKeeper, app.CallbackKeeper, maxCallbackGas)
+ callbacksMiddleware := ibccallbacks.NewIBCMiddleware(app.CallbackKeeper, maxCallbackGas)
+ callbacksMiddleware.SetICS4Wrapper(app.IBCKeeper.ChannelKeeper)
+ callbacksMiddleware.SetUnderlyingApplication(transferStack)
+ transferStack = callbacksMiddleware
```

Both setters are required — `NewIBCMiddleware` itself doesn't accept either argument anymore. A middleware constructed without `SetICS4Wrapper` or `SetUnderlyingApplication` compiles but panics with a nil-pointer dereference on the first packet send / receive (the constructor does panic on a nil contract keeper or zero gas, but the wrapper / underlying-app fields are checked at use, not construction).

### `evmkeeper.NewKeeper` — object store key, store-key slice, BankKeeper replaces PreciseBankKeeper

The third arg is the new EVM object-store key (Step 2). The fourth changed from `map[string]*storetypes.KVStoreKey` to `[]storetypes.StoreKey` — the keeper uses it for cross-module store access. Pass the `nonTransientKeys` slice from Step 2; the same slice is also what the STM runner tracks if you opt into Step 5.

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  app.EVMKeeper = evmkeeper.NewKeeper(
-     appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], keys,
+     appCodec, keys[evmtypes.StoreKey], oKeys[evmtypes.ObjectKey], nonTransientKeys,
      authtypes.NewModuleAddress(govtypes.ModuleName),
      app.AccountKeeper,
-     app.PreciseBankKeeper,
+     app.BankKeeper,
      app.StakingKeeper,
      app.FeeMarketKeeper,
      &app.ConsensusParamsKeeper,
      &app.Erc20Keeper,
      evmChainID,
      tracer,
  )
```

### `DefaultStaticPrecompiles` — Bank, dereferenced TransferKeeper, ClientKeeper

`TransferKeeper` is already a pointer (Step 3); pass it directly. `IBCKeeper.ClientKeeper` is the new dependency — it backs the ICS-02 client-router precompile (#768):

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  precompiletypes.DefaultStaticPrecompiles(
      *app.StakingKeeper,
      app.DistrKeeper,
-     app.PreciseBankKeeper,
+     app.BankKeeper,
      &app.Erc20Keeper,
-     &app.TransferKeeper,
+     app.TransferKeeper,
      app.IBCKeeper.ChannelKeeper,
+     app.IBCKeeper.ClientKeeper,
      app.GovKeeper,
      app.SlashingKeeper,
      appCodec,
  )
```

### `erc20keeper.NewKeeper` — Bank, dereferenced TransferKeeper

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  app.Erc20Keeper = erc20keeper.NewKeeper(
      keys[erc20types.StoreKey],
      appCodec,
      authtypes.NewModuleAddress(govtypes.ModuleName),
      app.AccountKeeper,
-     app.PreciseBankKeeper,
+     app.BankKeeper,
      app.EVMKeeper,
      app.StakingKeeper,
-     &app.TransferKeeper,
+     app.TransferKeeper,
  )
```

### `node.RegisterNodeService` — earliest-version callback

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  func (app *EVMD) RegisterNodeService(clientCtx client.Context, cfg config.Config) {
-     node.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg)
+     node.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg, func() int64 {
+         return app.CommitMultiStore().EarliestVersion()
+     })
  }
```

### Hydrate EVM globals on restart (#1126)

Required regardless of which path you choose. After `LoadLatestVersion` (inside `if loadLatest`), hydrate the EVM globals from KV so `evmCoinInfo` is populated before any RPC handler runs:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
+ ctx := app.NewContextLegacy(true, cmtproto.Header{
+     Height:  app.LastBlockHeight(),
+     ChainID: app.ChainID(),
+ })
+ vmModule.HydrateGlobals(ctx)
```

Without this, RPC calls that arrive before `PreBlock` panic on a nil `evmCoinInfo`.

`vmModule` here is the value returned by `vm.NewAppModule(...)`. v0.6 calls `vm.NewAppModule(...)` inline inside `app.ModuleManager = module.NewManager(...)`, so before the `HydrateGlobals` call you'll need to refactor: bind the result to a local first, then pass that local into `module.NewManager`:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- app.ModuleManager = module.NewManager(
-     // ... other modules
-     vm.NewAppModule(app.EVMKeeper, app.AccountKeeper, app.BankKeeper, app.AccountKeeper.AddressCodec()),
-     // ...
- )
+ vmModule := vm.NewAppModule(app.EVMKeeper, app.AccountKeeper, app.BankKeeper, app.AccountKeeper.AddressCodec())
+
+ app.ModuleManager = module.NewManager(
+     // ... other modules
+     vmModule,
+     // ...
+ )
```

Imports to add: `cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"`.

### Wire the EVM tx runner (#1132)

Required regardless of path. `vmrunner.SetRunner` installs the baseapp tx runner wrapped with the EVM module's `PatchTxResponses` post-execution log/tx index fix-up. Without it, `log.Index` and `transactionIndex` on receipts are wrong (the bug #1132 corrected). The wrapper works for both sequential and parallel inner runners — only the inner runner choice differs by path:

* **Sequential (default)** — pass `txnrunner.NewDefaultRunner(txDecoder)`.
* **Parallel** — pass `txnrunner.NewSTMRunner(...)` (see Step 5b).

Bind the tx decoder to a local at the top of `NewExampleApp` so it can be reused by the runner (and by `bApp := baseapp.NewBaseApp(...)` if you want to share it):

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  txConfig := encodingConfig.TxConfig
+ txDecoder := encodingConfig.TxConfig.TxDecoder()
```

Then add the runner call just before `return app`. The sequential form:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
+ vmrunner.SetRunner(bApp, txnrunner.NewDefaultRunner(txDecoder))
```

Imports to add: `"github.com/cosmos/cosmos-sdk/baseapp/txnrunner"`, `vmrunner "github.com/cosmos/evm/x/vm/runner"`.

### Removed `EVMD` methods

These were deleted; remove any callers:

* `GetTKey`, `GetMemKey` — transient/mem stores no longer exist as separate maps.
* `GetAuthzKeeper` — the helper was redundant; expose `app.AuthzKeeper` directly if you need it.
* `GetPreciseBankKeeper` — module is removed (Step 6).
* `SetClientCtx` and the `clientCtx` field — unused.

## Step 5: Enable Krakatoa and/or BlockSTM

Two independent v0.7 opt-ins:

* **Krakatoa (5a)** is required for forks that already used the v0.6 `ExperimentalEVMMempool` (the type was removed); optional for forks on CometBFT's stock mempool.
* **BlockSTM with virtual fee collection (5b)** is always opt-in. Skip 5b to keep sequential execution.

Step 5c documents `baseapp.SetOptimisticExecution()` separately — it's an SDK-level feature, not new in v0.7, but the reference `evmd/app.go` enables it and forks that haven't yet adopted it can use this upgrade as the moment to do so.

> **State-breaking features.** Each ships as part of the binary; adopting or dropping any of them requires a coordinated `MsgSoftwareUpgrade` and a binary swap, the same as any other consensus change.

### 5a. Krakatoa application-layer mempool

Krakatoa is the only app-side EVM mempool in v0.7. The `ExperimentalEVMMempool` type from v0.6 is gone, so optionality depends on what your v0.6 fork already used:

* **If you wired `ExperimentalEVMMempool` in v0.6**: this step is **required**. The construction signature changed and the handler set was redesigned; you must migrate or your fork won't compile.
* **If you ran on CometBFT's stock mempool in v0.6** (no app-side EVM mempool): this step is **optional**. Adopt Krakatoa to gain app-level `CheckTx` / `RecheckTx` control, or skip it and stay on stock CometBFT.

Independent of BlockSTM either way.

To migrate from `ExperimentalEVMMempool`, replace the construction with `evmmempool.NewMempool` and the new handler set. The full reference is `evmd/mempool.go`'s `configureEVMMempool` — read it before applying the diff below; the local variables it constructs (`mpConfig`, `txEncoder`, `evmRechecker`, `cosmosRechecker`, `cosmosPoolMaxTx`, `checkTxTimeout`) are what the new `NewMempool` signature consumes:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
// Setup block (in evmd/mempool.go's configureEVMMempool, all required):
mpConfig        := server.ResolveMempoolConfig(app.GetAnteHandler(), appOpts, logger)
txEncoder       := evmmempool.NewTxEncoder(app.txConfig)
evmRechecker    := evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder)
cosmosRechecker := evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder)
cosmosPoolMaxTx := server.GetCosmosPoolMaxTx(appOpts, logger)
checkTxTimeout  := server.GetMempoolCheckTxTimeout(appOpts, logger)
```

`server.ResolveMempoolConfig`, `server.GetCosmosPoolMaxTx`, and `server.GetMempoolCheckTxTimeout` are all in `github.com/cosmos/evm/server`; the `evmconfig.GetLegacyPoolConfig` / `GetBlockGasLimit` / `GetMinTip` helpers from v0.6 collapsed into `ResolveMempoolConfig`. `app.TxDecode` and the `SetInsertTxHandler` / `SetReapTxsHandler` setters are new in cosmos-sdk v0.54 baseapp.

> **Ordering: `app.SetAnteHandler(...)` must run before `configureEVMMempool`.** `ResolveMempoolConfig` calls `app.GetAnteHandler()` and stashes the result on `mpConfig.AnteHandler`, which the recheckers then close over. If the ante handler hasn't been set yet, `GetAnteHandler` returns nil and the chain panics on first `RecheckTx`. There's no compile-time signal — get the ordering right.
> **`evmRechecker` and `cosmosRechecker` must be distinct instances**, even though they wrap identical config. They feed different subpools (the EVM eth-tx pool vs. the Cosmos pool) and need independent state for promotion/demotion bookkeeping. Sharing one instance compiles fine but produces silent cross-pool state interference at promote/demote time.

Then replace the construction and handler wiring:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- evmMempool := evmmempool.NewExperimentalEVMMempool(
-     app.CreateQueryContext, logger,
-     app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, app.clientCtx,
-     mempoolConfig, cosmosPoolMaxTx,
- )
- app.EVMMempool = evmMempool
- app.SetMempool(evmMempool)
- checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
- app.SetCheckTxHandler(checkTxHandler)
- abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
- abciProposalHandler.SetSignerExtractionAdapter(/* ... */)
- app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
+ mempool := evmmempool.NewMempool(
+     app.CreateQueryContext, logger,
+     app.EVMKeeper, app.FeeMarketKeeper, app.txConfig,
+     evmRechecker, cosmosRechecker, mpConfig, cosmosPoolMaxTx,
+ )
+ app.EVMMempool = mempool
+ app.SetPrepareProposal(baseapp.NewDefaultProposalHandler(mempool, NewNoCheckProposalTxVerifier(app.BaseApp)).PrepareProposalHandler())
+ app.SetInsertTxHandler(mempool.NewInsertTxHandler(app.TxDecode))
+ app.SetReapTxsHandler(mempool.NewReapTxsHandler())
+ app.SetCheckTxHandler(mempool.NewCheckTxHandler(app.TxDecode, checkTxTimeout))
+ app.SetMempool(mempool)
```

> The reference application uses a `NewNoCheckProposalTxVerifier` at proposal time when verifying the txs in a given proposal. This is a performance optimization since with the Krakatoa mempool, it is a requirement that every tx within the proposal has already been validated, thus this check is redundant. If you choose to adopt this pattern as well copy the reference `NewNoCheckProposalTxVerifier` and pass this to your `PrepareProposalHandler`.

To opt out entirely, drop the `configureEVMMempool(...)` call from `NewExampleApp`, or set `mempool.max-txs=-1` in `app.toml` (the SDK `FlagMempoolMaxTxs` — note this is the top-level `[mempool]` key, not `evm.mempool.max-txs`). `configureEVMMempool` reads it via `server.GetCosmosPoolMaxTx`, bails out on a negative value, and falls back to CometBFT's stock mempool.

> ⚠️ **Set `mempool.type = "app"` in `config.toml`.** CometBFT v0.39 requires the app-side mempool type whenever the application supplies an EVM mempool. The default is `"flood"`; Krakatoa errors out at startup if it's left as the default. This is a one-line patch to each validator's `config.toml` (or your fork's equivalent), applied at the same time as the binary swap. Forks that opt out of Krakatoa (the previous paragraph) don't need this.
> Note that by setting `mempool.type = "app"` in `config.toml`, there are additional configuration parameters you may want to configure that will affect Krakatoa mempool operations. Please see the [CometBFT application mempool documentation](https://docs.cosmos.network/cometbft/latest/docs/core/mempool) for more info.

### 5b. (Optional) BlockSTM parallel execution and virtual fee collection

BlockSTM enables parallel execution of the state-transition function and bundles virtual fee collection (per-tx bank-balance accounting reduced at `EndBlock`). **Not** required — chains that skip this section keep sequential execution. Independent of Krakatoa, and independent of optimistic execution (5c).

The two pieces below are wired together; you opt into the bundle, not into individual lines.

#### Swap the inner tx runner to BlockSTM

Step 4 already wired `vmrunner.SetRunner(bApp, ...)` with the sequential `txnrunner.NewDefaultRunner(txDecoder)` as the inner runner. To opt into parallel execution, swap the inner runner for `txnrunner.NewSTMRunner(...)`:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- vmrunner.SetRunner(bApp, txnrunner.NewDefaultRunner(txDecoder))
+ vmrunner.SetRunner(bApp, txnrunner.NewSTMRunner(
+     txDecoder,
+     nonTransientKeys,
+     min(goruntime.GOMAXPROCS(0), goruntime.NumCPU()),
+     true,
+     func(ms storetypes.MultiStore) string {
+         return app.EVMKeeper.GetParams(sdk.NewContext(ms, cmtproto.Header{}, false, log.NewNopLogger())).EvmDenom
+     },
+ ))
```

The EvmDenom callback reads from EVM params on the live multi-store; do not hard-code `sdk.DefaultBondDenom` — chains that customized their EVM denom will diverge.

Additional import: `goruntime "runtime"`.

#### Virtual fee collection (18-decimal chains only)

Virtual fee collection is part of the BlockSTM bundle — it's what lets fee settlement happen in parallel without contending the fee collector account on every tx. Two pieces are required.

First, extend the `oKeys` declaration from Step 2 with `banktypes.ObjectStoreKey` and wire it into the bank keeper. **Order matters**: the `oKeys` declaration must include `banktypes.ObjectStoreKey` *before* the `for _, k := range oKeys` loop that builds `nonTransientKeys`, otherwise the bank object store won't be in the slice the EVM keeper sees. Apply the change at the Step 2 declaration site, not later:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
- oKeys := storetypes.NewObjectStoreKeys(evmtypes.ObjectKey)
+ oKeys := storetypes.NewObjectStoreKeys(banktypes.ObjectStoreKey, evmtypes.ObjectKey)
```

Then, after `BankKeeper` is constructed, wire the object-store key into it:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
+ app.BankKeeper = app.BankKeeper.WithObjStoreKey(oKeys[banktypes.ObjectStoreKey])
```

`WithObjStoreKey(storetypes.StoreKey) BaseKeeper` is part of the `bankkeeper.Keeper` interface in cosmos-sdk v0.54, so the call works whether your `EVMD.BankKeeper` field is the interface (`bankkeeper.Keeper`, the reference default) or the concrete `bankkeeper.BaseKeeper`. The method returns `BaseKeeper`, but `BaseKeeper` satisfies `Keeper`, so assigning the result back to an interface-typed field type-checks. No field-widening required.

Then, after `WithStaticPrecompiles`, enable virtual fees on the EVM keeper:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  app.EVMKeeper = evmkeeper.NewKeeper(/* ... */).WithStaticPrecompiles(/* ... */)
+ app.EVMKeeper.EnableVirtualFeeCollection()
```

> ⚠️ **Do not call `EnableVirtualFeeCollection()` if your gas token is not 18-decimal.** `x/vm/keeper.DeductFees` reads the EVM denom's bank metadata and panics if the display-denom unit's exponent is not 18 (`x/vm/keeper/fees.go:156`). See Step 6. A non-18-decimal chain that still wants BlockSTM has to skip this final piece — but the parallel path's main throughput win comes from virtual fees, so the practical recommendation is to migrate to 18 decimals first.

### 5c. (Optional) Optimistic execution

**Not new in v0.7** — `baseapp.SetOptimisticExecution()` has been an SDK feature since v0.50. Documented here because the reference `evmd/app.go` ships with it enabled, and forks that haven't yet adopted it can fold the wiring into this upgrade. Independent of BlockSTM and Krakatoa.

The feature overlaps `FinalizeBlock` for height `H` with `ProcessProposal` for height `H+1`: while CometBFT is still finalizing the current block, the app speculatively executes the next proposed block. If the speculative result matches what `FinalizeBlock` is later asked to commit, the result is reused; if it diverges, the speculative state is discarded and execution falls back to the standard path.

The reference `evmd/app.go` enables it. Add the option to `baseAppOptions` **before** `baseapp.NewBaseApp(...)` is called — append after the `baseAppOptions ...func(*baseapp.BaseApp)` parameter is in scope but before the constructor consumes it:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  func NewExampleApp(
      logger log.Logger,
      db dbm.DB,
      loadLatest bool,
      appOpts servertypes.AppOptions,
      baseAppOptions ...func(*baseapp.BaseApp),
  ) *EVMD {
      // ... encoding/txDecoder setup ...

+     // enable optimistic execution
+     baseAppOptions = append(
+         baseAppOptions,
+         baseapp.SetOptimisticExecution(),
+     )

      bApp := baseapp.NewBaseApp(
          appName, logger, db, txDecoder,
          baseAppOptions...,
      )
```

Operational notes:

* **Determinism is preserved.** Speculative execution uses a sandboxed cache; on a mismatch the state is dropped, not committed. The user-visible behavior is identical to non-optimistic execution — only the latency of `FinalizeBlock` shifts.
* **Composes with BlockSTM (5b) and Krakatoa (5a).** Optimistic execution runs the speculative block through whichever runner is wired (`vmrunner.SetRunner`), so you get parallel-on-speculative + parallel-on-final when 5b is also enabled.
* **State-breaking.** Like 5a and 5b, this is baked into the binary. Adopting or dropping it requires a coordinated `MsgSoftwareUpgrade`, not a runtime toggle. See Step 8.
* **Resource cost.** The speculative path uses an extra goroutine and an extra working state cache per height. On memory-tight nodes you may prefer to disable it; the SDK doc linked at the top of this guide covers the trade-offs.

## Step 6: Drop `x/precisebank`

`x/precisebank` is deprecated (#1019). It only ever made sense for non-18-decimal gas tokens — it bridged them into the EVM's 18-decimal world. The reference v0.6 `evmd/app.go` wired it unconditionally despite the inline comment "PreciseBank is not needed if SDK use 18 decimals for gas coin", so most v0.6 forks have it threaded through `evmkeeper`, `erc20keeper`, and the precompiles regardless of their gas token's decimals.

In v0.7 the module is gone from the main tree. `EvmCoinInfo.Decimals` is also marked deprecated (#1029).

> **Non-18-decimal chains are not fully supported going forward.** v0.7.0 still ships precisebank under `contrib/x/precisebank`, but future EVM releases will not test or maintain it. Either stay on v0.6.x until you can migrate to 18 decimals, or pin `cosmos/evm/contrib/x/precisebank` and **do not** enable virtual fee collection (5b).

If your chain has a 6-decimal staking denom, an alternative is to run a separate, natively 18-decimal gas denom backed by a converter precompile, with no precisebank dependency at all. See [Migrating off x/precisebank: Gas Converter Precompile](/evm/next/documentation/migrations/gas-converter-migration).

If your fork wired `PreciseBankKeeper` (whether or not your gas token is 18-decimal), unwire it:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  type EVMD struct {
      // ... other keepers
-     PreciseBankKeeper precisebankkeeper.Keeper
  }

- // Set up PreciseBank keeper
- app.PreciseBankKeeper = precisebankkeeper.NewKeeper(/* ... */)

  app.ModuleManager = module.NewManager(
      // ... other modules
-     precisebank.NewAppModule(app.PreciseBankKeeper, app.BankKeeper, app.AccountKeeper),
  )

- func (app *EVMD) GetPreciseBankKeeper() *precisebankkeeper.Keeper { /* ... */ }
```

Also drop `precisebanktypes.StoreKey` from the `keys` declaration and the module name from your `SetOrderBeginBlockers` / `SetOrderEndBlockers` / `SetOrderInitGenesis` lists.

Replace every `app.PreciseBankKeeper` reference (in `evmkeeper.NewKeeper`, `erc20keeper.NewKeeper`, `DefaultStaticPrecompiles`, and any custom modules) with `app.BankKeeper`. Verify decimal expectations at each call site — anywhere your code assumed the precisebank denom translation was happening, you now need to either operate on 18-decimal values directly or do the conversion yourself.

Tests that referenced precisebank also need cleanup. In the reference repo this meant deleting `evmd/tests/integration/x_precisebank_test.go` and removing `PreciseBankMintEventCount` / `PreciseBankBurnEventCount` and their consumers from `evmd/tests/ibc/helper.go`. Audit your fork's test tree for any `precisebank` symbols and drop them — the code won't compile against v0.7 with them present.

## Step 7: Update custom code

### Custom ante handlers / mempool plugins

The transient stores backing gas accounting and the feemarket are gone. Read gas-wanted from the SDK context instead — `ctx.GasMeter().GasConsumed()`. If your decorator wrote to the feemarket transient store, remove that logic — `feemarkettypes.TransientKey` and the keeper methods `GetTransientGasWanted`, `SetTransientBlockGasWanted`, `AddTransientGasWanted` are all gone. The same applies to the EVM keeper: `GetBlockBloomTransient`, `SetBlockBloomTransient`, `GetTxIndexTransient`, `SetTxIndexTransient`, and `WithDefaultEvmCoinInfo` were removed.

### Custom `BankKeeper` / `BankWrapper` implementations

The interfaces in `x/vm/types/interfaces.go` changed. Any fork that supplies a non-default bank wrapper must update.

`BankKeeper` gained five methods to support virtual fee collection and parallel-safe balance accounting:

```go theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
SendCoinsFromModuleToAccountVirtual(
    ctx context.Context, senderModule string,
    recipientAddr sdk.AccAddress, amt sdk.Coins,
) error
SendCoinsFromAccountToModuleVirtual(
    ctx context.Context, senderAddr sdk.AccAddress,
    recipientModule string, amt sdk.Coins,
) error
UncheckedSetBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin) error
LockedCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins
BlockedAddr(addr sdk.AccAddress) bool
```

`BankWrapper` is a hard API break: `MintAmountToAccount` and `BurnAmountFromAccount` were **removed** and replaced by a single `SetBalance(ctx, account, amt *big.Int) error`. Callers and implementations must both change.

`EmitBlockBloomEvent`'s argument type changed from `ethtypes.Bloom` to `[]byte`.

A new `VMKeeper` interface (`GetEvmCoinInfo`) was added; some helpers now take it instead of a concrete keeper.

### Custom indexers / receipt consumers

* Read `log.Index` and `log.TxIndex` only after post-execution patching (#1132). Recomputing from raw event order will diverge from canonical receipts.
* Drop any workaround that special-cased the `MaxUint64` overflow on `transactionIndex` — fixed in #1047.
* Don't assume tx counts in receipt arrays match raw block tx counts. StateDB-error txs are now skipped during receipt conversion (#1107).

### Forks of precompile `abi.json`

PR #758 made the precompile ABI files strict — only valid Ethereum ABI fields are accepted. Forks that vendor or override `abi.json` for any precompile must remove non-ABI extension fields, or they will fail to parse at startup.

### Removed helpers

* `Params.GetActiveStaticPrecompilesAddrs()` is gone — derive `[]common.Address` yourself if you used it.

### Mocks of `EVMKeeper`

Regenerate against v0.7.0 — the constructor signature (Step 4) and static-precompile dependencies (Step 4) shifted.

## Step 8: Coordinate the upgrade

This is a normal Cosmos consensus-breaking upgrade.

* **All validators must run the same v0.7 binary post-upgrade.** Mixing v0.6 and v0.7 binaries will fork.
* **Step 5 (Krakatoa, BlockSTM + virtual fees, optimistic execution) is state-breaking.** Each is baked into the binary — adopting or dropping any of them later is another coordinated `MsgSoftwareUpgrade`, not a runtime toggle.
* **There is no on-chain governance flag** for these toggles. Switching paths later requires a new binary release and another `MsgSoftwareUpgrade`.

Update the upgrade handler's `UpgradeName` constant **and its doc comment** to match the new release. The doc-comment drift between releases is a recurring foot-gun — the system test reads the constant verbatim, but readers and downstream handlers grep the comment for context:

```diff theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
  // evmd/upgrades.go (or wherever your fork keeps its upgrade name)
- // NOTE: This upgrade defines a reference implementation of what an upgrade
- // could look like when an application is migrating from EVMD version
- // v0.4.0 to v0.5.x
- const UpgradeName = "v0.5.0-to-v0.6.0"
+ // NOTE: This upgrade defines a reference implementation of what an upgrade
+ // could look like when an application is migrating from EVMD version
+ // v0.6.x to v0.7.0
+ const UpgradeName = "v0.6.0-to-v0.7.0"
```

The reference upgrade handler runs `RunMigrations` with empty `StoreUpgrades` — no module schema changes. Submit a `MsgSoftwareUpgrade` with `name: "v0.6.0-to-v0.7.0"` at your target height and swap the binary at the halt height.

## Step 9: Verify

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

### Sanity check the upgrade path

```bash theme={"theme":{"light":"github-light-high-contrast","dark":"github-dark-high-contrast"}}
make test-system
```

Drives a `MsgSoftwareUpgrade` for `v0.6.0-to-v0.7.0`, swaps to the v0.7 binary at the halt height, and runs a contended-account workload (many txs hitting the same hot contract per block). On the parallel path this exercises the BlockSTM scheduler's conflict detection across the upgrade boundary; on the sequential path it's still a useful end-to-end smoke test.

The test reads a v0.6-era binary from `tests/systemtests/binaries/v0.6/evmd`. **For your fork, that binary is your own chain build pinned to the v0.6 cosmos-evm dependency** — not a generic `cosmos/evm@v0.6.0` binary. The legacy build's keepers, ante decorators, and module set must match what your validators are actually running pre-upgrade, otherwise the test exercises the wrong starting state.

Wire whatever `make` (or shell) target you use for this — the key constraints are:

* Output goes to `tests/systemtests/binaries/v0.6/evmd` (or wherever your `chainupgrade/v6_v7.go` reads from).
* Your `test-system` Make target (or equivalent) depends on it and on the current binary, in that order.
* If your previous release had a `build-v05` target chained from `test-system`, retarget that dependency to the new `build-v06` (or whatever you name it).
