Feemarket

EIP-1559 modualarized

The feemarket module from Ontomir evm implements EIP-1559 dynamic fee pricing for Ontomir SDK chains with EVM compatibility.

This is part of Ontomir/evm, not standard Ontomir SDK. For conceptual understanding of EIP-1559, see [EIP-1559 Fee Market](/docs/evm/next/documentation/concepts/eip-1559-feemarket).

Parameters

The module contains the following parameters :

Parameter
Type
Default
Description

NoBaseFee

bool

false

Disables EIP-1559 base fee (enables legacy pricing)

BaseFeeChangeDenominator

uint32

8

Controls max base fee change per block (higher = more stable)

ElasticityMultiplier

uint32

2

Determines target block utilization (2 = 50% target)

EnableHeight

int64

0

Block height to activate fee market (0 = genesis)

BaseFee

sdk.Dec

1000000000

Current base fee (auto-adjusts each block)

MinGasPrice

sdk.Dec

0

Minimum gas price floor (cannot go below)

MinGasMultiplier

sdk.Dec

0.5

Minimum gas used multiplier for gas wanted

Parameter Details

Controls fee volatility by limiting the maximum change per block:

Value
Max Change/Block
Blocks to 10x
Use Case

4

±25%

~10 blocks

Testnets, rapid price discovery

8

±12.5%

~20 blocks

Ethereum standard, balanced

16

±6.25%

~40 blocks

Stable fees, production chains

32

±3.125%

~80 blocks

Very stable, enterprise chains

Sets the target block utilization:

Value
Target %
Behavior
Recommended For

1

100%

Only increases at full blocks

Maximum throughput chains

2

50%

Balanced (Ethereum standard)

General purpose chains

4

25%

Aggressive fee increases

Anti-spam focused chains

10

10%

Very aggressive pricing

High-value transaction chains

Sets the absolute price floor:

BaseFee = max(CalculatedBaseFee, MinGasPrice)

This prevents fees from dropping too low during periods of low activity, maintaining spam resistance.

Parameter Storage Format

sdk.Dec (LegacyDec) values are stored as strings with 18 decimal precision :

Human Value
Stored String
Formula

1 unit

"1000000000000000000"

1 × 10^18

0.5 (50%)

"500000000000000000"

0.5 × 10^18

1 billion

"1000000000000000000000000000"

10^9 × 10^18

Configuration

Adjust parameters to achieve specific network behaviors :

Goal

Parameter

Adjustment

Effect

Faster fee response

base_fee_change_denominator

8 → 4

2x faster adjustments (±25% vs ±12.5%)

More stable fees

base_fee_change_denominator

8 → 16

2x slower adjustments (±6.25% vs ±12.5%)

Earlier fee increases

elasticity_multiplier

2 → 4

Fees rise at 25% vs 50% utilization

Stronger spam deterrent

min_gas_price

Low → High

Higher transaction cost floor

Configuration Profiles

```json Ethereum-Compatible expandable { "no_base_fee": false, "base_fee_change_denominator": 8, // ±12.5% max per block "elasticity_multiplier": 2, // 50% target utilization "enable_height": 0, // Active from genesis "base_fee": "1000000000000000000000", // Starting base fee "min_gas_price": "1000000000000000000", // Price floor "min_gas_multiplier": "500000000000000000" // 0.5 (anti-manipulation) } // Behavior: Standard Ethereum, balanced for general use // 100k gas tx: ~0.1 tokens at base, up to 10x during congestion ```

{
  "no_base_fee": false,
  "base_fee_change_denominator": 16,     // ±6.25% max per block
  "elasticity_multiplier": 2,            // 50% target utilization
  "enable_height": 0,
  "base_fee": "100000000000000000000",   // Lower starting fee
  "min_gas_price": "10000000000000000",  // Lower floor
  "min_gas_multiplier": "500000000000000000"
}
// Behavior: Slow, predictable changes for DeFi/DEX chains
// 100k gas tx: ~0.01 tokens, gradual increases
{
  "no_base_fee": false,
  "base_fee_change_denominator": 4,      // ±25% max per block
  "elasticity_multiplier": 4,            // 25% target utilization
  "enable_height": 0,
  "base_fee": "10000000000000000000000",  // High starting fee
  "min_gas_price": "10000000000000000000000", // High floor
  "min_gas_multiplier": "1000000000000000000" // 1.0 (strict)
}
// Behavior: Rapid adjustments, high minimum for premium chains
// 100k gas tx: ~1 token minimum, aggressive scaling

Scenario Comparisons

| Config | 5 Blocks | 10 Blocks | 10x Time | | ------------------------- | -------- | --------- | ----------- | | **Ethereum** (d=8, e=2) | +34% | +79% | \~20 blocks | | **Stable** (d=16, e=2) | +16% | +34% | \~40 blocks | | **Aggressive** (d=4, e=4) | +101% | +304% | \~8 blocks | | Config | 5 Blocks | 10 Blocks | Floor | | -------------- | -------- | --------- | ----------- | | **Ethereum** | -26% | -45% | \~15 blocks | | **Stable** | -14% | -26% | \~30 blocks | | **Aggressive** | -61% | -85% | \~6 blocks |

Decimal Precision Support

The module supports different token decimal configurations:

Decimals
Conversion Factor
Min Unit Gas
Example

6

10^12

10^-12

USDC

8

10^10

10^-10

BTC-wrapped

18

1

1

ETH, most EVM

Configuration In Production

Parameters can be updated through governance proposals :

// MsgUpdateParams structure
// Source: github.com/Ontomir/evm/blob/v0.4.1/proto/Ontomir/evm/feemarket/v1/tx.proto
{
  "@type": "/Ontomir.evm.feemarket.v1.MsgUpdateParams",
  "authority": "Ontomir10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn",
  "params": {
    "no_base_fee": false,
    "base_fee_change_denominator": 16,
    "elasticity_multiplier": 2,
    "enable_height": 100000,
    "base_fee": "1000000000000000000000",
    "min_gas_price": "1000000000000000000",
    "min_gas_multiplier": "500000000000000000"
  }
}

Submit with:

# Submit governance proposal
# Source: github.com/Ontomir/evm/blob/v0.4.1/x/feemarket/keeper/msg_server.go#L17-L39
evmd tx gov submit-proposal update-feemarket-params.json \
  --from <key> \
  --deposit 1000000000test

Best Practices

Parameter Selection by Use Case

Chain Type

Denominator

Elasticity

MinGasPrice

Why

Testing/Development

8

2

Zero/Low

Quick iteration

Production General

8-16

2

Medium

Balance stability and responsiveness

High-Frequency Trading

16-32

1-2

Low

Predictable fees, high throughput

Enterprise/Private

32

2-3

High

Maximum stability, cost predictability

Gaming/Micro-tx

4-8

1

Very Low

Fast adjustments, minimal fees

Block Time Correlation

Faster blocks require higher denominators to maintain stability:

Recommended Denominator = 8 × (6s / YourBlockTime)

Examples:

  • 1s blocks → denominator ≥16 (prevents wild swings)

  • 6s blocks → denominator = 8 (Ethereum standard)

  • 12s blocks → denominator = 4 (can be more responsive)

Monitoring Key Metrics

Metric

Formula

Healthy

Action if Unhealthy

Fee Volatility

σ(fees)/μ(fees)

<20% daily

Increase denominator

Utilization Rate

gasUsed/maxGas

40-60% avg

Adjust elasticity

Floor Hit Rate

count(fee==min)\/blocks

<10%

Lower min_gas_price

Recovery Speed

Blocks to return to baseline

20-40

Tune denominator

Integration Examples

```javascript Wallet-Integration expandable // Get current base fee from latest block const block = await provider.getBlock('latest'); const baseFee = block.baseFeePerGas;

// Add buffer for inclusion certainty (20% buffer) const maxFeePerGas = baseFee * 120n / 100n; const maxPriorityFeePerGas = ethers.parseUnits("2", "gwei");

// Send EIP-1559 transaction const tx = await wallet.sendTransaction({ type: 2, maxFeePerGas, maxPriorityFeePerGas, to: "0x...", value: ethers.parseEther("1.0"), gasLimit: 21000 });


```javascript Fee-Monitoring expandable
// Get current gas price (includes base fee)
const gasPrice = await provider.send('eth_gasPrice', []);
console.log('Current gas price:', ethers.formatUnits(gasPrice, 'gwei'), 'gwei');

// Get fee history
const feeHistory = await provider.send('eth_feeHistory', [
  '0x10', // last 16 blocks
  'latest',
  [25, 50, 75]
]);

// Process base fees from history
const baseFees = feeHistory.baseFeePerGas.map(fee =>
  ethers.formatUnits(fee, 'gwei')
);
console.log('Base fee history:', baseFees);

// Check if fees are elevated
const currentBaseFee = BigInt(feeHistory.baseFeePerGas[feeHistory.baseFeePerGas.length - 1]);
const threshold = ethers.parseUnits('100', 'gwei');
const isHighFee = currentBaseFee > threshold;

Interfaces

EVM JSON-RPC

The fee market integrates with standard Ethereum JSON-RPC methods :

Returns the current base fee as hex string. In EIP-1559 mode, returns the base fee; in legacy mode, returns the median gas price.

// Request
{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}

// Response
{"jsonrpc":"2.0","id":1,"result":"0x3b9aca00"} // 1 gwei in hex

Returns suggested priority fee (tip) per gas. In Ontomir/evm with the experimental mempool, transactions are prioritized by effective gas tip (fees), with ties broken by arrival time.

// Request
{"jsonrpc":"2.0","method":"eth_maxPriorityFeePerGas","params":[],"id":1}

// Response
{"jsonrpc":"2.0","id":1,"result":"0x77359400"} // Suggested tip in wei

Returns base fee and gas utilization history for a range of blocks. Number of blocks to return (hex or decimal)

<ParamField body="newestBlock" type="string" required>
  Latest block to include ("latest", "pending", or block number)
</ParamField>

<ParamField body="rewardPercentiles" type="array">
  Array of percentiles for priority fee calculations
</ParamField>
// Request
{
  "jsonrpc":"2.0",
  "method":"eth_feeHistory",
  "params":["0x5", "latest", [25, 50, 75]],
  "id":1
}

// Response
{
  "jsonrpc":"2.0",
  "id":1,
  "result":{
    "baseFeePerGas": [
      "0x3b9aca00",
      "0x3c9aca00",
      "0x3d9aca00",
      "0x3e9aca00",
      "0x3f9aca00"
    ],
    "gasUsedRatio": [0.5, 0.55, 0.6, 0.48, 0.52],
    "oldestBlock": "0x1234",
    "reward": [
      ["0x0", "0x0", "0x0"],
      ["0x0", "0x0", "0x0"],
      ["0x0", "0x0", "0x0"],
      ["0x0", "0x0", "0x0"],
      ["0x0", "0x0", "0x0"]
    ]
  }
}

Block responses include `baseFeePerGas` field when EIP-1559 is enabled.

{
  "jsonrpc":"2.0",
  "id":1,
  "result":{
    "number": "0x1234",
    "hash": "0x...",
    "baseFeePerGas": "0x3b9aca00", // Base fee for this block
    // ... other block fields
  }
}

Transaction Types

The fee market supports both legacy and EIP-1559 transaction types (types):

```json Legacy-Transaction { "type": "0x0", "gasPrice": "0x3b9aca00", // Must be >= current base fee "gas": "0x5208", "to": "0x...", "value": "0x...", "data": "0x..." } ```

{
  "type": "0x2",
  "maxFeePerGas": "0x3b9aca00",      // Maximum total fee willing to pay
  "maxPriorityFeePerGas": "0x77359400", // Tip that affects transaction priority in mempool
  "gas": "0x5208",
  "to": "0x...",
  "value": "0x...",
  "data": "0x..."
}

CLI

```bash Query-Parameters # Query module parameters # Source: github.com/Ontomir/evm/blob/v0.4.1/x/feemarket/client/cli/query.go evmd query feemarket params ```

# Query current base fee
# Source: github.com/Ontomir/evm/blob/v0.4.1/x/feemarket/client/cli/query.go
evmd query feemarket base-fee
# Query block gas wanted
# Source: github.com/Ontomir/evm/blob/v0.4.1/x/feemarket/client/cli/query.go
evmd query feemarket block-gas

gRPC

```proto Query-Parameters expandable // Request all module parameters // Proto: github.com/Ontomir/evm/blob/v0.4.1/proto/Ontomir/evm/feemarket/v1/query.proto grpcurl -plaintext localhost:9090 feemarket.Query/Params

// Response { "params": { "no_base_fee": false, "base_fee_change_denominator": 8, "elasticity_multiplier": 2, "enable_height": 0, "base_fee": "1000000000000000000000", "min_gas_price": "1000000000000000000", "min_gas_multiplier": "500000000000000000" } }


```proto Query-Base-Fee
// Request current base fee
// Proto: github.com/Ontomir/evm/blob/v0.4.1/proto/Ontomir/evm/feemarket/v1/query.proto
grpcurl -plaintext localhost:9090 feemarket.Query/BaseFee

// Response
{
  "base_fee": "1000000000000000000000"
}
// Request block gas wanted
// Proto: github.com/Ontomir/evm/blob/v0.4.1/proto/Ontomir/evm/feemarket/v1/query.proto
grpcurl -plaintext localhost:9090 feemarket.Query/BlockGas

// Response
{
  "gas": "5000000"
}

REST

```http Query-Parameters expandable # REST endpoint for parameters # Proto: github.com/Ontomir/evm/blob/v0.4.1/proto/Ontomir/evm/feemarket/v1/query.proto GET /Ontomir/feemarket/v1/params

// Response { "params": { "no_base_fee": false, "base_fee_change_denominator": 8, "elasticity_multiplier": 2, "enable_height": 0, "base_fee": "1000000000000000000000", "min_gas_price": "1000000000000000000", "min_gas_multiplier": "500000000000000000" } }


```http Query-Base-Fee
# REST endpoint for base fee
# Proto: github.com/Ontomir/evm/blob/v0.4.1/proto/Ontomir/evm/feemarket/v1/query.proto
GET /Ontomir/feemarket/v1/base_fee

// Response
{
  "base_fee": "1000000000000000000000"
}
# REST endpoint for block gas
# Proto: github.com/Ontomir/evm/blob/v0.4.1/proto/Ontomir/evm/feemarket/v1/query.proto
GET /Ontomir/feemarket/v1/block_gas

// Response
{
  "gas": "5000000"
}

Technical Implementation

State

The module maintains the following state :

State Objects

State Object
Description
Key
Value
Store

BlockGasWanted

Gas wanted in the current block

[]byte{1}

[]byte{uint64}

Transient

BaseFee

Current base fee

Params.BaseFee

sdk.Dec

KV

Params

Module parameters

[]byte{0x00}

Params

KV

Genesis State

type GenesisState struct {
    Params Params
    BaseFee sdk.Dec
}

Begin Block

At the beginning of each block, the module calculates and sets the new base fee :

func (k Keeper) BeginBlock(ctx sdk.Context) error {
    baseFee := k.CalculateBaseFee(ctx)

    // Skip if base fee is nil (not enabled)
    if baseFee.IsNil() {
        return nil
    }

    // Update the base fee in state
    k.SetBaseFee(ctx, baseFee)

    // Emit event with new base fee
    ctx.EventManager().EmitEvent(
        sdk.NewEvent(
            "fee_market",
            sdk.NewAttribute("base_fee", baseFee.String()),
        ),
    )
    return nil
}

End Block

At the end of each block, the module updates the gas wanted for base fee calculation :

func (k Keeper) EndBlock(ctx sdk.Context) error {
    gasWanted := k.GetTransientGasWanted(ctx)
    gasUsed := ctx.BlockGasMeter().GasConsumedToLimit()

    // Apply MinGasMultiplier to prevent manipulation
    // gasWanted = max(gasWanted * MinGasMultiplier, gasUsed)
    minGasMultiplier := k.GetParams(ctx).MinGasMultiplier
    limitedGasWanted := NewDec(gasWanted).Mul(minGasMultiplier)
    updatedGasWanted := MaxDec(limitedGasWanted, NewDec(gasUsed)).TruncateInt()

    // Store for next block's base fee calculation
    k.SetBlockGasWanted(ctx, updatedGasWanted)

    ctx.EventManager().EmitEvent(
        sdk.NewEvent(
            "block_gas",
            sdk.NewAttribute("height", fmt.Sprintf("%d", ctx.BlockHeight())),
            sdk.NewAttribute("amount", fmt.Sprintf("%d", updatedGasWanted)),
        ),
    )
    return nil
}

Base Fee Calculation Algorithm

The calculation follows the EIP-1559 specification with Ontomir SDK adaptations :

```go func CalcGasBaseFee(gasUsed, gasTarget, baseFeeChangeDenom uint64, baseFee, minUnitGas, minGasPrice LegacyDec) LegacyDec { // No change if at target if gasUsed == gasTarget { return baseFee }

  // Calculate adjustment magnitude
  delta := abs(gasUsed - gasTarget)
  adjustment := baseFee.Mul(delta).Quo(gasTarget).Quo(baseFeeChangeDenom)

  if gasUsed > gasTarget {
      // Increase base fee (minimum 1 unit)
      baseFeeDelta := MaxDec(adjustment, minUnitGas)
      return baseFee.Add(baseFeeDelta)
  } else {
      // Decrease base fee (not below minGasPrice)
      return MaxDec(baseFee.Sub(adjustment), minGasPrice)
  }

}


The `minUnitGas` is calculated as `1 / 10^decimals` to ensure minimum increments based on token precision ([source](https://github.com/Ontomir/evm/blob/v0.4.1/utils/utils.go#L229-L254)).
</Expandable>

### Keeper

The keeper manages fee market state and calculations ([source](https://github.com/Ontomir/evm/blob/v0.4.1/x/feemarket/keeper/keeper.go)):

| Method                                        | Description           | Usage                               |
| --------------------------------------------- | --------------------- | ----------------------------------- |
| `GetBaseFee()` / `SetBaseFee()`               | Current base fee      | Read/update during block processing |
| `GetParams()` / `SetParams()`                 | Module parameters     | Governance updates                  |
| `GetBlockGasWanted()` / `SetBlockGasWanted()` | Gas tracking          | Base fee calculation                |
| `CalculateBaseFee()`                          | Compute next base fee | Called in BeginBlock                |

The module also implements `PostTxProcessing` hooks for EVM transaction processing ([source](https://github.com/Ontomir/evm/blob/v0.4.1/x/feemarket/keeper/hooks.go)).

### Events

The module emits the following events ([types](https://github.com/Ontomir/evm/blob/v0.4.1/x/feemarket/types/events.go)):

#### Fee Market Block Fees

| Attribute Key | Attribute Value           |
| ------------- | ------------------------- |
| height        | Current block height      |
| base\_fee     | New base fee              |
| gas\_wanted   | Total gas wanted in block |
| gas\_used     | Total gas used in block   |

Example:

```json
{
"type": "fee_market_block_fees",
"attributes": [
  {"key": "height", "value": "12345"},
  {"key": "base_fee", "value": "1000000000"},
  {"key": "gas_wanted", "value": "5000000"},
  {"key": "gas_used", "value": "4800000"}
]
}

AnteHandlers

The module integrates with EVM transactions through the EVMMonoDecorator, which performs all fee-related validations in a single pass :

EVMMonoDecorator

The primary ante handler that validates and processes EVM transaction fees :

```go // 1. Check mempool minimum fee (for pre-London transactions) if ctx.IsCheckTx() && !simulate && !isLondon { requiredFee := mempoolMinGasPrice.Mul(gasLimit) if fee.LT(requiredFee) { return ErrInsufficientFee } }

// 2. Calculate effective fee for EIP-1559 transactions if ethTx.Type() >= DynamicFeeTxType && baseFee != nil { feeAmt = ethMsg.GetEffectiveFee(baseFee) fee = NewDecFromBigInt(feeAmt) }

// 3. Check global minimum gas price if globalMinGasPrice.IsPositive() { requiredFee := globalMinGasPrice.Mul(gasLimit) if fee.LT(requiredFee) { return ErrInsufficientFee } }

// 4. Track gas wanted for base fee calculation if isLondon && baseFeeEnabled { feeMarketKeeper.AddTransientGasWanted(ctx, gasWanted) }

</Expandable>

#### Fee Validation Functions

##### CheckMempoolFee

Validates transaction fees against the local mempool minimum ([source](https://github.com/Ontomir/evm/blob/v0.4.1/ante/evm/02_mempool_fee.go#L13-L29)):

<Expandable title="Implementation details">
```go
func CheckMempoolFee(fee, mempoolMinGasPrice, gasLimit LegacyDec, isLondon bool) error {
    // Skip for London-enabled chains (EIP-1559 handles this)
    if isLondon {
        return nil
    }

    requiredFee := mempoolMinGasPrice.Mul(gasLimit)
    if fee.LT(requiredFee) {
        return ErrInsufficientFee
    }
    return nil
}

Note: This check is bypassed for London-enabled chains since EIP-1559 base fee mechanism handles minimum pricing.

CheckGlobalFee

Enforces the chain-wide minimum gas price set via governance :

```go func CheckGlobalFee(fee, globalMinGasPrice, gasLimit LegacyDec) error { if globalMinGasPrice.IsZero() { return nil }

  requiredFee := globalMinGasPrice.Mul(gasLimit)
  if fee.LT(requiredFee) {
      return ErrInsufficientFee
  }
  return nil

}


For EIP-1559 transactions, if the effective price falls below `MinGasPrice`, users must increase the priority fee (tip) until the effective price meets the minimum.

In Ontomir/evm's experimental mempool, transactions are ordered by effective gas tip (calculated as `min(maxPriorityFeePerGas, maxFeePerGas - baseFee)`), with higher tips getting priority. When tips are equal, transactions are ordered by arrival time ([source](https://github.com/Ontomir/evm/blob/v0.4.1/mempool/miner/ordering.go#L63-L71)).
</Expandable>

##### CheckGasWanted

Tracks cumulative gas wanted for base fee calculation ([source](https://github.com/Ontomir/evm/blob/v0.4.1/ante/evm/10_gas_wanted.go#L48-L82)):

<Expandable title="Implementation details">
```go
func CheckGasWanted(ctx Context, feeMarketKeeper FeeMarketKeeper, tx Tx, isLondon bool) error {
    if !isLondon || !feeMarketKeeper.GetBaseFeeEnabled(ctx) {
        return nil
    }

    gasWanted := tx.GetGas()
    blockGasLimit := BlockGasLimit(ctx)

    // Reject if tx exceeds block limit
    if gasWanted > blockGasLimit {
        return ErrOutOfGas
    }

    // Add to cumulative gas wanted for base fee adjustment
    feeMarketKeeper.AddTransientGasWanted(ctx, gasWanted)
    return nil
}

This cumulative gas wanted is used at the end of the block to calculate the next block's base fee.

Module Distinctions

Ontomir/evm vs Standard Ontomir SDK

This fee market module from Ontomir evm differs from standard Ontomir SDK fee handling:

Aspect

Standard Ontomir SDK

Ontomir/evm Feemarket

Fee Model

Static, validator-configured min gas prices

Dynamic EIP-1559 base fee

Price Discovery

Manual adjustment by validators

Automatic based on utilization

Transaction Types

Ontomir SDK Tx only

Both Ontomir & Ethereum txs

Decimal Handling

Chain-specific precision

18-decimal EVM compatibility

State Storage

Not tracked

Base fee in state, gas in transient store

Consensus Impact

No consensus on fees

Base fee part of consensus

Key Integration Points

The module integrates with EVM through:

  • AnteHandlers: Custom decorators for EVM transaction validation

  • Hooks: PostTxProcessing for EVM state updates

  • RPC: Ethereum JSON-RPC methods for fee queries

References

Specifications

Tools & Libraries

最后更新于