> For the complete documentation index, see [llms.txt](https://docs.lagoon.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.lagoon.finance/developer-hub/integration/async-deposit-flow.md).

# Async flow

Every call of an asynchronous vault position, from deposit request to exit, with the balance changes and gotchas of each.

A Lagoon vault is an [ERC-7540](https://eips.ethereum.org/EIPS/eip-7540) asynchronous vault: deposits and redemptions are requested, then settled by the curator at a single valuation point, then claimed. This page lists every call of that lifecycle, with the token movements and edge cases of each. It describes **v0.6.0**; where **v0.5.x** behaves differently, a tab shows both.

## Contracts involved

| Contract             | Address            | Role                                                                                                            |
| -------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
| **Vault**            | One per vault      | ERC-7540 vault and ERC-20 share token, at the same address. Every call on this page except `approve` targets it |
| **Underlying asset** | `vault.asset()`    | ERC-20 the vault is denominated in. Only called for `approve`                                                   |
| **pendingSilo**      | Owned by the vault | Escrows pending requests. Never called by users, but the counterparty of several transfers below                |

Full signatures are in the [Smart Contract Reference](/developer-hub/smart-contract-reference.md#anyone-user-facing).

{% hint style="info" %}
Request ids are epoch-based. Pass `0` as a wildcard to the request views, e.g. `pendingDepositRequest(0, user)`. [Why 0?](https://eips.ethereum.org/EIPS/eip-7540#request-ids)
{% endhint %}

## Position lifecycle

{% stepper %}
{% step %} <mark style="color:$primary;">**Request a deposit**</mark>

<sup>**Assets are escrowed until the curator settles the epoch**</sup>

| Contract         | Method                                                                                                                                   | Balance change                                                                                                                                                                                                                                                     |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Underlying asset | `approve(vault, amount)`                                                                                                                 | none                                                                                                                                                                                                                                                               |
| Vault            | [`requestDeposit(uint256 assets, address controller, address owner)`](/developer-hub/smart-contract-reference.md#requestdeposit)         | Asset: `owner` → pendingSilo. No share yet                                                                                                                                                                                                                         |
| Vault            | Same, with native value (`msg.value != 0`)                                                                                               | Native balance leaves `msg.sender` (the user, or an operator acting for `owner`) with no ERC-20 Transfer. The pendingSilo wraps it with `deposit()` on the wrapped native token, which emits that token's own event (`Deposit` on canonical WETH9, not a Transfer) |
| Vault            | [`requestDeposit(assets, controller, owner, address referral)`](/developer-hub/smart-contract-reference.md#requestdeposit-with-referral) | Same as above, plus a `Referral` event (on v0.6.0, only when `referral` is not `address(0)`)                                                                                                                                                                       |

**Reverts when**

{% tabs %}
{% tab title="v0.6.0" %}

| Condition                                                                                                                                                                           | Error                    |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `owner`, `controller` or `msg.sender` fails [`isAllowed`](/developer-hub/smart-contract-reference.md#isallowed) (not whitelisted, blacklisted or sanctioned)                        | `AddressNotAllowed`      |
| `totalAssets()` + pending deposits (underlying held by the pendingSilo) + `assets` > `maxCap()`                                                                                     | `MaxCapReached`          |
| An earlier deposit request is still waiting for settlement: a valuation was already proposed for its epoch. A new request in the current epoch is added to the existing one instead | `OnlyOneRequestAllowed`  |
| Native value sent but the underlying is not the wrapped native token                                                                                                                | `CantDepositNativeToken` |
| Vault paused                                                                                                                                                                        | `EnforcedPause`          |
| {% endtab %}                                                                                                                                                                        |                          |

{% tab title="v0.5.x" %}

| Condition                                                                                                                                                                           | Error                    |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| Whitelist enabled and an involved address not whitelisted                                                                                                                           | `NotWhitelisted`         |
| An earlier deposit request is still waiting for settlement: a valuation was already proposed for its epoch. A new request in the current epoch is added to the existing one instead | `OnlyOneRequestAllowed`  |
| The valuation is still valid (`isTotalAssetsValid()` is `true`): use the [synchronous flow](/developer-hub/integration/synchronous-deposit-flow.md)                                 | `OnlySyncDepositAllowed` |
| Native value sent but the underlying is not the wrapped native token                                                                                                                | `CantDepositNativeToken` |
| Vault paused                                                                                                                                                                        | `EnforcedPause`          |

There is no deposit cap before v0.6.0.
{% endtab %}
{% endtabs %}

**Notes**

* `controller` owns the request and claims the shares; `owner` is the address the assets are pulled from. For a user depositing for themselves, both are the user's address.
* A claimable (already settled) request does not block a new one: `requestDeposit` claims it first, so the call can also deliver previously settled shares.
* With native value, `assets` is ignored and replaced by `msg.value`, and no approval is needed. The user is credited, and later paid back, in the wrapped token.
* USDT-like tokens revert when an allowance moves from one non-zero value to another: send `approve(vault, 0)` first.

{% hint style="warning" %}
The pending amount is internal epoch accounting, not a token balance. Read it with [`pendingDepositRequest(0, user)`](/developer-hub/smart-contract-reference.md#pendingdepositrequest).
{% endhint %}
{% endstep %}

{% step %} <mark style="color:$primary;">**Claim the shares**</mark>

<sup>**Available once the curator settles the deposit epoch**</sup>

| Contract | Method                                                                                                                                | Balance change                                      |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| Vault    | [`deposit(uint256 assets, address receiver, address controller)`](/developer-hub/smart-contract-reference.md#deposit-with-controller) | Shares: vault → `receiver` (a Transfer, not a mint) |
| Vault    | [`mint(uint256 shares, address receiver, address controller)`](/developer-hub/smart-contract-reference.md#mint-with-controller)       | Same, expressed in shares                           |

**Reverts when**

| Condition                                                                                                                                                                                                                       | Error                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| The epoch is not settled yet                                                                                                                                                                                                    | `RequestIdNotClaimable`                        |
| Caller is not `controller` or one of its operators                                                                                                                                                                              | `ERC7540InvalidOperator`                       |
| `controller`, `receiver` or `msg.sender` fails [`isAllowed`](/developer-hub/smart-contract-reference.md#isallowed), the super operator being exempt. On v0.5.1, `controller` or `receiver` not whitelisted; v0.5.0 has no check | `AddressNotAllowed` (v0.5.1: `NotWhitelisted`) |
| Vault paused                                                                                                                                                                                                                    | `EnforcedPause`                                |

**Notes**

* `assets` must be at most [`claimableDepositRequest(0, user)`](/developer-hub/smart-contract-reference.md#claimabledepositrequest), which is denominated in assets, not shares. Partial claims are allowed.
* Settlement is a curator action with no time guarantee.
* Claiming is not required to start earning. `requestDeposit` and `claimSharesAndRequestRedeem` both claim implicitly.
* v0.6.0 deducts an entry fee from the shares, at the rate in force at the settled epoch. v0.5.x has no entry fee.

{% hint style="warning" %}
Shares are minted to the vault at settlement, so until the claim the user's `balanceOf` does not include them. Display `claimableDepositRequest(0, user)` as a separate "claimable" state.
{% endhint %}

{% hint style="success" %}
The curator can push settled shares to users with [`claimSharesOnBehalf(address[] controllers)`](/developer-hub/smart-contract-reference.md#claimsharesonbehalf), so they never send a claim transaction themselves.
{% endhint %}
{% endstep %}

{% step %} <mark style="color:$primary;">**Request a redemption**</mark>

<sup>**Shares are escrowed until the curator settles the redeem epoch**</sup>

| Contract | Method                                                                                                                          | Balance change                                                                                                                            |
| -------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Vault    | [`claimSharesAndRequestRedeem(uint256 sharesToRedeem)`](/developer-hub/smart-contract-reference.md#claimsharesandrequestredeem) | Shares: user → pendingSilo. A claimable deposit (shares: vault → user) and a claimable redemption (asset: vault → user) are claimed first |
| Vault    | [`requestRedeem(uint256 shares, address controller, address owner)`](/developer-hub/smart-contract-reference.md#requestredeem)  | Shares: `owner` → pendingSilo. A claimable redemption of `controller` is claimed first (asset: vault → `controller`)                      |

**Reverts when**

{% tabs %}
{% tab title="v0.6.0" %}

| Condition                                                                                                                                                                          | Error                   |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| An earlier redeem request is still waiting for settlement: a valuation was already proposed for its epoch. A new request in the current epoch is added to the existing one instead | `OnlyOneRequestAllowed` |
| `owner`, `controller` or `msg.sender` fails [`isAllowed`](/developer-hub/smart-contract-reference.md#isallowed) (the super operator is exempt)                                     | `AddressNotAllowed`     |
| Vault not `Open` (closing or closed)                                                                                                                                               | `NotOpen(state)`        |
| Vault paused                                                                                                                                                                       | `EnforcedPause`         |
| {% endtab %}                                                                                                                                                                       |                         |

{% tab title="v0.5.x" %}

| Condition                                                                                                                                                                          | Error                   |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| An earlier redeem request is still waiting for settlement: a valuation was already proposed for its epoch. A new request in the current epoch is added to the existing one instead | `OnlyOneRequestAllowed` |
| Whitelist enabled and `owner` or `controller` not whitelisted (v0.5.0 checks only `owner`)                                                                                         | `NotWhitelisted`        |
| Vault not `Open` (closing or closed)                                                                                                                                               | `NotOpen(state)`        |
| Vault paused                                                                                                                                                                       | `EnforcedPause`         |
| {% endtab %}                                                                                                                                                                       |                         |
| {% endtabs %}                                                                                                                                                                      |                         |

**Notes**

* No approval is needed: the vault moves its own share token.
* `claimSharesAndRequestRedeem` claims any claimable deposit, then requests the redemption, so the user can redeem shares they have not claimed yet. With nothing claimable it behaves exactly like `requestRedeem(shares, msg.sender, msg.sender)`.
* Read the pending amount with [`pendingRedeemRequest(0, user)`](/developer-hub/smart-contract-reference.md#pendingredeemrequest).
* Both calls claim a claimable redemption implicitly, so a new request can also deliver previously settled assets.
  {% endstep %}

{% step %} <mark style="color:$primary;">**Claim the assets**</mark>

<sup>**Available once the curator settles the redeem epoch**</sup>

| Contract | Method                                                                                                                  | Balance change                                                         |
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Vault    | [`redeem(uint256 shares, address receiver, address controller)`](/developer-hub/smart-contract-reference.md#redeem)     | Asset: vault → `receiver`. The shares were already burnt at settlement |
| Vault    | [`withdraw(uint256 assets, address receiver, address controller)`](/developer-hub/smart-contract-reference.md#withdraw) | Same, expressed in assets                                              |

**Reverts when**

| Condition                                                                                                                                                                                                                       | Error                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| The epoch is not settled yet                                                                                                                                                                                                    | `RequestIdNotClaimable`                        |
| Caller is not `controller` or one of its operators                                                                                                                                                                              | `ERC7540InvalidOperator`                       |
| `controller`, `receiver` or `msg.sender` fails [`isAllowed`](/developer-hub/smart-contract-reference.md#isallowed), the super operator being exempt. On v0.5.1, `controller` or `receiver` not whitelisted; v0.5.0 has no check | `AddressNotAllowed` (v0.5.1: `NotWhitelisted`) |
| Vault paused                                                                                                                                                                                                                    | `EnforcedPause`                                |

**Notes**

* `shares` must be at most [`claimableRedeemRequest(0, user)`](/developer-hub/smart-contract-reference.md#claimableredeemrequest). Partial claims are allowed.

{% tabs %}
{% tab title="v0.6.0" %}

* The exit fee uses the rate in force at the settled epoch, not the current one.
* The curator can push settled assets to users with [`claimAssetsOnBehalf(address[] controllers)`](/developer-hub/smart-contract-reference.md#claimassetsonbehalf).
  {% endtab %}

{% tab title="v0.5.x" %}

* There is no exit fee.
* The curator cannot claim assets on behalf of users: the user, or an operator they approved with `setOperator`, claims them.
  {% endtab %}
  {% endtabs %}
  {% endstep %}
  {% endstepper %}

## Cancel a pending request

{% tabs %}
{% tab title="v0.6.0" %}

| Contract | Method                                                                                                                  | Balance change                              |
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| Vault    | [`cancelRequestDeposit()`](/developer-hub/smart-contract-reference.md#cancelrequestdeposit)                             | Asset: pendingSilo → user                   |
| Vault    | [`cancelRequestDeposit(address controller)`](/developer-hub/smart-contract-reference.md#cancelrequestdeposit-on-behalf) | Same, called by an operator of `controller` |
| Vault    | [`cancelRequestRedeem(address controller)`](/developer-hub/smart-contract-reference.md#cancelrequestredeem)             | Shares: pendingSilo → `controller`          |

**Reverts when**

| Condition                                                                                                   | Error                             |
| ----------------------------------------------------------------------------------------------------------- | --------------------------------- |
| The valuation manager already called `updateNewTotalAssets` for the request's epoch                         | `RequestNotCancelable(requestId)` |
| Deposit cancel only: `controller` fails [`isAllowed`](/developer-hub/smart-contract-reference.md#isallowed) | `AddressNotAllowed`               |
| Operator variants: caller is not `controller`, one of its operators or the super operator                   | `ERC7540InvalidOperator`          |
| Vault paused                                                                                                | `EnforcedPause`                   |
| {% endtab %}                                                                                                |                                   |

{% tab title="v0.5.x" %}

| Contract | Method                                                                                      | Balance change            |
| -------- | ------------------------------------------------------------------------------------------- | ------------------------- |
| Vault    | [`cancelRequestDeposit()`](/developer-hub/smart-contract-reference.md#cancelrequestdeposit) | Asset: pendingSilo → user |

A redemption request cannot be cancelled, and there is no operator variant.

**Reverts when**

| Condition                                                                           | Error                             |
| ----------------------------------------------------------------------------------- | --------------------------------- |
| The valuation manager already called `updateNewTotalAssets` for the request's epoch | `RequestNotCancelable(requestId)` |
| Caller not whitelisted (v0.5.1 only; v0.5.0 has no check)                           | `NotWhitelisted`                  |
| Vault paused                                                                        | `EnforcedPause`                   |
| {% endtab %}                                                                        |                                   |
| {% endtabs %}                                                                       |                                   |

{% hint style="info" %}
To know whether a request is cancellable, read `isCancelable` on `pendingDeposit` or `pendingRedeem` from the [Lagoon API](#reading-requests-with-the-lagoon-api). The flag only applies the epoch rule: a paused vault or a failed access check still blocks the cancel.
{% endhint %}

## Exit a closed vault

When the curator closes a vault, every position becomes synchronously redeemable at the final price. There are no more epochs.

| Contract | Method                                                                                                                  | Balance change                                          |
| -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| Vault    | [`redeem(uint256 shares, address receiver, address controller)`](/developer-hub/smart-contract-reference.md#redeem)     | Shares of `controller` burnt. Asset: vault → `receiver` |
| Vault    | [`withdraw(uint256 assets, address receiver, address controller)`](/developer-hub/smart-contract-reference.md#withdraw) | Same, expressed in assets                               |

**Notes**

* These are the same methods as **Claim the assets**. On a closed vault with nothing claimable (`claimableRedeemRequest(0, controller) == 0`) they burn and pay out synchronously; otherwise they claim the settled request.
* The exit fee still applies (v0.6.0; v0.5.x has no exit fee).
* A user with a settled request on a closed vault must claim it first, then redeem the remainder.

## Reading requests with the Lagoon API

The request views and the cancel check are also available from the [Lagoon API](/developer-hub/lagoon-api.md): one query returns them for every vault the user holds on a chain. Pick the entry whose `vault.address` matches.

```graphql
query GetUserRequests($address: Address!, $chainId: Int!) {
  userByAddress(address: $address, chainId: $chainId) {
    vaultPositions {
      vault {
        address
      }
      state {
        pendingDeposit {
          assets
          isCancelable
        }
        claimableDeposit {
          assets
          shares
        }
        pendingRedeem {
          shares
          isCancelable
        }
        claimableRedeem {
          assets
          shares
        }
      }
    }
  }
}
```

[**Try in Apollo Sandbox →**](https://api.lagoon.finance/query?document=query%20GetUserRequests%28%24address%3A%20Address%21%2C%20%24chainId%3A%20Int%21%29%20%7B%0A%20%20userByAddress%28address%3A%20%24address%2C%20chainId%3A%20%24chainId%29%20%7B%0A%20%20%20%20vaultPositions%20%7B%0A%20%20%20%20%20%20vault%20%7B%0A%20%20%20%20%20%20%20%20address%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20state%20%7B%0A%20%20%20%20%20%20%20%20pendingDeposit%20%7B%0A%20%20%20%20%20%20%20%20%20%20assets%0A%20%20%20%20%20%20%20%20%20%20isCancelable%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20claimableDeposit%20%7B%0A%20%20%20%20%20%20%20%20%20%20assets%0A%20%20%20%20%20%20%20%20%20%20shares%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20pendingRedeem%20%7B%0A%20%20%20%20%20%20%20%20%20%20shares%0A%20%20%20%20%20%20%20%20%20%20isCancelable%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20claimableRedeem%20%7B%0A%20%20%20%20%20%20%20%20%20%20assets%0A%20%20%20%20%20%20%20%20%20%20shares%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D)

```bash
curl -sS -X POST https://api.lagoon.finance/query \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetUserRequests($address: Address!, $chainId: Int!) { userByAddress(address: $address, chainId: $chainId) { vaultPositions { vault { address } state { pendingDeposit { assets isCancelable } claimableDeposit { assets shares } pendingRedeem { shares isCancelable } claimableRedeem { assets shares } } } } }",
    "variables": { "address": "0xUSER", "chainId": 1 }
  }'
```

| Field                         | Used in                                            | On-chain equivalent                                                                     |
| ----------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------- |
| `pendingDeposit.assets`       | Request a deposit                                  | `pendingDepositRequest(0, user)`                                                        |
| `pendingDeposit.isCancelable` | Cancel a pending request                           | — (no view function: cancellable until a valuation is proposed for the request's epoch) |
| `claimableDeposit.assets`     | Claim the shares, `assets` argument of `deposit`   | `claimableDepositRequest(0, user)`                                                      |
| `claimableDeposit.shares`     | Claim the shares, `shares` argument of `mint`      | `maxMint(user)`                                                                         |
| `pendingRedeem.shares`        | Request a redemption                               | `pendingRedeemRequest(0, user)`                                                         |
| `pendingRedeem.isCancelable`  | Cancel a pending request. Always `false` on v0.5.x | — (same rule)                                                                           |
| `claimableRedeem.shares`      | Claim the assets, `shares` argument of `redeem`    | `claimableRedeemRequest(0, user)`                                                       |
| `claimableRedeem.assets`      | Claim the assets, `assets` argument of `withdraw`  | `maxWithdraw(user)`                                                                     |

Amounts are in base units. Large ones are returned as strings, so wrap them in `BigInt(...)`:

```typescript
const res = await fetch("https://api.lagoon.finance/query", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query: GET_USER_REQUESTS, variables: { address: user, chainId } }),
});
const positions = (await res.json()).data.userByAddress?.vaultPositions ?? [];
const position = positions.find((p) => p.vault.address === vault.toLowerCase());

const { claimableDeposit, claimableRedeem, pendingRedeem } = position?.state ?? {};

if (claimableDeposit && BigInt(claimableDeposit.assets) > 0n) {
  await client.writeContract({
    abi: vaultAbi,
    address: vault,
    functionName: "deposit",
    args: [BigInt(claimableDeposit.assets), user, user],
  });
}

if (claimableRedeem && BigInt(claimableRedeem.shares) > 0n) {
  await client.writeContract({
    abi: vaultAbi,
    address: vault,
    functionName: "redeem",
    args: [BigInt(claimableRedeem.shares), user, user],
  });
}

const showCancelRedeem = pendingRedeem?.isCancelable === true;
```
