---
eip: 7540
title: Asynchronous ERC-4626 Tokenized Vaults
description: Extension of ERC-4626 with asynchronous deposit and redemption support
author: Jeroen Offerijns (@hieronx), Alina Sinelnikova (@ilinzweilin), Vikram Arun (@vikramarun), Joey Santoro (@joeysantoro), Farhaan Ali (@0xfarhaan), João Martins (@0xTimepunk)
discussions-to: https://ethereum-magicians.org/t/eip-7540-asynchronous-erc-4626-tokenized-vaults/16153
status: Final
type: Standards Track
category: ERC
created: 2023-10-18
requires: 20, 165, 4626, 7575
---

## Abstract

The following standard extends [ERC-4626](./eip-4626.md) by adding support for asynchronous deposit and redemption flows. The async flows are called Requests.

New methods are added to asynchronously Request a deposit or redemption, and view the status of the Request. The existing `deposit`, `mint`, `withdraw`, and `redeem` ERC-4626 methods are used for executing Claimable Requests. 

Implementations can choose whether to add asynchronous flows for deposits, redemptions, or both. 

## Motivation

The ERC-4626 Tokenized Vaults standard has helped to make yield-bearing tokens more composable across decentralized finance. The standard is optimized for atomic deposits and redemptions up to a limit. If the limit is reached, no new deposits or redemptions can be submitted.

This limitation does not work well for any smart contract system with asynchronous actions or delays as a prerequisite for interfacing with the Vault (e.g. real-world asset protocols, undercollateralized lending protocols, cross-chain lending protocols, liquid staking tokens, or insurance safety modules). 

This standard expands the utility of ERC-4626 Vaults for asynchronous use cases. The existing Vault interface (`deposit`/`withdraw`/`mint`/`redeem`) is fully utilized to claim asynchronous Requests.

## Specification

### Definitions:

The existing definitions from [ERC-4626](./eip-4626.md) apply. In addition, this spec defines:

- Request: a request to enter (`requestDeposit`) or exit (`requestRedeem`) the Vault
- Pending: the state where a Request has been made but is not yet Claimable
- Claimable: the state where a Request is processed by the Vault enabling the user to claim corresponding `shares` (for async deposit) or `assets` (for async redeem)
- Claimed: the state where a Request is finalized by the user and the user receives the output token (e.g. `shares` for a deposit Request)
- Claim function: the corresponding Vault method to bring a Request to Claimed state (e.g. `deposit` or `mint` claims `shares` from `requestDeposit`). Lowercase claim always describes the verb action of calling a Claim function.
- asynchronous deposit Vault: a Vault that implements asynchronous Requests for deposit flows
- asynchronous redemption Vault: a Vault that implements asynchronous Requests for redemption flows
- fully asynchronous Vault: a Vault that implements asynchronous Requests for both deposit and redemption flows
- controller: owner of the Request, who can manage any actions related to the Request including claiming the `assets` or `shares`
- operator: an account that can manage Requests on behalf of another account.

### Request Flows

[ERC-7540 Vaults](./eip-7540.md) MUST implement one or both of asynchronous deposit and redemption Request flows. If either flow is not implemented in a Request pattern, it MUST use the ERC-4626 standard synchronous interaction pattern. 

All ERC-7540 asynchronous tokenized Vaults MUST implement ERC-4626 with overrides for certain behavior described below.

Asynchronous deposit Vaults MUST override the ERC-4626 specification as follows:

1. The `deposit` and `mint` methods do not transfer `assets` to the Vault, because this already happened on `requestDeposit`.
2. `previewDeposit` and `previewMint` MUST revert for all callers and inputs.

Asynchronous redeem Vaults MUST override the ERC-4626 specification as follows:

1. The `redeem` and `withdraw` methods do not transfer `shares` to the Vault, because this already happened on `requestRedeem`. 
2. The `owner` field of `redeem` and `withdraw` SHOULD be renamed to `controller`, and the controller MUST be `msg.sender` unless the `controller` has approved the `msg.sender` as an operator.
3. `previewRedeem` and `previewWithdraw` MUST revert for all callers and inputs.

### Request Lifecycle

After submission, Requests go through Pending, Claimable, and Claimed stages. An example lifecycle for a deposit Request is visualized in the table below.

| **State**   | **User**                         | **Vault** |
|-------------|---------------------------------|-----------|
| Pending     | `requestDeposit(assets, controller, owner)` | `asset.transferFrom(owner, vault, assets)`; `pendingDepositRequest[controller] += assets` |
| Claimable   |                                 | *Internal Request fulfillment*:  `pendingDepositRequest[controller] -= assets`; `claimableDepositRequest[controller] += assets` |
| Claimed     | `deposit(assets, receiver, controller)` | `claimableDepositRequest[controller] -= assets`; `vault.balanceOf[receiver] += shares` |

Note that `maxDeposit` increases and decreases in sync with `claimableDepositRequest`.

Requests MUST NOT skip or otherwise short-circuit the Claim state. In other words, to initiate and claim a Request, a user MUST call both request* and the corresponding Claim function separately, even in the same block. Vaults MUST NOT "push" tokens onto the user after a Request, users MUST "pull" the tokens via the Claim function.

For asynchronous Vaults, the exchange rate between `shares` and `assets` including fees and yield is up to the Vault implementation. In other words, pending redemption Requests MAY NOT be yield-bearing and MAY NOT have a fixed exchange rate.

### Request Ids
The request ID (`requestId`) of a request is returned by the corresponding `requestDeposit` and `requestRedeem` functions.

Multiple requests may have the same `requestId`, so a given Request is discriminated by both the `requestId` and the `controller`. 

Requests of the same `requestId` MUST be fungible with each other (except in the special case `requestId == 0` described below). I.e. all Requests with the same `requestId` MUST transition from Pending to Claimable at the same time and receive the same exchange rate between `assets` and `shares`. If a Request with `requestId != 0` becomes partially claimable, all requests of the same `requestId` MUST become claimable at the same pro-rata rate.

There are no assumptions or requirements of requests with different `requestId`. I.e. they MAY transition to Claimable at different times and exchange rates with no ordering or correlation enforced in any way.

When `requestId==0`, the Vault MUST use purely the `controller` to discriminate the request state. The Pending and Claimable state of multiple requests from the same `controller` would be aggregated. If a Vault returns `0` for the `requestId` of any request, it MUST return `0` for all requests.

### Methods

#### requestDeposit

Transfers `assets` from `owner` into the Vault and submits a Request for asynchronous `deposit`. This places the Request in Pending state, with a corresponding increase in `pendingDepositRequest` for the amount `assets`. 

The output `requestId` is used to partially discriminate the request along with the `controller`. See [Request Ids](#request-ids) section for more info.

When the Request is Claimable, `claimableDepositRequest` will be increased for the `controller`. `deposit` or `mint` can subsequently be called by `controller` to receive `shares`. A Request MAY transition straight to Claimable state but MUST NOT skip the Claimable state.

The `shares` that will be received on `deposit` or `mint` MAY NOT be equivalent to the value of `convertToShares(assets)` at the time of Request, as the price can change between Request and Claim.

MUST support [ERC-20](./eip-20.md) `approve` / `transferFrom` on `asset` as a deposit Request flow.

`owner` MUST equal `msg.sender` unless the `owner` has approved the `msg.sender` as an operator.

MUST revert if all of `assets` cannot be requested for `deposit`/`mint` (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc).

Note that most implementations will require pre-approval of the Vault with the Vault's underlying `asset` token.

MUST emit the `DepositRequest` event.

```yaml
- name: requestDeposit
  type: function
  stateMutability: nonpayable

  inputs:
    - name: assets
      type: uint256
    - name: controller
      type: address
    - name: owner
      type: address
  outputs:
    - name: requestId
      type: uint256
```

#### pendingDepositRequest

The amount of requested `assets` in Pending state for the `controller` with the given `requestId` to `deposit` or `mint`.

MUST NOT include any `assets` in Claimable state for `deposit` or `mint`.

MUST NOT show any variations depending on the caller.

MUST NOT revert unless due to integer overflow caused by an unreasonably large input.

```yaml
- name: pendingDepositRequest
  type: function
  stateMutability: view

  inputs:
    - name: requestId
      type: uint256
    - name: controller
      type: address

  outputs:
    - name: assets
      type: uint256
```

#### claimableDepositRequest

The amount of requested `assets` in Claimable state for the `controller` with the given `requestId` to `deposit` or `mint`.

MUST NOT include any `assets` in Pending state for `deposit` or `mint`.

MUST NOT show any variations depending on the caller.

MUST NOT revert unless due to integer overflow caused by an unreasonably large input.

```yaml
- name: claimableDepositRequest
  type: function
  stateMutability: view

  inputs:
    - name: requestId
      type: uint256
    - name: controller
      type: address

  outputs:
    - name: assets
      type: uint256
```

#### requestRedeem

Assumes control of `shares` from `owner` and submits a Request for asynchronous `redeem`. This places the Request in Pending state, with a corresponding increase in `pendingRedeemRequest` for the amount `shares`. 

The output `requestId` is used to discriminate the request along with the `controller`. See [Request Ids](#request-ids) section for more info.

`shares` MAY be temporarily locked in the Vault until the Claimable or Claimed state for accounting purposes, or they MAY be burned immediately upon `requestRedeem`. 

In either case, the `shares` MUST be removed from the custody of `owner` upon `requestRedeem` and burned by the time the request is Claimed.

Redeem Request approval of `shares` for a `msg.sender` NOT equal to `owner` may come either from ERC-20 approval over the `shares` of `owner` or if the `owner` has approved the `msg.sender` as an operator. This MUST be consistent with similar behaviour pointed out in [ERC-6909](./eip-6909.md), within "Approvals and Operators" section: "In accordance with the transferFrom method, spenders with operator permission are not subject to allowance restrictions, spenders with infinite approvals SHOULD NOT have their allowance deducted on delegated transfers, but spenders with non-infinite approvals MUST have their balance deducted on delegated transfers."

When the Request is Claimable, `claimableRedeemRequest` will be increased for the `controller`. `redeem` or `withdraw` can subsequently be called by `controller` to receive `assets`. A Request MAY transition straight to Claimable state but MUST NOT skip the Claimable state.

The `assets` that will be received on `redeem` or `withdraw` MAY NOT be equivalent to the value of `convertToAssets(shares)` at the time of Request, as the price can change between Pending and Claimed.

MUST revert if all of `shares` cannot be requested for `redeem` / `withdraw` (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc).

MUST emit the `RedeemRequest` event.

```yaml
- name: requestRedeem
  type: function
  stateMutability: nonpayable

  inputs:
    - name: shares
      type: uint256
    - name: controller
      type: address
    - name: owner
      type: address
  outputs:
    - name: requestId
    - type: uint256
```

#### pendingRedeemRequest

The amount of requested `shares` in Pending state for the `controller` with the given `requestId` to `redeem` or `withdraw`.

MUST NOT include any `shares` in Claimable state for `redeem` or `withdraw`.

MUST NOT show any variations depending on the caller.

MUST NOT revert unless due to integer overflow caused by an unreasonably large input.

```yaml
- name: pendingRedeemRequest
  type: function
  stateMutability: view

  inputs:
    - name: requestId
      type: uint256
    - name: controller
      type: address

  outputs:
    - name: shares
      type: uint256
```

#### claimableRedeemRequest

The amount of requested `shares` in Claimable state for the `controller` with the given `requestId` to `redeem` or `withdraw`.

MUST NOT include any `shares` in Pending state for `redeem` or `withdraw`.

MUST NOT show any variations depending on the caller.

MUST NOT revert unless due to integer overflow caused by an unreasonably large input.

```yaml
- name: claimableRedeemRequest
  type: function
  stateMutability: view

  inputs:
    - name: requestId
      type: uint256
    - name: controller
      type: address

  outputs:
    - name: shares
      type: uint256
```

#### `isOperator`

Returns `true` if the `operator` is approved as an operator for a `controller`.

```yaml
- name: isOperator
  type: function
  stateMutability: view

  inputs:
    - name: controller
      type: address
    - name: operator
      type: address

  outputs:
    - name: status
      type: bool
```

#### `setOperator`

Grants or revokes permissions for `operator` to manage Requests on behalf of the `msg.sender`.

MUST set the operator status to the `approved` value.

MUST log the `OperatorSet` event.

MUST return True.

```yaml
- name: setOperator
  type: function
  stateMutability: nonpayable

  inputs:
    - name: operator
      type: address
    - name: approved
      type: bool

  outputs:
    - name: success
      type: bool
```

#### `deposit` and `mint` overloaded methods

Implementations MUST support an additional overloaded `deposit` and `mint` method on the specification from [ERC-4626](./eip-4626.md), with an additional `controller` input of type `address`:

- `deposit(uint256 assets, address receiver, address controller)`
- `mint(uint256 shares, address receiver, address controller)`

Calls MUST revert unless `msg.sender` is either equal to `controller` or an operator approved by `controller`.

The `controller` field is used to discriminate the Request for which the `assets` should be claimed in the case where `msg.sender` is NOT `controller`.

When the `Deposit` event is emitted, the first parameter MUST be the `controller`, and the second parameter MUST be the `receiver`.

### Events

#### DepositRequest

`owner` has locked `assets` in the Vault to Request a deposit with request ID `requestId`. `controller` controls this Request. `sender` is the caller of the `requestDeposit` which may not be equal to the `owner`.

MUST be emitted when a deposit Request is submitted using the `requestDeposit` method.

```yaml
- name: DepositRequest
  type: event

  inputs:
    - name: controller
      indexed: true
      type: address
    - name: owner
      indexed: true
      type: address
    - name: requestId
      indexed: true
      type: uint256
    - name: sender
      indexed: false
      type: address
    - name: assets
      indexed: false
      type: uint256
```

#### RedeemRequest

`sender` has locked `shares`, owned by `owner`, in the Vault to Request a redemption. `controller` controls this Request, but is not necessarily the `owner`.

MUST be emitted when a redemption Request is submitted using the `requestRedeem` method.

```yaml
- name: RedeemRequest
  type: event

  inputs:
    - name: controller
      indexed: true
      type: address
    - name: owner
      indexed: true
      type: address
    - name: requestId
      indexed: true
      type: uint256
    - name: sender
      indexed: false
      type: address
    - name: shares
      indexed: false
      type: uint256
```

#### `OperatorSet`

The `controller` has set the `approved` status to an `operator`.

MUST be logged when the operator status is set.

MAY be logged when the operator status is set to the same status it was before the current call.

```yaml
- name: OperatorSet
  type: event

  inputs:
    - name: controller
      indexed: true
      type: address
    - name: operator
      indexed: true
      type: address
    - name: approved
      indexed: false
      type: bool
```

### [ERC-165](./eip-165.md) support

Smart contracts implementing this Vault standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface` function.

All asynchronous Vaults MUST return the constant value `true` if either `0xe3bc4e65` (representing the operator methods that all ERC-7540 Vaults implement) or `0x2f0a18c5` (representing the [ERC-7575](./eip-7575.md) interface) is passed through the `interfaceID` argument.

Asynchronous deposit Vaults MUST return the constant value `true` if `0xce3bbe50` is passed through the `interfaceID` argument.

Asynchronous redemption Vaults MUST return the constant value `true` if `0x620ee8e4` is passed through the `interfaceID` argument.

### [ERC-7575](./eip-7575.md) support

Smart contracts implementing this Vault standard MUST implement the [ERC-7575](./eip-7575.md) standard (in particular the `share` method). 

## Rationale

### Including Request IDs but not including a Claim by ID method
Requests in an Asynchronous Vault have properties of NFTs or Semi-Fungible tokens due to their asynchronicity. However, trying to pigeonhole all ERC-7540 Vaults into supporting [ERC-721](./eip-721) or [ERC-1155](./eip-1155) for Requests would create too much interface bloat. 

Using both an id and address to discriminate Requests allows for any of these use cases to be developed at an external layer without adding too much complexity to the core interface.

Certain Vaults, especially `requestId==0` cases, benefit from using the underlying [ERC-4626](./eip-4626) methods for claiming because there is no discrimination at the `requestId` level. This standard is written primarily with those use cases in mind. A future standard can optimize for nonzero request ID with support for claiming and transferring requests discriminated also with a `requestId`.

### Symmetry and Non-inclusion of requestWithdraw and requestMint

In ERC-4626, the spec was written to be fully symmetrical with respect to converting `assets` and `shares` by including deposit/withdraw and mint/redeem.

Due to the nature of Requests, asynchronous Vaults can only operate with certainty on the quantity that is fully known at the time of the Request (`assets` for `deposit` and `shares` for `redeem`). Therefore the deposit Request flow cannot work with a `mint` call, because the amount of `assets` for the requested `shares` amount may fluctuate before the fulfillment of the Request. Likewise, the redemption Request flow cannot work with a `withdraw` call.

### Optionality of Flows

Certain use cases are only asynchronous on one side of the deposit or redeem Request flow. A good example of an asynchronous redemption Vault is a liquid staking token. The unstaking period necessitates support for asynchronous withdrawals, however, deposits can be fully synchronous.

### Non-inclusion of a Request Cancelation Flow

In many cases, canceling a Request may not be straightforward or even technically feasible. The state transition of cancelations could be synchronous or asynchronous, and the way to claim a cancelation interfaces with the remaining Vault functionality in complex ways.

A separate EIP should be developed to standardize the behavior of cancelling a pending Request. Defining the cancel flow is still important for certain classes of use cases for which the fulfillment of a Request can take a considerable amount of time.

### Request Implementation Flexibility

The standard is flexible enough to support a wide range of interaction patterns for Request flows. Pending Requests can be handled via internal accounting, globally or on per-user levels, use ERC-20 or [ERC-721](./eip-721.md), etc.

Likewise yield on redemption Requests can accrue or not, and the exchange rate of any Request may be fixed or variable depending on the implementation.

### Not Allowing Short-circuiting for Claims

If claims can short-circuit, this creates ambiguity for integrators and complicates the interface with overloaded behavior on Request functions.

An example of a short-circuiting Request flow could be as follows: user triggers a Request which enters Pending state. When the Vault fulfills the Request, the corresponding `assets/shares` are pushed straight to the user. This requires only 1 step on the user's behalf.

This approach has a few issues:
- cost/lack of scalability: as the number of vault users grows it can become intractably expensive to offload the Claim costs to the Vault operator
- hinders integration potential: Vault integrators would need to handle both the 2-step and 1-step cases, with the 1-step pushing arbitrary tokens in from an unknown Request at an unknown time. This pushes complexity out onto integrators and reduces the standard's utility.

The 2-step approach used in the standard may be abstracted into a 1-step approach from the user perspective through the use of routers, relayers, message signing, or account abstraction.

In the case where a Request may become Claimable immediately in the same block, there can be router contracts that atomically check for Claimable amounts immediately upon Request. Frontends can dynamically route Requests in this way depending on the state and implementation of the Vault to handle this edge case.

### No Outputs for Request Functions

`requestDeposit` and `requestRedeem` may not have a known exchange rate that will happen when the Request becomes Claimed. Returning the corresponding `assets` or `shares` could not work in this case.

The Requests could also output a timestamp representing the minimum amount of time expected for the Request to become Claimable, however, not all Vaults will be able to return a reliable timestamp.

### No Event for Claimable State

The state transition of a Request from Pending to Claimable happens at the Vault implementation level and is not specified in the standard. Requests may be batched into the Claimable state, or the state may transition automatically after a timestamp has passed. It is impractical to require an event to emit after a Request becomes Claimable at the user or batch level.

### Reversion of Preview Functions in Async Request Flows

The preview functions do not take an address parameter, therefore the only way to discriminate discrepancies in the exchange rate is via the `msg.sender`. However, this could lead to integration/implementation complexities where support contracts cannot determine the output of a claim on behalf of a `controller`.

In addition, there is no on-chain benefit to previewing the Claim step as the only valid state transition is to Claim anyway. If the output of a Claim is undesirable for any reason, the calling contract can revert on the output of that function call.

It reduces code and implementation complexity at little to no cost to simply mandate reversion for the preview functions of an async flow.

### Mandated Support for [ERC-165](./eip-165.md)

Implementing support for [ERC-165](./eip-165.md) is mandated because of the [optionality of flows](#optionality-of-flows). Integrations can use the `supportsInterface` method to check whether a vault is fully asynchronous, partially asynchronous, or fully synchronous (for which it is just following the [ERC-4626](./eip-4626)), and use a single contract to support all cases.

### Not Allowing Pending Claims to be Fungible
The async pending claims represent a sort of semi-fungible intermediate share class. Vaults can elect to wrap these claims in any token standard they like, for example, ERC-20, [ERC-1155](./eip-1155.md), or ERC-721 depending on the use case. This is intentionally left out of the spec to provide flexibility to implementers.

## Backwards Compatibility

The interface is fully backward compatible with [ERC-4626](./eip-4626.md). The specification of the `deposit`, `mint`, `redeem`, and `withdraw` methods is different as described in [Specification](#specification).

## Reference Implementation

```solidity
    // This code snippet is incomplete pseudocode used for example only and is no way intended to be used in production or guaranteed to be secure

    mapping(address => uint256) public pendingDepositRequest;
    
    mapping(address => uint256) public claimableDepositRequest;

    mapping(address controller => mapping(address operator => bool)) public isOperator;

    function requestDeposit(uint256 assets, address controller, address owner) external returns (uint256 requestId) {
        require(assets != 0);
        require(owner == msg.sender || isOperator[owner][msg.sender]);

        requestId = 0; // no requestId associated with this request

        asset.safeTransferFrom(owner, address(this), assets); // asset here is the Vault underlying asset

        pendingDepositRequest[controller] += assets;

        emit DepositRequest(controller, owner, requestId, msg.sender, assets);
        return requestId;
    }

    /**
     * Include some arbitrary transition logic here from Pending to Claimable
     */

    function deposit(uint256 assets, address receiver, address controller) external returns (uint256 shares) {
        require(assets != 0);
        require(controller == msg.sender || isOperator[controller][msg.sender]);

        claimableDepositRequest[controller] -= assets; // underflow would revert if not enough claimable assets

        shares = convertToShares(assets); // this naive example uses the instantaneous exchange rate. It may be more common to use the rate locked in upon Claimable stage.

        balanceOf[receiver] += shares;

        emit Deposit(controller, receiver, assets, shares);
    }

    function setOperator(address operator, bool approved) public returns (bool) {
        isOperator[msg.sender][operator] = approved;
        emit OperatorSet(msg.sender, operator, approved);
        return true;
    }

```

## Security Considerations

In general, asynchronicity concerns make state transitions in the Vault much more complex and vulnerable to security risks. Access control on Vault operations, clear documentation of state transitions, and invariant checks should all be performed to mitigate these risks. For example:

* The view methods for viewing Pending and Claimable request states (e.g. pendingDepositRequest) are estimates useful for display purposes but can be outdated. The inability to know the final exchange rate on any Request requires users to trust the implementation of the asynchronous Vault in the computation of the exchange rate and fulfillment of their Request.
* Shares or assets locked for Requests can be stuck in the Pending state. Vaults may elect to allow for the fungibility of pending claims or implement some cancellation functionality to protect users.

### Operators

An operator has the ability to transfer the `asset` of the vault from the approver to any address, and simultaneously grants control over the `share` of the vault. 

Any user approving an operator must trust that operator with both the `asset` and `share` of the Vault.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
