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

# Wrap an existing vault

> Connect a standard ERC-4626 vault to Railnet — the plug-and-play path

<Info>In Railnet smart contracts, a Yield Source is connected via a **Vehicle** adapter. See [Glossary](/developers/glossary) for all terminology.</Info>

This tutorial walks you through wrapping an existing ERC-4626 vault as a Railnet yield source using the built-in ERC4626Vehicle adapter. By the end, you will have a deployed adapter that accepts deposits, issues shares, and processes redemptions — all through the STEAM state machine.

A synchronous adapter completes its operations within a single transaction. When `create()` is called, the query transitions directly from `EMPTY` to `UNLOCKING`, meaning assets are immediately deposited and ready for the user to claim.

## Prerequisites

* A deployed `ERC4626VehicleFactory` (or the factory for your adapter type) — see [Supported protocols](/developers/vehicles/supported-protocols) for factory addresses per chain
* An underlying yield source (e.g., an ERC-4626 vault address)
* The underlying asset authorized in the [AssetRegistry](/developers/contracts/asset-registry) — the factory reads the initial deposit size from there at spawn time
* Underlying assets (e.g., USDC) for the initial deposit
* Foundry installed and configured

## Tutorial

<Steps>
  <Step title="Understand the deployment model">
    Adapters are deployed through **factory contracts**. The factory handles deterministic `CREATE2` deployment, reads the initial deposit size from the [AssetRegistry](/developers/contracts/asset-registry), performs the initial deposit to protect against inflation attacks, burns the initial shares, and enables the adapter for public use.

    You call the factory's `spawn` function with a `SpawnParams` struct that configures your adapter.
  </Step>

  <Step title="Prepare spawn parameters">
    Define the configuration for your adapter deployment. This includes references to your yield source, access control, and fee management. The initial deposit amount itself comes from the AssetRegistry — it is not passed here.

    <CodeGroup>
      ```solidity Solidity theme={null}
      ERC4626VehicleFactory.SpawnParams memory params = ERC4626VehicleFactory.SpawnParams({
          vault: address(myVault),
          accessControl: myAccessControl,
          feeManager: myFeeManager,
          modulesManager: myModulesManager,
          querySalt: bytes32(0),
          deploymentSalt: bytes32(uint256(1)),
          initialExpectedSupply: 1e18
      });
      ```

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

    <Note>
      The initial deposit bootstraps the adapter with real liquidity. The factory burns the shares minted from this deposit to prevent inflation attacks on the share price. Size the registry entry per asset — see [Asset registry](/developers/contracts/asset-registry) for guidance.
    </Note>
  </Step>

  <Step title="Deploy the adapter">
    Call the factory's `spawn` function. This deploys the Vehicle proxy, initializes it, performs the initial deposit, burns shares, validates supply, and enables the adapter.

    <CodeGroup>
      ```solidity Solidity theme={null}
      ERC4626Vehicle vehicle = factory.spawn(params);
      ```

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

    The factory emits a `SpawnedERC4626Vehicle` event with the new adapter address.
  </Step>

  <Step title="Create a deposit query">
    In STEAM, every operation starts with a **Query**. Define your deposit query with the owner, receiver, mode, input assets, and a unique salt.

    <CodeGroup>
      ```solidity Solidity theme={null}
      Query memory depositQuery = Query({
          owner: address(this),
          receiver: address(this),
          mode: Mode.DEPOSIT,
          input: new Asset[](1),
          output: new Asset[](0),
          salt: bytes32(uint256(123)),
          data: ""
      });
      depositQuery.input[0] = Asset({
          asset: address(underlyingAsset),
          value: 100e18
      });
      ```

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

  <Step title="Execute the deposit">
    The deposit follows the STEAM two-step lifecycle: `create` then `unlock`.

    First, approve the Vehicle to pull your assets, then create the query:

    <CodeGroup>
      ```solidity Solidity theme={null}
      underlyingAsset.approve(address(vehicle), 100e18);
      vehicle.create(depositQuery);
      ```

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

    Because this is a synchronous adapter, `create()` immediately deposits the assets into the underlying vault and transitions the query to `UNLOCKING`.

    Then settle the query to receive your shares:

    <CodeGroup>
      ```solidity Solidity theme={null}
      vehicle.unlock(depositQuery);
      ```

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

    The query transitions to `SETTLED`, and you receive Vehicle shares.
  </Step>

  <Step title="Verify the deposit">
    Check your balance of Vehicle shares to confirm the deposit succeeded.

    <CodeGroup>
      ```solidity Solidity theme={null}
      uint256 shares = vehicle.balanceOf(address(this));
      // shares > 0 and matches expected amount based on vault exchange rate
      ```

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

  <Step title="Execute a redemption">
    To redeem, create a new query with `Mode.REDEEM` and provide your Vehicle shares as input.

    <CodeGroup>
      ```solidity Solidity theme={null}
      Query memory redeemQuery = Query({
          owner: address(this),
          receiver: address(this),
          mode: Mode.REDEEM,
          input: new Asset[](1),
          output: new Asset[](0),
          salt: bytes32(uint256(456)),
          data: ""
      });
      redeemQuery.input[0] = Asset({
          asset: address(vehicle),
          value: shares
      });

      // Create and unlock in sequence
      vehicle.create(redeemQuery);
      vehicle.unlock(redeemQuery);
      ```

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

    The Vehicle burns your shares, withdraws assets from the underlying vault, and transfers the underlying assets to your receiver address.
  </Step>

  <Step title="Verify the redemption">
    Confirm you received the underlying assets.

    <CodeGroup>
      ```solidity Solidity theme={null}
      uint256 finalBalance = underlyingAsset.balanceOf(address(this));
      // finalBalance increased by the amount withdrawn from the vault
      ```

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

## Sync adapter state flow

In a synchronous adapter, the query lifecycle is straightforward:

```
EMPTY --[create()]--> UNLOCKING --[unlock()]--> SETTLED
```

There is no `PROCESSING` or `PAUSED` state because the underlying protocol completes the operation immediately within the `create()` transaction.

## When `max*` views are unreliable

Some ERC-4626 vaults return misleading values from `maxDeposit`, `maxMint`, `maxWithdraw`, or `maxRedeem` — for example, paused pools that still accept some flows, pools with artificially low caps that don't reflect real liquidity, or vaults where the views aren't kept in sync with the underlying protocol state.

Railnet ships a custom variant, `ERC4626Vehicle.maxFunctionsIgnored`, that skips those view checks and lets the underlying vault call revert naturally when a deposit or redeem cannot proceed. Deploy it through the same factory flow — point the factory at the variant's beacon instead of the standard `ERC4626Vehicle` beacon. The `SpawnParams` struct is identical.

<Tip>
  Only reach for this variant when you have confirmed the underlying vault's `max*` functions are unreliable. The standard `ERC4626Vehicle` gives callers upfront feedback on capacity limits; the variant trades that for tolerance of broken views.
</Tip>

## What your adapter implements

When building a custom sync adapter (rather than using the built-in ERC4626Vehicle), you inherit from `SingleAssetBaseVehicle` and implement:

| Method           | Purpose                                                                      |
| ---------------- | ---------------------------------------------------------------------------- |
| `_create()`      | Execute the deposit or redeem on your protocol. Return `UNLOCKING` for sync. |
| `_unlock()`      | Transfer output assets to the receiver. Return `SETTLED`.                    |
| `_maxDeposit()`  | Return the maximum depositable amount based on protocol constraints.         |
| `_maxRedeem()`   | Return the maximum redeemable amount based on protocol liquidity.            |
| `_totalAssets()` | Return the total underlying assets managed by this adapter.                  |

## Next steps

<CardGroup cols={2}>
  <Card title="Build a custom adapter" icon="wrench" href="/developers/vehicles/build-custom-adapter">
    Handle protocols with withdrawal delays or cooldown periods.
  </Card>

  <Card title="Write adapter tests" icon="flask-vial" href="/developers/contracts/testing">
    Validate your adapter with the STEAM testing framework.
  </Card>
</CardGroup>
