Skip to content

fix(eth): retry gas estimate without fee fields on base-fee rejection (Gnosis/Nethermind)#2728

Closed
CharlVS wants to merge 1 commit into
GLEECBTC:devfrom
CharlVS:fix/gnosis-estimategas-fee-fallback
Closed

fix(eth): retry gas estimate without fee fields on base-fee rejection (Gnosis/Nethermind)#2728
CharlVS wants to merge 1 commit into
GLEECBTC:devfrom
CharlVS:fix/gnosis-estimategas-fee-fallback

Conversation

@CharlVS

@CharlVS CharlVS commented Jun 8, 2026

Copy link
Copy Markdown

What

Make eth_estimateGas resilient to nodes that reject EIP-1559 fee fields when max_fee_per_gas is below the current block base fee. On such a rejection, the estimate now transparently retries without the fee fields (gas usage is independent of fee values).

Why

Nethermind v1.38.0 (released 2026-06-02, the client behind Gnosis) started validating the EIP-1559 fee fields during eth_estimateGas. When a non-zero fee field is present, ShouldValidateGas re-enables the base-fee/premium check even though the call runs with SkipValidation, so the call is rejected with "miner premium is negative" (renamed by PR #11190 to the geth-style "max fee per gas less than block base fee") whenever max_fee_per_gas < baseFee.

KDF intentionally passes fee fields into the estimate request for contracts that branch on gas price (e.g. TUSD — atomicDEX-API#643). When Gnosis's base fee spiked above our (high-policy) estimate, the ERC20 approve gas estimate was rejected, which broke the fee preimage and therefore order posting.

This is a behavior change on the node side (Nethermind's own PR #11192 is literally titled "removes MaxFeePerGas for eth_estimateGas"); the fix belongs on our end — a gas-usage estimate should not be gated by fee caps.

How

  • is_max_fee_per_gas_below_base_fee_error() — single classifier recognizing all three node wordings: geth's "max fee per gas less than block base fee", older Nethermind's "fee cap less than block base fee", and the new "miner premium is negative".
  • EthCoin::estimate_gas_with_pay_for_gas_option() — estimates with fee fields (preserving the TUSD behavior) and, on a base-fee rejection, retries without any fee fields. Both swap-side estimate paths (estimate_gas_for_contract_call, get_fee_to_send_taker_fee) route through it.
  • Withdraw path (get_eth_gas_details_from_withdraw_fee) reuses the classifier so the new wording surfaces as the structured GasFeeCapBelowBaseFee/GasFeeCapTooLow error instead of a raw Transport error. It deliberately does not retry — withdraw uses a user-supplied fee that must be reported as too low.
  • parse_fee_cap_error() extended to also parse Nethermind v1.38.0's maxFeePerGas: N, baseFee: N wording.

Reviewer notes

  • The fee-less retry is safe on both clients: omitting all fee fields (incl. gas_price, which maps to both EIP-1559 fields for legacy txs in Nethermind) makes Nethermind's ShouldValidateGas return false and geth run with NoBaseFee, so the estimate succeeds regardless of base fee.
  • The existing get_swap_fee_policy_for_estimate (always-High) mitigation is retained; the retry is the safety net for when even the High estimate's cap is exceeded.

Tests

  • test_estimate_gas_retries_without_fee_fields_when_max_fee_below_base_fee — mocks a Nethermind-style rejection while fee fields are present, success once omitted; asserts exactly one fee-bearing attempt + one fee-less retry succeeds.
  • test_estimate_gas_does_not_retry_on_unrelated_error — an unrelated error (execution reverted) propagates with no retry.
  • test_parse_fee_cap_error — both geth and Nethermind wordings parse; the numberless message yields None.

cargo check -p coins --lib --tests, cargo fmt --check, and cargo clippy (zero new warnings) all pass; new + existing eth gas-estimate tests pass.

🤖 Generated with Claude Code

Nethermind v1.38.0 (the client behind Gnosis) started validating the
EIP-1559 fee fields during eth_estimateGas and rejecting the call with
"miner premium is negative" (renamed by PR #11190 to the geth-style
"max fee per gas less than block base fee") when max_fee_per_gas is
below the current block base fee.

KDF intentionally passes fee fields into the estimate request for
contracts that branch on gas price (e.g. TUSD, atomicDEX-API#643). When
Gnosis's base fee spiked above our (high-policy) estimate, the approve
gas estimate was rejected, which broke fee preimages and therefore order
posting.

Gas usage is independent of the fee values, so estimate calls now retry
without the fee fields when a node reports the cap is below the base fee.
This is safe on both clients: omitting all fee fields makes Nethermind's
ShouldValidateGas return false and geth run with NoBaseFee, so the
estimate succeeds regardless of base fee.

Changes:
- Add is_max_fee_per_gas_below_base_fee_error() recognizing the geth,
  older-Nethermind, and new "miner premium is negative" wordings.
- Add EthCoin::estimate_gas_with_pay_for_gas_option(): tries with fee
  fields, retries without them on a base-fee rejection. Both swap-side
  estimate paths (estimate_gas_for_contract_call, get_fee_to_send_taker_fee)
  route through it.
- Withdraw path reuses the classifier so the new wording surfaces as the
  structured GasFeeCapBelowBaseFee/GasFeeCapTooLow error instead of a raw
  Transport error. It deliberately does not retry, since withdraw uses a
  user-supplied fee that must be reported as too low.
- Extend parse_fee_cap_error() to also parse Nethermind v1.38.0's
  "maxFeePerGas: N, baseFee: N" wording.
- Add unit tests for the retry, the no-retry-on-unrelated-error path, and
  the error parser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CharlVS CharlVS closed this Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant