Skip to main content
This page covers the smart contract implementation details. See Glossary.
STEAM (State Transition Engine for Asset Management) is a standardized interface that defines how deposit and redemption operations work across all of Railnet. Smart contracts implementing STEAM are called Vehicles.

Why STEAM exists

Traditional vault standards like ERC-4626 assume synchronous operations: deposit assets, receive shares instantly. But many DeFi operations are inherently asynchronous:
  • Cooldown periods — Ethena requires waiting before unstaking sUSDe
  • Withdrawal queues — Syrup processes withdrawals in a FIFO queue
  • Multi-step strategies — Multi-Vehicles coordinate across multiple protocols
ERC-4626 has no mechanism for tracking in-flight operations. Failed transactions simply revert, losing all context. STEAM solves this by shifting from balance tracking to state transitions.
STEAM is more than an interface — it is a framework for building reliable DeFi infrastructure. By standardizing how time, failure, and complex state are handled, STEAM enables Railnet to orchestrate assets across diverse yield sources under a single operational model.

The Query model

The core of STEAM is the Query — a structured request representing a deposit or redemption operation. Each Query carries its own identity, ownership, and state.
The Query ID is computed as keccak256(abi.encode(chainId, vehicleAddress, query)). The salt field ensures that repeated operations with identical parameters produce unique IDs.

The state machine

Every Query moves through a defined set of states. This makes the status of any operation transparent and auditable at all times.

States

State diagram

Transition types

State transitions are categorized by their trigger mechanism:
  • Method-driven (synchronous) — Occur within the same transaction as a direct call to a Vehicle method (create(), resume(), unlock(), recover())
  • Protocol-driven (asynchronous) — Triggered by external protocol events, off-chain verification, or settlement delays

The Query lifecycle

Every operation follows three phases:
1

Creation

You call create() with a Query struct. The Vehicle pulls all input assets from the owner and starts the operation.
  • For sync protocols (Aave, Compound): the Query transitions directly to UNLOCKING
  • For async protocols (Ethena, Syrup): the Query enters PROCESSING — see Sync vs async operations
If create() fails for any reason, the entire transaction reverts. No assets are transferred, no query is created.
2

Execution

The Vehicle interacts with the underlying protocol. For async operations, the Query may pass through PAUSED if an external condition must be met (cooldown period, oracle update).Once the condition is satisfied, resume() moves the Query back to PROCESSING, and eventually the protocol signals success (UNLOCKING) or failure (RECOVERING).
3

Settlement

You call unlock() to claim the output assets. The Query transitions to SETTLED and assets or shares are distributed to the receiver.If the operation failed, you call recover() instead. The Query transitions to REJECTED and input assets are returned.

Lifecycle methods

Events

Vehicles emit two events to enable off-chain tracking and automation: The possibleNext array in the Updated event signals whether the state may change asynchronously:
  • Empty array — The state is stable. No further transitions will occur until the owner calls a method (unlock(), recover(), resume()).
  • Non-empty array — The state may change asynchronously via protocol-driven transitions. Off-chain systems should monitor for further Updated events.
Use Updated events with non-empty possibleNext arrays to drive automation. See Keeper setup for patterns.

Transition flows

Synchronous flow (Aave, Compound, ERC-4626)

The Vehicle completes the entire operation during create(), immediately reaching UNLOCKING. Call unlock() to finalize.

Asynchronous flow (Ethena, Syrup)

The Vehicle enters PROCESSING, waits for external conditions, and advances to UNLOCKING when ready. A Keeper typically automates this.

Error recovery

If an operation fails, the Query enters RECOVERING. Call recover() to reclaim assets.

Partial settlement

When unlock() or recover() cannot fully distribute assets in a single call, the Query returns to PROCESSING instead of reaching a terminal state.
This happens with protocols that release assets in batches. Each partial call returns Asset[] showing what was distributed in that step. The cycle repeats until all assets are fully claimed or recovered.

Critical constraints

These constraints are enforced by the STEAM standard and must never be violated:
  • Terminality — SETTLED and REJECTED are final states. No further transitions are possible.
  • Path isolation — Transitions between UNLOCKING (success path) and RECOVERING (error path) are strictly forbidden, even when going through PROCESSING.
  • Atomic creationcreate() can never produce REJECTED or RECOVERING. Any failure during creation reverts the entire transaction.
  • Owner enforcement — All method-driven transitions must verify that msg.sender is the query.owner.
  • State stability — If the possibleNext array in the Updated event is empty, the state is stable until the next user-initiated method call.

View methods

Vehicles provide methods to inspect state and simulate operations:
estimate() provides a best-effort preview, not a commitment. Always implement slippage protection when using estimates for transaction previews.

Fee semantics

The estimate() and convert() methods serve different purposes:
  • estimate() includes all applicable entry/exit fees, slippage, and operational costs
  • convert() excludes all fees, providing a pure share-to-asset conversion
Fees are applied during create() when assets are pulled. They are not applied during recovery operations, allowing users to reclaim assets without additional charges.

Next steps

Sync vs async operations

How synchronous and asynchronous Vehicles differ in practice

Accounting and flow of funds

How assets move through Multi-Vehicles and Sectors

Create a sync Vehicle

Build a Vehicle for protocols with instant settlement

Create an async Vehicle

Build a Vehicle for protocols with delayed settlement