Skip to main content
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.
While Railnet is not fully live, the integration and distribution of Advanced Strategies is done directly from the Vault 7540.
For a detailed overview of how Advanced Strategies work (architecture, vault lifecycle, NAV reporting, fee structure, roles), see Advanced Strategies.

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.
NetworkChain IDAssetStrategy7540 Vault address
Ethereum1WBTCWBTC Test Yield0x4255f01607F785dE275b8ec1255f3a938dA17f63
Ethereum84532USDCUSDC Test Yield0x7baeb9f29a284567b1c85b2469f456911bca1f81

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

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.

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.
GET
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 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:
cURL
curl 'https://api.railnet.org/v1/railnet/async-vaults/vaults?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828'
Example response:
JSON
{
  "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"
    }
  ]
}
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.

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.
GET
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.
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 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:
cURL
curl 'https://api.railnet.org/v1/railnet/async-vaults/operations?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828'
Example response (truncated to one example per event type):
JSON
{
  "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"
    }
  ]
}
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.

Get user positions

To display a user’s position inside a vault (current balance, rewards earned, pending deposits, pending redemptions), query the /positions endpoint.
GET
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:
cURL
curl 'https://api.railnet.org/v1/railnet/async-vaults/positions?vault_ids=1_0x8b21Fa55e3AFC537dE8ABB74b2068746c676c828&wallets=0x7A34B9fB6FCD8Ef11360D60e89A19903233f2630'
Example response:
JSON
{
  "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.
GET
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, 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:
cURL
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
{
  "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"
    }
  ]
}
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.

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

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

Check the current allowance

Call allowance on the underlying ERC-20 asset contract:
Solidity
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.
2

Request approval if insufficient

Prompt the user to send an approval transaction to the underlying asset contract:
Solidity
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.

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
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.
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.
Example (TypeScript, ethers v6):
TypeScript
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.
1

Submit the request

Solidity
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.
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.
2

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

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: 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 requestIds yourself.
GET
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.
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 and watch for a row with type = "settlement" and an epoch_id matching your request.
4

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.
Solidity
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.
The share price used for the claim is locked at settlement time, regardless of when the user claims.
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.
Example (TypeScript, ethers v6):
TypeScript
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.
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.
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
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 and read its balance with the standard ERC-20 balanceOf view on the deposit asset contract:
Solidity
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
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
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.
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.
Example (TypeScript, ethers v6):
TypeScript
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()
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.

Asynchronous redemption

When sync redemption is unavailable (or you simply prefer the async path: lower fees, no haircut), use the request → settle → claim flow.
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.
1

Submit the request

Solidity
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).
2

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: 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 requestIds yourself.
GET
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.
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 and watch for a row with type = "settlement" and an epoch_id matching your request.
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.
3

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.
Solidity
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.
The redemption price is locked at settlement time, regardless of when the user claims.
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.
Example (TypeScript, ethers v6):
TypeScript
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

Advanced Strategies overview

Architecture, vault lifecycle, NAV reporting, fee structure, and roles.

Vault 7540 mechanics

How the 7540 Vault enforces sync vs. async paths and settles requests.

Reporting API reference

Full request and response schemas for vault, position, and operations endpoints.

Integrate a Conduit

Build an earn experience on top of a Railnet Conduit instead.