Skip to main content
What you’ll be able to do
  • Schedule a contract call for a future time or block height.
  • Verify the target contract’s execution result.
  • Cancel pending entries and withdraw available escrow.
  • Add an oracle condition or encrypt the call’s payload.
The scheduler is not on Metal Testnet yet. Follow this guide on a network that has code at its address.
Metal’s scheduler at 0x00000000000000000000000000000000000000fe runs contract calls at a future time or block height, using tokens deposited in escrow to pay for execution. Its API calls the account that deposits tokens and schedules the call the payer. Scheduling and execution produce separate transaction receipts, so record both and verify the target contract’s state before marking the action complete.

Check deployment and fund the signing account

Check for code at the scheduler address. Read the scheduler’s gasToken, minGasPrice, gasPerBlock, maxHorizonSlots, maxLateSlots, and cancelRefundBps to use this network’s token and scheduling limits. Use the token and spending approval procedure to verify the scheduler’s gas token. Later, you will approve and deposit the amount needed for the call. Keep enough of the signing account’s balance in the gas token outside escrow to pay the transaction fees for approval, scheduling, cancellation, and withdrawal. Use fee estimates to calculate those costs.

Authorize the target action

The target contract sees the scheduler as msg.sender, and any account can schedule a call to it. The target therefore needs to check which actions its owner has authorized. ScheduledValue lets its owner authorize one value change per action ID, with an earliest execution time or height. Each authorization can be used once.
Deploy the target, then choose a unique bytes32 action ID and the value to set. You will authorize them after choosing the execution time or height below. The target enforces the stored value and earliest execution point even if another account schedules the same action. Enforce any additional application rules, such as an expiry time, in the target too.

Encode a single execution

The code below prepares a call to run(actionId) for two minutes after the current block’s timestamp and calculates the required deposit. Use the deployed contract address as target and your chosen actionId. See Scheduler reference for the full parameter definitions.
From the target owner’s account, call authorize(actionId, nextValue, params.slot, 0), where nextValue is the value you chose. Verify that transaction’s receipt and finality before scheduling, and save the action ID, expected value, and timestamp in application storage. Choose enough lead time for authorization and funding so that params.slot remains in the future and within maxHorizonSlots when you submit the schedule. To schedule by height, set slot: 0n and height: head.number + 20n, then authorize with time zero and that height. Exactly one trigger must be nonzero. The gas limits above cover this value-setting target. For a different action, allow enough execution gas for the individual calls and overhead, and check that budget fits gasPerBlock. Each execution attempt consumes the full reserved amount (lock), including when the target fails or uses less gas. The transaction that submits schedule pays its own fee. Class 1 is BestEffort, so the call may wait for block capacity. Class 0 reserves Guaranteed capacity at its slot or height, and scheduling can fail if that capacity is full. Both classes depend on the chain producing blocks. The parameters above disable retries and recurrence for a single execution attempt.

Deposit and schedule

Read the available escrow with balance(payer). If it is less than lock, approve the scheduler to spend the difference in its gas token, then call deposit for that amount. Verify the Deposited event and the increase in available escrow. Use SDK signing to sign schedule(params), and save the transaction hash, signed bytes, and full parameters before broadcasting. Follow receipt and finality verification to verify the transaction, then decode the scheduler’s Scheduled event. Check the payer, trigger, and class, and save the entry’s id from that event. At the scheduling transaction’s receipt block, check for status 1 (Scheduled) and a decrease in available escrow equal to lock. Read at that block because later state may already reflect execution. Save the original trigger and maxLateSlots to determine when a pending entry can expire.

Verify the actual execution

Query the scheduler’s Executed and ExecutionFailed logs for the saved entry ID from the schedule block onward. Use bounded block ranges and persist the last checked block. Each execution is a transaction of type 0x7e generated by the chain, with a receipt separate from the scheduling receipt. Use receipt and finality verification for the execution transaction, then check both protocol and application evidence: For this nonrecurring target, successful status is 2 (Executed). Follow settlement semantics before exporting the result to another system. Reconcile the amount charged for execution independently of the target result.

Cancel, expire, and withdraw

To cancel a pending entry, call cancel(id) from its payer. Verify Cancelled(id, refund), status 4 (Cancelled), and the transaction’s receipt and finality. Cancellation returns floor(lock * cancelRefundBps / 10000) to available escrow and credits the remainder to the treasury’s escrow. Revoke the target authorization too if you want to prevent the action from running through another entry. Another account may have scheduled the same authorized action, and execution can happen before revocation or cancellation. Inspect finalized target state before marking the application action cancelled. With lateExecute: false, a time-triggered entry can expire once chain time is strictly greater than its original slot plus maxLateSlots. Anyone can then call expire(id) to release the reserved amount. Verify Expired(id, lock), status 5 (Expired), and the full amount returned to available escrow. Height entries have no time deadline. See Scheduler reference for expiry based on queue position or encrypted payloads. Read balance(payer) and submit withdraw(amount) from the payer for up to that available escrow balance. Verify Withdrawn, the gas token’s matching transfer from scheduler to payer, and the reduced escrow balance. Pending entries keep their execution costs reserved, while completed attempts consume those costs even when the target fails. A withdrawal is subject to the payer’s receive policy. A refusal in REVERT mode rolls back the withdrawal and preserves available escrow. In QUARANTINE mode, the withdrawal consumes that escrow and transfers the tokens to ReceivePolicy quarantine. Save the matching quarantine receipt and finalized token transfer, then follow the receipt’s recovery procedure to recover those tokens.

Add an oracle condition

Choose a listed oracle item that has been written at least once, and verify its ID, decimals, and units before converting the threshold. Set when to { item: itemId, kind: 1, operand: thresholdInBaseUnits } for a value at least equal to the threshold, or kind 2 for a value at most equal to it. Kind 3 fires on a write while the item is not halted. Use BestEffort class, height: 0n, no recurrence, and slot as the earliest qualifying write timestamp. The condition is evaluated on later oracle writes whose timestamps are at or after slot, even if the value already meets the threshold when you schedule. A qualifying write queues the entry into a future slot when capacity permits. While waiting, the entry remains pending until it fires, you cancel it, or the item leaves the catalog and makes it eligible for expiry. If your application also requires bounds on freshness, confidence, or flags, check them in the target before acting. After execution, verify the target event and state, and record the qualifying write alongside the execution result. If the item never produces a qualifying write, cancel the entry and reconcile its refund.

Encrypt the payload

Read the beacon group key and encrypt the calls using a sealing implementation that follows the encrypted payload encoding. That format defines the padded calls, random salt, commitment, encrypted data key, and associated data bound to the payer. The calls stay private until their decryption key is available. Set calls: [], put the encrypted bytes in ciphertext, and set the matching commitment. Calculate cost using the ciphertext length. Use a slot trigger with recurrence and reserves disabled. To combine encryption with an oracle condition, use the condition identity specified in the same reference. Once the key arrives and the calls run, verify the target outcome as you would for an unencrypted entry. Malformed or mismatched plaintext fails the entry and consumes its reserved amount without retry. If the key is late, an encrypted entry waiting in a slot becomes eligible for expiry when block.timestamp > entry.key + 1 + 30. Calling expire then refunds the full reserved amount. Read entry(id) with the interface for reading entries to obtain key, the queued slot. For an oracle condition, this grace period starts after the entry leaves the watch list and enters that slot. Use key to calculate the deadline because dueSlot retains the original earliest qualifying write timestamp. Validators can recover the decryption key before it is published onchain, allowing a proposer to see the payload before other users.

Test and recover

Compile the target and check the ABI calls, then test unauthorized calls, repeated execution, revoked authorization, successful execution, and target failure. A revoked action provides a failure case whose target state must remain unchanged. Test cancellation with a separate action scheduled far enough in the future, then withdraw the refund. Use controlled fixtures to test expiry boundaries and classify receipts when live timing is unreliable. Include withdrawal refusal and quarantine fixtures. If adding a reserve, test that capture can consume it into quarantine while the entry becomes Executed. Verify recipient payment separately as described in Reserves. After an RPC error, look up the saved scheduling transaction hash and use the entry ID and last checked block to resume monitoring execution. Resolve the original submission before scheduling a replacement. Keep the parameters, network limits, transaction hashes, finality checks, target outcomes, and changes in escrow balance with the application action’s record.