# Scheduled Transactions

**URL:** <https://forum.monad.xyz/t/scheduled-transactions/567>\
**Category:** MIPs\
**Created:** [October 22, 2026, 1:35pm UTC](https://forum.monad.xyz/t/scheduled-transactions/567 "2026-10-22T13:35:23Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Andrei](https://yyz1.discourse-cdn.com/flex035/user_avatar/forum.monad.xyz/andrei/32/430_2.png) [@Andrei](https://forum.monad.xyz/u/Andrei)\
**Post date:** [October 22, 2026, 1:35pm UTC](https://forum.monad.xyz/t/scheduled-transactions/567/1 "2026-10-22T13:35:23Z")

</div>

We are excited to share the following proposal for bringing scheduled transactions to the Monad protocol: any account or contract can schedule a transaction for a future block, with the gas paid upfront and execution guaranteed by the protocol. This removes the dependence on off-chain keepers required for many applications to function properly and enables multiple flows that were awkward or impossible before.

Check out our [accompanying blog post](https://www.category.xyz/blogs/scheduled-transactions), working through five prototypical examples in detail:

- On-chain keepers/cron jobs: a contract can schedule its next run without an off-chain daemon.
- Recurring payments: monthly salary payments without needing periodic transactions to be submitted.
- Long-running/asynchronous computations, i.e., that schedule their own continuation, allowing them to span several blocks.
- Escrow timeouts: escrowed swap funds can be made to return to their owner if the counterparty misses the deadline.
- Order-book cleanup: expired orders can be automatically removed at expiry instead of lingering in storage.

_We welcome discussion and feedback from the community on all aspects of the design, with particular focus on:_

- _If you build on Monad: which of your flows depend on a keeper today, and would anything in the design get in your way?_
- _Which of the extensions proposed in [Section 13](#s13) (range scheduling, the best-effort ASAP mode, time-based scheduling) matter most for what you are building?_

# Scheduled Transactions

**Status:** Draft (v1, design and architecture, specification to be finalized)

**Authors:** Andrei Constantinescu, Lioba Heimbach

**Contents**

1. [Abstract](#s1)
2. [Motivation](#s2)
3. [General Design](#s3)
4. [Block Assembly](#s4)
5. [Ordering](#s5)
6. [Admission Rules](#s6)
  - [6.1 Block Capacity Allocation](#s61)

7. [Fee Mechanism](#s7)
  - [7.1 Capacity Fee](#s71)
  - [7.2 Storage Fee](#s72)

8. [Scheduling Interface](#s8)
  - [8.1 Cancellation](#s81)

9. [Ownership Model and Escrowing](#s9)
10. [Storage and Transaction Format](#s10)
11. [Execution Semantics](#s11)
  - [11.1 `ORIGIN`, `CALLER`, and Contracts as Origin](#s111)

12. [Auditing, Tracing and Debugging](#s12)
13. [Extensions](#s13)
14. [Appendix](#s14)
  - [14.1 Possible Precompile Interface](#s141)

## 1. Abstract

We introduce scheduled transactions as a mechanism for EOAs, contracts (and delegated EOAs) to schedule transactions ahead of time, enabling many novel flows in DeFi and beyond. Scheduling happens through a `schedule(target_block, tx)` precompile function. Scheduled transactions are **not** included in the blocks by consensus. Instead, they are stored in the state tree until their due time and patched into the respective block before the block executes. They appear in the final ETH-style blocks reported by RPCs for tracing, debugging and auditing purposes. In terms of block placement and ordering, they are placed at the end of the respective block and ordered in a first-come-first-served fashion. To ensure all blocks maintain dual capacity: for normal transactions and scheduled transactions alike, blocks are split into two lanes: 90% for normal transactions, and 10% for scheduled ones, the latter being released in steps.

## 2. Motivation

A meaningful amount of DeFi depends on off-chain keeper infrastructure to function, because the EVM has no native way to make something happen at a future time. Contracts are purely reactive and only execute when poked: perp DEXes run off-chain processes to update funding rates on a cadence, vaults rely on keepers to harvest and rebalance, recurring payments and order expiry are driven by off-chain executors that users have to trust to show up on time. This infrastructure is a hidden trust assumption and a single point of failure.

Keeper liveness and latency variance pose a meaningful risk to both UX and market health. The result is “hybrid” DeFi, where the applications require more than just contracts to function.

**Proposal:** A protocol-level scheduled transaction primitive. Queue a call to execute a transaction at a specific block height, with execution guaranteed by the Monad protocol and gas prepaid at scheduling time. This primitive moves advanced markets, which usually need to compromise with some centralized infrastructure, closer to being fully on-chain and simplifies infrastructure requirements for builders.

**Applications:** scheduled transactions serve three kinds of flows.

- _On a cadence:_ on-chain keepers for funding rate updates, vault rebalancing and game loops, as well as recurring payments and vesting payouts set up once by the user.
- _At a chosen block:_ settlement of auctions, batch auctions, lotteries and DAO votes, options that exercise at expiry, unstaking that completes on its own, token approvals that expire.
- _In response to an earlier action:_ escrowed funds returned when a counterparty goes silent, order-book cleanup, long-running computations that span across several blocks.

[Our blog post](https://www.category.xyz/blogs/scheduled-transactions) works through several of these in detail. Tell us what matters for what you are building, and whether anything in the design would get in your way.

## 3. General Design

Unlike usual transactions, which get included in blocks by consensus, _scheduled transactions_ are included in blocks as a result of scheduling actions taken by other transactions.

We distinguish:

- Scheduling transactions → that schedule transactions for later blocks;
- Scheduled transactions → that get scheduled as a result.

Main Properties:

- A transaction can be both scheduled and scheduling: scheduled transactions can themselves schedule new transactions.
- A single transaction can schedule several transactions. They do not have to be statically declared and can be constructed dynamically at execution time.
- Scheduled transactions are prepaid for at scheduling time.
- Scheduled transactions are committed to the state tree until their target block comes.
- Scheduled transactions appear in the final blocks reported by RPCs and facilitate auditing and tracing by providing a causal backlink to the transaction that scheduled them.
- A special `schedule()` precompile function is introduced to allow scheduling. It can be called by both EOAs (including delegated ones) and contracts.
- Scheduled transactions use an _ownership_ model where the caller of the `schedule()` function is considered to _own_ the respective scheduled transaction, granting the owner cancellation rights over the transaction. The prepaid gas is debited from the owner’s account balance (which notably can be a smart contract account).
- Scheduled transactions can be cancelled _by their owner_ by calling a `cancel()` precompile function.

**Example flow 1:** Alice wants to schedule transaction `tx` for block 1000. Alice submits a _scheduling_ transaction `tx'` with `<tx, 1000>` in its payload. When `tx'` executes, it attempts to schedule `tx` for block 1000 by calling `schedule(1000, tx)`, debiting Alice’s account to prepay for `tx`. If the scheduling succeeds, `tx` is considered _scheduled_ for block 1000.

**Example flow 2:** Alice submits `tx_1`. When `tx_1` runs, it schedules `tx_2`. When `tx_2` runs, it schedules `tx_3`, and so on.

**Example flow 3:** Alice submits a transaction calling into a contract C. In turn, C calls `schedule(..., tx)` to schedule a transaction `tx`. Here, the owner of `tx` is C (not Alice!), and the prepayment is done from C’s account. Alice cannot cancel `tx` unless she “makes C do it”.

## 4. Block Assembly

Transactions scheduled for block number `n` are appended to the **end** of block `n` before the block starts execution. The transactions become part of the externally-visible block/ledger. Consensus is **not** responsible for including those transactions: they are patched in at the boundary between consensus and execution: either in a translational layer, or as an early step in execution.

## 5. Ordering

Transactions scheduled for block number `n` are ordered in first-come-first-served (FIFO) fashion.

**Example:** Consider two transactions included in the ledger: `tx_1` and `tx_2`, with the former occurring before the latter. Assume:

- `tx_1` schedules `tx_S_1` and `tx_S_2` for block `n` in this order;
- `tx_2` schedules `tx_S_3` for block `n`.

Then, at the end of block `n`, the three scheduled transactions will come in the order `tx_S_1`, `tx_S_2`, `tx_S_3`.

## 6. Admission Rules

When a transaction `tx` in block `n1` attempts to schedule another transaction for block `n2`, the system accepts the scheduling request if the following conditions are met:

- `n1 + MIN_WINDOW <= n2 <= n1 + MAX_WINDOW`;
- Block `n2` has enough remaining capacity for scheduled transactions (see [6.1 Block Capacity Allocation](#s61));
- The caller can cover the scheduling fee and the transaction’s `value` upfront; the fee is charged and the `value` escrowed at scheduling time (see [7. Fee Mechanism](#s7) and [9. Ownership Model and Escrowing](#s9)).
- The transaction’s gas limit covers the intrinsic gas of the scheduled call (21,000 plus the calldata charge, as for any transaction) and does not exceed `MAX_SCHEDULED_GAS`.

**Rationale:**

- `MIN_WINDOW` is in place for several reasons. For one, scheduling for the same block can create client implementation difficulties: new transactions should not be appended to a block on the fly after it started executing. Moreover, scheduling over too small a time horizon incentivizes sophisticated economic behavior. Last but not least, choosing `MIN_WINDOW >= k`, where `k` is Monad’s deferred execution delay, lets the client patch scheduled transactions into block `n` as soon as consensus has produced it, without waiting for the execution results of blocks `n − k + 1` to `n − 1`, enabling pipelining. We propose `MIN_WINDOW = k`.
- `MAX_WINDOW` is in place to navigate protocol upgrades. If a later MIP modifies the semantics of scheduled transactions, orchestrating the migration could become challenging with pending transactions scheduled arbitrarily far in the future. Enforcing this upper bound protects the protocol and users alike. Note that this is not a hard constraint for scheduling recurring jobs: they can be scheduled as a transaction that automatically reschedules itself every time it runs, and this is an expected pattern where the scheduled call’s last action is to schedule its own next run.

### 6.1 Block Capacity Allocation

For a block `n` with gas target `G`, scheduled transactions may reserve at most `C = 0.10 × G` gas. The capacity is released iteratively.

A reservation for `g` gas is accepted only if: `reserved_gas[n] + g <= released_capacity[n]`, where:

- `reserved_gas[n]` is the current amount of gas scheduled for block `n`;
- `released_capacity[n]` is the currently opened-up capacity of the scheduled lane of block `n`. It ranges from `0` to `C`.

`C` is released in `K` equal steps of `S = C / K`, one every `MAX_WINDOW / K` blocks, starting at `n - MAX_WINDOW`. A scheduled transaction carries at most `MAX_SCHEDULED_GAS <= C` gas.

**Rationale:** Releasing the scheduled capacity of a block in steps, rather than all at once, limits griefing. Otherwise an attacker could reserve the entire scheduled lane of a block ahead of time, leaving later transactions no chance to compete. The steps should not be too small either. A reservation for `g` gas fits only if at least `g` of the released capacity is still unreserved. If each step releases less than `g`, an attacker can take up each step as it opens, and a reservation of size `g` never fits. The guarantee is therefore that transactions up to `S` can always compete for each released step. Larger transactions, up to `MAX_SCHEDULED_GAS`, can only be placed where enough released capacity remains unreserved.

## 7. Fee Mechanism

The two lanes (normal and scheduled) use different pricing mechanisms. The normal lane, for example, uses a base-fee controller. Keeping their capacity budgets separate gives that controller a consistent capacity target, independent of scheduled lane utilization. Below we discuss the fee mechanism for the scheduled lane.

Consider a transaction in block `n1` scheduling a transaction comprising `g` units of gas for block `n2`. The total amount paid when scheduling the transaction is: `scheduled_fee = capacity_fee + storage_fee`.

Payment is locked when the reservation is accepted. The capacity fee is charged on reserved `g = gas_limit`, with no refund for unused gas, consistent with Monad’s current fee mechanism for normal transactions.

A fraction `c%` of the `scheduled_fee` is distributed pro rata to all active validators according to their stake and the remaining `(100 − c)%` is burned. The fee is held by the precompile until the transaction fires or is cancelled, to account for possible cancellation refunds (see [8.1 Cancellation](#s81)). The burned share is burned at that point; the validators’ share may, for performance reasons, be distributed in aggregate (e.g. once per epoch).

**Rationale:** the fee should not go to a single proposer because proposers have no optionality as to whether to include scheduled transactions. Instead, the distributed rewards go to all validators to cover for their execution costs.

### 7.1 Capacity Fee

The capacity fee starts from a base rate fixed at booking time, `alpha × base_fee(n1)` per unit of gas (e.g., `alpha = 1.1`), scaled by a multiplier that rises as block `n2`’s scheduled lane fills up. When `x` units of gas are reserved for `n2`, the marginal multiplier is `multiplier(x) = 1 + (M − 1) × x / C`.

**Example:** With `M = 10`, it rises from `1×` when the lane is empty to `10×` when it’s full. Utilization is always measured against the final capacity `C` (not `released_capacity[n2]`) so the multiplier never decreases (apart from cancellations).

A reservation pays the base rate times the multiplier, summed over every unit of capacity it occupies, `[reserved_gas[n2], reserved_gas[n2] + g]`. Since the multiplier is linear, this equals `g` units charged at the multiplier for the midpoint of that range:

`capacity_fee = alpha × base_fee(n1) × g × multiplier(reserved_gas[n2] + g/2)`

Because the fee depends only on which capacity a reservation occupies, splitting it into consecutive smaller reservations made in the same block gives the same total.

**Rationale:** Scheduled capacity gets more expensive as it becomes scarce. A user who needs a slot in a busy block can still get one by paying more. The base rate depends on the base fee at scheduling time to prevent scheduling over short time horizons from being less expensive than submitting normal transactions.

### 7.2 Storage Fee

Storage is charged separately: `storage_fee = storage_price_per_byte × tx_size × (n2 - n1)`

where `storage_price_per_byte` is the price of storing one byte for one block, and `tx_size` is the encoded size of the scheduled transaction in bytes. The fee covers the number of bytes the pending transaction occupies in the state tree and the number of blocks it stays there.

## 8. Scheduling Interface

Scheduling a transaction is done via calling a new precompile `schedule(target_block, tx)` function. This can be called by EOAs (whether normal or delegated) and contracts alike. The function either accepts the scheduling request or reverts (see [6. Admission Rules](#s6) for when a request is rejected):

1. If the scheduling request is accepted, `schedule()` returns a handle for the scheduled transaction, which can be used to call `getStatus(handle)` and `cancel(handle)`;
2. There is also a function `getQuote(target_block, gas_limit, data_size)` to check for availability of scheduled gas at a given block height and get a quote for its price before committing to a `schedule()` call.

The precompile can only be invoked with a plain `CALL`, plus `STATICCALL` for its view functions. In particular, it rejects `DELEGATECALL`s. In the appendix, [Section 14.1](#s141) contains a proposal of the precompile interface.

### 8.1 Cancellation

Consider a transaction `tx` comprising `g` gas that got scheduled for block `n2`. When `cancel()` is called to cancel it in block `n1`, the call succeeds if the following conditions are met:

- `tx` is not too close to its due date: `n2 >= n1 + MIN_WINDOW`;
- The caller of `cancel()` is the owner of `tx` (see [9. Ownership Model and Escrowing](#s9)).

A successful cancellation releases the reserved capacity of block `n2` and refunds the owner the escrowed value and the following costs:

- **Storage cost:** `storage_price_per_byte × tx_size × (n2 - n1)`, i.e., the storage cost for the portion it no longer needs to be stored for.
- **Capacity fee:** `beta × min_base_fee × g × multiplier(g/2)` (e.g., `beta = 0.5`), i.e., a fraction of the capacity fee at the base fee floor and minimum capacity multiplier for the target block.

**Rationale:** providing cancellation refunds ensures good state hygiene: users have an incentive to cancel scheduled transactions even when they would have no effect on the current system state. The capacity fee is only partially refunded, and at the lowest possible rate, for two reasons. First, holding a reservation has value in itself: the owner keeps the option to fire or not until shortly before the target block, and the non-refunded part is the price of that option. Second, refunding at the rate originally paid would let users cancel and rebook the same transaction whenever the price drops; refunding at the floor price and the empty-lane multiplier means rebooking can never come out ahead.

## 9. Ownership Model and Escrowing

Scheduled transactions introduce a special model of transaction _ownership_. Assume a transaction `tx` is successfully scheduled. `tx`’s _owner_ is the address that made the successful `schedule()` call that scheduled it. The owner is responsible for prepaying for gas at scheduling time from its own account balance and holds the rights to cancel the transaction. Moreover, if the `tx` does a `value` transfer, this value is additionally escrowed at scheduling time, and credited back to the owner immediately before the scheduled call runs, outside its call frame, so the owner keeps the value even if the call reverts.

**Example:** Suppose Alice calls C1, which in turn calls C2. If C2 then successfully calls `schedule(target_block, tx)`, contract C2 becomes the owner of `tx`, and the gas prepayment at scheduling time is taken from C2’s balance, with `tx.value` additionally escrowed. It is then only C2 who can, in the future, cancel `tx`. Cancellation returns the escrowed value and part of the costs.

**Rationale:** this ownership model enables smart contracts to schedule or cancel transactions in response to processing calls. Transactions scheduled by smart contracts this way cannot be cancelled by EOAs directly, unless going through the smart contracts that scheduled them.

## 10. Storage and Transaction Format

Successfully scheduled transactions are committed to the state tree (at least) until their time to be included in the block comes. Their representation in the state tree is implementation-defined, generally stored as contract storage at the scheduling precompile’s address. When they are included in a block passed to execution, they materialize as a _new_ [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) _transaction type_ with the following fields:

- `type = 123` (placeholder)
- `origin` (its _owner_: the address that made the corresponding `schedule()` call)
- `to` (non-nullable)
- `value`
- `calldata`
- `gas_limit`
- `causal_link` (id of the transaction that scheduled it, for debugging and auditing purposes)
- `schedule_id` (the handle returned by `schedule()`: sequence number assigned at scheduling time, ensures no hash collisions)

Notably, they have **no signature** (and hence an explicit `origin` instead) and **no nonce**. This is because they need no replay protection: they are created by the system, users cannot send them directly.

The requirement that the `to` field is non-nullable comes from the semantics of the null case: it corresponds to contract creations. We forbid scheduling such creations because scheduled transactions neither carry nor bump a nonce, so the derived address could collide with the owner’s next deployment. Notably, this is not overly restrictive: contract creations can still be scheduled, but only through contracts using the `CREATE` or `CREATE2` opcodes.

Garbage collection of scheduled transactions from the state tree can be delayed after their firing time to make the output of `getStatus()` more informative in on-chain querying contexts, but garbage collection must eventually happen.

## 11. Execution Semantics

Executing a scheduled transaction `tx` is similar to executing a normal transaction, with small changes to the transaction prologue/validation:

- There is no signature, so signature validation is skipped;
- There is no `tx.nonce`, so nonce validation and the nonce bump on `tx.origin` are skipped;
- The gas debit from `tx.origin` is skipped (it already happened at scheduling time). Accordingly, `GASPRICE` returns 0 and the receipt reports `effectiveGasPrice = 0`;
- The escrowed `tx.value` is returned to the owner.

**Rationale on nonces:** This gives better UX when a single EOA wants to use both scheduled transactions and normal transactions. The normal ones require a nonce and perform the nonce bump, while scheduled ones do neither. This keeps the current nonce invariants intact, as nonces only count signed transactions, and scheduled transactions are unsigned.

### 11.1 `ORIGIN`, `CALLER`, and Contracts as Origin

Per [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718#origin-and-caller), every transaction type must present a `CALLER` for the first frame and the same address as `ORIGIN`. Scheduled transactions start executing with `CALLER` = `ORIGIN` = `origin` (the owner that scheduled the transaction), so a transaction scheduled by Alice acts as Alice, and one scheduled by a contract acts as that contract. Contracts that authorize the caller need no change as a result.

Note that `origin` can be a contract. This is new to the EVM: [EIP-3607](https://eips.ethereum.org/EIPS/eip-3607) rejects transactions whose origin has code (with the exception of delegated EOAs as in [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)). EIP-3607 continues to apply to signed transactions, but is not applied to scheduled ones, since its concern (an address collision letting a user bypass the contract code) does not arise: a scheduled transaction with a contract as `origin` was issued by that code.

Contracts that gate on `CALLER == ORIGIN` to exclude programmable callers will see scheduled transactions pass that check. This is not a new exposure: under EIP-7702 a delegated EOA can already call itself and then the gated contract, which sees `CALLER` = `ORIGIN` = the EOA while the EOA’s code is what is calling.

## 12. Auditing, Tracing and Debugging

Scheduled transactions are patched into the execution block and appear in the final executed blocks that RPCs serve. These transactions are part of the `transactions_root`, `receipts_root` and the logs bloom filter, etc. They contain the special `causal_link` unique to their transaction format that allows one to backtrace the reason a certain scheduled transaction was included, useful for debugging and auditing purposes.

## 13. Extensions

- **Range scheduling:** with the proposed design, one can only ask to be scheduled for a specific block height. The scheduling fails if there is no spare capacity at that height. Variants can be added where the caller asks for the earliest or cheapest block in some range `[n1, n2]` where capacity is still available and gets their transaction committed for that block. Alternatively, it could ask for the earliest block where the given amount of gas fits and the price is lower than a given target. These unlock simpler and cheaper retry logic for application developers (i.e., if `n2 - n1` is large enough, scheduling will essentially never fail or be too expensive). It also combats attacks where someone reads a contract’s logic and figures out in which blocks it would try to schedule, buying out the block-space there in advance.
- **Do not escrow value:** Currently, the transaction’s value is escrowed when it is scheduled. A possible extension would leave that value in the owner’s account until execution (or better said, assume the owner acquires it in time for execution, reverting if not). However, scheduled transactions would then interact with Monad’s reserve balance.
- **Possible as-soon-as-possible (ASAP) extension:** Monad could also offer a cheaper, _best-effort_ scheduling mode: “execute this transaction as soon as there is spare capacity within the range `[n1, n2]`.” Normal transactions and _guaranteed_ scheduled transactions would take priority. If no suitable capacity becomes available before the deadline, the transaction would expire without execution. This helps the Monad protocol leverage spare capacity to do useful work and lets users with non-critical tasks benefit from it at a discount. Pricing, queue admission and block-patching decisions would need a separate design.
- **Possible time-based extension:** The proposed design allows scheduling for a specific block height, while time is the prevailing notion in Web2. One would be tempted to measure time in blocks: every block is `300ms`, but this does not account for variance in block-time. In the unlikely event of a stall, transactions could still execute late, even if the stall has been long recovered from. Addressing these limitations requires extending the design to allow the target for scheduling to be a time `T` instead of a block-height `h`. The scheduled transaction will fire at or after time `T`. Under good network conditions, this will be soon after `T`, while during adverse network conditions the system will manage the backlog and prioritize draining it after returning to normal operation. Capacity management and block-patching need adjusting to become queue-based, accounting for scheduled transactions no longer being bound to a block height ahead of time.

## 14. Appendix

### 14.1 Possible Precompile Interface

The scheduling interface is exposed as a _precompile_ (i.e., to be thought of as a contract living at a fixed address, but whose implementation is provided by the protocol).

```auto
interface IScheduler {
    /// Schedule a call of `to` with `data` to run at block `target_block`.
    /// The CALLER becomes the owner: sole canceller, and the CALLER == ORIGIN
    /// of the eventual execution.
    ///
    /// msg.value must cover the scheduling fee plus `value`:
    /// - the fee (capacity fee + storage fee, see 7. Fee Mechanism) is held by
    /// the precompile until the transaction fires or is cancelled;
    /// - `value` is escrowed and credited back to the owner right before
    /// the scheduled call runs, so the call cannot fail for lack of funds;
    /// - any remainder is refunded to the caller immediately.
    /// Reverts if msg.value is insufficient, `target_block` is outside
    /// [current + MIN_WINDOW, current + MAX_WINDOW], the block's released
    /// scheduled capacity cannot fit `gas_limit` or `gas_limit` exceeds 
    /// MAX_SCHEDULED_GAS.
    /// On success the call is guaranteed to run at `target_block` and `handle`
    /// is returned; the same number is carried by the scheduled transaction
    /// as its `schedule_id`.
    function schedule(
        uint64 target_block,
        address to,
        uint256 value,
        uint64 gas_limit,
        bytes calldata data
    ) external payable returns (uint64 handle);

    /// Cancel a scheduled transaction. Owner-only; allowed while
    /// target_block >= current + MIN_WINDOW. Frees the reserved capacity,
    /// returns the escrowed `value` and refunds the unused portion of the
    /// storage fee plus a fraction of the capacity fee (see 8.1 Cancellation).
    /// Reverts if the caller is not the owner, the entry does not exist, or
    /// the cancellation window has closed.
    function cancel(uint64 handle) external;

    // ── Reads ────────────────────────────────────────────────────────────

    /// Scheduling lifecycle of an entry. Whether a fired transaction succeeded
    /// or reverted is not exposed on-chain: it is read off-chain from its receipt
    /// (e.g., via RPC), which it has like any other transaction.
    enum Status {
        Unknown, // never existed, or no longer retained
        Pending, // reserved for its target block, not yet fired or cancelled
        Cancelled, // cancelled by the owner before firing
        Completed // fired at its target block (success or revert alike)
    }

    /// Lifecycle state of `handle`. Entries may be reclaimed some time after
    /// firing or cancellation, after which they read `Unknown`.
    function getStatus(uint64 handle) external view returns (Status);

    /// Feasibility and price of scheduling a call of `gas_limit` gas and
    /// `data_size` calldata bytes at `target_block`, as of the current block.
    /// `available` is false when `target_block` is outside the scheduling
    /// window or the block's released scheduled capacity cannot fit
    /// `gas_limit`; `cost` is then zero. Otherwise `cost` is the fee
    /// `schedule()` would charge right now (capacity fee at the lane's
    /// current fill level plus storage fee), excluding `value`.
    function getQuote(uint64 target_block, uint64 gas_limit, uint32 data_size)
        external view returns (bool available, uint256 cost);

    // ── Parameters ───────────────────────────────────────────────────────

    /// Minimum distance, in blocks, from the current block to `target_block`.
    /// `schedule()` requires target_block >= current + MIN_WINDOW, and
    /// `cancel()` is allowed only while target_block >= current + MIN_WINDOW.
    function getMinWindow() external view returns (uint64);

    /// Maximum distance, in blocks, from the current block to `target_block`.
    /// `schedule()` requires target_block <= current + MAX_WINDOW.
    function getMaxWindow() external view returns (uint64);

    /// Maximum `gas_limit` a single scheduled transaction may carry.
    function getMaxScheduledGas() external view returns (uint64);

    // ── Events ───────────────────────────────────────────────────────────

    /// Emitted by the precompile in the receipt of the scheduling transaction.
    event Scheduled(
        uint64 indexed handle,
        address indexed owner,
        uint64 indexed target_block,
        address to,
        uint256 value,
        uint64 gas_limit
    );

    /// Emitted by the precompile in the receipt of the cancelling transaction.
    event Cancelled(uint64 indexed handle, address indexed owner);
}

```

---

<div class="post-metadata">

**Author:** ![DanielVF](https://yyz1.discourse-cdn.com/flex035/user_avatar/forum.monad.xyz/danielvf/32/248_2.png) [@DanielVF](https://forum.monad.xyz/u/DanielVF)\
**Post date:** [October 22, 2026, 6:27pm UTC](https://forum.monad.xyz/t/scheduled-transactions/567/2 "2026-10-22T18:27:29Z")

</div>

First read through: I love it! Thank you! This would have been extremely useful when I was building smart contracts. The gas pricing avoids all the pitfalls I know about so far.

I’d like to make sure the escrow / coming out of escrow follows EIP-7708 (eth transfer logs), since I think that’s going into Monad.

From a protocol POV, time flexible transactions make a lot of sense. Let me just specify a “do not run before” time, and then Monad drop the transaction into a block on or after that start time, any time there’s spare capacity of either scheduled or normal transactions, as long as it’s under the target gas limit. There would be one FIFO queue for these time flexible transactions, and they drop onto the end the queue when their block comes up.
