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

# Sync vs async operations

> Understanding synchronous and asynchronous yield operations in Railnet

<Info>In user-facing documentation, a Vehicle is referred to as a **Yield Source**. See [Glossary](/developers/glossary).</Info>

A fundamental architectural decision in Railnet is whether a Vehicle operates synchronously or asynchronously. This distinction determines how Queries move through the STEAM state machine and what operational patterns you need to follow.

## The core distinction

The difference between sync and async Vehicles lies in the Query lifecycle.

<Tip>
  For the complete state machine reference — all seven states, every valid transition, and the critical constraints — see [The STEAM standard](/developers/contracts/steam-standard).
</Tip>

### Synchronous Vehicles

Sync Vehicles complete their primary operation within a single transaction. A Query transitions directly from EMPTY to UNLOCKING via `create()` and then to SETTLED via `unlock()`. There is no waiting period or external dependency.

<CodeGroup>
  ```solidity Solidity theme={null}
  // Sync flow: single transaction
  // EMPTY -> UNLOCKING -> SETTLED
  ```

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

**Examples:**

* **Aave V3 Vehicle** -- Supplies and withdraws assets from Aave V3 lending pools
* **Compound V3 Vehicle** -- Interacts with Compound V3 markets
* **ERC-4626 Vehicle** -- Wraps any standard ERC-4626 vault into a STEAM-compliant interface

Sync Vehicles provide the best user experience with the lowest operational overhead.

### Asynchronous Vehicles

Async Vehicles handle operations that cannot complete immediately due to protocol-level constraints. A Query enters PROCESSING after creation and remains there until external conditions are met.

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

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

**Examples:**

* **Ethena Vehicle** -- Handles sUSDe cooldown periods when active. Operates synchronously when cooldown is disabled.
* **Syrup Vehicle** -- Interacts with Maple/Syrup withdrawal queues, which may have per-address limitations

## Why some operations must be async

Asynchronicity is rarely a choice. It is a requirement imposed by the underlying protocol's design.

<AccordionGroup>
  <Accordion title="Protocol cooldowns (Ethena)">
    Ethena's sUSDe protocol implements a `cooldownDuration`. When you want to unstake USDe, you must first initiate a cooldown. The assets are only available for withdrawal after this period elapses.

    The Ethena Vehicle tracks this period, keeping the Query in PROCESSING until the cooldown ends.

    <Note>
      When Ethena's `cooldownDuration` is set to zero, the Ethena Vehicle operates synchronously — no PROCESSING state, no Account Clone needed.
    </Note>
  </Accordion>

  <Accordion title="Withdrawal queues (Syrup)">
    Syrup (Maple Finance) uses a withdrawal queue system to manage pool liquidity. The protocol processes withdrawals in a FIFO queue, meaning you must wait for sufficient liquidity.

    Additionally, Syrup often imposes a "one-request-per-account" limitation. Without Railnet's Account Clone pattern, the entire protocol would be limited to one active withdrawal at a time.
  </Accordion>

  <Accordion title="Multi-step strategies">
    Complex yield strategies might involve multiple swaps, bridge operations, or liquidity provisioning steps that cannot safely be compressed into a single atomic transaction.
  </Accordion>
</AccordionGroup>

## The PROCESSING state

The PROCESSING state is the hallmark of an asynchronous operation.

* In **sync Vehicles**, `create()` moves the Query directly to UNLOCKING because assets are immediately deposited and ready to claim
* In **async Vehicles**, `create()` moves the Query to PROCESSING, signaling that the operation is underway but not yet ready for settlement

The transition from PROCESSING to UNLOCKING is detected through the Vehicle's `state()` view function, which checks the underlying protocol's status. If conditions are met (e.g., `block.timestamp >= cooldownEnd`), `state()` reports UNLOCKING. A Keeper or caller then calls `unlock()` to finalize.

If an external condition must be met before processing can continue, the Query may enter PAUSED. Once the condition is satisfied, `resume()` moves it back to PROCESSING.

## Error handling: sync vs async

How errors surface depends on the Vehicle type:

* **Sync Vehicles** — if `create()` fails, the entire transaction reverts. No query is created, no assets are transferred. There is nothing to recover.
* **Async Vehicles** — if the underlying protocol operation fails after entering PROCESSING, the Query transitions to RECOVERING. The owner calls `recover()` to reclaim input assets, and the Query terminates in REJECTED.

This distinction matters operationally: sync errors are invisible (reverted transactions), while async errors require active monitoring and recovery.

## Account Clones: isolating async operations

Some protocols restrict operations to the address that initiated them. Ethena's `cooldownShares` is address-specific — multiple cooldowns from the same address override each other. Syrup's withdrawal manager allows only one active request per address. In both cases, concurrent operations from a single Vehicle address would conflict.

Railnet solves this with the **Account Clone** pattern:

<Steps>
  <Step title="Deploy clone">
    The Vehicle deploys a minimalistic Account contract clone when an async redemption is initiated.
  </Step>

  <Step title="Transfer assets">
    Assets (such as pool tokens) are transferred to this specific Account.
  </Step>

  <Step title="Initiate protocol interaction">
    The Account initiates the protocol operation (e.g., `requestRedeem` or `cooldownShares`).
  </Step>

  <Step title="Track in query">
    The Query state stores the address of this dedicated Account, linking the operation to its isolated identity.
  </Step>
</Steps>

This provides **per-query isolation**. Each redemption has its own identity on the underlying protocol, enabling Railnet to handle hundreds of concurrent asynchronous requests without address conflicts.

## Impact on callers

When interacting with async Vehicles, you follow a multi-step process:

<Steps>
  <Step title="Create the query">
    Call `create()`. The Query enters the PROCESSING state. Assets are transferred to the Vehicle (or its Account Clone).
  </Step>

  <Step title="Monitor readiness">
    Poll `state(query)` to check the current state. The Vehicle evaluates underlying protocol conditions on each call.
  </Step>

  <Step title="Finalize the operation">
    * On **success** (state is UNLOCKING): call `unlock()` to claim output assets. The Query transitions to SETTLED.
    * On **failure** (state is RECOVERING): call `recover()` to reclaim input assets. The Query transitions to REJECTED.
  </Step>
</Steps>

<Tip>
  In most deployments, a **Keeper** network automates steps 2 and 3. You create the Query and the Keeper handles monitoring and finalization. See [Keeper setup](/developers/contracts/keeper) for details.
</Tip>

## Multi-Vehicle handling of async sub-vehicles

When a Multi-Vehicle dispatches operations to async sub-vehicles, the SubQueryEngine manages the nested STEAM lifecycle. If a sub-vehicle enters PROCESSING, the Multi-Vehicle's own Query may remain open until settlement completes.

**Ephemeral accounting** tracks expected outputs during in-flight operations to prevent share price distortion. See [Accounting and flow of funds](/developers/contracts/accounting) for how this works.

The Keeper system automates monitoring and advancing nested sub-queries. See [Multi-Vehicle architecture](/developers/contracts/multi-vehicle#sync-and-async-sub-vehicles) for the full operational picture.

## Choosing between sync and async

When integrating a new protocol into Railnet, the choice is dictated by the protocol's deposit and withdrawal mechanics:

| Factor                   | Sync Vehicle                             | Async Vehicle                                     |
| ------------------------ | ---------------------------------------- | ------------------------------------------------- |
| **Operations**           | Immediate entry and exit                 | Delays, queues, or per-address restrictions       |
| **User experience**      | Best (single transaction)                | Requires monitoring or Keeper automation          |
| **Operational overhead** | Lowest                                   | Higher (clone management, state tracking)         |
| **Use when**             | Protocol allows instant deposit/withdraw | Protocol has cooldowns, queues, or address limits |

<Note>
  Async is not a limitation -- it is what allows Railnet to provide institutional-grade access to the full spectrum of DeFi yield sources, regardless of their underlying complexity.
</Note>
