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.
25
MON 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
UserOp #1: Swap 2 MON → USDC
UserOp #2: NFT mint (0.5 MON)
UserOp #3: Bridge 15 MON out
UserOp #4: Stake 1 MON
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
contract50 MON
exempt
← → keys
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
- ✓
CALLworks - ✗
STATICCALL,DELEGATECALL,CALLCODErevert - ✗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