Abstract
System transactions provide a way for the Stable protocol to emit EVM events for Stable SDK operations. When staking events like unbonding completions occur in the SDK layer, the protocol automatically generates EVM transactions that emit corresponding events, making these operations fully visible to EVM tooling and applications.Motivation
EVM users and applications on Stable expect to monitor blockchain events through standard EVM interfaces likeeth_getLogs. But critical operations happen in Stable SDK modules that don’t naturally emit EVM events. This creates a visibility gap: EVM dapps can’t easily track when a user’s tokens finish unbonding.
System transactions bridge this gap. When the staking module completes an unbonding operation, Stable’s x/stable module detects the event and generates a system transaction that calls the StableSystem precompile ( 0x0000000000000000000000000000000000009999). And then, the precompile emits proper EVM events that any dapp can subscribe to. System transactions run with a special sender address (0x0000000000000000000000000000000000000001) that only the protocol can use. This prevents anyone from spoofing protocol events while keeping the event emission trustless and verifiable on-chain.
Specification
System transactions work through three main components: the x/stable module’s EndBlocker, the PrepareProposal handler, and the StableSystem precompile.Architecture Overview

StableSystem Precompile
The StableSystem precompile lives at0x0000000000000000000000000000000000009999 and handles protocol-level operations that need to emit EVM events. Currently it supports unbonding completion notifications.
System Transaction Sender
System transactions use0x0000000000000000000000000000000000000001 as the sender address. This address:
- Requires no signature verification
- Can only be used by transactions created in PrepareProposal
- Cannot be spoofed by users or contracts
- Skips fee deduction via the SystemTxDecorator ante handler
msg.sender == 0x1. Precompiles can use this to gate protocol-only operations.
Event-Driven Flow
When a user’s unbonding period completes, here’s what happens:- Stable SDK Layer: The staking module’s EndBlocker completes the unbonding and emits EventTypeCompleteUnbonding with the delegator address, validator address, and amount.
- Detection: The x/stable module’s EndBlocker runs after staking and scans for unbonding events in the block’s event log. For each completion, it queues an entry in state with the delegator address, validator address, amount, and block height.
- System TX Generation: In the next block’s PrepareProposal, the app queries all queued completions. If any exist, it creates a system transaction calling StableSystem.notifyUnbondingCompletions(blockHeight) with the current block height. This transaction goes at the front of the block, before any user transactions.
- Execution: During block execution, the system transaction runs first. The precompile queries state for queued completions at that block height, emits an UnbondingCompleted event for each one (up to 100), and deletes them from the queue.
- EVM Visibility: The events appear in transaction receipts and logs, visible to eth_getLogs queries, block explorers, and any application monitoring the StableSystem precompile.
Batch Processing
To prevent blocks from becoming too large, the system processes at most 100 unbonding completions per block. If 150 completions queue up:- Block N: Creates system tx processing completions 0-99
- Block N+1: Creates system tx processing completions 100-149
Usage Examples
The most common use case is a staking dashboard that needs to notify users when their unbonding periods complete. Here’s how to set up a listener for unbonding completions.Filtering Events for Specific Users
To only receive events for a particular delegator address, use the indexed event parameters to create a filter:Querying Historical Events
If your dApp needs to show a history of past unbonding completions, you can query historical events using event filters with block ranges:Integration Guide
Step 1: Add the Stable System Contract Interface
First, add the StableSystem precompile interface to your project. If you’re using Foundry or Hardhat, create a new interface file:Step 2: Set Up Event Listeners
Initialize your ethers.js provider and create a contract instance pointing to the StableSystem precompile address. The precompile is always deployed at0x00000000000....0000009999 on both Stable testnet and mainnet.
Note: The precompile is not deployed on Stable Mainnet yet, it will be provided after v1.2.0 upgrade.
Step 3: Handle Events in Your Application Logic
Subscribe to events and update your application state accordingly. Common patterns include:- Balance Updates: When an unbonding completes, refresh the user’s token balance
- Notification System: Show toast notifications when the user’s unbondings complete
- Dashboard Statistics: Update staking metrics and charts in real-time
- Transaction History: Add completed unbondings to the user’s activity feed
Step 4: Handle Connection Issues
Since event subscriptions rely on persistent websocket connections, implement reconnection logic for production dApps:Why This Approach?
Compared to Custom Indexers
Previously, Stable SDK required dApp developers to run custom indexers that watch for SDK events and store them in a database. This adds operational overhead and introduces potential points of failure. With system transactions, there’s no need for separate indexer infrastructure. Events are natively available through the EVM’s log system, which every RPC node already indexes and serves. Any standard web3 library can subscribe to these events without additional tooling.Compared to Polling SDK Endpoints
Without system transactions, EVM dApps would need to periodically call Stable SDK REST endpoints to check if unbonding periods have completed. This creates several problems:- Increased Latency: Polling intervals of 5-10 seconds mean users might wait that long before seeing updates
- Higher Load: Every dApp instance polling endpoints increases load on RPC infrastructure
- Complexity: dApps need to handle both web3 providers (for EVM interactions) and Stable SDK REST clients (for SDK queries)
- No Real-time Updates: Polling inherently can’t provide instant notifications
Security Guarantees
Trustless Event Emission
System transactions are created during thePrepareProposal ABCI phase, which only validators can execute. User-submitted transactions cannot spoof the system sender address (0x1) because the EVM’s state transition logic enforces that only transactions to the StableSystem precompile address can skip signature verification.
This means:
- Users cannot forge unbonding completion events
- Users cannot call
notifyUnbondingCompletionsfrom their own transactions - The only way to emit an
UnbondingCompletedevent is for an actual unbonding to complete in the Stable SDK staking module
No Additional Trust Assumptions
System transactions don’t introduce new security assumptions beyond what’s already required for blockchain consensus. If you trust that validators are correctly executing blocks, you can trust that system transaction events accurately reflect Stable SDK state changes. The event emission process is deterministic: given the same SDK events inEndBlock, all honest validators will produce identical system transactions during PrepareProposal. The consensus mechanism ensures validators agree on which system transactions to include.
Block Finality
The Stable blockchain uses fast finality through StableBFT’s consensus mechanism. Once a block is committed, it’s immediately final and cannot be reorganized. This means that once you receive anUnbondingCompleted event, you can trust it’s permanent.
There’s no need to wait for multiple confirmations like on probabilistic finality chains. dApps can update user balances and display notifications immediately upon receiving the event.
Performance & Limitations
Batch Size Constraints
Each block processes at most 100 unbonding completions through system transactions. This limit exists to prevent unbounded block sizes during periods of high unbonding activity. In practice, 100 completions per block provides throughput of ~9000 completions per minute assuming the average block time of 0.7 seconds. Normal staking activity rarely reaches this limit. During exceptional circumstances, completions might queue for several blocks before fully processing.Gas Consumption
System transactions consume gas during execution, which is accounted for in the block’s gas limit. The gas cost scales linearly with the number of completions being processed:- Base function call: ~21,000 gas
- Per-event emission: ~3,000 gas
- Reading state: ~2,000 gas per completion
Notification Latency
When an unbonding period completes during block N:- The Stable module’s
EndBlockqueues the completion in block N’s state - Block N+1’s
PrepareProposalcreates a system transaction - The system transaction executes during block N+1, emitting the event
High Load Scenarios
If unbonding completions arrive faster than 100 per block, they accumulate in the queue. The queue is processed in FIFO order, so the oldest completions are always notified first. During sustained high load, the queue could grow temporarily. However, once the spike subsides, subsequent blocks with fewer completions will gradually drain the queue. The system is designed to handle bursts without dropping events.Future Extensions
The system transaction mechanism provides a general pattern for bridging any Stable SDK operation into the EVM event space. While currently used only for unbonding completions, the architecture can be extended to cover additional use cases:Staking Operations
Beyond unbonding, other staking events could emit EVM notifications:- Commission rate changes by validators
- Validator jailing and unjailing

