Integrate the EVM mempool with your Ontomir EVM chain
Overview
This guide provides step-by-step instructions for integrating the EVM mempool into your Ontomir EVM chain. For conceptual information about mempool design and architecture, see the Mempool Concepts page.
Step 1: Add EVM Mempool to App Struct
Update your app/app.go to include the EVM mempool:
type App struct {
*baseapp.BaseApp
// ... other keepers
// Ontomir EVM keepers
FeeMarketKeeper feemarketkeeper.Keeper
EVMKeeper *evmkeeper.Keeper
EVMMempool *evmmempool.ExperimentalEVMMempool
}
Step 2: Configure Mempool in NewApp Constructor
The mempool must be initialized **after** the antehandler has been set in the app.
Add the following configuration in your NewApp constructor:
```go // Set the EVM priority nonce mempool if evmtypes.GetChainConfig() != nil { mempoolConfig := &evmmempool.EVMMempoolConfig{ AnteHandler: app.GetAnteHandler(), BlockGasLimit: 100_000_000, }
}
Full Configuration Options
Defaults and Fallbacks
If BlockGasLimit is 0, the mempool uses a fallback of 100_000_000 gas.
If OntomirPoolConfig is not provided, a default PriorityNonceMempool is created with:
Priority = (fee_amount / gas_limit) in the chain bond denom
Comparator = big-int comparison (higher is selected first)
MinValue = 0
If BroadcastTxFn is not provided, a default is created that uses the app clientCtx/txConfig to broadcast EVM transactions when they are promoted from queued → pending.
MinTip is optional. If unset, selection uses the effective tip from each tx (min(gas_tip_cap, gas_fee_cap - base_fee)).
v0.4.x to v0.5.0 Migration
Breaking Change: Pre-built pools replaced with configuration objects
Before (v0.4.x):
After (v0.5.0):
Custom Ontomir Mempool Configuration
The mempool uses a PriorityNonceMempool for Ontomir transactions by default. You can customize the priority calculation:
Custom Block Gas Limit
Different chains may require different gas limits based on their capacity:
Event Bus Integration
For best results, connect the mempool to CometBFT's EventBus so it can react to finalized blocks:
This enables chain-head notifications so the mempool can promptly promote/evict transactions when blocks are committed.
Architecture Components
The EVM mempool consists of several key components:
ExperimentalEVMMempool
The main coordinator implementing Ontomir SDK's ExtMempool interface (mempool/mempool.go).
Key Methods:
Insert(ctx, tx): Routes transactions to appropriate pools
Select(ctx, filter): Returns unified iterator over all transactions
Remove(tx): Handles transaction removal with EVM-specific logic
evmMempool := evmmempool.NewExperimentalEVMMempool(
app.CreateQueryContext,
logger,
app.EVMKeeper,
app.FeeMarketKeeper,
app.txConfig,
app.clientCtx,
mempoolConfig,
)
app.EVMMempool = evmMempool
// Replace BaseApp mempool
app.SetMempool(evmMempool)
// Set custom CheckTx handler for nonce gap support
checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
app.SetCheckTxHandler(checkTxHandler)
// Set custom PrepareProposal handler
abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
abciProposalHandler.SetSignerExtractionAdapter(
evmmempool.NewEthSignerExtractionAdapter(
sdkmempool.NewDefaultSignerExtractionAdapter(),
),
)
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
</Expandable>
<Warning>
**Breaking Change from v0.4.x:** The global mempool registry (`SetGlobalEVMMempool`) has been removed. Mempool is now passed directly to the JSON-RPC server during initialization.
</Warning>
## Configuration Options
The `EVMMempoolConfig` struct provides several configuration options for customizing the mempool behavior:
### Minimal Configuration
```go
mempoolConfig := &evmmempool.EVMMempoolConfig{
AnteHandler: app.GetAnteHandler(),
BlockGasLimit: 100_000_000, // 100M gas limit
}
type EVMMempoolConfig struct {
// Required: AnteHandler for transaction validation
AnteHandler sdk.AnteHandler
// Required: Block gas limit for transaction selection
BlockGasLimit uint64
// Optional: Custom legacy pool configuration (replaces TxPool)
LegacyPoolConfig *legacypool.Config
// Optional: Custom Ontomir pool configuration (replaces OntomirPool)
OntomirPoolConfig *sdkmempool.PriorityNonceMempoolConfig[math.Int]
// Optional: Custom broadcast function for promoted transactions
BroadcastTxFn func(txs []*ethtypes.Transaction) error
// Optional: Minimum tip required for EVM transactions
MinTip *uint256.Int
}
// Define custom priority calculation for Ontomir transactions
priorityConfig := sdkmempool.PriorityNonceMempoolConfig[math.Int]{
TxPriority: sdkmempool.TxPriority[math.Int]{
GetTxPriority: func(goCtx context.Context, tx sdk.Tx) math.Int {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return math.ZeroInt()
}
// Get fee in bond denomination
bondDenom := "test" // or your chain's bond denom
fee := feeTx.GetFee()
found, coin := fee.Find(bondDenom)
if !found {
return math.ZeroInt()
}
// Calculate gas price: fee_amount / gas_limit
gasPrice := coin.Amount.Quo(math.NewIntFromUint64(feeTx.GetGas()))
return gasPrice
},
Compare: func(a, b math.Int) int {
return a.BigInt().Cmp(b.BigInt()) // Higher values have priority
},
MinValue: math.ZeroInt(),
},
}
mempoolConfig := &evmmempool.EVMMempoolConfig{
AnteHandler: app.GetAnteHandler(),
BlockGasLimit: 100_000_000,
OntomirPoolConfig: &priorityConfig, // Pass config instead of pre-built pool
}
// Example: 50M gas limit for lower capacity chains
mempoolConfig := &evmmempool.EVMMempoolConfig{
AnteHandler: app.GetAnteHandler(),
BlockGasLimit: 50_000_000,
}
// After starting the CometBFT node
if m, ok := app.GetMempool().(*evmmempool.ExperimentalEVMMempool); ok {
m.SetEventBus(bftNode.EventBus())
}
if errors.Is(err, ErrNonceGap) {
// Route to local queue instead of rejecting
err := mempool.InsertInvalidNonce(request.Tx)
// Must intercept error and return success to EVM client
return interceptedSuccess
}