Skip to main content
Monad uses a precompile to manage validator delegation and rewards. This page covers the key concepts and workflows for interacting with the staking precompile. For the full interface reference, visit the API page. To learn how Monad’s staking system works, visit the learn tab. The staking precompile is at address .

Key concepts

Epochs and timing

Staking state changes don’t take effect immediately. Monad divides time into epochs, and most actions only activate at the start of a new epoch. Every 50,000 blocks (~5.5 hours) is a boundary block that commits upcoming staking changes. After a 5,000-round delay (EPOCH_DELAY_ROUNDS), the new epoch starts. timeline showing the placement of boundary blocks within an epoch This means your action activates in either:
  • Epoch n+1 — if submitted before the boundary block
  • Epoch n+2 — if submitted after the boundary block (in the epoch delay period)
Use getEpoch() to check the current epoch and whether the boundary has passed:
(uint64 epoch, bool inEpochDelayPeriod) = IMonadStaking(STAKING_ADDRESS).getEpoch();
// If inEpochDelayPeriod is false: changes take effect in epoch + 1
// If inEpochDelayPeriod is true:  changes take effect in epoch + 2
A round is not a block — rounds increment even on missed proposals. You cannot calculate epoch boundaries with modular arithmetic on block numbers. Always use getEpoch().

Withdrawal delay

Undelegated stake is not immediately available. After calling undelegate, you must wait WITHDRAWAL_DELAY (1 epoch) before calling withdraw to reclaim the funds.

Common actions

Delegate

To delegate MON to a validator, call delegate(validatorId) with the amount as msg.value:
IMonadStaking(STAKING_ADDRESS).delegate{value: amount}(validatorId);
  • msg.value must be at least DUST_THRESHOLD (1 gwei).
  • Your delegation becomes active in the next epoch (or the one after, if past the boundary block).
  • If this causes the validator’s total stake to meet ACTIVE_VALIDATOR_STAKE, the validator is added to the active set.

Undelegate and withdraw

Removing stake is a two-step process: Step 1: Undelegate — Initiate the withdrawal by specifying the amount and a withdrawId (0–255):
IMonadStaking(STAKING_ADDRESS).undelegate(validatorId, amount, withdrawId);
Step 2: Withdraw — After WITHDRAWAL_DELAY epochs have passed, call withdraw to reclaim the funds:
IMonadStaking(STAKING_ADDRESS).withdraw(validatorId, withdrawId);
timeline of undelegation and withdrawal

Timeline of withdrawability of stake relative to undelegate

  • You can only undelegate active stake (not pending delegations).
  • Each (validator, delegator) pair supports up to 256 concurrent withdrawal requests.
  • withdrawIds can be reused after the withdrawal completes.

Claim and compound rewards

Rewards accumulate automatically as your validator produces blocks. You have two options:
  • Claim rewards — withdraw accumulated rewards to your account:
    IMonadStaking(STAKING_ADDRESS).claimRewards(validatorId);
    
    Claims take effect immediately — no epoch delay.
  • Compound rewards — re-delegate accumulated rewards, increasing your stake:
    IMonadStaking(STAKING_ADDRESS).compound(validatorId);
    
    Compounded rewards activate in the next epoch (following the standard timing rules).

Query staking state

Key view methods for reading staking state:
MethodPurpose
getValidator(validatorId)Full validator state across execution, consensus, and snapshot views
getDelegator(validatorId, address)Delegator’s stake, rewards, and pending changes for a specific validator
getWithdrawalRequest(validatorId, address, withdrawId)Status of a pending withdrawal
getEpoch()Current epoch and whether boundary has passed
getConsensusValidatorSet(startIndex)Current epoch’s leader validators (paginated)
getSnapshotValidatorSet(startIndex)Next epoch’s leader validators (paginated)
getExecutionValidatorSet(startIndex)All validators meeting active staking criteria (paginated)
getProposerValId()Validator ID of the current block’s proposer
getDelegations(address, startValId)All validators a delegator has delegated to (paginated)
getDelegators(validatorId, startDelegator)All delegators for a validator (paginated)
Paginated methods return up to 100 results per call. Pass startIndex = 0 for the first call, then use nextIndex for subsequent calls until isDone is true.

Constraints

Because the staking system is a precompile rather than a smart contract, it has some behavioral differences.
  • Only CALL is allowed. STATICCALL, DELEGATECALL, and CALLCODE will revert. This means all view methods use nonpayable state mutability rather than view.
  • No forked environment testing. The staking system is a precompile, not a smart contract — there is no code at the address, so forked testing environments won’t work.
  • Boundary block timing. Actions submitted during the epoch delay period (after the boundary block) won’t take effect until two epochs later. Check getEpoch() if timing matters.
  • Dust threshold. Delegations below DUST_THRESHOLD (1 gwei) will revert.
  • EIP-7702 caveat. If an account delegates to the staking precompile address using EIP-7702, all calls to it will revert.

Further reading

  • API reference — Full reference for the staking precompile with method signatures, parameters, gas costs, events, structs, and ABI
  • How staking works — How Monad’s staking system determines validator voting weights, epoch scheduling, and reward distribution