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

# Integrate an Advanced Strategy

> Build an earn experience on top of Railnet — deposits, withdrawals, balances, and reporting

This guide walks you through integrating an Advanced Strategy into your platform. By the end, you'll be able to display vaults in-app, handle deposits and redemptions, and monitor positions through the Railnet API.

<Note>
  While Railnet is not fully live, the integration and distribution of Advanced Strategies is done directly from the Vault 7540.
</Note>

For a detailed overview of how Advanced Strategies work (architecture, vault lifecycle, NAV reporting, fee structure, roles), see [Advanced Strategies](/strategies/advanced).

## Prerequisites

Before integrating, make sure you have the following.

### Advanced Strategy distribution address

Each Advanced Strategy is distributed from a single shared 7540 Vault.

For testing, use the ready-to-use deployments on mainnets below.

| Network  | Chain ID | Asset | Strategy        | 7540 Vault address                           |
| -------- | -------- | ----- | --------------- | -------------------------------------------- |
| Ethereum | 1        | WBTC  | WBTC Test Yield | `0x4255f01607F785dE275b8ec1255f3a938dA17f63` |
| Ethereum | 1        | USDC  | USDC Test Yield | `0x7baeb9f29a284567b1c85b2469f456911bca1f81` |

### Referral code

Every deposit your platform routes carries a `referral` address as a function parameter. This is what attributes the deposit to your organization for reporting and revenue share. The attribution is recorded on-chain via the `Referral` event:

```solidity Solidity theme={null}
event Referral(
    address indexed referral,
    address indexed owner,
    uint256 indexed requestId,
    uint256 assets
);
```

**Contact the Railnet team to be assigned a referral address before going live.** Without it, deposits cannot be attributed, and your organization and the TVL driven by your platform will not be credited.

<Tip>
  For testing purposes, you can set the `referral` parameter to the zero address (`0x0000000000000000000000000000000000000000`). The deposit will go through normally; it just will not be attributed to any partner.
</Tip>

### Reporting API

The Railnet Reporting API powers vault details, position tracking, and activity history. Full documentation, including request and response schemas, is available at [docs.api.railnet.org/#tag/Async-Vaults](https://docs.api.railnet.org/#tag/Async-Vaults).

<Note>
  **Testing against the ready-to-use deployments?** Those vaults report to the staging environment. Swap the API base URL in every example below from `https://api.railnet.org` to `https://api.staging.railnet.org`. The endpoint paths and request/response shapes are identical; only the host changes. The reference docs link above (`docs.api.railnet.org`) is the same regardless of environment.
</Note>

## Reporting

Use the Railnet Reporting API to surface vault details, user positions, and activity history inside your platform. All endpoints share the base path `/v1/railnet/async-vaults`.

### Fetch vault information

To populate your platform's vault listing or product detail page (name, asset, icon, APY, TVL, share price, fees), query the `/vaults` endpoint.

```http GET theme={null}
https://api.railnet.org/v1/railnet/async-vaults/vaults
```

**Parameters:**

* `vault_ids` (query): filter to one or more specific vaults. Each id is formatted as `<chain_id>_<vault_address>`. Multiple values are comma-separated.
* `chain_ids`, `asset_ids`, `status` (query, optional): additional filters. See [the /vaults reference](https://docs.api.railnet.org/#tag/Async-Vaults/operation/getVaults) for the full list.

**Returns:**

A `data` array of vault objects, with fields grouped by:

* **Identity:** `name`, `symbol`, `state` (`Open` / `Closing` / `Closed`).
* **Underlying asset:** `deposit_asset.symbol`, `deposit_asset.decimals` (use `decimals` to format `_in_asset` amounts).
* **Vault performance:** `apy.last_7d`, `apy.last_30d`, `apy.last_1y`, `apy.all_time` (returned in percentage points).
* **TVL:** `total_value_in_asset` (TVL in min units of the deposit asset), `total_value_in_usd` (USD).
* **Share price:** `share_price_in_asset`, `share_price_in_usd` (use to convert between shares and assets when previewing operations).
* **Fees:** `fees.management_bps`, `fees.performance_bps`, `fees.protocol_bps` (basis points).
* **Freshness:** `updated_at` — when the data was last refreshed.

**Example request:**

```bash cURL theme={null}
curl 'https://api.railnet.org/v1/railnet/async-vaults/vaults?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828'
```

**Example response:**

```json JSON theme={null}
{
  "data": [
    {
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "chain_id": "1",
      "address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "name": "Monarq WBTC Market Neutral Yield",
      "symbol": "mWBTCmny",
      "state": "Open",
      "access_type": "whitelist",
      "deposit_asset": {
        "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
        "symbol": "WBTC",
        "decimals": 8
      },
      "receipt_asset": {
        "address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
        "symbol": "mWBTCmny",
        "decimals": 18
      },
      "oracle_address": "0xd95cf0e27f0a3e1804d3b47123e855ec5d89b335",
      "treasury_address": "0x9d24f6d435534ced20a8bc2535c717b33b5dc264",
      "apy": {
        "last_7d": 4.734806735054686,
        "last_30d": 0.40323883256330184,
        "last_1y": 0.15105215039228348,
        "all_time": 0.15105215039228348
      },
      "fees": {
        "management_bps": 100,
        "performance_bps": 1500,
        "protocol_bps": 1000
      },
      "total_value_in_asset": "1147484262",
      "total_value_in_usd": "882968.82",
      "total_supply_in_shares": "11472941074835549159",
      "share_price_in_asset": "100016574",
      "share_price_in_usd": "76960.9824",
      "high_water_mark": "100016574",
      "nav_updated_at": "2026-03-16T14:20:47Z",
      "updated_at": "2026-04-27T15:30:27Z"
    }
  ]
}
```

<Tip>
  You can cache this data and reuse it later to map it to a user position or surface it in different parts of your UI.
</Tip>

#### Vault activity

`/vaults` gives you the vault's current state. To complement it with the vault's recent activity (deposits, redemptions, NAV proposals and NAV settlements), query the `/operations` endpoint with only the `vault_ids` parameter and **no `wallets`**. This returns vault-scoped activity across all users, sorted by timestamp.

```http GET theme={null}
https://api.railnet.org/v1/railnet/async-vaults/operations
```

**Parameters:**

* `vault_ids` (query, required): one or more vault ids (`<chain_id>_<vault_address>`). Multiple values are comma-separated.
* `wallets` (query): omit to get vault-scoped activity across all users. When set, the endpoint returns wallet-scoped activity instead — see [Get user activity](#get-user-activity).

**Returns:**

A `data` array of operation entries, returned newest-first. Without a `wallets` filter, the feed mixes vault-level events (no `wallet` field) with user-level events (each carries a `wallet` field; the same five types are surfaced in [Get user activity](#get-user-activity) when scoped to a wallet).

**Vault-level events:**

* **`nav_proposal`:** the asset manager has proposed a new NAV for the next settlement. `proposed_nav_in_asset` is the proposed value in the deposit asset's min units. Capital does not move at this stage — it's an announcement.
* **`nav_update`:** the proposed NAV has been applied on-chain. `total_assets` is the new vault NAV (deposit asset's min units), used as the share price reference until the next NAV update.
* **`settlement`:** an epoch has been settled. `epoch_id` identifies which epoch was processed; `settlement_type` is `"deposit"` or `"redeem"` (a single epoch settles one direction); `total_assets` and `total_supply` reflect vault state immediately after settlement.

**User-level events:**

* **`request_deposit`:** a user submitted an async deposit request. `amount_in_asset` is the deposited amount; `epoch_id` is the epoch the request will settle in.
* **`cancel_deposit`:** a user canceled a pending deposit request before settlement. `epoch_id` matches the canceled request.
* **`claim_shares`:** a user claimed shares from a settled deposit. `amount_in_asset` is the deposit consumed; `amount_in_shares` is the shares minted. Sync deposits also produce this row but skip the request and settlement steps.
* **`request_redemption`:** a user submitted an async redemption request. `amount_in_shares` is the shares put in escrow; `epoch_id` is the epoch.
* **`claim_assets`:** a user claimed assets from a settled redemption. `amount_in_asset` is the underlying received; `amount_in_shares` is the shares burned.

Every entry carries `vault_id`, `vault_chain_id`, `vault_address`, `tx_hash`, and `timestamp`. User-level events additionally carry `wallet`, plus `sender`, `recipient`, `controller`, and `owner` where applicable.

**Example request:**

```bash cURL theme={null}
curl 'https://api.railnet.org/v1/railnet/async-vaults/operations?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828'
```

**Example response** (truncated to one example per event type):

```json JSON theme={null}
{
  "data": [
    {
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "settlement",
      "epoch_id": 25,
      "tx_hash": "0xe89db01a60b8d550442464b9ba3af99e67841f9148419b6a23047e01f9e2557b",
      "timestamp": "2026-04-27T15:55:59Z",
      "settlement_type": "deposit",
      "total_assets": "1149773705",
      "total_supply": "11486467563864330733"
    },
    {
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "nav_update",
      "tx_hash": "0xe89db01a60b8d550442464b9ba3af99e67841f9148419b6a23047e01f9e2557b",
      "timestamp": "2026-04-27T15:55:59Z",
      "total_assets": "1148773705"
    },
    {
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "nav_proposal",
      "tx_hash": "0xb33c3306d503645065fffe84f8ca492c1839a1cbf65a792987b407c7309f0913",
      "timestamp": "2026-04-27T12:00:59Z",
      "proposed_nav_in_asset": "1148773705"
    },
    {
      "wallet": "0x2b0ade7531a3578b811efa1b2ed64a73f0cd272b",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "request_deposit",
      "amount_in_asset": "1000000",
      "epoch_id": 25,
      "tx_hash": "0x149319e24811ed7a97c16b6b5072f5c4141fc66a63c34c62830ea6bf1719c508",
      "timestamp": "2026-04-27T08:10:47Z",
      "sender": "0x2b0ade7531a3578b811efa1b2ed64a73f0cd272b",
      "controller": "0x2b0ade7531a3578b811efa1b2ed64a73f0cd272b",
      "owner": "0x2b0ade7531a3578b811efa1b2ed64a73f0cd272b"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "claim_shares",
      "amount_in_asset": "306748442",
      "amount_in_shares": "3066976094060032029",
      "tx_hash": "0xd8b5de00c7aa0d80c757955c1e1c568dd5f2e23d3c1d60a818ed19f4039775f4",
      "timestamp": "2026-04-22T13:10:47Z",
      "sender": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "recipient": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "cancel_deposit",
      "epoch_id": 5,
      "tx_hash": "0xf3381a27b3416303fc5644cb933fe7b191e4c52265fe96254c978fb6dbbe7920",
      "timestamp": "2026-04-07T09:54:47Z",
      "controller": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "claim_assets",
      "amount_in_asset": "152352",
      "amount_in_shares": "1523820667574956",
      "tx_hash": "0x0749e648bf4c531c0a935eefc86e6d8bcc14145325ba7c464ffd495cbed9f370",
      "timestamp": "2026-03-24T14:02:59Z",
      "sender": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "recipient": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "request_redemption",
      "amount_in_shares": "1523820667574956",
      "epoch_id": 2,
      "tx_hash": "0x850c4fc7cc02799ba6178d1be83d81488d8d3ea128b65c5690be3c59d7a878a9",
      "timestamp": "2026-03-23T12:15:59Z",
      "sender": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "controller": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "owner": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    }
  ]
}
```

<Tip>
  Use this feed to render a "Recent activity" panel on the vault page, surface the latest settlement (the most recent `settlement` event), or join `epoch_id` across `request_*`, `cancel_deposit`, and `settlement` rows to see which user requests were fulfilled.
</Tip>

### Track historical performance

To chart a vault's share price or TVL over time, query the `/period-summaries` endpoint. A **period** is the interval between two consecutive NAV updates (each corresponds to a [`nav_update` event](#vault-activity) / on-chain `TotalAssetsUpdated`). Each entry captures the vault's assets, share supply, and share price at the start and end of that interval.

```http GET theme={null}
https://api.railnet.org/v1/railnet/async-vaults/period-summaries
```

**Parameters:**

* `vault_ids` (query, required): one or more vault ids (`<chain_id>_<vault_address>`). Multiple values are comma-separated.
* `start_date` (query, optional): filter to periods on or after this date, inclusive. Format `YYYY-MM-DD`.
* `end_date` (query, optional): filter to periods on or before this date, inclusive. Format `YYYY-MM-DD`.

**Returns:**

A `data` array with one object per vault. Each vault object carries:

* **Identity:** `vault_id`, `vault_chain_id`, `vault_address`.
* **Decimals:** `deposit_asset_decimals` (to format the `total_assets_*` and `share_price_*` amounts, which are in the deposit asset's min units) and `share_decimals` (to format the `total_supply_*` amounts).
* **`periods`:** an array of period entries, each with:
  * **Boundary:** `timestamp` — when the period ended (the NAV update fired); `duration` — length of the period in seconds (`"0"` means two NAV updates landed in the same block).
  * **Assets:** `total_assets_at_start` (post-settlement from the previous boundary, including deposit inflows) and `total_assets_at_end` (after the NAV update, before this boundary's settlement), both in the deposit asset's min units.
  * **Supply:** `total_supply_at_start`, `total_supply_at_end` (before fees), and `net_total_supply_at_end` (after fee shares are minted to fee recipients — higher than `total_supply_at_end` by the minted amount), all in share min units.
  * **Share price:** `share_price_in_asset_at_start` and `share_price_in_asset_at_end` (net of fees), in the deposit asset's min units per whole share. Derived as `total_assets × 10^share_decimals / total_supply`.

**Example request:**

```bash cURL theme={null}
curl 'https://api.railnet.org/v1/railnet/async-vaults/period-summaries?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828&start_date=2026-04-23&end_date=2026-04-27'
```

**Example response:**

```json JSON theme={null}
{
  "data": [
    {
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "deposit_asset_decimals": 8,
      "share_decimals": 18,
      "periods": [
        {
          "timestamp": "2026-04-23T12:00:47Z",
          "duration": "86400",
          "total_assets_at_start": "1145000000",
          "total_assets_at_end": "1146500000",
          "total_supply_at_start": "11450000000000000000",
          "total_supply_at_end": "11450000000000000000",
          "net_total_supply_at_end": "11451512004030129301",
          "share_price_in_asset_at_start": "100000000",
          "share_price_in_asset_at_end": "100117881"
        },
        {
          "timestamp": "2026-04-27T15:55:59Z",
          "duration": "86400",
          "total_assets_at_start": "1147484262",
          "total_assets_at_end": "1148773705",
          "total_supply_at_start": "11472941074835549159",
          "total_supply_at_end": "11472941074835549159",
          "net_total_supply_at_end": "11474453078865678460",
          "share_price_in_asset_at_start": "100016574",
          "share_price_in_asset_at_end": "100128456"
        }
      ]
    }
  ]
}
```

<Tip>
  Plot `share_price_in_asset_at_end` (or `total_assets_at_end` for TVL) against `timestamp` to build a performance chart. To show the return realized in a single period, compare `share_price_in_asset_at_start` to `share_price_in_asset_at_end`; annualize using `duration` when you need an APY-style figure.
</Tip>

### Get user positions

To display a user's position inside a vault (current balance, rewards earned, pending deposits, pending redemptions), query the `/positions` endpoint.

```http GET theme={null}
https://api.railnet.org/v1/railnet/async-vaults/positions
```

**Parameters:**

* `wallets` (query, required): the user wallet(s) you want to retrieve positions for. Multiple values are comma-separated.
* `vault_ids` (query): scope to one or more specific vaults. Each id is `<chain_id>_<vault_address>`. Multiple values are comma-separated.

**Returns:**

A `data` array of position objects, one per `(wallet, vault)` pair, with fields grouped by:

* **Current balance:** `balance_in_asset` (the user's position expressed in the vault's underlying asset, in min units), `balance_in_usd` (USD), `balance_in_shares` (yield-bearing receipt token amount). You can abstract the receipt token away and display only the underlying-asset amount.
* **Rewards earned:** `total_rewards_in_asset`, `total_rewards_in_usd` (cumulative yield generated since the user first deposited).
* **Pending deposits:** `pending_deposits.unfulfilled` lists requests still awaiting settlement; `pending_deposits.claimable_in_shares` / `claimable_in_asset` is what is settled and ready for the user to claim on-chain (call `deposit` / `mint` on the vault).
* **Pending redemptions:** mirror of the above for redeem requests; `pending_redemptions.claimable_in_asset` / `claimable_in_shares` is what the user can claim on-chain via `redeem` / `withdraw`.
* **Lifetime activity:** `net_deposited_in_asset`, `net_withdrawn_in_asset` (in min units of the deposit asset).
* **Freshness:** `updated_at`.

**Example request:**

```bash cURL theme={null}
curl 'https://api.railnet.org/v1/railnet/async-vaults/positions?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828&wallets=0x7A34B9fB6FCD8Ef11360D60e89A19903233f2630'
```

**Example response:**

```json JSON theme={null}
{
  "data": [
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_name": "Monarq WBTC Market Neutral Yield",
      "vault_symbol": "mWBTCmny",
      "balance_in_shares": "6450004469650832281",
      "balance_in_asset": "645107349",
      "balance_in_usd": "496398.68",
      "pending_deposits": {
        "unfulfilled": [],
        "claimable_in_shares": "0",
        "claimable_in_asset": "0"
      },
      "pending_redemptions": {
        "unfulfilled": [],
        "claimable_in_asset": "0",
        "claimable_in_shares": "0"
      },
      "net_deposited_in_asset": "644942010",
      "net_withdrawn_in_asset": "152352",
      "total_rewards_in_asset": "317691",
      "total_rewards_in_usd": "244.46",
      "updated_at": "2026-04-27T15:52:21Z"
    }
  ]
}
```

### Get user activity

To display a user's transaction history (deposit requests, cancellations, share claims, redemption requests, asset claims), query the same `/operations` endpoint used for vault activity, this time scoped to one or more wallets.

```http GET theme={null}
https://api.railnet.org/v1/railnet/async-vaults/operations
```

**Parameters:**

* `vault_ids` (query, required): one or more vault ids (`<chain_id>_<vault_address>`). Multiple values are comma-separated.
* `wallets` (query, required): the user wallet(s) whose history you want. Multiple values are comma-separated.

**Returns:**

A `data` array of user-level operation entries, returned newest-first. Each entry includes:

* **`type`:** the action the user took. The values you will encounter are `request_deposit`, `cancel_deposit`, `claim_shares` (claiming after a settled deposit), `request_redemption`, and `claim_assets` (claiming after a settled redemption). Sync paths produce the same `claim_*` records but skip the request and settlement steps.
* **Amounts:** `amount_in_asset` and / or `amount_in_shares` depending on the operation type. Format with the corresponding `decimals` from `/vaults`.
* **Vault context:** `vault_id`, `vault_chain_id`, `vault_address`. Useful when you query multiple `vault_ids` at once and need to know which vault each row belongs to.
* **On-chain reference:** `tx_hash` and `timestamp`. Use `tx_hash` to link out to a block explorer.
* **Request identifiers:** `epoch_id` on `request_deposit`, `cancel_deposit`, and `request_redemption`. This is the same id you receive from the `requestDeposit` / `requestRedeem` calls on-chain and the same id reported on `settlement` rows from the [vault activity feed](#vault-activity), so you can join the user's request to the settlement that fulfilled it.
* **Counterparties:** `sender`, `recipient`, `controller`, `owner` (set per operation type). Most platforms only need to display the user's own wallet, but these are useful when your platform manages claims on behalf of users (`controller != owner`).

**Example request:**

```bash cURL theme={null}
curl 'https://api.railnet.org/v1/railnet/async-vaults/operations?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828&wallets=0x7A34B9fB6FCD8Ef11360D60e89A19903233f2630'
```

**Example response** (truncated to one example per operation type):

```json JSON theme={null}
{
  "data": [
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "claim_shares",
      "amount_in_asset": "306748442",
      "amount_in_shares": "3066976094060032029",
      "tx_hash": "0xd8b5de00c7aa0d80c757955c1e1c568dd5f2e23d3c1d60a818ed19f4039775f4",
      "timestamp": "2026-04-22T13:10:47Z",
      "sender": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "recipient": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "request_deposit",
      "amount_in_asset": "306748442",
      "epoch_id": 23,
      "tx_hash": "0xe8627f4bd1a73c21b04ac8b88b839b69694257a1f6649df63a2f4ce72b5620c1",
      "timestamp": "2026-04-20T14:09:59Z",
      "sender": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "controller": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "owner": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "cancel_deposit",
      "epoch_id": 5,
      "tx_hash": "0xf3381a27b3416303fc5644cb933fe7b191e4c52265fe96254c978fb6dbbe7920",
      "timestamp": "2026-04-07T09:54:47Z",
      "controller": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "request_redemption",
      "amount_in_shares": "1523820667574956",
      "epoch_id": 2,
      "tx_hash": "0x850c4fc7cc02799ba6178d1be83d81488d8d3ea128b65c5690be3c59d7a878a9",
      "timestamp": "2026-03-23T12:15:59Z",
      "sender": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "controller": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "owner": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    },
    {
      "wallet": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "vault_id": "1_0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "vault_chain_id": "1",
      "vault_address": "0x8b21fa55e3afc537de8abb74b2068746c676c828",
      "type": "claim_assets",
      "amount_in_asset": "152352",
      "amount_in_shares": "1523820667574956",
      "tx_hash": "0x0749e648bf4c531c0a935eefc86e6d8bcc14145325ba7c464ffd495cbed9f370",
      "timestamp": "2026-03-24T14:02:59Z",
      "sender": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630",
      "recipient": "0x7a34b9fb6fcd8ef11360d60e89a19903233f2630"
    }
  ]
}
```

<Tip>
  Use this endpoint to render a "Transaction history" tab on the user's account page. For pending state ("you have a deposit request waiting on the next settlement"), prefer `/positions` (`pending_deposits.unfulfilled` and `pending_redemptions.unfulfilled`) since it pre-computes claimability for you.
</Tip>

## Vault interactions

Now that you have access to the full reporting data, you can start interacting with the vaults. In this section, we cover how to perform deposits and redemptions via direct contract calls, along with related actions such as canceling a deposit, claiming shares, and claiming assets.

### Deposit flow

The 7540 Vault supports two deposit paths:

* **Synchronous:** the user deposits and receives shares in the same transaction. Available when the vault's NAV is fresh and sync mode permits it.
* **Asynchronous:** the user submits a request, the asset manager settles at the next NAV update, and the user (or your platform on their behalf) claims the resulting shares.

<Info>
  **The two paths are mutually exclusive:** the vault enforces which one to use based on whether the vault's NAV is currently fresh.

  * When the NAV is fresh and sync mode permits sync deposits, only `syncDeposit` works (`requestDeposit` reverts with `OnlySyncDepositAllowed`).
  * When the NAV is expired, only `requestDeposit` works (`syncDeposit` reverts with `TotalAssetsExpired`).

  Your platform must always check which path is currently active before submitting the transaction.
</Info>

#### Choose the deposit path

Before you submit the user's transaction, your platform must call `previewSyncDeposit` on the vault to find out which path is currently active.

```solidity Solidity theme={null}
function previewSyncDeposit(uint256 assets) external view returns (uint256 shares);
```

**Parameters:**

* `assets`: the amount of underlying asset (in the asset's min units) the user is about to deposit. The vault uses this both as the gate input (returns `0` if sync deposit is not currently allowed) and to compute the corresponding number of shares net of entry fees.

**Returns:**

* `shares`: the number of vault shares the user would receive after the entry fee. A non-zero value means the **sync path is the only one currently allowed** (`requestDeposit` would revert with `OnlySyncDepositAllowed`). A zero value means the **async path is the only one currently allowed** (`syncDeposit` would revert with `TotalAssetsExpired` or `SyncOperationNotAllowed`).

**Example (TypeScript, ethers v6):**

```typescript TypeScript theme={null}
import { Contract, parseUnits } from 'ethers'

const VAULT_ABI = [
  'function asset() view returns (address)',
  'function previewSyncDeposit(uint256 assets) view returns (uint256)',
  'function syncDeposit(uint256 assets, address receiver, address referral) payable returns (uint256)',
  'function requestDeposit(uint256 assets, address controller, address owner, address referral) payable returns (uint256)',
]
const ASSET_ABI = [
  'function decimals() view returns (uint8)',
  'function approve(address spender, uint256 amount) returns (bool)',
]

const vault = new Contract(VAULT_ADDRESS, VAULT_ABI, signer)
const assetAddress: string = await vault.asset()
const asset = new Contract(assetAddress, ASSET_ABI, signer)

const decimals: number = await asset.decimals()
const amount: bigint = parseUnits('0.1', decimals) // e.g. 0.1 WBTC

// Step 1: approve the vault to pull the underlying asset.
await (await asset.approve(VAULT_ADDRESS, amount)).wait()

// Step 2: detect which path is currently active.
const expectedShares: bigint = await vault.previewSyncDeposit(amount)

if (expectedShares > 0n) {
  // NAV is fresh: only the sync path is allowed. The user receives shares in this same tx.
  const tx = await vault.syncDeposit(amount, USER_ADDRESS, REFERRAL_ADDRESS)
  await tx.wait()
} else {
  // NAV is expired (or sync deposits are disabled): only the async path is allowed.
  // The user will be able to claim their shares after the next settlement.
  const tx = await vault.requestDeposit(amount, USER_ADDRESS, USER_ADDRESS, REFERRAL_ADDRESS)
  await tx.wait()
}
```

#### Approve the vault

Both deposit paths (sync and async) require the user's wallet to have approved the 7540 Vault to spend the deposit amount of the underlying asset. Before submitting `syncDeposit` or `requestDeposit`, your platform must check the current allowance and, if it is insufficient, prompt the user to send a new approval.

<Steps>
  <Step title="Check the current allowance">
    Call `allowance` on the underlying ERC-20 asset contract:

    ```solidity Solidity theme={null}
    function allowance(address owner, address spender) external view returns (uint256);
    ```

    **Parameters:**

    * `owner`: the user's wallet address (the holder of the deposit asset).
    * `spender`: the 7540 Vault address.

    **Returns:** the amount of asset the vault is currently authorized to pull from `owner`. If this is less than the deposit amount the user intends to make, you must request a new approval before calling either deposit function.
  </Step>

  <Step title="Request approval if insufficient">
    Prompt the user to send an approval transaction to the underlying asset contract:

    ```solidity Solidity theme={null}
    function approve(address spender, uint256 amount) external returns (bool);
    ```

    **Parameters:**

    * `spender`: the 7540 Vault address.
    * `amount`: the amount the vault is allowed to pull, in the asset's min units. Pass at least the deposit amount.

    The transaction is sent **to the underlying asset contract** (not the vault), from the user's wallet, with `value = 0`. After it confirms, you can submit the deposit transaction.
  </Step>
</Steps>

#### Synchronous deposit

When `previewSyncDeposit` returns a non-zero value, the user can deposit and receive shares in a single transaction. Make sure the vault is approved to pull `assets` of the underlying token from the depositor first.

```solidity Solidity theme={null}
function syncDeposit(
    uint256 assets,
    address receiver,
    address referral
) external payable returns (uint256 shares);
```

**Parameters:**

* `assets`: the amount of underlying to deposit, in the asset's min units. Must be `>0` and within the vault's `maxCap` headroom (`totalAssets() + assets + siloBalance <= maxCap()`); otherwise the call reverts with `MaxCapReached`.
* `receiver`: the address that will receive the minted shares.
* `referral`: your Railnet-assigned referral address. Use the zero address (`0x0000000000000000000000000000000000000000`) for testing if you do not have a referral address yet.

**Returns:**

* `shares`: the number of vault shares minted to `receiver`, net of the entry fee.

<Info>
  **Native ETH path:** if the underlying asset is wETH, you can fund the deposit with native ETH by passing `msg.value` and leaving `assets = 0`. The vault wraps the ETH on the user's behalf. Sending `msg.value` when the underlying is not wETH reverts with `CantDepositNativeToken`.
</Info>

**Example (TypeScript, ethers v6):**

```typescript TypeScript theme={null}
import { Contract, parseUnits } from 'ethers'

const VAULT_ABI = [
  'function asset() view returns (address)',
  'function previewSyncDeposit(uint256 assets) view returns (uint256)',
  'function syncDeposit(uint256 assets, address receiver, address referral) payable returns (uint256)',
]
const ASSET_ABI = [
  'function decimals() view returns (uint8)',
  'function allowance(address owner, address spender) view returns (uint256)',
  'function approve(address spender, uint256 amount) returns (bool)',
]

const vault = new Contract(VAULT_ADDRESS, VAULT_ABI, signer)
const asset = new Contract(await vault.asset(), ASSET_ABI, signer)

const decimals: number = await asset.decimals()
const amount: bigint = parseUnits('0.1', decimals)

// 1. Verify the sync path is currently available.
const expectedShares: bigint = await vault.previewSyncDeposit(amount)
if (expectedShares === 0n) {
  throw new Error('Sync deposit not available; use requestDeposit instead')
}

// 2. Ensure allowance covers the deposit.
const owner: string = await signer.getAddress()
const currentAllowance: bigint = await asset.allowance(owner, VAULT_ADDRESS)
if (currentAllowance < amount) {
  await (await asset.approve(VAULT_ADDRESS, amount)).wait()
}

// 3. Submit the sync deposit. The user receives `expectedShares` in the same tx.
const tx = await vault.syncDeposit(amount, USER_ADDRESS, REFERRAL_ADDRESS)
await tx.wait()
```

#### Asynchronous deposit

When `previewSyncDeposit` returns `0`, the user must use the async path: **request → settle → claim**. Make sure the vault is approved to pull `assets` from the owner first.

<Steps>
  <Step title="Submit the request">
    ```solidity Solidity theme={null}
    function requestDeposit(
        uint256 assets,
        address controller,
        address owner,
        address referral
    ) external payable returns (uint256 requestId);
    ```

    **Parameters:**

    * `assets`: the amount of underlying to deposit, in the asset's min units. Subject to the vault's `maxCap` (`MaxCapReached` revert if exceeded).
    * `controller`: the address that owns the request and can later claim the resulting shares. Set it equal to `owner` unless your platform manages claims on behalf of users (e.g. claiming into a different receiver).
    * `owner`: the address that provides the underlying.
    * `referral`: your Railnet-assigned referral address. Use the zero address (`0x0000000000000000000000000000000000000000`) for testing.

    **Returns:**

    * `requestId`: the epoch ID this request was registered against. **You do not need to store it for the normal claim flow:** the Reporting API tracks settlement state by wallet. The id is still useful as a cross-reference to the matching `settlement` row in the `/operations` feed (where it appears as `epoch_id`) for observability or audit purposes.

    After this call, assets move into escrow. **No shares are minted yet**; the share price is determined at settlement.

    <Warning>
      A `controller` cannot have two pending requests across different epochs. Calling `requestDeposit` again in the same epoch adds to the existing request; calling it in a later epoch while a previous one is still pending reverts with `OnlyOneRequestAllowed`.
    </Warning>
  </Step>

  <Step title="Cancel the request (optional, same epoch only)">
    If the user changes their mind before the next settlement, the controller can cancel the request and recover the full deposited amount.

    ```solidity Solidity theme={null}
    function cancelRequestDeposit() external;
    ```

    **Parameters:** none. The vault uses `msg.sender` as the controller.

    **Behavior:** refunds the full deposited amount of the controller's pending request to `msg.sender`. Only works while the request is still in the current epoch; once a settlement has rolled the epoch, the call reverts with `RequestNotCancelable`.
  </Step>

  <Step title="Wait for settlement">
    After the next NAV update, the asset manager settles the epoch and the user's shares become claimable. The recommended way to detect this is via the Reporting API's [`/positions` endpoint](#get-user-positions): you are most likely already polling it for the user's portfolio view, and it aggregates settlement state per `(wallet, vault)` so you do **not** need to track `requestId`s yourself.

    ```http GET theme={null}
    https://api.railnet.org/v1/railnet/async-vaults/positions?vault_ids=<chain_id>_<vault_address>&wallets=<owner>
    ```

    **Fields to watch inside `data[0].pending_deposits`:**

    * `unfulfilled`: array of requests still in escrow, waiting for the next settlement. While this is non-empty, the user's deposit is "pending" in your UI.
    * `claimable_in_asset`: total asset amount that has settled and is ready to claim, in the asset's min units. Pass this directly to `deposit` (Option A in the next step).
    * `claimable_in_shares`: total share amount that has settled and is ready to claim, in share min units. Pass this directly to `mint` (Option B in the next step).

    When `claimable_in_asset > 0` (or `claimable_in_shares > 0`), the request is settled and the user can claim. `pending_deposits.claimable_in_*` is aggregated across all of the user's settled-but-unclaimed deposits in this vault, so a single value covers multiple settled epochs.

    <Tip>
      **Alternatives** (use only if you do not want a server-side dependency on the Reporting API):

      * On-chain view `claimableDepositRequest(uint256 requestId, address controller)` returns the claimable asset amount for a single request; pass `requestId = 0` to query the controller's latest request.
      * Subscribe to the vault's `SettleDeposit` events (the `epochId` field matches the `requestId` returned in step 1).
      * Poll the Reporting API's [`/operations` endpoint](https://docs.api.railnet.org/#tag/Async-Vaults/operation/getOperations) and watch for a row with `type = "settlement"` and an `epoch_id` matching your request.
    </Tip>
  </Step>

  <Step title="Claim shares">
    Once `pending_deposits.claimable_in_asset > 0`, the controller (or anyone they have authorized) can claim the shares. Two equivalent functions are available so you can claim in whichever unit is more convenient: pass `claimable_in_asset` to `deposit` (Option A), or `claimable_in_shares` to `mint` (Option B). Both support partial claims.

    <Tabs>
      <Tab title="Option A: claim by asset amount">
        ```solidity Solidity theme={null}
        function deposit(
            uint256 assets,
            address receiver,
            address controller
        ) external returns (uint256 shares);
        ```

        **Parameters:**

        * `assets`: the asset amount to claim, in the asset's min units. Must be `<=` `pending_deposits.claimable_in_asset` from the `/positions` response. Pass any positive value `<=` claimable for partial claims.
        * `receiver`: the address that receives the minted shares.
        * `controller`: the request controller.

        **Returns:**

        * `shares`: the number of shares minted to `receiver`, computed at the epoch's snapshot share price net of the entry fee that was active at settlement.
      </Tab>

      <Tab title="Option B: claim by share amount">
        ```solidity Solidity theme={null}
        function mint(
            uint256 shares,
            address receiver,
            address controller
        ) external returns (uint256 assets);
        ```

        **Parameters:**

        * `shares`: the number of shares to claim, in share min units. Must be `<=` `pending_deposits.claimable_in_shares` from the `/positions` response. Pass any positive value `<=` claimable for partial claims.
        * `receiver`: the address that receives the minted shares.
        * `controller`: the request controller.

        **Returns:**

        * `assets`: the asset amount consumed from the claimable balance to mint `shares`, at the epoch's snapshot price net of entry fee.
      </Tab>
    </Tabs>

    The share price used for the claim is locked at settlement time, regardless of when the user claims.

    <Note>
      Some strategies are configured to auto-claim shares on behalf of depositors during settlement. In that case, shares appear in `receiver`'s wallet without your platform calling `deposit` or `mint`. Always check `balanceOf(receiver)` (or `claimableDepositRequest`) before prompting the user to claim.
    </Note>
  </Step>
</Steps>

**Example (TypeScript, ethers v6):**

```typescript TypeScript theme={null}
import { Contract, parseUnits } from 'ethers'

const VAULT_ABI = [
  'function asset() view returns (address)',
  'function previewSyncDeposit(uint256 assets) view returns (uint256)',
  'function requestDeposit(uint256 assets, address controller, address owner, address referral) payable returns (uint256)',
  'function deposit(uint256 assets, address receiver, address controller) returns (uint256)',
]
const ASSET_ABI = [
  'function decimals() view returns (uint8)',
  'function allowance(address owner, address spender) view returns (uint256)',
  'function approve(address spender, uint256 amount) returns (bool)',
]

const vault = new Contract(VAULT_ADDRESS, VAULT_ABI, signer)
const asset = new Contract(await vault.asset(), ASSET_ABI, signer)
const owner: string = await signer.getAddress()
const decimals: number = await asset.decimals()
const amount: bigint = parseUnits('0.1', decimals)
const vaultId = `${CHAIN_ID}_${VAULT_ADDRESS.toLowerCase()}`

// 1. Confirm the async path is the active one. If preview returns >0, use the sync path instead.
if ((await vault.previewSyncDeposit(amount)) > 0n) {
  throw new Error('Sync deposit is currently allowed; use syncDeposit')
}

// 2. Ensure allowance covers the deposit.
if ((await asset.allowance(owner, VAULT_ADDRESS)) < amount) {
  await (await asset.approve(VAULT_ADDRESS, amount)).wait()
}

// 3. Submit the request. controller = owner since we are not claiming on the user's behalf.
//    No need to capture the requestId: the Reporting API tracks settlement state by wallet.
await (await vault.requestDeposit(amount, owner, owner, REFERRAL_ADDRESS)).wait()

// 4. Poll the Reporting API /positions endpoint to detect settlement.
async function getClaimableAsset(): Promise<bigint> {
  const url = `https://api.railnet.org/v1/railnet/async-vaults/positions?vault_ids=${vaultId}&wallets=${owner}`
  const res = await fetch(url)
  const { data } = await res.json()
  if (!data?.length) return 0n
  return BigInt(data[0].pending_deposits.claimable_in_asset)
}

let claimableAssets: bigint = await getClaimableAsset()
while (claimableAssets === 0n) {
  await new Promise((r) => setTimeout(r, 30_000))
  claimableAssets = await getClaimableAsset()
}

// 5. Claim the shares. Pass the exact asset amount returned by /positions.
await (await vault.deposit(claimableAssets, owner, owner)).wait()
```

### Redemption flow

The 7540 Vault supports two redemption paths:

* **Synchronous (instant):** the user burns their shares and receives the underlying assets in the same transaction. Available when sync mode permits it, NAV is fresh, and the strategy holds enough liquid assets to cover the payout.
* **Asynchronous:** the user submits a redeem request, the asset manager settles at the next NAV update, and the user (or your platform) claims the resulting assets.

<Info>
  Unlike the deposit flow (where the vault enforces which path is active), redemption gives you a real choice: `requestRedeem` always works, and `syncRedeem` is an opportunistic single-transaction shortcut when conditions are met. Prefer sync when it is available, since it spares the user the wait for the next settlement.
</Info>

There is no `referral` parameter on redemption; attribution carries forward from the original deposit.

#### Choose the redemption path

Two conditions must hold for sync redeem to succeed: the vault's `previewSyncRedeem` returns a non-zero value, **and** the strategy's treasury holds enough underlying to honor the payout. `previewSyncRedeem` checks the first; the second has to be verified separately because the preview function does not look at the treasury's balance.

```solidity Solidity theme={null}
function previewSyncRedeem(uint256 shares) external view returns (uint256 assets);
```

**Parameters:**

* `shares`: the number of vault shares the user intends to redeem.

**Returns:**

* `assets`: the amount of underlying the user would receive net of exit and haircut fees. Returns `0` if sync redeem is not currently possible (paused, sync mode disabled, NAV expired, or async-only mode active). A non-zero value means sync redeem is **eligible**, but you still need to verify treasury liquidity before submitting.

**Treasury balance check:**

`previewSyncRedeem` only confirms that sync redeem is allowed by the vault's policy; it does not check that the treasury actually holds the assets to pay out. To verify, take the `treasury_address` field from the [`/vaults` response](#fetch-vault-information) and read its balance with the standard ERC-20 `balanceOf` view on the deposit asset contract:

```solidity Solidity theme={null}
function balanceOf(address account) external view returns (uint256);
```

Sync redeem is viable only when:

```
balanceOf(treasury_address) >= previewSyncRedeem(shares)
```

If the balance is short, fall back to the async path: the request stays in queue and is retried by the asset manager at the next settlement, once liquidity returns to the treasury.

**Example (TypeScript, ethers v6):**

```typescript TypeScript theme={null}
import { Contract } from 'ethers'

const VAULT_ABI = [
  'function previewSyncRedeem(uint256 shares) view returns (uint256)',
  'function syncRedeem(uint256 shares, address receiver, uint256 minimumAssets) returns (uint256)',
  'function requestRedeem(uint256 shares, address controller, address owner) returns (uint256)',
]
const ASSET_ABI = ['function balanceOf(address account) view returns (uint256)']

const vault = new Contract(VAULT_ADDRESS, VAULT_ABI, signer)
const shares: bigint = USER_SHARES_TO_REDEEM
const vaultId = `${CHAIN_ID}_${VAULT_ADDRESS.toLowerCase()}`

// 1. Check sync eligibility (sync mode + NAV freshness, computed on-chain).
const expectedAssets: bigint = await vault.previewSyncRedeem(shares)

// 2. Check treasury balance (not covered by the preview). Fetch the treasury and the deposit
//    asset addresses from /vaults; in production you likely already cached this.
let useSync = false
if (expectedAssets > 0n) {
  const res = await fetch(
    `https://api.railnet.org/v1/railnet/async-vaults/vaults?vault_ids=${vaultId}`
  )
  const { data } = await res.json()
  const treasury: string = data[0].treasury_address
  const depositAsset = new Contract(data[0].deposit_asset.address, ASSET_ABI, signer)
  const treasuryBalance: bigint = await depositAsset.balanceOf(treasury)
  useSync = treasuryBalance >= expectedAssets
}

if (useSync) {
  // Sync path is viable.
} else {
  // Either sync redeem is not currently allowed, or the strategy is short on
  // idle liquidity. The async path always works.
}
```

#### Synchronous redemption

When both `previewSyncRedeem(shares) > 0` AND the strategy's treasury holds enough underlying, the user can burn shares and receive assets in a single transaction.

```solidity Solidity theme={null}
function syncRedeem(
    uint256 shares,
    address receiver,
    uint256 minimumAssets
) external returns (uint256 assets);
```

**Parameters:**

* `shares`: the number of vault shares to burn. The full amount is always burned (no partial output). `msg.sender` must own these shares; sync redeem cannot be called on behalf of another address.
* `receiver`: the address that receives the underlying assets.
* `minimumAssets`: slippage floor. The call reverts with `BelowMinimumAssets(assets, minimumAssets)` if the post-fee output is below this. Compute it from `previewSyncRedeem(shares)` minus an acceptable buffer (e.g. 50 to 100 bps).

**Returns:**

* `assets`: the amount of underlying transferred to `receiver`, net of exit fee and haircut fee. Always `>= minimumAssets` on success.

<Info>
  **Fees applied:** an exit fee (capped at 2%) and a haircut fee (capped at 20%, burned and accruing to remaining shareholders) may be applied. Sync redemptions can be intentionally more expensive than async redemptions: the haircut discourages runs and protects long-term holders.
</Info>

**Example (TypeScript, ethers v6):**

```typescript TypeScript theme={null}
import { Contract } from 'ethers'

const VAULT_ABI = [
  'function previewSyncRedeem(uint256 shares) view returns (uint256)',
  'function syncRedeem(uint256 shares, address receiver, uint256 minimumAssets) returns (uint256)',
]

const vault = new Contract(VAULT_ADDRESS, VAULT_ABI, signer)
const owner: string = await signer.getAddress()
const shares: bigint = USER_SHARES_TO_REDEEM

// 1. Compute the expected output and the slippage floor.
const expectedAssets: bigint = await vault.previewSyncRedeem(shares)
if (expectedAssets === 0n) {
  throw new Error('Sync redeem not available; use requestRedeem')
}
const slippageBps = 50n // 0.5%
const minimumAssets: bigint = (expectedAssets * (10_000n - slippageBps)) / 10_000n

// 2. Submit the redeem. The user receives >= minimumAssets in the same tx.
const tx = await vault.syncRedeem(shares, owner, minimumAssets)
await tx.wait()
```

<Warning>
  Run the treasury-balance check in production before this call; otherwise `syncRedeem` reverts with a generic ERC-20 transfer failure rather than a clean error.
</Warning>

#### Asynchronous redemption

When sync redemption is unavailable (or you simply prefer the async path: lower fees, no haircut), use the **request → settle → claim** flow.

<Warning>
  Unlike async deposit, there is **no cancel** for redemption requests: once submitted, the shares are locked until settlement. Surface a confirmation step in your UX before the user signs.
</Warning>

<Steps>
  <Step title="Submit the request">
    ```solidity Solidity theme={null}
    function requestRedeem(
        uint256 shares,
        address controller,
        address owner
    ) external returns (uint256 requestId);
    ```

    **Parameters:**

    * `shares`: the number of vault shares to redeem.
    * `controller`: the address that owns the request and can later claim the resulting assets. Set it equal to `owner` unless your platform manages claims on behalf of users.
    * `owner`: the holder of the shares being redeemed. If `msg.sender != owner`, the caller must be an approved operator on the vault, or hold an ERC-20 allowance from `owner` for at least `shares`.

    **Returns:**

    * `requestId`: the epoch ID this request was registered against. **You do not need to store it for the normal claim flow:** the Reporting API tracks settlement state by wallet. The id is still useful as a cross-reference to the matching `settlement` row in the `/operations` feed (where it appears as `epoch_id`) for observability or audit purposes.

    After this call, shares move into escrow. **The redemption price is not fixed yet**; it is determined at settlement. A controller cannot have two pending requests across different epochs (`OnlyOneRequestAllowed` revert).
  </Step>

  <Step title="Wait for settlement">
    After the next NAV update, the asset manager settles the epoch and the user's assets become claimable. The recommended way to detect this is via the Reporting API's [`/positions` endpoint](#get-user-positions): you are most likely already polling it for the user's portfolio view, and it aggregates settlement state per `(wallet, vault)` so you do **not** need to track `requestId`s yourself.

    ```http GET theme={null}
    https://api.railnet.org/v1/railnet/async-vaults/positions?vault_ids=<chain_id>_<vault_address>&wallets=<owner>
    ```

    **Fields to watch inside `data[0].pending_redemptions`:**

    * `unfulfilled`: array of requests still in escrow, waiting for the next settlement. While this is non-empty, the user's redemption is "pending" in your UI.
    * `claimable_in_shares`: total share amount that has settled and is ready to claim, in share min units. Pass this directly to `redeem` (Option A in the next step).
    * `claimable_in_asset`: total asset amount that has settled and is ready to claim, in the asset's min units. Pass this directly to `withdraw` (Option B in the next step).

    When `claimable_in_shares > 0` (or `claimable_in_asset > 0`), the request is settled and the user can claim. `pending_redemptions.claimable_in_*` is aggregated across all of the user's settled-but-unclaimed redemptions in this vault, so a single value covers multiple settled epochs.

    <Tip>
      **Alternatives** (use only if you do not want a server-side dependency on the Reporting API):

      * On-chain views: `pendingRedeemRequest(requestId, controller)` returns the share amount still pending settlement; `claimableRedeemRequest(requestId, controller)` returns the share amount settled and ready to claim. Pass `requestId = 0` to query the controller's latest request.
      * Subscribe to the vault's `SettleRedeem` events (the `epochId` field matches the `requestId` returned in step 1).
      * Poll the Reporting API's [`/operations` endpoint](https://docs.api.railnet.org/#tag/Async-Vaults/operation/getOperations) and watch for a row with `type = "settlement"` and an `epoch_id` matching your request.
    </Tip>

    <Info>
      Async redemption settlement is all-or-nothing per epoch. If the strategy is short on liquid assets when the asset manager attempts to settle, the request stays pending and is retried at the next NAV update. The user does not need to (and cannot) resubmit.
    </Info>
  </Step>

  <Step title="Claim assets">
    Once `pending_redemptions.claimable_in_shares > 0`, the controller (or anyone they have authorized) can claim the assets. Two equivalent functions are available so you can claim in whichever unit is more convenient: pass `claimable_in_shares` to `redeem` (Option A), or `claimable_in_asset` to `withdraw` (Option B). Both support partial claims.

    <Tabs>
      <Tab title="Option A: claim by share amount">
        ```solidity Solidity theme={null}
        function redeem(
            uint256 shares,
            address receiver,
            address controller
        ) external returns (uint256 assets);
        ```

        **Parameters:**

        * `shares`: the number of shares to claim, in share min units. Must be `<=` `pending_redemptions.claimable_in_shares` from the `/positions` response. Pass any positive value `<=` claimable for partial claims.
        * `receiver`: the address that receives the underlying assets.
        * `controller`: the request controller.

        **Returns:**

        * `assets`: the asset amount transferred to `receiver`, computed at the epoch's snapshot share price net of the exit fee that was active at settlement.
      </Tab>

      <Tab title="Option B: claim by asset amount">
        ```solidity Solidity theme={null}
        function withdraw(
            uint256 assets,
            address receiver,
            address controller
        ) external returns (uint256 shares);
        ```

        **Parameters:**

        * `assets`: the asset amount to claim, in the asset's min units. Must be `<=` `pending_redemptions.claimable_in_asset` from the `/positions` response. Pass any positive value `<=` claimable for partial claims.
        * `receiver`: the address that receives the underlying assets.
        * `controller`: the request controller.

        **Returns:**

        * `shares`: the number of shares consumed from the claimable balance to release `assets`, at the epoch's snapshot price net of exit fee.
      </Tab>
    </Tabs>

    The redemption price is locked at settlement time, regardless of when the user claims.

    <Note>
      Some strategies are configured to auto-claim assets on behalf of redeemers during settlement. In that case, assets appear in `receiver`'s wallet without your platform calling `redeem` or `withdraw`. Always check `balanceOf(receiver)` (in the underlying asset) or `pending_redemptions.claimable_in_asset` from `/positions` before prompting the user to claim.
    </Note>
  </Step>
</Steps>

**Example (TypeScript, ethers v6):**

```typescript TypeScript theme={null}
import { Contract } from 'ethers'

const VAULT_ABI = [
  'function requestRedeem(uint256 shares, address controller, address owner) returns (uint256)',
  'function redeem(uint256 shares, address receiver, address controller) returns (uint256)',
]

const vault = new Contract(VAULT_ADDRESS, VAULT_ABI, signer)
const owner: string = await signer.getAddress()
const shares: bigint = USER_SHARES_TO_REDEEM
const vaultId = `${CHAIN_ID}_${VAULT_ADDRESS.toLowerCase()}`

// 1. Submit the redeem request. controller = owner since the user claims to themselves.
//    No need to capture the requestId: the Reporting API tracks settlement state by wallet.
await (await vault.requestRedeem(shares, owner, owner)).wait()

// 2. Poll the Reporting API /positions endpoint to detect settlement.
async function getClaimableShares(): Promise<bigint> {
  const url = `https://api.railnet.org/v1/railnet/async-vaults/positions?vault_ids=${vaultId}&wallets=${owner}`
  const res = await fetch(url)
  const { data } = await res.json()
  if (!data?.length) return 0n
  return BigInt(data[0].pending_redemptions.claimable_in_shares)
}

let claimableShares: bigint = await getClaimableShares()
while (claimableShares === 0n) {
  await new Promise((r) => setTimeout(r, 30_000))
  claimableShares = await getClaimableShares()
}

// 3. Claim the assets. Pass the exact share amount returned by /positions.
await (await vault.redeem(claimableShares, owner, owner)).wait()
```

## Next steps

<CardGroup cols={2}>
  <Card title="Advanced Strategies overview" icon="layer-group" href="/strategies/advanced">
    Architecture, vault lifecycle, NAV reporting, fee structure, and roles.
  </Card>

  <Card title="Vault 7540 mechanics" icon="vault" href="/strategies/advanced/vault">
    How the 7540 Vault enforces sync vs. async paths and settles requests.
  </Card>

  <Card title="Reporting API reference" icon="book" href="https://docs.api.railnet.org/#tag/Async-Vaults/">
    Full request and response schemas for vault, position, and operations endpoints.
  </Card>

  <Card title="Integrate a Conduit" icon="plug" href="/developers/integrate-conduit">
    Build an earn experience on top of a Railnet Conduit instead.
  </Card>
</CardGroup>
