# Sync vs async operations \[Understanding synchronous and asynchronous yield operations in Railnet]

Some protocols hand back assets in the same transaction; others make you wait. Ethena enforces a cooldown before unstaking sUSDe, and an ERC-7540 vault only registers a request a curator settles later in a batch. A Vehicle wrapping one of those cannot settle synchronously, so STEAM keeps the operation alive in `PROCESSING` until the protocol is ready.

## The two lifecycles

| | Sync Vehicle | Async Vehicle |
|---|---|---|
| Examples | Aave V3, Morpho Blue, ERC-4626 | ERC-7540 (Lagoon, Centrifuge), Ethena with cooldown on |
| Lifecycle | `EMPTY -> UNLOCKING -> SETTLED` | `EMPTY -> PROCESSING -> (PAUSED ->) UNLOCKING -> SETTLED` |
| Transactions | One | Two or more |
| Failure | `create()` reverts; nothing is created | `PROCESSING -> RECOVERING`, owner calls `recover()`, ends `REJECTED` |
| Settlement | Caller | Keeper, or anyone calling `unlock()` |

The transition out of `PROCESSING` is protocol-driven: `state(query)` re-evaluates the underlying protocol on every call and reports `UNLOCKING` once the condition holds, even though nothing has been committed on-chain yet. See [the storedState lag](/developers/contracts/steam-standard#the-query-registry).

## ERC-7540 request queues

An ERC-7540 vault does not settle on request. `requestDeposit` and `requestRedeem` register a request that a curator settles in batches, and only then can the result be claimed. The `ERC7540Vehicle` keeps its own demand queue: one demand per Query, batched into a single vault request under the Vehicle's own address. Each Query holds a demand ID, and `state()` reports `UNLOCKING` once that demand is claimable.

`processRequest()` submits the whole backlog and is permissionless. The size-bounded variant, for draining a backlog the vault will not accept in one request, needs `VEHICLE_PROCESS_QUEUE` scoped to the Vehicle.

Lagoon and Centrifuge vaults both run through this Vehicle in full-async mode: the vault must keep its synchronous entrypoints disabled so every operation flows through the request-and-settle cycle. There is no dedicated Lagoon adapter.

## Ethena Account clones

Ethena's `cooldownShares` is address-specific, and a new cooldown from the same address overrides the previous one. Concurrent redemptions driven from one Vehicle address would clobber each other, so the `EthenaVehicle` clones the minimal `Account` contract through the `CoreFactory`, salted with the query ID, and transfers the cooling sUSDe to it. The Vehicle drives the protocol through `performCall(target, value, data)` — `cooldownShares` on entry, `unstake` at settlement — and records the clone address in its own per-query storage. Each redemption gets its own identity on Ethena.

When Ethena's `cooldownDuration` is zero, the Vehicle runs synchronously and needs no clone.

## Keepers

A keeper is an off-chain bot that watches job listings and submits batched execution reports to the `Keeper` contract, which calls the listed target. It polls each job's status (`POLL`, `EXEC`, `STOP`) and executes when ready, so users never return for a second transaction: the keeper calls `process()` on a Conduit, or advances a MultiVehicle sub-query, as soon as the underlying protocol settles. Any call in a report that reverts reverts the whole batch.

Keepers hold roles, not privileges over funds: `KEEPER_ON_REPORT` on the `Keeper` contract for the forwarder, `CONDUIT_PROCESS` on each Conduit handed over, `JOB_LISTING_REGISTER` / `JOB_LISTING_EXECUTE` on the job listing, and `MULTI_VEHICLE_PROGRESS_QUERY` on the `SubQueryEngine`. Granting the role is what hands a contract to automation; without it the keeper logs the contract as unauthorized and leaves it alone.

Automation is never the only way out: a query owner can always call `process()`, `unlock()` or `recover()` once `state(query)` says the operation is ready.
