A Monad staking transaction can be successful while the stake is still pending. A validator can have one stake value in execution state and another weight in consensus. An undelegation can be recorded on-chain while the MON is still unavailable to spend.
These are not contradictory states. They are different stages of the same protocol pipeline.
The simplest useful model is:
A wallet transaction changes staking execution state first. Epoch machinery decides when that change becomes consensus-effective. A separate withdrawal transaction may still be required before funds become liquid again.
That distinction is the key to reading Monad staking data correctly — whether you are a delegator checking a position, a validator watching set membership, or an Explorer developer turning precompile output into human-readable fields.
This article explains the mechanism behind the interface. For the step-by-step delegation flow, see How to stake MON with NodeStake Explorer.
The staking system is a native precompile
Monad exposes staking through the native precompile at 0x0000000000000000000000000000000000001000. It behaves like a contract from a caller's perspective — actions have ABI selectors, calldata, transaction receipts and events — but its state transition logic is implemented by the protocol rather than deployed EVM bytecode.
The main user actions are:
delegate(uint64)undelegate(uint64,uint256,uint8)withdraw(uint64,uint8)claimRewards(uint64)compound(uint64)
The main read paths are:
getEpoch()getValidator(validatorId)getDelegator(validatorId, delegator)getWithdrawalRequest(validatorId, delegator, withdrawId)getExecutionValidatorSet(startIndex)getConsensusValidatorSet(startIndex)getSnapshotValidatorSet(startIndex)
This matters because an Explorer should not infer staking state from token transfers alone. It must decode the precompile's own views, events and epoch state.
“A 50,000-block epoch” is useful shorthand, but not the whole rule
Monad documentation describes epochs around two protocol constants:
BOUNDARY_BLOCK_PERIOD = 50,000 blocksEPOCH_DELAY_ROUNDS = 5,000 rounds
The precise statement is that boundary blocks are 50,000 blocks apart. At a boundary, the system commits the staking and validator-set changes that will drive the next epoch. The epoch does not switch at that same instant. The network then passes through a 5,000-round delay so nodes can transition consistently before the new epoch becomes current.
That gives each epoch two timing regions:
- Before the boundary — a new delegation, compound or commission change is queued for epoch
n + 1. - After the boundary, during the delay period — the boundary snapshot has already been taken, so a new change misses the next set and is queued for epoch
n + 2.
A transaction included in the boundary block itself is also too late for the just-taken snapshot, because the system snapshot runs at the start of the block before user transactions.
Why the same action can activate next epoch or the epoch after that
Assume getEpoch() returns epoch 1200.
| Submission time | inEpochDelayPeriod | Expected activation |
|---|---|---|
| Before the boundary | false | Epoch 1201 |
| In the boundary block | transitions at the boundary | Epoch 1202 |
| After the boundary, during the delay | true | Epoch 1202 |
This is why a staking UI should say “next or following epoch” until it has read the boundary flag. A successful receipt confirms that the action entered execution state; it does not prove that the new stake is already part of consensus.
Why block height alone cannot tell you the epoch
It is tempting to calculate an epoch with something like:
epoch = floor(blockHeight / 50,000)That is not reliable on Monad.
The boundary period is measured in blocks, but the delay period is measured in consensus rounds. A failed proposal or timeout advances the round even when no new block is produced. From that moment, round count and block height have diverged. Block-height modular arithmetic cannot tell you exactly when the 5,000-round delay ends.
It also cannot tell you, by itself, whether the current block is before or after the staking snapshot. The source of truth is:
(uint64 epoch, bool inEpochDelayPeriod) = staking.getEpoch();NodeStake Explorer therefore reads getEpoch() directly. Its “Next Epoch” countdown is explicitly an estimate: it locates historical epoch transitions through block-tagged getEpoch() calls, observes the recent block pace, and projects a remaining time. The displayed countdown can help with orientation, but the returned epoch number and delay flag decide the actual activation path.
Execution, consensus and snapshot are three views of one transition
The most important technical distinction in Monad staking is that there is no single validator-set balance that serves every purpose.
Execution validator set
The execution set is the real-time eligibility pool. Delegations, undelegations, validator registration, compounding and commission changes update execution state when their transactions execute.
For each validator, getValidator() exposes this view through fields including:
stake— the upcoming stake pool balance in execution statecommission— the latest execution-state commissionflags— eligibility and status flagsunclaimedRewardsandaccRewardPerToken— reward-accounting state
The execution set can be larger than the consensus set. A validator must satisfy the protocol's self-delegation and total-stake thresholds to enter the eligible pool, and only the top 200 by stake are selected for consensus.
As verified against the current Monad documentation on August 28, 2026, the thresholds are 100,000 MON self-delegated by the validator's authAddress, 10,000,000 MON total stake, and a maximum active-set size of 200. These are protocol constants, not universal PoS assumptions, and can change in a future protocol release.
Consensus validator set
The consensus set holds the selected leaders and their epoch-effective voting weights. getValidator() exposes consensusStake and consensusCommission, while getConsensusValidatorSet() returns the selected validator IDs.
Outside the boundary delay, this is the set that defines the current epoch's leader schedule and the denominator readers normally mean by “network voting power.”
Snapshot validator set
The snapshot set exists so the transition across the boundary is deterministic. The current REVM implementation copies the pre-boundary consensus image into snapshot state, rebuilds the consensus selection from the execution set, and uses snapshot stake and commission for reward accounting during the boundary delay. After the epoch-change system call completes, the boundary flag clears and the newly selected consensus view is current.
This is a subtle but important API detail. High-level explanations describe the conceptual flow as “execution → snapshot → consensus.” At the current precompile storage and syscall level, the snapshot view is also the previous consensus image used during the delay, while the consensus fields already hold the newly selected set. Both descriptions point to the same transition, but an API consumer must inspect inEpochDelayPeriod to know which view is operational at that moment.
| View | What it answers | Main precompile data |
|---|---|---|
| Execution | What staking state looks like after the latest transactions | getExecutionValidatorSet(), getValidator().stake, commission |
| Consensus | Which validators and weights have been selected for the consensus epoch | getConsensusValidatorSet(), consensusStake, consensusCommission |
| Snapshot | Which pre-boundary consensus weights preserve reward continuity during the delay | getSnapshotValidatorSet(), snapshotStake, snapshotCommission |
The practical rule is simple: always pair a validator-set value with the epoch number and delay-period flag used to interpret it.
Voting Power, Active Stake and Pending Change are not synonyms
These labels describe different levels of state.
Voting Power
On the NodeStake Monad validator directory, voting power is based on the validator's consensus stake. The percentage is:
validator consensusStake / total consensusStake of the selected setIt is a consensus-weight metric, not the validator's latest execution-state balance and not a delegator's personal stake.
During the normal part of an epoch, consensusStake is the current voting weight. During the boundary delay, the delay badge matters because snapshot state preserves the pre-boundary reward view while consensus state holds the newly selected set.
Active Stake
For an individual delegator, NodeStake reads getDelegator(validatorId, address).stake. This is the precompile's active stake field for that validator-delegator pair.
It should not be confused with:
- liquid MON in the wallet;
- the validator's total execution stake;
- the validator's consensus voting power; or
- MON already placed into a withdrawal request.
The distinction becomes visible after any epoch-delayed action. Execution bookkeeping can change while the consensus weight is still finishing its scheduled transition.
Pending Change
Pending additions are represented by two windows in getDelegator():
deltaStakewithdeltaEpoch— stake queued for the next applicable epoch;nextDeltaStakewithnextDeltaEpoch— stake that missed the latest boundary and is queued one epoch later.
NodeStake's account page adds deltaStake + nextDeltaStake for the displayed Pending stake, and shows the corresponding effective epoch or epochs.
Pending stake is not the same thing as a pending withdrawal. Once undelegate() creates a withdrawal request, that amount is tracked through getWithdrawalRequest() with its own withdrawId, amount and epoch.
Rewards: accrued, claimed and compounded are three different states
When a validator produces a block, Monad allocates the inflationary block reward and priority fees. Validator commission applies to the inflationary reward; the remainder is distributed to delegators pro rata through an accumulator-based reward system. Priority fees currently go to the validator unless the validator shares value through externalReward().
For a delegator, the relevant read is getDelegator().unclaimedRewards.
Claim
claimRewards(validatorId) settles the delegator's reward state, transfers the claimable MON to the caller, and clears the claimed reward balance. Once the claim transaction succeeds, there is no epoch activation delay: the MON is liquid in the account.
State transition:
unclaimed rewards -> claim transaction -> liquid MONCompound
compound(validatorId) converts the available reward into additional delegation. The reward balance is consumed immediately by the transaction, but the new stake follows the normal activation rule:
unclaimed rewards -> compound transaction -> pending stake -> active at n+1 or n+2This is why claimable rewards can drop to zero while active stake has not increased yet. The value has not disappeared; it has moved into a pending stake window.
Compound is also a one-time protocol action. Pressing Compound once does not authorize perpetual auto-compounding.
A validator-level “Claimable Rewards” value is not your wallet's claim
getValidator().unclaimedRewards is validator pool reward-accounting state. NodeStake's validator detail page can display that validator-level value, while the connected account's claim action uses getDelegator().unclaimedRewards for the specific wallet and validator. They answer different questions and should not be compared as if they were the same balance.
Undelegate and Withdraw form a two-transaction lifecycle
On Monad, “unstake” is not the transaction that returns MON to the wallet.
1. Undelegate creates a withdrawal request
undelegate(validatorId, amount, withdrawId) can remove only activated stake. Pending delegations cannot be undelegated until they become active.
The call creates a withdrawal request identified by the tuple:
(validatorId, delegator address, withdrawId)Each validator-delegator pair can have up to 256 in-flight request IDs (0 through 255), and an ID becomes reusable after its request is withdrawn.
The consensus effect is still scheduled:
- submitted before the boundary: stake becomes inactive in epoch
n + 1; - submitted during the delay: stake becomes inactive in epoch
n + 2.
2. The withdrawal delay starts from deactivation
WITHDRAWAL_DELAY is one epoch, but that does not mean “one epoch after clicking Undelegate” in every case. The amount first reaches its scheduled deactivation epoch, then must satisfy the one-epoch withdrawal delay.
Expressed from submission epoch n:
- before-boundary undelegation: withdrawable in
n + 1 + WITHDRAWAL_DELAY; - delay-period undelegation: withdrawable in
n + 2 + WITHDRAWAL_DELAY.
The request returned by getWithdrawalRequest() includes the withdrawal amount, an accumulator checkpoint and withdrawEpoch. NodeStake shows the request's withdrawal epoch, its available epoch, and a Waiting or Ready state.
3. Withdraw returns the funds
After the request matures, withdraw(validatorId, withdrawId) transfers the withdrawal amount — plus any reward amount resolved for that request — to the delegator and clears the request slot.
The complete lifecycle is:
active stake
-> undelegate transaction confirmed
-> scheduled deactivation
-> withdrawal delay
-> request ready
-> withdraw transaction confirmed
-> liquid wallet balanceAn undelegation receipt, a countdown reaching zero, or a Ready badge is not final proof that funds are back in the wallet. Completion requires a successful Withdraw transaction and a refreshed on-chain balance.
How NodeStake Explorer maps the staking precompile
NodeStake's Monad staking interface is built from precompile reads rather than a single derived “staking balance.” The current field mapping is:
| NodeStake Explorer field | Chain source | Interpretation |
|---|---|---|
| Epoch | getEpoch().epoch | Current consensus epoch reported by the precompile |
| Delay period | getEpoch().inEpochDelayPeriod | Whether the staking boundary has already passed |
| Next Epoch | Historical getEpoch() anchors + observed block pace | Approximate countdown, not an exact on-chain timer |
| Total Stake | Sum of registered validators' execution stake | Latest execution-state stake, so it can lead consensus |
| Active Validators | getConsensusValidatorSet() | Validators in the selected consensus set |
| Validator Total Staked | getValidator().consensusStake | Epoch-effective consensus stake shown on validator detail |
| Voting Power | consensusStake / total consensusStake | Validator share of selected-set voting weight |
| Current Commission | snapshotCommission during delay, otherwise consensusCommission | Commission used for the operational reward view |
| Next-epoch Commission | consensusCommission during delay, otherwise execution commission | Already selected or latest queued rate |
| Account Staked | Sum of getDelegator().stake | Delegator stake across validator positions |
| Account Rewards | Sum of getDelegator().unclaimedRewards | Wallet-specific claimable staking rewards |
| Pending stake | deltaStake + nextDeltaStake | Stake queued for one of the next activation windows |
| Withdrawal amount / epoch | getWithdrawalRequest() | A specific request identified by validator, address and ID |
The withdrawal view also reports its lookup coverage. Withdrawal requests are keyed by ID rather than exposed as one simple account-wide array, so an Explorer must combine known event references with bounded precompile reads instead of pretending that an incomplete scan is complete history.
A reliable way to verify any staking action
For every staking operation, check three separate layers:
- Transaction state — did the transaction succeed, with the intended account, validator, amount and method?
- Scheduled protocol state — which epoch will apply the change, and is the network before or after the boundary?
- Terminal balance or set state — did the delegation become active, did voting power change, did rewards reach the wallet, or did withdrawn MON become liquid?
That framework prevents the most common Monad staking mistake: treating the first visible confirmation as the end of a multi-stage state transition.
Closing read
Monad staking is easier to understand when you stop looking for one authoritative “staked balance.” The protocol intentionally maintains several views because execution can accept changes faster than consensus should apply them.
- Execution tells you what the latest transactions have changed.
- Consensus tells you what has been selected for epoch voting.
- Snapshot preserves the boundary transition and reward continuity.
- Delegator deltas tell you what is waiting to activate.
- Withdrawal requests tell you what has left the staking position but has not yet returned to the wallet.
NodeStake Explorer turns those raw views into familiar fields, but the underlying timing still matters. Read the epoch and delay flag alongside the number, distinguish transaction success from activation, and do not call an undelegation complete until the final Withdraw transaction has returned the MON on-chain.
Technical references
- Monad staking architecture: https://docs.monad.xyz/monad-arch/consensus/staking
- Monad staking overview: https://docs.monad.xyz/reference/staking/overview
- Monad staking API reference and ABI: https://docs.monad.xyz/reference/staking/api
- Monad REVM staking implementation: https://github.com/category-labs/monad-revm
- NodeStake Explorer — Monad staking: https://explorer.nodestake.org/monad/staking
Protocol constants, validator-set behavior and Explorer fields can change. The protocol references and current REVM behavior were last checked on August 28, 2026. This article is educational and is not financial advice.