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

# The STEAM standard

> A universal interface for DeFi yield operations

<Info>This page covers the smart contract implementation details. See [Glossary](/developers/glossary).</Info>

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**.

<Info>
  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.
</Info>

## 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.

<CodeGroup>
  ```solidity Solidity theme={null}
  struct Query {
      address owner;      // Who controls the query
      address receiver;   // Who receives the output
      Asset[] input;      // What goes in
      Asset[] output;     // What comes out (filled on settlement)
      Mode mode;          // DEPOSIT or REDEEM
      bytes32 salt;       // Unique identifier component
      bytes data;         // Protocol-specific parameters
  }

  struct Asset {
      address asset;      // Token address
      uint256 value;      // Amount
  }
  ```

  ```typescript TypeScript theme={null}
  // Coming soon
  ```
</CodeGroup>

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          | Description                                              | Terminal |
| -------------- | -------------------------------------------------------- | -------- |
| **EMPTY**      | No query exists yet. Default state before creation.      | No       |
| **PROCESSING** | Vehicle received assets, performing protocol operations. | No       |
| **PAUSED**     | Awaiting an external condition (cooldown, oracle, KYC).  | No       |
| **UNLOCKING**  | Operation succeeded, output assets ready for claim.      | No       |
| **RECOVERING** | Error occurred, assets being recovered.                  | No       |
| **REJECTED**   | Query failed, assets returned to owner.                  | Yes      |
| **SETTLED**    | Query complete, assets distributed to receiver.          | Yes      |

### State diagram

```mermaid theme={null}
stateDiagram-v2
    [*] --> EMPTY
    EMPTY --> PROCESSING: create()
    EMPTY --> UNLOCKING: create()

    PROCESSING --> PAUSED: (external)
    PROCESSING --> UNLOCKING: (external)
    PROCESSING --> RECOVERING: (external)

    PAUSED --> PROCESSING: resume()

    UNLOCKING --> SETTLED: unlock()
    UNLOCKING --> PROCESSING: unlock() partial

    RECOVERING --> REJECTED: recover()
    RECOVERING --> PROCESSING: recover() partial

    SETTLED --> [*]
    REJECTED --> [*]
```

### 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:

<Steps>
  <Step title="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](/developers/contracts/sync-vs-async)

    If `create()` fails for any reason, the entire transaction reverts. No assets are transferred, no query is created.
  </Step>

  <Step title="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).
  </Step>

  <Step title="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.
  </Step>
</Steps>

## Lifecycle methods

| Method           | Valid from | Transitions to                   | Description                                 |
| ---------------- | ---------- | -------------------------------- | ------------------------------------------- |
| `create(query)`  | EMPTY      | PROCESSING or UNLOCKING          | Initiates a new query. Pulls input assets.  |
| `resume(query)`  | PAUSED     | PROCESSING                       | Resumes after an external condition is met. |
| `unlock(query)`  | UNLOCKING  | SETTLED or PROCESSING (partial)  | Distributes output assets to receiver.      |
| `recover(query)` | RECOVERING | REJECTED or PROCESSING (partial) | Returns input assets after failure.         |

## Events

Vehicles emit two events to enable off-chain tracking and automation:

| Event                              | Parameters                                                   | Emitted when                          |
| ---------------------------------- | ------------------------------------------------------------ | ------------------------------------- |
| `Created(id, query)`               | Indexed query ID + full Query struct                         | A new query is created via `create()` |
| `Updated(id, state, possibleNext)` | Indexed query ID + new state + array of possible next states | Any state transition occurs           |

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.

<Tip>
  Use `Updated` events with non-empty `possibleNext` arrays to drive automation. See [Keeper setup](/developers/contracts/keeper) for patterns.
</Tip>

## Transition flows

### Synchronous flow (Aave, Compound, ERC-4626)

<CodeGroup>
  ```solidity Solidity theme={null}
  // Complete in a single transaction
  // EMPTY -> UNLOCKING -> SETTLED
  ```

  ```typescript TypeScript theme={null}
  // Coming soon
  ```
</CodeGroup>

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

### Asynchronous flow (Ethena, Syrup)

<CodeGroup>
  ```solidity Solidity theme={null}
  // Requires multiple transactions
  // EMPTY -> PROCESSING -> (PAUSED ->) UNLOCKING -> SETTLED
  ```

  ```typescript TypeScript theme={null}
  // Coming soon
  ```
</CodeGroup>

The Vehicle enters PROCESSING, waits for external conditions, and advances to UNLOCKING when ready. A [Keeper](/developers/contracts/keeper) typically automates this.

### Error recovery

<CodeGroup>
  ```solidity Solidity theme={null}
  // Error path
  // PROCESSING -> RECOVERING -> REJECTED
  ```

  ```typescript TypeScript theme={null}
  // Coming soon
  ```
</CodeGroup>

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.

<CodeGroup>
  ```solidity Solidity theme={null}
  // Partial claim cycle
  // UNLOCKING -> PROCESSING -> ... -> UNLOCKING -> SETTLED

  // Partial recovery cycle
  // RECOVERING -> PROCESSING -> ... -> RECOVERING -> REJECTED
  ```

  ```typescript TypeScript theme={null}
  // Coming soon
  ```
</CodeGroup>

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

<Warning>
  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 creation** -- `create()` 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.
</Warning>

## View methods

Vehicles provide methods to inspect state and simulate operations:

| Method                            | Returns              | Description                                        |
| --------------------------------- | -------------------- | -------------------------------------------------- |
| `state(query)`                    | State                | Current state of a specific query                  |
| `estimate(assets, mode, type)`    | Asset\[]             | Expected output **including fees** (non-binding)   |
| `convert(assets, sharesToAssets)` | Asset\[]             | Pure conversion **excluding fees**                 |
| `error(query)`                    | bytes                | Error data for queries in RECOVERING or REJECTED   |
| `asset()`                         | address              | The base asset of the vehicle (e.g., USDC)         |
| `routes()`                        | (Route\[], Route\[]) | Deposit and redeem route combinations              |
| `totalAssets()`                   | uint256              | Total value of all managed assets                  |
| `maxDeposit(account)`             | Asset\[]             | Maximum deposit amounts per asset for an account   |
| `maxRedeem(account)`              | Asset\[]             | Maximum redeemable shares per asset for an account |
| `ready()`                         | bool                 | Whether the vehicle accepts new queries            |

<Note>
  `estimate()` provides a best-effort preview, not a commitment. Always implement slippage protection when using estimates for transaction previews.
</Note>

## 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

<CardGroup cols={2}>
  <Card title="Sync vs async operations" icon="arrows-split-up-and-left" href="/developers/contracts/sync-vs-async">
    How synchronous and asynchronous Vehicles differ in practice
  </Card>

  <Card title="Accounting and flow of funds" icon="chart-line" href="/developers/contracts/accounting">
    How assets move through Multi-Vehicles and Sectors
  </Card>

  <Card title="Create a sync Vehicle" icon="bolt" href="/developers/vehicles/wrap-existing-vault">
    Build a Vehicle for protocols with instant settlement
  </Card>

  <Card title="Create an async Vehicle" icon="clock" href="/developers/vehicles/build-custom-adapter">
    Build a Vehicle for protocols with delayed settlement
  </Card>
</CardGroup>
