Detect reserve violations mid-execution

Monad reserves 10 MON per EOA to ensure solvency across async execution. MIP-4 lets contracts check if that threshold has been crossed.

The information on this page should not be quoted. Please refer to MIP-4 for the authoritative spec.

10 MON reserve

25

MON balance

Starting balance

Why reserve balance exists

Monad separates consensus from execution. When block N is proposed, the leader only has state from block N-3 (~1.2 seconds ago).

Without protection, a user could appear solvent on stale state but have already spent their funds. The 10 MON reserve ensures EOAs remain solvent across the async execution gap.

Block N-3

State known to consensus

100 MON

Block N-2

Alice spends 95 MON

5 MON

Block N-1

Processing...

5 MON

Block N

Leader proposes Alice's tx

?

Leader sees 100 MON, but Alice actually has 5 MON

Without reserve

Alice submits txs that pass consensus validation (she looks solvent) but fail during execution. Network wastes blockspace processing invalid transactions.

With 10 MON reserve

Alice can only commit 10 MON in gas fees across the 3-block window. The leader rejects transactions that would exceed this budget, even on stale state.

The bundler problem

An ERC-4337 bundler processes multiple UserOperations in one transaction. A single reserve violation reverts the entire bundle.

Without MIP-4, the bundler has no way to know which UserOp caused the failure. With MIP-4, the bundler can call dippedIntoReserve() after each op to identify and revert only the offending op, letting the rest of the bundle complete.

Bundler: 5 UserOperations

1

UserOp #1: Swap 2 MON → USDC

2

UserOp #2: NFT mint (0.5 MON)

3

UserOp #3: Bridge 15 MON out

4

UserOp #4: Stake 1 MON

5

UserOp #5: Swap 0.1 MON → WETH

Watch the reserve in action

Step through a transaction that moves MON between accounts. The 10 MON reserve line shows when an account is in violation. Call dippedIntoReserve() at any point to check.

Alice

25 MON

reserve: 10

Bob

15 MON

reserve: 10

Pool

contract

50 MON

exempt

Step 0 / 5

Technical details

The precompile lives at 0x1001 with a single method: dippedIntoReserve() (selector 0x3a61584e). It costs 100 gas, equivalent to a transient storage read.

The check is global: it evaluates all accounts touched in the transaction, not just the caller's. It returns true if any account's balance is currently below its reserve threshold; it clears back to false if that balance recovers above the threshold mid-transaction.

Call restrictions

  • CALL works
  • STATICCALL, DELEGATECALL, CALLCODE revert
  • Nonzero value reverts
  • Extra calldata beyond the 4-byte selector reverts

Important behaviors

  • Reverts consume all gas (precompile behavior, not Solidity-style refund)
  • Smart contracts (non-EIP-7702) are exempt from reserve balance
  • Emptying exception: an undelegated EOA's first transaction in k blocks may spend below reserve, letting users fully withdraw. EIP-7702-delegated accounts cannot use this exception.
  • O(1) cost: tracks violations incrementally via a failed-address set

Usage in Solidity

interface IReserveBalance {
    function dippedIntoReserve() external returns (bool);
}

// Call the precompile at 0x1001
IReserveBalance reserve = IReserveBalance(address(0x1001));

// After a risky operation:
if (reserve.dippedIntoReserve()) {
    // Some account dropped below 10 MON reserve
    // Revert, adjust, or take alternate path
}

Continue the discussion on Monad Forum

Questions, feedback, or a better idea? Weigh in on the forum thread.

Open forum thread