MonadBFT Finality & Transaction Correlation

MonadBFT Finality Model

Monad uses MonadBFT, a consensus protocol with single-slot finality (~800 ms).

Consensus Stages

              ┌────────────┐
   ┌──────────│  Proposed  │──────────┐
   │          └─────┬──────┘          │
   │                │                 │
   │          ┌─────▼──────┐          │
   │          │   Voted    │          │
   │          │   (QC)     │──────────┤
   │          └─────┬──────┘          │
   │                │                 │
   │          ┌─────▼──────┐          │
   │          │ Finalized  │          │
   │          │ (commit)   │──────────┤
   │          └─────┬──────┘          │
   │                │                 │
   │          ┌─────▼──────┐   ┌─────▼──────┐
   │          │  Verified  │   │  Rejected  │
   │          │ (terminal) │   │ (terminal) │
   │          └────────────┘   └────────────┘
   │                                ▲
   └────────────────────────────────┘
         (rejected from any stage)
StageTrigger eventLatencyGuarantee
ProposedBlockStart~0 msNone — speculative
VotedBlockQC~400 msSpeculative finality (2/3+ validators)
FinalizedBlockFinalized~800 msFull finality — irreversible
VerifiedBlockVerifiedAfter finalizationState root verification (terminal)
RejectedBlockRejectVariesBlock discarded (terminal)

Stage Gating (min_stage)

{"subscribe": {"events": ["TxnLog"], "min_stage": "Finalized"}}
  • Events from blocks below the specified stage are discarded (not buffered for later delivery)
  • Events from blocks at or above the specified stage are delivered immediately
  • Events with commit_stage = null are delivered (fail-open)

Choosing the Right min_stage

min_stageLatencyRiskBest for
"Proposed" (default)~0 msBlock may be rejectedReal-time dashboards, monitoring, speculative UIs
"Voted"~400 msVery unlikely to be rejected (2/3+ validators agreed)DEX trades, semi-critical notifications
"Finalized"~800 msNone — irreversibleBridges, exchanges, payment processors
"Verified">800 msNone — state root verifiedProof verification, cross-chain state proofs

Trade-off: Lower min_stage = lower latency but higher risk of processing events from rejected blocks. Higher min_stage = guaranteed finality but added latency.

The commit_stage Field

Every event carries a commit_stage field: the block's consensus stage at the moment the event was serialized for broadcast (not the block's current live stage). This is a frozen snapshot:

  • Live stream: Almost always "Proposed". Execution events are written immediately at block execution, before any consensus votes
  • Resume/replay from ring buffer: May be "Voted", "Finalized", or even "Verified". The block progressed through consensus while the message sat in the buffer waiting to be replayed
  • null: Block is not in the lifecycle tracker (rare edge case). Delivered as fail-open

Important: To know the current stage of a block, do not rely on commit_stage. Subscribe to Lifecycle events instead.

Pattern: Buffering + Lifecycle

For applications requiring finality (bridge, exchange):

  1. Subscribe to the events you need + Lifecycle
  2. Buffer events by block_number
  3. When Lifecycle confirms to_stage: "Finalized", process the buffer
  4. If to_stage: "Rejected", discard the buffer

Transaction Correlation

With "correlate": true in the extended subscription, if a TxnHeaderStart passes the filters (sender/to/function_selector), the server automatically delivers all subsequent events for that transaction.

How It Works

EventAction
BlockStart / BlockEndReset the correlation set
TxnHeaderStartIf it passes the filter → add txn_idx to the set
Any event with txn_idxIf txn_idx is in the set → deliver (bypassing the normal filter)
TxnEndIf txn_idx is in the set → deliver, remove from set

What Is Auto-Delivered

With correlate enabled, for a matching transaction you will receive:

  • TxnHeaderStart (passed the filter)
  • TxnLog (all transaction logs)
  • TxnCallFrame (all internal calls)
  • TxnEvmOutput (execution result)
  • TxnEnd (completion)

Example: Tracking delegate() on the Staking Contract

{
  "subscribe": {
    "events": ["TxnHeaderStart"],
    "filters": [{
      "event_name": "TxnHeaderStart",
      "field_filters": [
        {"field": "to", "filter": {"values": ["0x0000000000000000000000000000000000001000"]}},
        {"field": "function_selector", "filter": {"values": ["0x84994fec"]}}
      ]
    }],
    "correlate": true
  }
}

Result: the full chain TxnHeaderStart → TxnLog → TxnEvmOutput → TxnEnd for each delegate() call.