Skip to content

bug: CCTP v2 receiveMessage() on destination chains fails with 'max fee per gas less than block base fee' when using default viem gas estimation #153

Description

@osr21

Summary

When calling MessageTransmitterV2.receiveMessage() on destination chains (Ethereum Sepolia, Base Sepolia, Arbitrum Sepolia) via viem + MetaMask after a CCTP v2 burn from Arc Testnet, transactions frequently fail immediately with:

The contract function "receiveMessage" reverted with the following reason:
RPC submit: max fee per gas less than block base fee:
  address 0x6B1E65761707f976dcE0d6f58520Fbf4eC0daa0C
  maxFeePerGas: 20000000, baseFee: 20002000

Root Cause

viem's writeContract estimates maxFeePerGas from the destination chain's public RPC at time T₀. By the time the signed transaction reaches the mempool at time T₁, the base fee has ticked up — sometimes by as little as 2000 wei (~0.000002 gwei). EIP-1559 requires maxFeePerGas ≥ baseFee, so the node rejects immediately.

This is especially pronounced on Arbitrum Sepolia where base fees are tiny (~0.02 gwei). A 2000 wei movement is a ~0.01% change in absolute terms but represents a 10% relative jump, making under-estimation common.

Fix

Fetch estimateFeesPerGas() from a public client pointed at the destination chain and apply a 2× multiplier before passing to writeContract:

const destPublicClient = createPublicClient({ 
  chain: destChain, 
  transport: http(destRpc) 
});

const feeData = await destPublicClient.estimateFeesPerGas().catch(() => null);
const gasBump = feeData?.maxFeePerGas != null
  ? {
      maxFeePerGas: feeData.maxFeePerGas * 2n,
      maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? 1_000_000n,
    }
  : {};

const hash = await walletClient.writeContract({
  address: MESSAGE_TRANSMITTER_V2,
  abi: RECEIVE_MESSAGE_ABI,
  functionName: "receiveMessage",
  args: [message, attestation],
  account,
  chain: destChain,
  ...gasBump,
});

The 2× multiplier is safe in practice — at 0.02 gwei base fee, paying up to 0.04 gwei costs less than $0.0001 and guarantees the tx lands even if the base fee spikes between estimation and broadcast.

Affected Flows

Any CCTP v2 integration that calls receiveMessage() from a browser wallet on a low-base-fee destination chain. This includes all Circle sample apps that use viem + MetaMask for the receive step.

Verified Fix

This fix was validated in production on Arbitrum Sepolia in the reference implementation:

Related

  • Likely affects: circlefin/circle-cctp-crosschain-transfer, circlefin/arc-multichain-wallet, circlefin/arc-fintech

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions