Skip to Content
Supra Layer 1SupraEVMHow Automation Works

How Automation Works

EVM automation lets a contract be called without anyone sending a transaction. A user registers a task naming a target call and a condition gating it; once the task is active, the chain schedules that call for execution in every committed block of a running cycle, until the task ends. Task lifecycle lists the ways a task ends.

This page covers the feature model behind AutomationRegistry: cycles, configuration, fees, task validity, and the difference between a user task and a system task. That page stays a contract reference — entry points, callers, errors, events, ABI — and links back here for the concepts.

Cycles and blocks

Two clocks govern a task, and they run at different rates.

Blocks are the execution interval. Once a task is ACTIVE, the node schedules its call in every committed block while the registry cycle is STARTED, for as long as the task remains active and its predicate allows it. It schedules nothing during the bookkeeping that follows a cycle’s end, or while the registry is SUSPENDED or READY. A task is also skipped, for that block only, when the lowest gas price included in the block is above the task’s gasPriceCap; see Gas and Fees.

Cycles are the fee, capacity and registration window. Registration is accepted only while the current cycle is in the STARTED state with automation and registration both enabled. A task’s expiry is checked against the end of the cycle it registers into; its fee cap is compared with the projected fee for the next cycle, the first one the task is charged for. A task does not skip straight from registration to execution: it is created PENDING and only becomes ACTIVE at the next cycle transition. A task registered mid-cycle therefore waits for the cycle to turn before it runs at all — but once it is active, it runs every block, not once per cycle.

Cycle states

StateMeaning
READYAutomation is available but not enabled. The automation registry holds no task, registration is refused, and the cycle does not progress
STARTEDAutomation is enabled and usable. Tasks can be registered while registration is enabled; the cycle’s end time is monitored, and when it passes the cycle index advances
FINISHEDThe active cycle has ended. The automation registry is transitioning to the next cycle, and bookkeeping for that cycle is in progress
SUSPENDEDGovernance has disabled automation. Bookkeeping runs over the registered tasks to refund and remove them, cleaning up the automation registry before it returns to READY

At genesis the automation registry starts in STARTED with cycle index 1 if the chain’s automation parameters enable automation, and in READY with cycle index 0 otherwise. A configuration staged with updateConfigBuffer is adopted at the next cycle boundary: when a cycle ends, or when automation is enabled again.

Cycle transitions

FromToWhenCycle index
READYSTARTEDThe owner enables automation+1
STARTEDFINISHEDThe cycle’s end time passes and at least one task is registeredunchanged
STARTEDSTARTEDThe cycle’s end time passes and no task is registered+1
STARTEDSUSPENDEDThe owner disables automation while at least one task is registeredunchanged
STARTEDREADYThe owner disables automation while no task is registeredunchanged
FINISHEDSTARTEDBookkeeping has processed every task+1
FINISHEDSUSPENDEDThe owner disables automation after the cycle ended but before any bookkeeping record for the next cycle was processedunchanged
SUSPENDEDREADYBookkeeping has refunded and removed every taskunchanged
SUSPENDEDSTARTEDBookkeeping has finished, and the owner enabled automation again during the suspension+1

Every transition emits AutomationCycleEvent, carrying the cycle index, the new state and the previous state.

What drives a transition

monitorCycleEnd() runs every block once it is registered in BlockMeta. Once block.timestamp passes the cycle’s end time, it moves the cycle from STARTED to FINISHED, or straight into a new STARTED cycle if no task is registered. monitorCycleEnd() only detects the end of a cycle; it does no task bookkeeping itself.

Genesis does not register monitorCycleEnd(). A foundation-wallet action registers it afterwards, and until then no cycle ends, so the cycle stays STARTED past its end time and every task stays PENDING.

processTasks(cycleIndex, taskIndexes) is the VM signer’s bookkeeping call, driven by the node once a transition is detected, and is called repeatedly in batches until every task has been processed. It does not execute a task’s registered action. In the FINISHED state it activates PENDING tasks, charges each user task its fee for the cycle being entered, and drops the tasks that cannot continue; once every task is processed, a new STARTED cycle begins. In the SUSPENDED state it refunds and removes every task without charging; once done, the automation registry moves to READY, or to STARTED if automation was enabled again meanwhile.

The owner’s enableAutomation() and disableAutomation() drive the remaining transitions; see Enabling and disabling automation.

removeRegisteredTask(cycleIndex, taskIndex, reason, details) is a separate VM-signer call the node uses to drop a single task mid-cycle: one it finds structurally invalid, or one a new epoch’s EVM gas config no longer admits. See Removal by the runtime.

None of monitorCycleEnd, processTasks or removeRegisteredTask can be invoked by any account, because no transaction can be sent from the VM signer’s reserved address; see SupraEVM Precompiles. They are described here so they can be recognised in traces and in RPC responses.

Enabling and disabling automation

Only the owner — the foundation wallet — calls enableAutomation() and disableAutomation(). Each reverts if automation is already in the requested state (AlreadyEnabled, AlreadyDisabled). When a call takes effect depends on the cycle state:

Cycle statedisableAutomation()enableAutomation()
READYReverts AlreadyDisabledImmediate: a new STARTED cycle begins, index +1, with any staged configuration adopted
STARTEDImmediate: SUSPENDED if any task is registered, otherwise READYReverts AlreadyEnabled
FINISHED, before any bookkeeping record is processedImmediate: SUSPENDEDReverts AlreadyEnabled
FINISHED, bookkeeping under wayDeferred: only the flag changes. See belowIf automation was disabled during this bookkeeping, re-enables it and the transition completes into STARTED as normal; otherwise reverts AlreadyEnabled
SUSPENDEDReverts AlreadyDisabledDeferred: only the flag changes, and when the suspension bookkeeping finishes the automation registry moves to STARTED, index +1, instead of READY

Disabling while bookkeeping is under way. The transition already in progress completes first: when the last task is processed the automation registry enters the next cycle — STARTED, index +1 — and, in the same transaction, moves on to SUSPENDED if tasks remain or to READY if none do. Two AutomationCycleEvents are emitted, and an indexer following them sees the intermediate cycle.

What a suspension refunds. Every user task is refunded its whole deposit, with no PENDING penalty, and an ACTIVE task also its fee for the remainder of the current cycle. When the suspension follows the end of a cycle before any task was charged for the next one, only the deposits are refunded, since no cycle fee was taken.

Disabling mid-cycle reverts with InvalidRegistryState if the cycle’s end time has already passed but monitorCycleEnd() has not yet ended it in that block.

Configuration

Every parameter lives in the registry’s Config struct and is read through getConfig(). Four size caps live outside Config, read through getDataLengthCaps().

ParameterConsumed by
registryMaxGasCapDenominator of the per-cycle fee and of congestion usage; ceiling on gas committable by user tasks for the next cycle
sysRegistryMaxGasCapSame ceiling for system tasks. Not a fee input — system tasks pay nothing
automationBaseFeeWeiPerSecFloor of the per-second automation fee
flatRegistrationFeeWeiOne-off charge taken at registration
congestionBaseFeeWeiPerSecScales the congestion surcharge. 0 disables congestion entirely
congestionThresholdPercentageOccupancy percentage above which the surcharge applies. 100 disables it
congestionExponentExponent of the surcharge curve
maxCongestionExponentGovernance ceiling on congestionExponent
taskDurationCapSecsMaximum lifetime of a user task
sysTaskDurationCapSecsMaximum lifetime of a system task
cycleDurationSecsLength of a cycle. Also caps the duration charged to a task within one cycle
taskCapacityMaximum concurrent user tasks
sysTaskCapacityMaximum concurrent system tasks
maxPayloadLength / maxPredicateLengthSize caps on _payloadTx and _predicate. Multiples of 32, since both values must be whole ABI encodings
maxAuxDataLength / maxAuxDataEntriesSize caps on _auxData

Values at genesis

Genesis derives the registry’s configuration from the chain’s Move automation configuration, so that both VMs’ automation is governed by one set of parameters:

EVM parameterDerived from the Move parameter
automationBaseFeeWeiPerSec, congestionBaseFeeWeiPerSec, flatRegistrationFeeWeiThe matching fee in quants, multiplied by 10¹⁰
registryMaxGasCap, sysRegistryMaxGasCapThe matching gas cap, multiplied by 3,000
taskDurationCapSecs, sysTaskDurationCapSecs, cycleDurationSecs, congestionThresholdPercentage, congestionExponentCopied unchanged
maxCongestionExponentSet to congestionExponent
taskCapacity, sysTaskCapacityCopied unchanged when their sum is at most 200; otherwise each is capped, at 160 and 40 respectively

One quant is 10¹⁰ wei, so each EVM fee equals the matching Move fee in SUPRA. The gas caps are multiplied by 3,000 because at the conversion of a Move gas price into an EVM one, described in The gas price floor, 3,000 EVM gas cost the same as one Move gas unit. A task therefore takes the same share of either registry for the same amount of work, and pays the same fee in SUPRA. maxPayloadLength and maxPredicateLength start at 4,096 and 2,048 bytes; maxAuxDataLength and maxAuxDataEntries start at 0, which disables _auxData.

Governance can change any of these values after genesis, so read the live configuration from the chain rather than relying on a printed figure. supra_devnet is the endpoint name defined in Foundry; a wallet-integration URL can be passed instead, and AUTOMATION_REGISTRY is the address from the contract registry:

cast call "$AUTOMATION_REGISTRY" \ "getConfig()((uint128,uint128,uint128,uint128,uint128,uint64,uint64,uint64,uint16,uint16,uint8,uint8,uint8))" \ --rpc-url supra_devnet cast call "$AUTOMATION_REGISTRY" "getDataLengthCaps()(uint16,uint16,uint16,uint16)" \ --rpc-url supra_devnet

getConfig() returns the fields in the order of the Config struct: registryMaxGasCap, sysRegistryMaxGasCap, automationBaseFeeWeiPerSec, flatRegistrationFeeWei, congestionBaseFeeWeiPerSec, taskDurationCapSecs, sysTaskDurationCapSecs, cycleDurationSecs, taskCapacity, sysTaskCapacity, congestionThresholdPercentage, congestionExponent, maxCongestionExponent. $AUTOMATION_REGISTRY is the registry’s address, found as in Getting Started.

Devnet’s Move automation configuration produces the following registry configuration at Devnet’s next genesis. A registry deployed by an earlier genesis keeps the values that genesis gave it until governance changes them.

ParameterDevnet value
registryMaxGasCap240,000,000
sysRegistryMaxGasCap60,000,000
automationBaseFeeWeiPerSec4,000,000,000,000,000 wei/s (0.004 SUPRA/s)
flatRegistrationFeeWei500,000,000,000,000,000 wei (0.5 SUPRA)
congestionBaseFeeWeiPerSec4,000,000,000,000,000 wei/s
congestionThresholdPercentage50%
congestionExponent / maxCongestionExponent6 / 6
taskDurationCapSecs172,800 seconds (2 days)
sysTaskDurationCapSecs2,626,560 seconds (30.4 days)
cycleDurationSecs600 seconds
taskCapacity / sysTaskCapacity160 / 40
maxPayloadLength / maxPredicateLength4,096 / 2,048 bytes
maxAuxDataLength / maxAuxDataEntries0 / 0 — _auxData is disabled

At these values a task with a _maxGasAmount of 100,000 costs 10¹⁵ wei (0.001 SUPRA) per cycle below the congestion threshold, and 5 × 10¹⁷ wei (0.5 SUPRA) to register, plus the fee cap it deposits. estimateAutomationFee returns the current per-cycle figure for a given gas amount.

Buffered versus immediate

updateConfigBuffer stages a full configuration change, adopted only at the next cycle transition, so an in-progress cycle’s rules cannot move underneath registered tasks. Two fields inside it escape the buffer and take effect immediately — nextCycleRegistryMaxGasCap and nextCycleSysRegistryMaxGasCap — so a lowered gas cap gates new registrations within the current cycle rather than waiting a full cycle to bite.

updateDataLengthCaps is always immediate. The size caps only gate new registrations, and registration is already blocked outside the STARTED state, so there is no mid-cycle fairness reason to defer them.

The fee model

Registering a user task charges flatRegistrationFeeWei + automationFeeCapForCycle in WSUPRA, the token of the WrappedSupra contract (contract registry key WrappedSupra), taken by transferFrom; see Predeployed Contracts. The cap becomes the task’s locked deposit, and is checked against a fee projection at registration — too low a cap fails registration outright with InsufficientFeeCapForCycle, naming the required amount. Registering a system task charges nothing.

The per-second automation fee is a base rate plus a congestion surcharge: automationFeePerSec = automationBaseFeeWeiPerSec + congestionFee. The surcharge is zero below congestionThresholdPercentage occupancy, and above it rises with the exponent congestionExponent, scaled by congestionBaseFeeWeiPerSec.

A task’s fee for one cycle is automationFeePerSec × chargedDuration × maxGasAmount / registryMaxGasCap, where chargedDuration is the task’s remaining lifetime clamped to the cycle length. A task still PENDING at a cycle transition is charged the full cycle interval regardless of a shorter registered lifetime.

At a cycle transition each task is handled by the first of these that applies:

ConditionResult
The task is cancelled or has expiredDropped; a user task’s deposit refunded in full
The epoch’s EVM gas config no longer admits the taskDropped before it is charged; a user task’s deposit refunded in full; TaskRemovedByGasConfigUpdate
The task is a system taskKept; never charged
The fee exceeds the task’s automationFeeCapForCycleDropped; deposit refunded in full; TaskCancelledCapacitySurpassed
The owner’s WSUPRA balance or allowance is below the feeDropped; deposit unlocked but not refunded; TaskCancelledInsufficientBalanceAllowance
OtherwiseFee taken; TaskCycleFeeWithdraw

RemovedTasks lists every task dropped at a transition, whichever row dropped it.

A task is admitted by the gas config while its maxGasAmount is within the chain’s per-transaction gas cap and, for a user task, its gasPriceCap is at least the chain’s minimum gas price. The rules and their reasoning are in Gas and Fees.

System tasks are never charged and never refunded — every fee and refund path is conditioned on the task being a user task.

These are the WSUPRA charges. A user task’s action also pays gas at execution, from the owner’s native balance; see What an execution looks like.

Task lifecycle

Every task is created PENDING. It becomes ACTIVE only at the next cycle transition, so a task registered mid-cycle waits for the cycle to turn before its first execution. Once active, it is scheduled in every committed block while the cycle is STARTED.

EndingTriggerEventRefund (user task)
Cancelled while PENDINGcancelTasks / cancelSystemTasks; removed immediatelyTasksCancelledHalf the deposit
Cancelled while ACTIVESame call; marked CANCELLED, keeps executing until the cycle ends, removed at the next cycle transitionTasksCancelledFull deposit, at that transition
Stopped while PENDINGstopTasks / stopSystemTasks; removed immediatelyTasksStoppedHalf the deposit, no cycle-fee refund
Stopped while ACTIVE or CANCELLEDSame call; removed immediatelyTasksStoppedFull deposit, plus half the residual cycle fee
ExpiredRemoved at a cycle transitionRemovedTasksFull deposit
Fee cap too lowCycle transition chargingTaskCancelledCapacitySurpassedFull deposit
Balance or allowance shortCycle transition chargingTaskCancelledInsufficientBalanceAllowanceDeposit unlocked, not refunded
Found invalid by the runtimeremoveRegisteredTask with reason ERRORTaskRemovedBySystemFull deposit plus half the residual cycle fee; half the deposit if PENDING
No longer admitted by the gas config, mid-cycleremoveRegisteredTask with reason GAS_CONFIG_UPDATETaskRemovedByGasConfigUpdateFull deposit plus the whole residual cycle fee
No longer admitted by the gas config, at a transitionCycle transition, before chargingTaskRemovedByGasConfigUpdateFull deposit

The half-deposit penalty attaches to the PENDING state specifically, on the cancel, stop and runtime-removal paths — not to cancellation as a whole. An ACTIVE or already-CANCELLED task refunds its deposit in full on each of those paths, and stopTasks and runtime removal also refund half of whatever cycle fee was left unused. A removal for a gas-config change carries no penalty: it refunds the whole deposit and the whole residual cycle fee. In every case the flat registration fee is not refunded.

Cancelling versus stopping

The two calls differ in when an active task stops running:

  • Cancelling a PENDING task removes it from the automation registry immediately. It never runs.
  • Cancelling an ACTIVE task does not remove it. Its state changes from ACTIVE to CANCELLED, it keeps being scheduled for execution in every block until the current cycle ends, and it is removed at the next cycle transition.
  • Stopping a task removes it immediately, whatever its task state. An active task is no longer scheduled from the next block.

The node fixes the set of tasks it schedules at the start of each cycle and, in every block, only drops from it the tasks that are no longer active; stopping makes a task inactive, cancelling leaves it active until the cycle ends. To halt an active task at once, stop it.

A batch passed to cancelTasks, cancelSystemTasks, stopTasks or stopSystemTasks skips any index that no longer exists rather than reverting the whole call, so one stale entry does not block the rest. All four require automation enabled and the cycle STARTED; in any other cycle state they revert, whatever the task’s own state.

Removal by the runtime

removeRegisteredTask is how the node drops a single task mid-cycle. Its reason argument is a TaskRemovalReason:

  • GAS_CONFIG_UPDATE — the EVM gas config that took effect at an epoch boundary no longer admits the task. The task is no longer scheduled from the first block of that epoch, and is removed when the removal record executes, with the full refund described above. TaskRemovedByGasConfigUpdate reports the task’s bounds next to the new config’s. The sequence, including how pending tasks are handled, is in Gas and Fees.
  • ERROR — the task is structurally broken, rather than having failed a normal check. TaskRemovedBySystem carries a details string naming the fault:
DetailsCause
MalformedPayloadpayloadTx failed to decode
MalformedPredicatepredicate failed to decode, or returned a non-boolean at execution
PredicateExecutionFailedThe predicate reverted, halted, attempted a state change, or was not a valid transaction
UnexecutableGasAmountmaxGasAmount does not fit the 64-bit gas limit of the transaction built for the task

The details string is the variant name followed by its payload, for example PredicateExecutionFailed("Revert { gas_used: ..., output: ... }"). Match on the prefix, not on equality.

UST versus GST

A task is either user-submitted (UST) or governance-submitted (GST). GST exists so protocol-level automation — upkeep registered by governance rather than an end user — runs without holding WSUPRA or competing with users for the user task budget. Registering a GST is a governance mechanism, not something users apply for; applications use UST tasks.

USTGST
Registered byAny account without code, via registerAuthorized accounts without code, via registerSystemTask
Authorization—Granted or revoked by the owner through grantAuthorization / revokeAuthorization
Registration feeflatRegistrationFeeWei + automationFeeCapForCycleNone
Per-cycle chargeYesNever
Gasless at executionNo — the owner’s native balance pays gasYes
CapacitytaskCapacitysysTaskCapacity
Gas capregistryMaxGasCapsysRegistryMaxGasCap
Duration captaskDurationCapSecssysTaskDurationCapSecs
maxGasAmount boundAt most the chain’s per-transaction gas capSame
gasPriceCap boundAt least the chain’s minimum gas priceExempt — a system task runs at a gas price of 0
_priorityIgnored — always the task indexHonoured if nonzero, else the task index
Cancel / stopcancelTasks, stopTaskscancelSystemTasks, stopSystemTasks
RefundsYesNever

A task runs with its owner as the transaction sender, and a sender with code (other than an EIP-7702 delegation) is refused. A task registered by a contract, including a system task registered by a multisig authorized submitter, is therefore removed at its first predicate evaluation. Register from an account without code.

The owner of a task calling the UST function on a GST task, or the reverse, reverts with UnsupportedTaskOperation. A caller that is not the owner reverts with UnauthorizedAccount instead.

Execution within a block is ordered by type first, then by priority ascending: every UST task runs before any GST task, and within each group the lower priority value runs first.

The predicate

Every task carries a predicate. It is validated at registration and evaluated again before each execution.

At registration, the predicate is size-capped, required to be a whole number of 32-byte words, decoded as (address, bytes), and staticcalled under the task’s _maxGasAmount; it must return exactly 32 bytes holding 0 or 1. _maxGasAmount is checked against the chain’s per-transaction gas cap before that call, so an oversized task is refused before it can run anything. A predicate is mandatory: an empty bytes value passes the size and length checks, then fails to decode, so registration without one is rejected rather than accepted as unconditional.

At execution, the predicate runs as a read-only transaction and the action runs as a second transaction, and both draw on the task’s _maxGasAmount. The predicate’s gas used, including its intrinsic gas (21,000 plus calldata), is deducted from _maxGasAmount, and the action then pays its own intrinsic gas out of the remainder. An expensive predicate leaves less gas for the action it gates. The registration-time staticcall does not count intrinsic gas, so a _maxGasAmount just above the predicate’s cost can pass registration and leave too little for the action to start; that execution is discarded and the task stays registered.

Predicate outcomeEffect
Returns trueThe action runs this block
Returns falseThe task is skipped for this block only, and stays registered
Reverts or haltsThe task is removed — PredicateExecutionFailed
Attempts a state changeThe task is removed — PredicateExecutionFailed
Is not a valid transaction (for example, gas below intrinsic)The task is removed — PredicateExecutionFailed
Returns a non-boolean valueThe task is removed — MalformedPredicate

The predicate’s outcome decides the task’s fate; the action’s does not. An action that reverts, halts or is discarded is recorded as the automated transaction’s output, and the task stays registered. A user task whose owner’s native balance is below gas_limit × max_fee_per_gas + value for the action is discarded in this way in each block and is not removed.

What an execution looks like

The predicate and the action are both transactions sent by the task owner:

  • from, msg.sender and tx.origin are the task owner. The registration-time staticcall differs: there msg.sender inside the predicate is the registry, so a predicate that tests msg.sender sees a different value at registration than at execution.
  • nonce is the task index. The owner’s account nonce is neither checked nor advanced, so (from, nonce) of an automated transaction overlaps the owner’s own nonce space. To tell automated from user transactions, use supraTxKind on the Ethereum API or txn_type on the REST API; see Automated Transactions over RPC.
  • For a user task, max_fee_per_gas is the lowest gas price included in the block and the gas used is paid from the owner’s native balance. For a system task it is 0.
  • The priority fee is 0. Native value in _payloadTx is debited from the owner’s native balance.

Within a block, automated transactions run after user transactions.

  • AutomationRegistry — the contract reference: entry points, callers, errors, events, and the ABI.
  • Automated Transactions over RPC — observing task executions and cycle bookkeeping through the RPC APIs.
  • BlockMeta — runs monitorCycleEnd() once per block, once it is registered.
  • Gas and Fees — how the chain’s EVM gas config bounds automation tasks, and what happens to a task when it changes.
  • Predeployed Contracts — Wrapped SUPRA, the token automation fees are paid in.
Last updated on