> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metalntwx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduler

> Scheduler parameters, escrow accounting, execution outcomes, and encrypted payload formats

The `Scheduler` system contract at `0x00000000000000000000000000000000000000fe`
runs calls at a future slot (a block timestamp in seconds), a block height, or
after an oracle condition is met. The account that calls `schedule`, named
`payer` in the API, deposits the gas token into escrow to fund execution.

Every block, the proposer derives the due entries from the scheduler's
storage and prepends one system transaction per entry. Every node re-derives
the same set and rejects a block whose leading transactions differ. Each run
produces a transaction receipt and logs. These system transactions have sender
`0xFE` and type byte `0x7e`. Only block execution creates them.

Entries support these options:

* **Open** entries carry their calls in plaintext.
* **[Sealed](https://docs.metalntwx.com/reference/contracts-and-automation/scheduler.md#encrypted-payloads)** entries keep their calls
  encrypted until the corresponding
  [beacon](https://docs.metalntwx.com/build/data-and-signatures/read-onchain-randomness.md) key becomes available.
* **Conditional** entries become eligible after an
  [oracle](https://docs.metalntwx.com/build/data-and-signatures/use-oracle-data.md) write satisfies their condition. Their
  calls can be open or sealed.

## Policy

The network sets the policy when it installs the scheduler. Each field has a
public getter:

| Getter | Meaning |
| - | - |
| `gasPerBlock` | Gas budget for scheduled entries per block, measured by `cost` (below) |
| `guaranteedGasPerSlot` | Gas the Guaranteed class may reserve per slot or height, measured by `cost` |
| `maxHorizonSlots` | Furthest slot or height ahead of the block an entry may target, and the longest recurrence period. A duration of 400 days is 34,560,000 slots |
| `maxLateSlots` | How many slots after its scheduled slot an entry may run. `lateExecute` permits execution beyond this deadline |
| `cancelRefundBps` | Share of the reserved amount a cancel returns. The rest goes to the treasury's escrow |
| `maxEntriesPerBlock` | Maximum entries queued at one slot or height. Blocks stop selecting BestEffort entries after reaching this count, including selected Guaranteed entries. Guaranteed entries remain subject to the gas budget |
| `treasury` | Escrow account credited with the non-refunded share of cancels |
| `minGasPrice` | Lowest `gasPrice` `schedule` accepts, in gas-token base units per unit of gas |
| `gasToken` | The token escrow is held in |

Once the scheduler activates, a block's timestamp may be at most 2 seconds ahead
of a validator's clock and 60 seconds behind it.

## Scheduling an entry

1. `approve(0xFE, amount)` on the scheduler's gas token, then `deposit(amount)`.
2. If the entry uses a token reserve, `approve(0xFE, reserveAmount)` on
   `reserveToken` before scheduling it.
3. Call `schedule(Params)`. It reserves `cost * gasPrice` from the available
   escrow balance, places any token reserve on hold, and returns the entry ID.

```solidity theme={null}
struct Call { address target; uint64 gasLimit; bytes data; }
struct Dependency { uint64 id; bool finished; }
struct Condition { bytes32 item; uint8 kind; uint256 operand; }

struct Params {
    Call[] calls;
    uint64 slot;
    uint64 height;
    uint64 gasLimit;
    uint64 gasPrice;
    uint8 class;          // 0 Guaranteed, 1 BestEffort
    bool lateExecute;
    uint8 maxRetries;
    uint32 backoffSlots;
    uint64 periodSlots;
    uint32 remaining;
    bool pauseOnFail;
    address reserveToken;
    uint96 reserveAmount;
    Dependency[] dependsOn;
    bytes ciphertext;     // sealed entries only
    bytes32 commitment;   // sealed entries only
    Condition when;       // conditional entries only
}
```

* `calls`: one to eight `(target, gasLimit, data)` calls, run in order and
  atomically. If any call fails, every earlier call reverts. No target may
  be the zero address, the scheduler, the gas token, or a MIP20 token. The ABI-encoded
  calls may take at most 4096 bytes.
* `slot` (strictly after the current block's timestamp) or `height`
  (strictly after the current block number): exactly one of them, within
  the horizon. A height trigger becomes due at that height and runs when its
  dependencies and the block's capacity permit. Height triggers have no lateness
  deadline and cannot recur.
* `gasLimit`: the gas limit for executing the calls. It covers each call's own
  limit, the overhead of making each call, and capturing any reserve, including
  receive policy screening and possible quarantine writes.
* `gasPrice`: at least `minGasPrice`.
* `class`: `0` Guaranteed or `1` BestEffort.
* `lateExecute`: past `slot + maxLateSlots`, run anyway (catch up after a
  halt) instead of expiring.
* `maxRetries`, `backoffSlots`: retries after a failed run.
* `periodSlots`, `remaining`, `pauseOnFail`: a recurrence.
* `reserveToken`, `reserveAmount`: an optional reserve. Use both zero for none.
* `dependsOn`: up to eight earlier entries, each `(id, finished)`. The
  dependency must have succeeded, or with `finished` merely ended (a paused
  series counts as ended). A recurring entry cannot have dependencies.

### Cost

An entry is budgeted at `cost(gasLimit, encodedCalls.length, dependencies,
recurring, reserved)`:

| Component | Gas |
| - | - |
| The entry's `gasLimit` | as given |
| The `execute` call | 160,000 |
| Encoded calls | 6,000 per 32-byte word |
| Each dependency | 5,000 |
| A recurrence | 160,000, plus 22,100 per word of encoded calls |
| A reserve | 80,000 |

`cost` must fit `gasPerBlock`. The amount reserved for execution is
`cost * gasPrice`. A run pays this amount in full to the block's proposer,
regardless of the gas the target calls use.

### Capacity

* `schedule` rejects a slot or height that already holds
  `maxEntriesPerBlock` entries.
* A Guaranteed entry reserves its `cost` from the slot's or height's
  `guaranteedGasPerSlot` budget. `schedule` rejects the entry if the remaining
  budget is too small. Each period of a Guaranteed series tries to reserve
  again. A recurrence whose next slot lacks Guaranteed capacity runs that
  period as BestEffort.
* A retry, a recurrence, or a moved entry spills to the first of the next 8
  slots with room. When none has room, the retry fails or the recurrence
  starves.
* A Scheduled entry holds its place in the queue and its Guaranteed capacity
  until it runs, is canceled, or expires. Holding that place locks
  `cost * gasPrice` of escrow at the entry's own gas price, which is at least
  `minGasPrice`.

### Reserves

A reserve holds the payment amount until the entry executes. `reserveToken`
must be a MIP20 token other than the scheduler's gas token. This keeps the hold
from locking the gas-token balance needed to pay fees for `cancel` or `withdraw`.

* `schedule` holds `reserveAmount` out of the `payer`'s spendable balance
  (`MIP20.holdFrom`).
* The scheduler captures the hold into the first call's target, then executes the
  calls atomically. A plain payment is one call with `target` set to the recipient
  and empty `data`.
* A failed run reverts the capture, so the hold stays through retries. A
  final failure, `cancel`, or `expire` releases it in full.
* A recurrence holds the next period's amount when it re-enqueues, so
  approve enough for every period. If the hold fails, the recurrence starves
  and its reserved execution fee returns to the available escrow balance.

Capture forces a refused credit into [ReceivePolicy quarantine](https://docs.metalntwx.com/reference/compliance/receive-policy.md),
even when the target selected `REVERT`. If the subsequent calls succeed, the
entry becomes `Executed` and consumes its reserve while the intended recipient
remains unpaid. Verify the reserve token's exact `Transfer` from the `payer` to
the target before recording payment. For quarantine, save the matching
quarantine receipt and transfer from the `payer` to ReceivePolicy. Record the
reserve as consumed and resolve recovery before authorizing another payment.
A later target revert rolls back both the capture and its quarantine receipt.

## When an entry runs

An entry becomes due when the block's timestamp reaches `slot` or its number
reaches `height`. Execution also depends on available capacity, completed
dependencies and, for a sealed entry, its decryption key.

A block's budget goes to due Guaranteed entries before BestEffort ones, and
to BestEffort entries in descending `gasPrice` order. All selected entries must
fit the gas budget. Guaranteed entries count toward `maxEntriesPerBlock` when
selecting BestEffort entries, but that count does not limit Guaranteed entries.
An entry waits when it exceeds the remaining budget or has a dependency still
waiting to execute.

A waiting BestEffort entry or an entry delayed by a dependency moves to the
first of the next 8 slots or heights with room, keeping its deadline. This lets
the queue cursor advance past its original position. A Guaranteed entry delayed
by a dependency becomes BestEffort when it moves, as a retry does. These entries
keep their position:

* a sealed entry, which waits for its decryption key.
* a Guaranteed entry waiting for budget.
* an entry that finds no room.

Entries move in id order, so a dependent never lands before its dependency
in the same queue. One that comes due before its dependency is held back
again.

The chosen entries run height entries before slot entries, then by key, then
Guaranteed before BestEffort, then by id, except that an entry follows any
dependency chosen for the same block.

The Guaranteed class reserves capacity for its slot or height. After a gap in
block production, skipped slots' Guaranteed entries also compete for the next
block's gas budget and run oldest first.

Each target sees `msg.sender == 0xFE` and must check the authorization supplied
in the calldata. The scheduler marks an entry `Executed` before making its
calls, which prevents a target from canceling, expiring or rerunning that entry
during execution.

## Failures, retries, and recurrence

A reverting call fails its run. An unfinished dependency delays the entry until
the dependency finishes. A failed entry can retry up to `maxRetries` times as a
time-triggered BestEffort entry. Each retry reserves another execution fee from
the available escrow balance and targets `now + backoffSlots`, or the next
slot when `backoffSlots` is zero. A dependency that was required to succeed but
failed, was canceled, expired or paused causes the entry to fail immediately.

A recurrence schedules another period after the previous one succeeds or ends
in failure. Its timing is calculated from the previous due slot. The series
stops when `remaining` reaches zero. It emits `RecurrenceStarved` when it cannot
fund the execution fee or token reserve for another period, or find room in
the 8 slots starting at its due slot.

With `pauseOnFail`, a failed period pauses the series (`Paused`) until the
`payer` calls `resume(id)` or `cancel(id)`. Resuming requires enough funds for
the execution fee and token reserve, plus capacity in one of those 8 slots.
If these requirements are unmet, `resume` reverts and the series stays paused.

## Cancelling, expiring, and withdrawing

Before execution, the `payer` can call `cancel(id)` to recover
`cancelRefundBps` of the reserved execution fee. Anyone can call `expire(id)`
for a full refund when an entry meets one of these conditions:

* It has passed its deadline with `lateExecute` disabled.
* A queue cursor (`cursorSlot` or `cursorHeight`) has passed its key while
  the entry remains `Scheduled`.
* It is a conditional entry whose item has left the oracle catalog.

A block advances the cursors before executing its selected entries. If an
entry remains `Scheduled` after its system transaction reverts, `expire`
recovers its reserved amount, as described in
[What a run looks like](#what-a-run-looks-like). Calling `expire` during a
scheduled entry's execution reverts with `Running`.

Cancellation and expiry release the token reserve in full and free the entry's
queue position and Guaranteed capacity. Expiry also removes conditional entries
from the watch list. To recover available escrow funds, the `payer` calls
`withdraw(amount)`.

For a withdrawal, verify a finalized `Transfer(scheduler, payer, amount)` from
the gas token before recording that the `payer` received the tokens. If its
receive policy refuses the credit in `REVERT` mode, withdrawal reverts and
preserves the available escrow balance. In `QUARANTINE` mode, withdrawal
succeeds and emits `Withdrawn`, while the funds go to ReceivePolicy. Save the
full quarantine receipt and follow the
[recovery procedure](https://docs.metalntwx.com/reference/compliance/receive-policy.md#claiming).

## Conditional entries

A conditional entry waits for an oracle write that satisfies its condition.
Set `when = (item, kind, operand)` and set `slot` to the earliest timestamp at
which a write may trigger it. `item` is the catalog item's ID,
`keccak256(name)`. The item must be listed in the oracle catalog and have at
least one recorded write, or scheduling reverts with `BadCondition`.

| `kind` | Fires on a write whose |
| - | - |
| `1` `WHEN_VALUE_AT_LEAST` | value is at least `operand`, the item neither disputed nor halted |
| `2` `WHEN_VALUE_AT_MOST` | value is at most `operand`, the item neither disputed nor halted |
| `3` `WHEN_NOT_HALTED` | item is not halted |

* The entry watches its item (`watchersOf(item)`, at most 256 per item)
  under the key `WATCH_KEY` (bit 62). It is never due and never expires
  while it watches, unless its item leaves the catalog.
* Each block evaluates the watchers of every item written since they were
  last evaluated, normally the items the parent block wrote.
  `watchedHeight(item)` is the height of the write they last saw.
* An entry fires when its predicate holds on the item's latest state and the
  write's timestamp reaches the entry's not-before slot. It moves into the
  first of the next 8 slots with room and follows the rules for time-triggered
  entries, including deadlines, retries and dependencies. If all 8 slots are
  full, it keeps watching until the item's next write, including a heartbeat.
* If the value already satisfies the predicate when the entry is scheduled,
  the next qualifying write triggers it.
* A halt or resume through a control item changes the item's flags.
  `WHEN_NOT_HALTED` fires at the first write after a resume.
* Conditional entries are BestEffort only, with no height trigger or
  recurrence.
* An entry leaves the watch list when it fires, is canceled, or expires.
* Each block evaluates at most 1024 watchers, apart from the budget for due
  entries, starting from an item that rotates with the block number. It
  reads only an entry's predicate (a few words, never its calls). An item
  past that budget is evaluated in a later block on its latest write then,
  so a value that moved back across the threshold in between does not fire
  it. Filling an item's 256 places, or enough items' places to spend the
  budget, takes that many entries locked at `minGasPrice`.

## Call authority

A call carries `0xFE`'s authority over its escrow and the holds and allowances
granted by accounts that call `schedule`. The scheduler therefore rejects
MIP20 tokens as call targets, including targets that become MIP20 tokens after
scheduling. To relay an ERC-3009 authorization, target a relayer contract that
submits it.

## What a run looks like

The system transaction's hash is:

```text theme={null}
keccak256(0x7e || rlp(chainId, id, attempt, payer, target, gasLimit, gasPrice, input))
```

The attempt counter gives each retry a distinct hash. `gasLimit` is the entry's
budgeted `cost`, so the reserved amount is the transaction's
`gasLimit * gasPrice`.

RPC presents the transaction using EIP-1559 fields, with `0xFE` as sender, the
attempt as nonce and a zero signature. A single-call entry shows the target
address and its data. A batch shows `0xFE` as the target and the ABI-encoded
calls as data. During execution, the system calls `execute(id)` on the
scheduler, which makes the target calls and records their outcome. The
transaction receipt's status describes execution of `execute(id)`.

A `0x7e` receipt has one of three outcomes:

* **Success.** `execute(id)` ran and paid the reserved amount to the proposer.
  Read its logs from `0xFE` to determine whether the target calls succeeded:
  `Executed(id, attempt)` records success, while
  `ExecutionFailed(id, attempt, willRetry)` records a failed call or dependency.
  Read `status(id)` for the entry's resulting state.
* **Reverted, with gas used.** `execute(id)` reverted, so the execution fee
  remains in escrow. The entry stays `Scheduled` behind the queue cursors and
  is excluded from future execution. Dependencies treat it as expired, and
  `expire(id)` returns its whole reserved amount.
* **Reverted, with zero gas used.** The system skipped `execute(id)` and charged
  no fee. Either an earlier target call canceled the entry, applying its
  cancellation refund, or escrow could not cover the execution fee. In the
  latter case, the entry stays `Scheduled` and can be expired.

Tracing (`debug_trace*`) replays a scheduled transaction as the same system
call block execution made, from `0xFE` to itself. Pending-block RPC
(`"pending"`) does not include due entries.

## Halts and catch-up

* Entries past their deadline run only with `lateExecute`. The rest wait for
  `expire`.
* Catch-up after a halt scans at most 4096 slots (and 4096 heights) and
  reads at most 1024 queued entries per block, heights first, then slots,
  each oldest first. Each read costs a few words of the entry's fixed
  fields. The calls are read, and decrypted when sealed, only for entries
  selected for execution.
* The cursors (`cursorSlot`, `cursorHeight`) mark the first key still
  holding a waiting or unread entry. Each block clears the queues and
  Guaranteed reserves of the keys the cursors pass. When the scheduler
  activates on a running chain, the cursors start at the activation block's
  next slot and height.
* Scheduled runs count against the block gas limit like any transaction.

## Interface

A finished entry (Executed, Failed, Cancelled, Expired) is pruned to its
first word: `status(id)` and the first fields of `entry(id)` stay readable,
and dependents read its status. `status` is `0` None, `1` Scheduled, `2`
Executed, `3` Failed, `4` Cancelled, `5` Expired, `6` Paused.

Transactions: `deposit`, `withdraw`, `schedule`, `cancel`, `expire`,
`resume`.

Reads: `status(id)`, `entry(id)`, `queueAt(key)`, `reservedGas(key)`,
`balance(payer)`, `cost(...)`, `nextId()`, `cursorSlot()`,
`cursorHeight()`, `watchersOf(item)`, `watchedHeight(item)`,
`sealedPending(slot)`, and the policy getters. A height's queue key is the
height with bit 63 (`HEIGHT_KEY`) set.

Events: `Scheduled`, `Executed`, `ExecutionFailed`, `Recurred`,
`RecurrenceStarved`, `Paused`, `Cancelled`, `Expired`, `Deposited`,
`Withdrawn`.

Errors: `NotChain`, `NotPayer`, `NotScheduled`, `NotPaused`, `NotDue`,
`NotExpired`, `BadTarget`, `BadCalls`, `BadTrigger`, `BadDependency`,
`SlotNotInFuture`, `BeyondHorizon`, `GasLimitOutOfRange`, `ZeroGasPrice`,
`GasPriceTooLow`, `DataTooLarge`, `BadClass`, `SlotReserveFull`,
`SlotFull`, `InsufficientEscrow`, `TransferFailed`, `BadReserve`,
`ReserveIsGasToken`, `CallFailed`, `CannotRecur`, `BadSeal`,
`BadCondition`, `WatchFull`, `Running`.

## Encrypted payloads

Seal the calls as follows, with `group_key = Beacon.groupKey()` at `0xFF`
and `payer` the account that will call `schedule`:

1. `calls` is `abi.encode(Call[])`, as the Scheduler stores an open entry's
   calls, at most 4092 bytes.
2. Pad it to a size class: `body = u32_be(len(calls)) || calls`, then zeros
   up to the next power of two, at least 256 and at most 4096 bytes.
3. Draw a random 32-byte `salt`. The commitment is
   `keccak256(salt || body)`.
4. Draw a random 32-byte data key and
   [timelock-encrypt](https://docs.metalntwx.com/reference/contracts-and-automation/scheduler.md#timelock-key-encoding) it
   to the identity `u64_be(slot)`: 112 bytes.
5. Draw a random 12-byte nonce and encrypt `salt || body` under the data
   key with ChaCha20-Poly1305 (RFC 8439), with associated data
   `u64_be(slot) || payer` (8 + 20 bytes). Append the 16-byte tag to the
   encrypted bytes.

The ciphertext is:

```text theme={null}
timelock(data_key) (112) || nonce (12) || encrypted(salt || body) (32 + len(body)) || tag (16)
```

The ciphertext is the body plus 172 bytes and reveals its size class. Pass it
to `schedule` in `ciphertext` and the commitment in `commitment`, with
`calls` empty. The salt keeps the commitment from confirming a guess at the
calls. `schedule` accepts a ciphertext longer than 172 and at most
4268 bytes. An entry that does not open to a well-formed body fails when the
entry runs.

Every entry at a slot opens with the same key. The associated data also binds
the payload to `payer`, so successful decryption requires the scheduling
account's address to match the address used during encryption.

## Rules

* A sealed entry requires a slot trigger and an installed beacon group key.
  It supports at most 4092 bytes of calls, with recurrence and token reserves
  disabled. A sealed conditional entry uses the slot as its not-before time.
* Everything else (gas, class, retries, dependencies) works as for an open
  entry. The reserved amount is priced on the ciphertext's length, padding included.
* A sealed entry keeps its queue position while waiting for its slot or
  condition key.

## What stays hidden

The `payer`, slot, gas limit and price, class, dependencies and size class are
public before execution. Targets, data and the calls' exact length stay hidden
until the decryption key becomes available.

* A sealed entry pays through its calls, for example by submitting a signed
  ERC-3009 transfer through a relayer contract. The spending account must keep
  enough funds available until execution.
* A gas limit rounded up, to a power of two for example, hides how much work
  the calls do.

## Opening

`sealedPending(slot)` counts a slot's unrevealed entries. Proposers prioritize
beacon keys for slots with pending entries for 30 seconds after each slot ends
(`GRACE_SLOTS`). Block production can continue while a key remains unavailable,
and omitting a key does not make a block invalid.

The block that carries the slot's key applies its beacon update first. It
decrypts the calls, checks the commitment and stores the revealed calls before
executing the entry as a system transaction. If the entry exceeds that block's
remaining budget, it runs in a later block. Anyone can decrypt its calls once
the key is onchain.

An entry fails without retry if decryption, commitment verification or decoding
fails, or if it exceeds eight calls or names a forbidden target. A payload
encrypted under a group key the beacon no longer holds is revealed as empty
and fails, consuming its reserved amount.

A queued entry still sealed at `block.timestamp > entry.key + 1 + 30` has
missed its decryption deadline. `expire(id)` refunds its reserved amount in
full, including when `lateExecute` is set. Here `entry.key` is the queued slot,
which ends at `entry.key + 1`, followed by a 30-second grace period. Conditional
entries enter this grace period once they leave the watch list for a slot.

> [!WARNING]
>
> A validator recovers the key from its peers' partials shortly before a
> block carries it. A proposer that leaves an available key out of its block
> can read the calls and order its own transactions ahead of them in that
> block. The next honest proposer carries the key.

## Sealed conditional entries

A [conditional entry](https://docs.metalntwx.com/build/contracts/schedule-a-contract-call.md#add-an-oracle-condition) can
also be sealed: its calls stay hidden until the condition holds. Seal them
to the condition instead of the slot:

```text theme={null}
condition = keccak256("metal-condition-v1" || payer (20) || item (32) || kind (1)
                      || uint256_be(operand) || u64_be(slot))
```

`payer` is the account that calls `schedule`, and `slot` is the entry's
not-before slot. Seal as for a slot, with two changes: the data key is encrypted
to the identity `condition` (32 bytes), and the associated data is `condition`.
Pass the ciphertext and commitment as for a sealed entry, together with
`when`.

* The validators' signers release a partial signature over `condition` only
  for a finalized block whose state, proven against the block's state root,
  holds an oracle write of `item` that satisfies the predicate and is no
  older than the not-before slot: the same rule that fires the entry. Nodes
  ask for it only once a block has fired the entry, proving the state its
  watchers were evaluated on, so an entry left watching (no room, or past
  the watcher budget) keeps its calls hidden.
* The required number of validator partial signatures reconstructs the
  condition's key. A proposer carries it in the block's beacon update, which
  accepts at most 32 condition keys per block. After inclusion,
  `Beacon.conditionKey(condition)` returns the key.
* The entry fires as an open conditional entry does, then waits for the
  condition's key while holding its slot's cursor. Once the key is included,
  the entry can open and run subject to the block's capacity. It expires with
  a full refund if it is still sealed 30 seconds after the end of the slot it
  fired into.
* A `payer`'s entries that share a condition share its key. Once one fires
  and its key lands, every payload that `payer` sealed to that condition
  opens, including one still watching for room. Use different not-before slots
  when entries need independent disclosure times. The condition includes
  `payer`, so each scheduling account uses a distinct key. Once that key is
  public, anyone can decrypt the calls.
* `sealedPending` counts slot-sealed entries only. Sealed conditional entries
  use their condition keys and have no key-inclusion requirement for block
  validity.

## Timelock key encoding

Encryption to a slot is Boneh-Franklin timelock encryption on BLS12-381
with the Fujisaki-Okamoto transform, public keys in G1 and signatures in
G2. It encrypts a 32-byte message `M` to an identity `id` under the group
key `PK` (`groupKey()`). A slot's identity is `u64_be(slot)`. A sealed
conditional entry's is its 32-byte condition.

1. `Q = hash_to_G2(varint(len(ns)) || ns || id)` with the RFC 9380 DST
   `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_`, where
   `ns = "metal-beacon-v1" || uint256_be(chain_id) || genesis_hash` and
   `varint` is unsigned LEB128. `keyAt(slot)` is the group's BLS signature
   on `Q`.
2. Draw a random 32-byte `sigma`.
3. `r` is the 48 bytes of `expand_message_xmd(SHA-256, sigma || M,
   "TLE_BLS12381_XMD:SHA-256_SSWU_RO_H3_")`, read big-endian and reduced
   modulo the group order.
4. `U = r * G1` (48-byte compressed).
5. `g = e(r * PK, Q)`, serialized as 576 big-endian bytes in blst's
   `blst_bendian_from_fp12` layout.
6. `V = sigma XOR sha256("h2" || g)` and `W = M XOR sha256("h4" || sigma)`.

The ciphertext is `U || V || W`, 112 bytes. With the key `K`, decryption
computes `g = e(U, K)`, recovers `sigma` from `V` and `M` from `W`, and
accepts only when `U` equals `r * G1` recomputed from them.

Decryption requires the corresponding slot or condition key.

## Read entry state

Use this interface to diagnose a pending entry at a chosen block. A finished
entry is pruned, so save its parameters and relevant state before completion.

```typescript theme={null}
import { parseAbi } from 'viem'

const schedulerEntryAbi = parseAbi([
  'struct Entry { address payer; uint64 key; uint8 status; uint8 attempts; uint8 maxRetries; uint8 flags; uint64 gasLimit; uint64 gasPrice; uint64 deadlineSlot; uint64 periodSlots; address reserveToken; uint96 reserveAmount; uint32 remaining; uint32 backoffSlots; uint8 dependsOnFinished; uint64 dueSlot; uint32 dataLen; uint8 whenKind; bytes calls; uint64[] dependsOn; bytes32 commitment; bytes32 whenItem; uint256 whenOperand; }',
  'function entry(uint64 id) view returns (Entry)',
  'function cursorSlot() view returns (uint64)',
  'function cursorHeight() view returns (uint64)',
  'function watchersOf(bytes32 item) view returns (uint64[])',
])
```

`key === (1n << 62n)` means the entry is still waiting for an oracle condition.
A height queue key has bit 63 set. Other keys are queued timestamps. For a
fired condition, `key` is the slot into which it fired and `deadlineSlot`
is that slot plus the configured late window. `dueSlot` remains its original
not-before timestamp. Use `key` for an unrevealed entry's 30-second grace
period, after excluding the watch-list sentinel. An entry remains sealed
when `(flags & 16) != 0`. Simulation of `expire(id)` checks complete eligibility,
including item removal and cursor position.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.