Tokens

Set of extensions and utilities for tokens (e.g ERC-20, ERC-721, ERC-1155) and derivated ERCs (e.g. ERC-4626, ERC-1363).

  • OnTokenTransferAdapter: Adapter of the ERC-1363 receiver interface to comply with Chainlink’s 667 interface.

  • ERC20Allowlist: Extension of ERC20 with transfers and approvals that require users to be registered into an allowlist.

  • ERC20Blocklist: Extension of ERC20 with transfers and approvals that can be disabled by adding users into a blocklist.

  • ERC20Collateral: Oracle-agnostic extension of ERC20 that limits the total supply based on a collateral amount.

  • ERC20Custodian: Extension of ERC20 that implements an access-control agnostic approach to define a custodian that can freeze user’s transfers and approvals.

  • ERC20Freezable: Extension of ERC20 that allows an authorized account to freeze a portion of an account’s balance, preventing it from being transferred until unfrozen.

  • ERC20Restricted: Extension of ERC20 that allows implementing user account transfer restrictions (blocklist or allowlist) inspired by EIP-7943.

  • ERC20uRWA: Extension of ERC20 according to EIP-7943, combining standard ERC-20 with RWA-specific features like account restrictions, asset freezing, and forced transfers.

  • ERC4626Fees: ERC4626 vault with fees on entry (deposit/mint) or exit (withdraw/redeem).

  • ERC7540: Implementation of ERC-7540 "Asynchronous ERC-4626 Tokenized Vaults", providing a base for vaults with asynchronous deposit and/or redemption flows.

  • ERC7540AdminDeposit: Admin-controlled (operator-triggered) fulfillment strategy for asynchronous deposits in ERC-7540 vaults.

  • ERC7540AdminRedeem: Admin-controlled (operator-triggered) fulfillment strategy for asynchronous redemptions in ERC-7540 vaults.

  • ERC7540DelayDeposit: Time-delay fulfillment strategy for asynchronous deposits, becoming permissionlessly claimable after a configurable waiting period.

  • ERC7540DelayRedeem: Time-delay fulfillment strategy for asynchronous redemptions, becoming permissionlessly claimable after a configurable waiting period.

  • ERC7540SyncDeposit: Module for enabling synchronous (ERC-4626) deposit flow in an ERC-7540 vault.

  • ERC7540SyncRedeem: Module for enabling synchronous (ERC-4626) redeem flow in an ERC-7540 vault.

  • ERC7540EpochDeposit: Epoch-based batch fulfillment strategy for asynchronous deposits, batching requests submitted in the same epoch and settling them at a single locked rate.

  • ERC7540EpochRedeem: Epoch-based batch fulfillment strategy for asynchronous redemptions, batching requests submitted in the same epoch and settling them at a single locked rate.

General

OnTokenTransferAdapter

import "@openzeppelin/community-contracts/token/OnTokenTransferAdapter.sol";

This contract exposes the 667 onTokenTransfer hook on top of {IERC1363Receiver-onTransferReceived}.

Inheriting from this adapter makes your ERC1363Receiver contract automatically compatible with tokens, such as Chainlink’s Link, that implement the 667 interface for transferAndCall.

Functions
  • onTokenTransfer(from, amount, data)

IERC1363Receiver
  • onTransferReceived(operator, from, value, data)

onTokenTransfer(address from, uint256 amount, bytes data) → bool public

ERC20

ERC20Allowlist

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Allowlist.sol";

Extension of {ERC20} that allows to implement an allowlist mechanism that can be managed by an authorized account with the _disallowUser and _allowUser functions.

The allowlist provides the guarantee to the contract owner (e.g. a DAO or a well-configured multisig) that any account won’t be able to execute transfers or approvals to other entities to operate on its behalf if _allowUser was not called with such account as an argument. Similarly, the account will be disallowed again if _disallowUser is called.

Deprecated. Use ERC20Restricted instead.
Functions
  • allowed(account)

  • _allowUser(user)

  • _disallowUser(user)

  • _update(from, to, value)

  • _approve(owner, spender, value, emitEvent)

ERC20
  • name()

  • symbol()

  • decimals()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _spendAllowance(owner, spender, value)

Events
  • UserAllowed(user)

  • UserDisallowed(user)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
  • ERC20Disallowed(user)

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

allowed(address account) → bool public

Returns the allowed status of an account.

_allowUser(address user) → bool internal

Allows a user to receive and transfer tokens, including minting and burning.

_disallowUser(address user) → bool internal

Disallows a user from receiving and transferring tokens, including minting and burning.

_update(address from, address to, uint256 value) internal

See {ERC20-_update}.

_approve(address owner, address spender, uint256 value, bool emitEvent) internal

See {ERC20-_approve}.

UserAllowed(address indexed user) event

Emitted when a user is allowed to transfer and approve.

UserDisallowed(address indexed user) event

Emitted when a user is disallowed.

ERC20Disallowed(address user) error

The operation failed because the user is not allowed.

ERC20Blocklist

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Blocklist.sol";

Extension of {ERC20} that allows to implement a blocklist mechanism that can be managed by an authorized account with the _blockUser and _unblockUser functions.

The blocklist provides the guarantee to the contract owner (e.g. a DAO or a well-configured multisig) that any account won’t be able to execute transfers or approvals to other entities to operate on its behalf if _blockUser was not called with such account as an argument. Similarly, the account will be unblocked again if _unblockUser is called.

Deprecated. Use ERC20Restricted instead.
Functions
  • blocked(account)

  • _blockUser(user)

  • _unblockUser(user)

  • _update(from, to, value)

  • _approve(owner, spender, value, emitEvent)

ERC20
  • name()

  • symbol()

  • decimals()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _spendAllowance(owner, spender, value)

Events
  • UserBlocked(user)

  • UserUnblocked(user)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
  • ERC20Blocked(user)

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

blocked(address account) → bool public

Returns the blocked status of an account.

_blockUser(address user) → bool internal

Blocks a user from receiving and transferring tokens, including minting and burning.

_unblockUser(address user) → bool internal

Unblocks a user from receiving and transferring tokens, including minting and burning.

_update(address from, address to, uint256 value) internal

See {ERC20-_update}.

_approve(address owner, address spender, uint256 value, bool emitEvent) internal

See {ERC20-_approve}.

UserBlocked(address indexed user) event

Emitted when a user is blocked.

UserUnblocked(address indexed user) event

Emitted when a user is unblocked.

ERC20Blocked(address user) error

The operation failed because the user is blocked.

ERC20Collateral

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Collateral.sol";

Extension of {ERC20} that limits the supply of tokens based on a collateral amount and time-based expiration.

The collateral function must be implemented to return the collateral data. This function can call external oracles or use any local storage.

Functions
  • constructor(liveness_)

  • liveness()

  • clock()

  • CLOCK_MODE()

  • collateral()

  • _update(from, to, value)

ERC20
  • name()

  • symbol()

  • decimals()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
  • ERC20ExceededSupply(increasedSupply, cap)

  • ERC20ExpiredCollateral(timestamp, expiration)

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

constructor(uint48 liveness_) internal

Sets the value of the _liveness. This value is immutable, it can only be set once during construction.

liveness() → uint48 public

Returns the minimum liveness duration of collateral.

clock() → uint48 public

Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting).

CLOCK_MODE() → string public

Description of the clock

collateral() → uint256 amount, uint48 timestamp public

Returns the collateral data of the token.

_update(address from, address to, uint256 value) internal

See {ERC20-_update}.

ERC20ExceededSupply(uint256 increasedSupply, uint256 cap) error

Total supply cap has been exceeded.

ERC20ExpiredCollateral(uint48 timestamp, uint48 expiration) error

Collateral amount has expired.

ERC20Custodian

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Custodian.sol";

Extension of {ERC20} that allows to implement a custodian mechanism that can be managed by an authorized account with the freeze function.

This mechanism allows a custodian (e.g. a DAO or a well-configured multisig) to freeze and unfreeze the balance of a user.

The frozen balance is not available for transfers or approvals to other entities to operate on its behalf if. The frozen balance can be reduced by calling freeze again with a lower amount.

Deprecated. Use ERC20Freezable instead.
Modifiers
  • onlyCustodian()

Functions
  • frozen(user)

  • freeze(user, amount)

  • availableBalance(account)

  • _isCustodian(user)

  • _update(from, to, value)

ERC20
  • name()

  • symbol()

  • decimals()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
  • TokensFrozen(user, amount)

  • TokensUnfrozen(user, amount)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
  • ERC20InsufficientUnfrozenBalance(user)

  • ERC20InsufficientFrozenBalance(user)

  • ERC20NotCustodian()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

onlyCustodian() modifier

Modifier to restrict access to custodian accounts only.

frozen(address user) → uint256 public

Returns the amount of tokens frozen for a user.

freeze(address user, uint256 amount) external

Adjusts the amount of tokens frozen for a user.

availableBalance(address account) → uint256 available public

Returns the available (unfrozen) balance of an account.

_isCustodian(address user) → bool internal

Checks if the user is a custodian.

_update(address from, address to, uint256 value) internal

Transfers a value amount of tokens from from to to, or alternatively mints (or burns) if from (or to) is the zero address. All customizations to transfers, mints, and burns should be done by overriding this function.

Emits a {Transfer} event.

TokensFrozen(address indexed user, uint256 amount) event

Emitted when tokens are frozen for a user.

TokensUnfrozen(address indexed user, uint256 amount) event

Emitted when tokens are unfrozen for a user.

ERC20InsufficientUnfrozenBalance(address user) error

The operation failed because the user has insufficient unfrozen balance.

ERC20InsufficientFrozenBalance(address user) error

The operation failed because the user has insufficient frozen balance.

ERC20NotCustodian() error

Error thrown when a non-custodian account attempts to perform a custodian-only operation.

ERC20Freezable

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Freezable.sol";

Extension of {ERC20} that allows to implement a freezing mechanism that can be managed by an authorized account with the _setFrozen function.

The freezing mechanism provides the guarantee to the contract owner (e.g. a DAO or a well-configured multisig) that a specific amount of tokens held by an account won’t be transferable until the frozen amount is reduced using _setFrozen.

Functions
  • frozen(account)

  • available(account)

  • _setFrozen(account, amount)

  • _update(from, to, value)

ERC20
  • name()

  • symbol()

  • decimals()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
  • ERC20InsufficientUnfrozenBalance(account, needed, available)

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

frozen(address account) → uint256 public

Returns the frozen balance of an account.

available(address account) → uint256 public

Returns the available (unfrozen) balance of an account. Up to {balanceOf}.

_setFrozen(address account, uint256 amount) internal

Internal function to set the frozen token amount for a account.

_update(address from, address to, uint256 value) internal

See {ERC20-_update}.

Requirements:

  • from must have sufficient unfrozen balance.

ERC20InsufficientUnfrozenBalance(address account, uint256 needed, uint256 available) error

The operation failed because the account has insufficient unfrozen balance.

ERC20Restricted

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol";

Extension of {ERC20} that allows to implement user account transfer restrictions through the canTransact function. Inspired by EIP-7943.

By default, each account has no explicit restriction. The canTransact function acts as a blocklist. Developers can override canTransact to check that restriction == ALLOWED to implement an allowlist.

Functions
  • getRestriction(account)

  • canTransact(account)

  • _update(from, to, value)

  • _setRestriction(account, restriction)

  • _blockUser(account)

  • _allowUser(account)

  • _resetUser(account)

  • _checkRestriction(account)

ERC20
  • name()

  • symbol()

  • decimals()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
  • UserRestrictionsUpdated(account, restriction)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
  • ERC20UserRestricted(account)

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

getRestriction(address account) → enum ERC20Restricted.Restriction public

Returns the restriction of a user account.

canTransact(address account) → bool public

Returns whether a user account is allowed to interact with the token.

Default implementation only disallows explicitly BLOCKED accounts (i.e. a blocklist).

To convert into an allowlist, override as:

function canTransact(address account) public view virtual override returns (bool) {
    return getRestriction(account) == Restriction.ALLOWED;
}

_update(address from, address to, uint256 value) internal

See {ERC20-_update}. Enforces restriction transfers (excluding minting and burning).

Requirements:

  • from must be allowed to transfer tokens (see canTransact).

  • to must be allowed to receive tokens (see canTransact).

_setRestriction(address account, enum ERC20Restricted.Restriction restriction) internal

Updates the restriction of a user account.

_blockUser(address account) internal

Convenience function to block a user account (set to BLOCKED).

_allowUser(address account) internal

Convenience function to allow a user account (set to ALLOWED).

_resetUser(address account) internal

Convenience function to reset a user account to default restriction.

_checkRestriction(address account) internal

Checks if a user account is restricted. Reverts with ERC20Restricted if so.

UserRestrictionsUpdated(address indexed account, enum ERC20Restricted.Restriction restriction) event

Emitted when a user account’s restriction is updated.

ERC20UserRestricted(address account) error

The operation failed because the user account is restricted.

ERC20uRWA

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20uRWA.sol";

Extension of {ERC20} according to EIP-7943.

Combines standard ERC-20 functionality with RWA-specific features like account restrictions, asset freezing, and forced asset transfers. This contract doesn’t expose minting or burning capabilities; if implemented in derived contracts as needed, they must include 7943-specific logic.

Functions
  • canTransact(account)

  • supportsInterface(interfaceId)

  • canTransfer(from, to, amount)

  • getFrozenTokens(account)

  • setFrozenTokens(account, amount)

  • forcedTransfer(from, to, amount)

  • _update(from, to, amount)

  • _checkEnforcer(from, to, amount)

  • _checkFreezer(account, amount)

ERC20Restricted
  • getRestriction(account)

  • _setRestriction(account, restriction)

  • _blockUser(account)

  • _allowUser(account)

  • _resetUser(account)

  • _checkRestriction(account)

ERC20Freezable
  • frozen(account)

  • available(account)

  • _setFrozen(account, amount)

ERC20
  • name()

  • symbol()

  • decimals()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC7943Fungible
  • ForcedTransfer(from, to, amount)

  • Frozen(account, amount)

ERC20Restricted
  • UserRestrictionsUpdated(account, restriction)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
IERC7943Fungible
  • ERC7943CannotTransact(account)

  • ERC7943CannotTransfer(from, to, amount)

  • ERC7943InsufficientUnfrozenBalance(account, amount, unfrozen)

ERC20Restricted
  • ERC20UserRestricted(account)

ERC20Freezable
  • ERC20InsufficientUnfrozenBalance(account, needed, available)

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

canTransact(address account) → bool public

Returns whether a user account is allowed to interact with the token.

Default implementation only disallows explicitly BLOCKED accounts (i.e. a blocklist).

To convert into an allowlist, override as:

function canTransact(address account) public view virtual override returns (bool) {
    return getRestriction(account) == Restriction.ALLOWED;
}

supportsInterface(bytes4 interfaceId) → bool public

Returns true if this contract implements the interface defined by interfaceId. See the corresponding ERC section to learn more about how these ids are created.

This function call must use less than 30 000 gas.

canTransfer(address from, address to, uint256 amount) → bool external

This function is only meant for external use. Overriding it will not apply the new checks to the internal _update function. Consider overriding _update accordingly to keep both functions in sync.

getFrozenTokens(address account) → uint256 amount public

It could return an amount higher than the account’s balance.

setFrozenTokens(address account, uint256 amount) → bool result public

See IERC7943Fungible.setFrozenTokens. Always returns true if successful. Reverts otherwise.

The amount is capped to the balance of the account to ensure the IERC7943Fungible.Frozen event emits values that consistently reflect the actual amount of tokens that are frozen.

forcedTransfer(address from, address to, uint256 amount) → bool result public

See IERC7943Fungible.forcedTransfer. Always returns true if successful. Reverts otherwise.

Bypasses the ERC20Restricted restrictions for the from address and adjusts the frozen balance to the new balance after the transfer.

This function uses _update to perform the transfer, ensuring all standard ERC20 side effects (such as balance updates and events) are preserved. If you override _update to add additional restrictions or logic, those changes will also apply here. Consider overriding this function to bypass newer restrictions if needed.

_update(address from, address to, uint256 amount) internal

Transfers a value amount of tokens from from to to, or alternatively mints (or burns) if from (or to) is the zero address. All customizations to transfers, mints, and burns should be done by overriding this function.

Emits a {Transfer} event.

_checkEnforcer(address from, address to, uint256 amount) internal

Internal function to check if the enforcer is allowed to forcibly transfer the amount of tokens.

Example usage with {AccessControl-onlyRole}:

function _checkEnforcer(address from, address to, uint256 amount) internal view override onlyRole(ENFORCER_ROLE) {}

_checkFreezer(address account, uint256 amount) internal

Internal function to check if the freezer is allowed to freeze the amount of tokens.

Example usage with {AccessControl-onlyRole}:

function _checkFreezer(address account, uint256 amount) internal view override onlyRole(FREEZER_ROLE) {}

ERC4626Fees

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC4626Fees.sol";

ERC-4626 vault with entry/exit fees expressed in basis point (bp).

Functions
  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • _deposit(caller, receiver, assets, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _entryFeeBasisPoints()

  • _exitFeeBasisPoints()

  • _entryFeeRecipient()

  • _exitFeeRecipient()

ERC4626
  • decimals()

  • asset()

  • totalAssets()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit()

  • maxMint()

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • deposit(assets, receiver)

  • mint(shares, receiver)

  • withdraw(assets, receiver, owner)

  • redeem(shares, receiver, owner)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

ERC20
  • name()

  • symbol()

  • totalSupply()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

Errors
ERC4626
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

previewDeposit(uint256 assets) → uint256 public

Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}.

previewMint(uint256 shares) → uint256 public

Preview adding an entry fee on mint. See {IERC4626-previewMint}.

previewWithdraw(uint256 assets) → uint256 public

Preview adding an exit fee on withdraw. See {IERC4626-previewWithdraw}.

previewRedeem(uint256 shares) → uint256 public

Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}.

_deposit(address caller, address receiver, uint256 assets, uint256 shares) internal

Send entry fee to _entryFeeRecipient. See {IERC4626-_deposit}.

_withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal

Send exit fee to _exitFeeRecipient. See {IERC4626-_deposit}.

_entryFeeBasisPoints() → uint256 internal

_exitFeeBasisPoints() → uint256 internal

_entryFeeRecipient() → address internal

_exitFeeRecipient() → address internal

ERC7540

ERC7540

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540.sol";

Implementation of the ERC-7540 "Asynchronous ERC-4626 Tokenized Vaults" as defined in ERC-7540.

This abstract contract provides a single base for building vaults with asynchronous deposit and/or redemption flows on top of ERC-4626. It integrates operator management (IERC7540Operator), the full ERC-4626 vault interface, and routing logic that delegates to either synchronous (standard ERC-4626) or asynchronous paths depending on the return value of _isDepositAsync and _isRedeemAsync.

Subcontracts choose their async behavior by overriding two internal pure boolean selectors:

  • _isDepositAsync: when true, the deposit side uses the Request lifecycle (Pending → Claimable → Claimed).

  • _isRedeemAsync: when true, the redeem side uses the Request lifecycle.

Each async path requires a fulfillment strategy that implements the virtual hooks declared in this contract (e.g. _consumeClaimableDeposit, _pendingDepositRequest, _asyncMaxDeposit, etc.).

Deposit and redeem strategies are independent: a vault can combine any deposit strategy with any redeem strategy (e.g. delay-based deposits with admin-based redeems).

Share custody during the async lifecycle is configurable via _depositShareOrigin and _redeemShareDestination. When these return address(0) (the default), shares are minted/burned at claim time. When they return a non-zero address, shares are pre-minted to (or transferred to) that address at fulfillment time and then transferred to the receiver on claim.

When implementing a custom fulfillment strategy, the following virtual hooks MUST be overridden for each async side enabled:

ERC-7540 introduces operator permissions that allow operators to manage requests on behalf of controllers. An operator approved by a controller can request deposits using the controller’s assets, request redemptions using the controller’s shares, and claim assets or shares on behalf of the controller. Users should only approve operators they fully trust with both their assets and shares.

This contract assumes the underlying asset is a well-behaved ERC-20: transfers move exactly the requested amount, balances do not change without explicit transfers, and balanceOf reports faithfully. Fee-on-transfer, rebasing, and similar non-standard asset behaviors are out of scope. When the asset misbehaves, internal accounting (notably totalAssets) can revert and freeze claim paths that depend on live conversions.

Modifiers
  • onlyOperatorOrController(async, controller, operator)

Functions
  • constructor(asset_)

  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _requestDeposit(assets, controller, owner, requestId)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _requestRedeem(shares, controller, owner, requestId)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isDepositAsync()

  • _isRedeemAsync()

  • _pendingDepositRequest(, )

  • _claimableDepositRequest(, )

  • _pendingRedeemRequest(, )

  • _claimableRedeemRequest(, )

  • _consumeClaimableDeposit(, )

  • _consumeClaimableMint(, )

  • _consumeClaimableWithdraw(, )

  • _consumeClaimableRedeem(, )

  • _asyncMaxDeposit()

  • _asyncMaxMint()

  • _asyncMaxWithdraw()

  • _asyncMaxRedeem()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

onlyOperatorOrController(bool async, address controller, address operator) modifier

Modifier that enforces operator-or-controller authorization.

When async is false the check is skipped, which allows the standard ERC-4626 flow where msg.sender is the caller and no controller/operator distinction exists.

constructor(contract IERC20 asset_) internal

Sets the underlying asset contract. This must be an ERC-20-compatible contract.

Caches the asset’s decimals() value at construction time. If the call fails (e.g. the asset has not been created yet), a default of 18 is used.

Requirements:

Either _isDepositAsync or _isRedeemAsync must return true. Use {ERC4626} otherwise.

supportsInterface(bytes4 interfaceId) → bool public

See {IERC165-supportsInterface}.

Reports support for IERC7540Operator unconditionally. Support for IERC7540Deposit and IERC7540Redeem is conditional on the corresponding async selector returning true.

isOperator(address controller, address operator) → bool status public

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

setOperator(address operator, bool approved) → bool public

Grants or revokes permissions for operator to manage requests on behalf of the caller.

  • MUST set the operator status to the approved value.

  • MUST emit the {OperatorSet} event when the operator status is set.

  • MUST return true.

_setOperator(address controller, address operator, bool approved) internal

Sets the operator approval status for controller to approved.

_checkOperatorOrController(bool async, address controller, address operator) internal

Reverts with ERC7540InvalidOperator if operator is not the controller and is not approved as an operator for controller. When async is false the check is a no-op, preserving standard ERC-4626 authorization semantics.

decimals() → uint8 public

Decimals are computed by adding the decimal offset on top of the underlying asset’s decimals. This "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the asset has not been created yet), a default of 18 is used to represent the underlying asset’s decimals.

See {IERC20Metadata-decimals}.

asset() → address public

Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.

  • MUST be an ERC-20 token contract.

  • MUST NOT revert.

share() → address public

The address of the underlying share received on deposit into the vault.

vault(address asset_) → address public

The address of the vault for a specific asset.

totalAssets() → uint256 public

See {IERC4626-totalAssets}.

Pending deposit assets are subtracted from the vault’s token balance, since they have not yet been converted into shares and must not be treated as yield for outstanding shareholders.

Internal flows preserve the invariant balanceOf(asset, vault) >= totalPendingDepositAssets() for any well-behaved ERC-20. Assets with transfer fees, negative rebases, or externally-mutable balances can violate it and cause this function to revert with an underflow. Strategies that read totalAssets on the claim path become uncallable in that state. Strategies that lock the rate at fulfillment time are unaffected.

totalSupply() → uint256 public

See {IERC20-totalSupply}.

Adds totalPendingRedeemShares to the ERC-20 supply. When shares are burned at request time (i.e. _redeemShareDestination returns address(0)), pending redeem shares are removed from the on-chain supply but still logically outstanding until claimed; this override compensates.

As a consequence, two standard ERC-20 assumptions do not hold: (a) totalSupply() may exceed the sum of all balanceOf() (pending shares are virtual and unowned); (b) totalSupply() can change without a matching Transfer event when totalPendingRedeemShares changes. Integrators that snapshot supply for governance or reward weighting, or reconstruct supply from event logs (indexers, bridges), must account for this.

pendingDepositRequest(uint256 requestId, address controller) → uint256 public

Requirements:

claimableDepositRequest(uint256 requestId, address controller) → uint256 public

Requirements:

pendingRedeemRequest(uint256 requestId, address controller) → uint256 public

Requirements:

claimableRedeemRequest(uint256 requestId, address controller) → uint256 public

Requirements:

totalPendingDepositAssets() → uint256 public

Returns the total amount of underlying assets currently pending in deposit Requests.

totalPendingRedeemShares() → uint256 public

Returns the total amount of vault shares currently pending in redeem Requests.

convertToShares(uint256 assets) → uint256 public

Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.

  • MUST NOT be inclusive of any fees that are charged against assets in the Vault.

  • MUST NOT show any variations depending on the caller.

  • MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.

  • MUST NOT revert.

This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.

convertToAssets(uint256 shares) → uint256 public

Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met.

  • MUST NOT be inclusive of any fees that are charged against assets in the Vault.

  • MUST NOT show any variations depending on the caller.

  • MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.

  • MUST NOT revert.

This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.

maxDeposit(address owner) → uint256 public

See {IERC4626-maxDeposit}.

When the deposit flow is synchronous, returns type(uint256).max (no vault-imposed limit). When async, delegates to _asyncMaxDeposit which must be provided by the fulfillment strategy.

maxMint(address owner) → uint256 public

See {IERC4626-maxMint}.

When the deposit flow is synchronous, returns type(uint256).max. When async, delegates to _asyncMaxMint.

maxWithdraw(address owner) → uint256 public

See {IERC4626-maxWithdraw}.

When the redeem flow is synchronous, returns the asset-equivalent of the owner’s share balance. When async, delegates to _asyncMaxWithdraw.

maxRedeem(address owner) → uint256 public

See {IERC4626-maxRedeem}.

When the redeem flow is synchronous, returns the owner’s share balance. When async, delegates to _asyncMaxRedeem.

previewDeposit(uint256 assets) → uint256 public

See {IERC4626-previewDeposit}.

MUST revert when _isDepositAsync returns true, per the ERC-7540 specification which mandates that preview functions revert for async flows.

previewMint(uint256 shares) → uint256 public

See {IERC4626-previewMint}.

MUST revert when _isDepositAsync returns true.

previewWithdraw(uint256 assets) → uint256 public

See {IERC4626-previewWithdraw}.

MUST revert when _isRedeemAsync returns true.

previewRedeem(uint256 shares) → uint256 public

See {IERC4626-previewRedeem}.

MUST revert when _isRedeemAsync returns true.

requestDeposit(uint256 assets, address controller, address owner) → uint256 public

Transfers assets from owner into the vault and submits a deposit Request for controller. The Request enters Pending state. Uses requestId = 0 by default; override _requestDeposit to use non-zero request IDs.

Requirements:

  • _isDepositAsync must return true.

  • owner must be msg.sender or msg.sender must be an approved operator of owner.

  • owner must have approved the vault for at least assets of the underlying token.

The controller is the only address authorized to claim the resulting Request. Passing an address with no claim authority (e.g. address(0), 0x…​dead) or any contract that cannot itself call deposit/mint or designate an operator via setOperator will permanently lock the committed assets, since claims are gated by onlyOperatorOrController on controller and there is no cancellation path. Callers are responsible for supplying a controller capable of authorizing claims.

deposit(uint256 assets, address receiver) → uint256 public

See {IERC4626-deposit}. Calls the three-argument overload with msg.sender as controller.

deposit(uint256 assets, address receiver, address controller) → uint256 public

When async, claims assets worth of shares from a Claimable deposit Request controlled by controller, and transfers the resulting shares to receiver. When sync, behaves as standard ERC-4626 deposit.

When _isDepositAsync is false, the controller parameter is ignored and receiver is used for limit checks, matching standard ERC-4626 semantics.

Requirements:

  • assets must not exceed maxDeposit for the relevant account.

  • When async, msg.sender must be controller or an approved operator of controller.

mint(uint256 shares, address receiver) → uint256 assets public

See {IERC4626-mint}. Calls the three-argument overload with msg.sender as controller.

mint(uint256 shares, address receiver, address controller) → uint256 public

When async, claims exactly shares from a Claimable deposit Request controlled by controller, and transfers them to receiver. When sync, behaves as standard ERC-4626 mint.

When _isDepositAsync is false, the controller parameter is ignored.

Requirements:

  • shares must not exceed maxMint for the relevant account.

  • When async, msg.sender must be controller or an approved operator of controller.

requestRedeem(uint256 shares, address controller, address owner) → uint256 public

Assumes control of shares from owner and submits a redeem Request for controller. The Request enters Pending state. Uses requestId = 0 by default.

Authorization for a msg.sender not equal to owner may come from either ERC-20 approval over the shares of owner or from operator approval (see IERC7540Operator). This is consistent with the approach described in ERC-6909.

Requirements:

The controller is the only address authorized to claim the resulting Request. Passing an address with no claim authority (e.g. address(0), 0x…​dead) or any contract that cannot itself call withdraw/redeem or designate an operator via setOperator will permanently lock the committed shares, since claims are gated by onlyOperatorOrController on controller and there is no cancellation path. Callers are responsible for supplying a controller capable of authorizing claims.

withdraw(uint256 assets, address receiver, address ownerOrController) → uint256 public

See {IERC4626-withdraw}.

When async, claims assets from a Claimable redeem Request controlled by ownerOrController, and transfers the underlying assets to receiver. When sync, behaves as standard ERC-4626 withdraw.

Per ERC-7540, when async the ownerOrController parameter acts as the controller (replacing the traditional ERC-4626 owner role).

Requirements:

  • assets must not exceed maxWithdraw for ownerOrController.

  • When async, msg.sender must be ownerOrController or an approved operator.

  • When sync, msg.sender must be ownerOrController or have sufficient ERC-20 allowance.

redeem(uint256 shares, address receiver, address ownerOrController) → uint256 public

See {IERC4626-redeem}.

When async, claims shares from a Claimable redeem Request controlled by ownerOrController, and transfers the corresponding underlying assets to receiver. When sync, behaves as standard ERC-4626 redeem.

Per ERC-7540, when async the ownerOrController parameter acts as the controller.

Requirements:

  • shares must not exceed maxRedeem for ownerOrController.

  • When async, msg.sender must be ownerOrController or an approved operator.

  • When sync, msg.sender must be ownerOrController or have sufficient ERC-20 allowance.

_convertToShares(uint256 assets, enum Math.Rounding rounding) → uint256 internal

Internal conversion function (from assets to shares) with support for rounding direction.

Uses virtual shares and virtual assets to mitigate the inflation attack vector described in ERC-4626 security considerations. The offset is configurable via _decimalsOffset.

_convertToAssets(uint256 shares, enum Math.Rounding rounding) → uint256 internal

Internal conversion function (from shares to assets) with support for rounding direction.

_requestDeposit(uint256 assets, address controller, address owner, uint256 requestId) → uint256 internal

Internal handler for deposit Requests. Increments totalPendingDepositAssets, transfers assets from owner into the vault, and emits IERC7540Deposit.DepositRequest.

Strategy extensions (e.g. ERC7540AdminDeposit) should override this to record per-controller pending state before calling super._requestDeposit(…​).

Pending accounting is updated before _transferIn to follow Checks-Effects-Interactions. Assets with transfer hooks (e.g. ERC-777) may observe totalAssets temporarily understated during the transfer, since _totalPendingDepositAssets is already incremented while the token balance has not yet increased.

Requirements:

_mintSharesOnDepositFulfill(uint256 assets, uint256 shares) internal

Mints shares to _depositShareOrigin as part of deposit fulfillment when using the pre-mint share custody model. Decrements totalPendingDepositAssets by assets.

This function requires _depositShareOrigin to return a non-zero address. When _depositShareOrigin returns address(0), shares are minted directly at claim time inside _deposit and this function must not be called.

_deposit(address callerOrController, address receiver, uint256 assets, uint256 shares) internal

Common workflow for deposit and mint claim operations.

Handles three cases depending on the vault configuration:

  1. Synchronous (_isDepositAsync returns false): transfers assets from callerOrController into the vault and mints new shares to receiver. Standard ERC-4626 behavior.

  2. Async, mint-on-claim (_depositShareOrigin returns address(0)): decrements _totalPendingDepositAssets and mints new shares to receiver. No asset transfer occurs since assets were already transferred during requestDeposit.

  3. Async, pre-minted (_depositShareOrigin returns non-zero): transfers pre-minted shares from the share origin address to receiver.

Emits {IERC4626-Deposit}. Per ERC-7540, the first event parameter is the controller in async mode and msg.sender in sync mode.

_requestRedeem(uint256 shares, address controller, address owner, uint256 requestId) → uint256 internal

Internal handler for redeem Requests. Assumes control of shares from owner and emits IERC7540Redeem.RedeemRequest.

Share custody depends on _redeemShareDestination:

  • address(0) (default): shares are burned immediately and _totalPendingRedeemShares is incremented to keep totalSupply accurate.

  • Non-zero address: shares are transferred to that address and held until fulfillment.

Authorization for a msg.sender not equal to owner is checked via operator status first; if that fails, ERC-20 allowance is spent via {ERC20-_spendAllowance}. This dual-authorization approach is consistent with ERC-6909 semantics.

Requirements:

_burnSharesOnRedeemFulfill(uint256, uint256 shares) internal

Burns shares from _redeemShareDestination as part of redeem fulfillment when using the escrow share custody model. Increments totalPendingRedeemShares by shares.

This function requires _redeemShareDestination to return a non-zero address. When _redeemShareDestination returns address(0), shares were already burned at request time inside _requestRedeem and this function must not be called.

_withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal

Common workflow for withdraw and redeem claim operations.

Handles two cases depending on the vault configuration:

  1. Synchronous (_isRedeemAsync returns false): spends ERC-20 allowance if caller is not owner, burns shares from owner, and transfers underlying assets to receiver.

  2. Async: decrements _totalPendingRedeemShares (shares were already burned/escrowed during the Request or fulfillment phase) and transfers underlying assets to receiver.

Emits {IERC4626-Withdraw}.

_transferIn(address from, uint256 assets) internal

Performs a transfer-in of underlying assets from from to the vault. The default implementation uses {SafeERC20-safeTransferFrom}. Used by _deposit (sync) and _requestDeposit (async).

_transferOut(address to, uint256 assets) internal

Performs a transfer-out of underlying assets from the vault to to. The default implementation uses {SafeERC20-safeTransfer}. Used by _withdraw.

_decimalsOffset() → uint8 internal

Returns the decimal offset used for virtual shares/assets in _convertToShares and _convertToAssets. Defaults to 0. Increase to strengthen inflation-attack protection at the cost of share-price granularity.

_depositShareOrigin() → address internal

Returns the address from which shares are transferred to the receiver on deposit claim.

  • address(0) (default): shares are minted directly to the receiver at claim time. Pending deposit assets are tracked via totalPendingDepositAssets and decremented in _deposit.

  • Non-zero address: shares are pre-minted to this address during fulfillment (via _mintSharesOnDepositFulfill) and transferred to the receiver on claim.

If overridden to return a non-zero address, that address must not be able to transfer shares (otherwise pre-minted shares could be moved before they are claimed). Use an unowned address such as address(0xdead). Avoid addresses in the precompile reserved range (address(1) through address(0x1ff), see EIP-7587).

_redeemShareDestination() → address internal

Returns the address to which shares are transferred (escrowed) on redeem request.

If overridden to return a non-zero address, that address must not be able to transfer shares (otherwise escrowed shares could be moved before they are burned). Use an unowned address such as address(0xdead). Avoid addresses in the precompile reserved range (address(1) through address(0x1ff), see EIP-7587).

_isDepositAsync() → bool internal

Returns true if the deposit flow is asynchronous (Request-based). When false, deposit and mint behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async deposit fulfillment strategy.

_isRedeemAsync() → bool internal

Returns true if the redeem flow is asynchronous (Request-based). When false, withdraw and redeem behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async redeem fulfillment strategy.

_pendingDepositRequest(uint256, address) → uint256 internal

Returns the amount of assets in Pending state for controller with the given requestId.

_claimableDepositRequest(uint256, address) → uint256 internal

Returns the amount of assets in Claimable state for controller with the given requestId.

_pendingRedeemRequest(uint256, address) → uint256 internal

Returns the amount of shares in Pending state for controller with the given requestId.

_claimableRedeemRequest(uint256, address) → uint256 internal

Returns the amount of shares in Claimable state for controller with the given requestId.

_consumeClaimableDeposit(uint256, address) → uint256 internal

Consumes assets worth of a Claimable deposit for controller and returns the corresponding number of shares. Called by deposit (three-argument overload) in async mode.

In async mode, this function may be susceptible to the inflation attack vector described in ERC-4626 security considerations if the shares are freed automatically (e.g. after a certain time period). Consider using _decimalsOffset to mitigate this risk.

_consumeClaimableMint(uint256, address) → uint256 internal

Consumes shares worth of a Claimable deposit for controller and returns the corresponding number of assets. Called by mint (three-argument overload) in async mode.

_consumeClaimableWithdraw(uint256, address) → uint256 internal

Consumes assets worth of a Claimable redeem for controller and returns the corresponding number of shares. Called by withdraw in async mode.

_consumeClaimableRedeem(uint256, address) → uint256 internal

Consumes shares worth of a Claimable redeem for controller and returns the corresponding number of assets. Called by redeem in async mode.

_asyncMaxDeposit(address) → uint256 internal

Returns the maximum assets that can be claimed via deposit for an async owner.

_asyncMaxMint(address) → uint256 internal

Returns the maximum shares that can be claimed via mint for an async owner.

_asyncMaxWithdraw(address) → uint256 internal

Returns the maximum assets that can be claimed via withdraw for an async owner.

_asyncMaxRedeem(address) → uint256 internal

Returns the maximum shares that can be claimed via redeem for an async owner.

ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max) error

Attempted to deposit more assets than the max amount for receiver.

ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max) error

Attempted to mint more shares than the max amount for receiver.

ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max) error

Attempted to withdraw more assets than the max amount for owner.

ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max) error

Attempted to redeem more shares than the max amount for owner.

ERC7540InvalidOperator(address controller, address operator) error

The operator is not the caller or an approved operator of the controller.

ERC7540SyncDeposit() error

A deposit Request was attempted but _isDepositAsync returns false.

ERC7540AsyncDeposit() error

A synchronous deposit preview was attempted but _isDepositAsync returns true.

ERC7540SyncRedeem() error

A redeem Request was attempted but _isRedeemAsync returns false.

ERC7540AsyncRedeem() error

A synchronous redeem preview was attempted but _isRedeemAsync returns true.

ERC7540MissingAsync() error

Neither _isDepositAsync nor _isRedeemAsync returns true.

ERC7540UnauthorizedMintSharesOnDepositFulfill() error

Invalid attempt at minting shares on a deposit fulfill when configuration mints them during claim.

ERC7540UnauthorizedBurnSharesOnRedeemFulfill() error

Invalid attempt at burning shares on a redeem fulfill when configuration burns them during request.

ERC7540AdminDeposit

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540AdminDeposit.sol";

Admin-controlled (operator-triggered) fulfillment strategy for asynchronous deposits.

Extends ERC7540 with a deposit flow where a privileged caller explicitly transitions requests from Pending to Claimable by calling _fulfillDeposit. The caller provides both the assets amount and the corresponding shares, giving the fulfiller explicit control over the exchange rate.

This is the most flexible fulfillment model. Epoch-based batch settlement, FIFO queues, and cross-chain oracle-gated settlement can all be composed on top.

Production equivalents include USDai, Nest (Plume), MetaVault, and Centrifuge.

All requests share requestId = 0 (per-controller accounting only).

Functions
  • _isDepositAsync()

  • _requestDeposit(assets, controller, owner, requestId)

  • _fulfillDeposit(assets, shares, controller)

  • _consumeClaimableDeposit(assets, controller)

  • _consumeClaimableMint(shares, controller)

  • _pendingDepositRequest(, controller)

  • _claimableDepositRequest(, controller)

  • _asyncMaxDeposit(owner)

  • _asyncMaxMint(owner)

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _requestRedeem(shares, controller, owner, requestId)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isRedeemAsync()

  • _pendingRedeemRequest(, )

  • _claimableRedeemRequest(, )

  • _consumeClaimableWithdraw(, )

  • _consumeClaimableRedeem(, )

  • _asyncMaxWithdraw()

  • _asyncMaxRedeem()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
  • DepositClaimable(controller, requestId, assets, shares)

IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
  • ERC7540DepositInsufficientPendingAssets(assets, pendingAssets)

ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

_isDepositAsync() → bool internal

Returns true if the deposit flow is asynchronous (Request-based). When false, deposit and mint behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async deposit fulfillment strategy.

_requestDeposit(uint256 assets, address controller, address owner, uint256 requestId) → uint256 internal

Records per-controller pending state before delegating to ERC7540._requestDeposit.

_fulfillDeposit(uint256 assets, uint256 shares, address controller) internal

Fulfills a pending deposit request by transitioning it from Pending to Claimable state.

The caller provides both assets and shares, locking the exchange rate at fulfillment time. The fulfiller should ensure the vault’s totalAssets() already reflects the deposited assets (e.g. after deploying them to a yield source) to avoid diluting existing holders.

Emits a DepositClaimable event.

Requirements:

  • assets must not exceed the pending deposit amount for the controller.

Multiple fulfillments with different exchange rates will blend into a weighted average. For example, fulfilling 50 assets → 100 shares (2:1) then 50 assets → 25 shares (0.5:1) produces claimableAssets=100 and claimableShares=125. A partial claim of 50 assets yields mulDiv(50, 125, 100) = 62 shares: the weighted average rate, not either original rate. Integrators expecting per-fulfillment rate isolation should use a different strategy (e.g. epochs).

_consumeClaimableDeposit(uint256 assets, address controller) → uint256 internal

Consumes assets from the claimable deposit and returns the proportional shares (rounded down).

_consumeClaimableMint(uint256 shares, address controller) → uint256 internal

Consumes shares from the claimable deposit and returns the proportional assets (rounded up).

_pendingDepositRequest(uint256, address controller) → uint256 internal

Returns the amount of assets in Pending state for controller with the given requestId.

_claimableDepositRequest(uint256, address controller) → uint256 internal

Returns the amount of assets in Claimable state for controller with the given requestId.

_asyncMaxDeposit(address owner) → uint256 internal

Returns the maximum assets that can be claimed via deposit for an async owner.

_asyncMaxMint(address owner) → uint256 internal

Returns the maximum shares that can be claimed via mint for an async owner.

DepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares) event

Emitted when a deposit request transitions from Pending to Claimable.

ERC7540DepositInsufficientPendingAssets(uint256 assets, uint256 pendingAssets) error

The assets to fulfill exceeds the pendingAssets for the controller.

ERC7540AdminRedeem

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540AdminRedeem.sol";

Admin-controlled (operator-triggered) fulfillment strategy for asynchronous redemptions.

Extends ERC7540 with a redeem flow where a privileged caller explicitly transitions requests from Pending to Claimable by calling _fulfillRedeem. The caller provides both the shares amount and the corresponding assets, giving the fulfiller explicit control over the exchange rate.

The fulfiller must ensure the vault holds enough underlying assets before calling _fulfillRedeem. Asset sourcing (unwinding positions, bridging cross-chain, etc.) is application-specific and is not part of this contract.

Production equivalents include USDai, Nest (Plume), MetaVault, and Centrifuge.

All requests share requestId = 0 (per-controller accounting only).

Functions
  • _isRedeemAsync()

  • _requestRedeem(shares, controller, owner, requestId)

  • _fulfillRedeem(shares, assets, controller)

  • _consumeClaimableWithdraw(assets, controller)

  • _consumeClaimableRedeem(shares, controller)

  • _pendingRedeemRequest(, controller)

  • _claimableRedeemRequest(, controller)

  • _asyncMaxWithdraw(owner)

  • _asyncMaxRedeem(owner)

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _requestDeposit(assets, controller, owner, requestId)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isDepositAsync()

  • _pendingDepositRequest(, )

  • _claimableDepositRequest(, )

  • _consumeClaimableDeposit(, )

  • _consumeClaimableMint(, )

  • _asyncMaxDeposit()

  • _asyncMaxMint()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
  • RedeemClaimable(controller, requestId, assets, shares)

IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
  • ERC7540RedeemInsufficientPendingShares(shares, pendingShares)

ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

_isRedeemAsync() → bool internal

Returns true if the redeem flow is asynchronous (Request-based). When false, withdraw and redeem behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async redeem fulfillment strategy.

_requestRedeem(uint256 shares, address controller, address owner, uint256 requestId) → uint256 internal

Records per-controller pending state before delegating to ERC7540._requestRedeem.

_fulfillRedeem(uint256 shares, uint256 assets, address controller) internal

Fulfills a pending redeem request by transitioning it from Pending to Claimable state.

The caller provides both shares and assets, locking the exchange rate at fulfillment time. The fulfiller must ensure the vault holds enough underlying assets to cover the assets amount before calling this function.

Emits a RedeemClaimable event.

Requirements:

  • shares must not exceed the pending redeem amount for the controller.

_consumeClaimableWithdraw(uint256 assets, address controller) → uint256 internal

Consumes assets from the claimable redeem and returns the proportional shares (rounded up).

_consumeClaimableRedeem(uint256 shares, address controller) → uint256 internal

Consumes shares from the claimable redeem and returns the proportional assets (rounded down).

_pendingRedeemRequest(uint256, address controller) → uint256 internal

Returns the amount of shares in Pending state for controller with the given requestId.

_claimableRedeemRequest(uint256, address controller) → uint256 internal

Returns the amount of shares in Claimable state for controller with the given requestId.

_asyncMaxWithdraw(address owner) → uint256 internal

Returns the maximum assets that can be claimed via withdraw for an async owner.

_asyncMaxRedeem(address owner) → uint256 internal

Returns the maximum shares that can be claimed via redeem for an async owner.

RedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares) event

Emitted when a redeem request transitions from Pending to Claimable.

ERC7540RedeemInsufficientPendingShares(uint256 shares, uint256 pendingShares) error

The shares to fulfill exceeds the pendingShares for the controller.

ERC7540DelayDeposit

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540DelayDeposit.sol";

Time-delay fulfillment strategy for asynchronous deposits.

Extends ERC7540 with a deposit flow where requests become permissionlessly claimable after a configurable waiting period. No privileged fulfiller is needed — once the delay elapses, the controller (or any keeper) can claim. The exchange rate is computed at claim time using the vault’s live convertToShares.

Production equivalents (redeem side): BeefySonic, MagmaV2, Tangle.

Requests are tracked using {Checkpoints-Trace208}, storing cumulative deposit amounts keyed by their maturity timepoint. The requestId returned by requestDeposit equals the absolute timestamp at which the request becomes claimable (clock() + depositDelay(controller)).

Override depositDelay to customize the waiting period (default: 1 hour) and clock to change the time source (default: block.timestamp).

This module does not support temporary share custody through _depositShareOrigin. The constructor tries to enforce that property, but the check may be insufficient if _depositShareOrigin reads from storage that is not yet initialized when the parent’s constructor runs
Functions
  • constructor()

  • clock()

  • CLOCK_MODE()

  • depositDelay()

  • _isDepositAsync()

  • _requestDeposit(assets, controller, owner, )

  • _consumeClaimableDeposit(assets, controller)

  • _consumeClaimableMint(shares, controller)

  • _pendingDepositRequest(requestId, controller)

  • _claimableDepositRequest(requestId, controller)

  • _asyncMaxDeposit(owner)

  • _asyncMaxMint(owner)

  • _readyDepositAt(owner, timepoint)

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _requestRedeem(shares, controller, owner, requestId)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isRedeemAsync()

  • _pendingRedeemRequest(, )

  • _claimableRedeemRequest(, )

  • _consumeClaimableWithdraw(, )

  • _consumeClaimableRedeem(, )

  • _asyncMaxWithdraw()

  • _asyncMaxRedeem()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
  • ERC7540DelayInvalidDepositShareOrigin()

ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

constructor() internal

clock() → uint48 public

Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting).

CLOCK_MODE() → string public

Description of the clock

depositDelay(address) → uint48 public

Returns the delay duration before a deposit request becomes claimable. Defaults to 1 hour.

For any given controller, the maturity timepoint clock() + depositDelay(controller) MUST be non-decreasing across successive requestDeposit calls. Overrides that shrink the delay faster than clock() advances will cause new requests to revert until the previous maturity is reached.

_isDepositAsync() → bool internal

Returns true if the deposit flow is asynchronous (Request-based). When false, deposit and mint behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async deposit fulfillment strategy.

_requestDeposit(uint256 assets, address controller, address owner, uint256) → uint256 internal

Pushes a new cumulative checkpoint at the maturity timepoint and delegates to ERC7540._requestDeposit with the timepoint as requestId.

_consumeClaimableDeposit(uint256 assets, address controller) → uint256 internal

Consumes assets from claimable deposits, returns proportional shares (rounded down).

Requirements:

  • maxMint must not be 0 for controller. Panics with division by zero otherwise.

_consumeClaimableMint(uint256 shares, address controller) → uint256 internal

Consumes shares from claimable deposits, returns proportional assets (rounded up).

_pendingDepositRequest(uint256 requestId, address controller) → uint256 internal

Returns the assets in Pending state for a specific requestId (timepoint). A request is pending only if its timepoint is strictly in the future.

_claimableDepositRequest(uint256 requestId, address controller) → uint256 internal

Returns the assets in Claimable state for a specific requestId (timepoint). A request is claimable once its timepoint has elapsed and the assets haven’t been claimed yet.

_asyncMaxDeposit(address owner) → uint256 internal

Returns the total claimable assets across all matured timepoints for owner.

_asyncMaxMint(address owner) → uint256 internal

Returns the share-equivalent of _asyncMaxDeposit (rounded down).

_readyDepositAt(address owner, uint48 timepoint) → uint256 internal

Internal helper: fetch the amount that is expected to be claimable at a given timepoint, if any. Any amount that has already been claimed is taken into consideration.

ERC7540DelayInvalidDepositShareOrigin() error

Triggered if _depositShareOrigin is not address(0), as this is not supported by this module.

ERC7540DelayRedeem

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540DelayRedeem.sol";

Time-delay fulfillment strategy for asynchronous redemptions.

Extends ERC7540 with a redeem flow where requests become permissionlessly claimable after a configurable waiting period. No privileged fulfiller is needed — once the delay elapses, the controller (or any keeper) can claim. The exchange rate is computed at claim time using the vault’s live convertToAssets.

Production equivalents: BeefySonic (protocol-dictated SFC unbonding), MagmaV2 (admin-configurable delay), Tangle (protocol-dictated).

Requests are tracked using {Checkpoints-Trace208}, storing cumulative redeem amounts keyed by their maturity timepoint. The requestId returned by requestRedeem equals the absolute timestamp at which the request becomes claimable (clock() + redeemDelay(controller)).

Override redeemDelay to customize the waiting period (default: 1 hour) and clock to change the time source (default: block.timestamp).

This module does not support temporary share custody through _redeemShareDestination. The constructor tries to enforce that property, but the check may be insufficient if _redeemShareDestination reads from storage that is not yet initialized when the parent’s constructor runs.
Functions
  • constructor()

  • clock()

  • CLOCK_MODE()

  • redeemDelay()

  • _isRedeemAsync()

  • _requestRedeem(shares, controller, owner, )

  • _consumeClaimableWithdraw(assets, controller)

  • _consumeClaimableRedeem(shares, controller)

  • _pendingRedeemRequest(requestId, controller)

  • _claimableRedeemRequest(requestId, controller)

  • _asyncMaxWithdraw(owner)

  • _asyncMaxRedeem(owner)

  • _readyRedeemAt(owner, timepoint)

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _requestDeposit(assets, controller, owner, requestId)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isDepositAsync()

  • _pendingDepositRequest(, )

  • _claimableDepositRequest(, )

  • _consumeClaimableDeposit(, )

  • _consumeClaimableMint(, )

  • _asyncMaxDeposit()

  • _asyncMaxMint()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
  • ERC7540DelayInvalidRedeemShareDestination()

ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

constructor() internal

clock() → uint48 public

Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting).

CLOCK_MODE() → string public

Description of the clock

redeemDelay(address) → uint48 public

Returns the delay duration before a redeem request becomes claimable. Defaults to 1 hour.

For any given controller, the maturity timepoint clock() + redeemDelay(controller) MUST be non-decreasing across successive requestRedeem calls. Overrides that shrink the delay faster than clock() advances will cause new requests to revert until the previous maturity is reached.

_isRedeemAsync() → bool internal

Returns true if the redeem flow is asynchronous (Request-based). When false, withdraw and redeem behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async redeem fulfillment strategy.

_requestRedeem(uint256 shares, address controller, address owner, uint256) → uint256 internal

Pushes a new cumulative checkpoint at the maturity timepoint and delegates to ERC7540._requestRedeem with the timepoint as requestId.

_consumeClaimableWithdraw(uint256 assets, address controller) → uint256 internal

Consumes assets from claimable redeems, returns proportional shares (rounded up).

_consumeClaimableRedeem(uint256 shares, address controller) → uint256 internal

Consumes shares from claimable redeems, returns proportional assets (rounded down).

_pendingRedeemRequest(uint256 requestId, address controller) → uint256 internal

Returns the shares in Pending state for a specific requestId (timepoint). A request is pending only if its timepoint is strictly in the future.

_claimableRedeemRequest(uint256 requestId, address controller) → uint256 internal

Returns the shares in Claimable state for a specific requestId (timepoint). A request is claimable once its timepoint has elapsed and the shares haven’t been claimed yet.

_asyncMaxWithdraw(address owner) → uint256 internal

Returns the asset-equivalent of _asyncMaxRedeem (rounded down).

_asyncMaxRedeem(address owner) → uint256 internal

Returns the total claimable shares across all matured timepoints for owner.

_readyRedeemAt(address owner, uint48 timepoint) → uint256 internal

Internal helper: fetch the amount that is expected to be claimable at a given timepoint, if any. Any amount that has already been claimed is taken into consideration.

ERC7540DelayInvalidRedeemShareDestination() error

Triggered if _redeemShareDestination is not address(0), as this is not supported by this module.

ERC7540SyncDeposit

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540SyncDeposit.sol";

Module for enabling synchronous behavior (ERC-4626) for the deposit flow of an ERC-7540 vault.

Note that an ERC-7540 vault is required to have at least one flow operating in asynchronous mode, so this module cannot be combined with ERC7540SyncRedeem.

Functions
  • _isDepositAsync()

  • _consumeClaimableDeposit(, )

  • _consumeClaimableMint(, )

  • _pendingDepositRequest(, )

  • _claimableDepositRequest(, )

  • _asyncMaxDeposit()

  • _asyncMaxMint()

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _requestDeposit(assets, controller, owner, requestId)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _requestRedeem(shares, controller, owner, requestId)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isRedeemAsync()

  • _pendingRedeemRequest(, )

  • _claimableRedeemRequest(, )

  • _consumeClaimableWithdraw(, )

  • _consumeClaimableRedeem(, )

  • _asyncMaxWithdraw()

  • _asyncMaxRedeem()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

_isDepositAsync() → bool internal

Returns true if the deposit flow is asynchronous (Request-based). When false, deposit and mint behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async deposit fulfillment strategy.

_consumeClaimableDeposit(uint256, address) → uint256 internal

Consumes assets from the claimable deposit and returns the proportional shares (rounded down).

_consumeClaimableMint(uint256, address) → uint256 internal

Consumes shares from the claimable deposit and returns the proportional assets (rounded up).

_pendingDepositRequest(uint256, address) → uint256 internal

Returns the amount of assets in Pending state for controller with the given requestId.

_claimableDepositRequest(uint256, address) → uint256 internal

Returns the amount of assets in Claimable state for controller with the given requestId.

_asyncMaxDeposit(address) → uint256 internal

Returns the maximum assets that can be claimed via deposit for an async owner.

_asyncMaxMint(address) → uint256 internal

Returns the maximum shares that can be claimed via mint for an async owner.

ERC7540SyncRedeem

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540SyncRedeem.sol";

Module for enabling synchronous behavior (ERC-4626) for the redeem flow of an ERC-7540 vault.

Note that an ERC-7540 vault is required to have at least one flow operating in asynchronous mode, so this module cannot be combined with ERC7540SyncDeposit.

Functions
  • _isRedeemAsync()

  • _consumeClaimableWithdraw(, )

  • _consumeClaimableRedeem(, )

  • _pendingRedeemRequest(, )

  • _claimableRedeemRequest(, )

  • _asyncMaxWithdraw()

  • _asyncMaxRedeem()

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _requestDeposit(assets, controller, owner, requestId)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _requestRedeem(shares, controller, owner, requestId)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isDepositAsync()

  • _pendingDepositRequest(, )

  • _claimableDepositRequest(, )

  • _consumeClaimableDeposit(, )

  • _consumeClaimableMint(, )

  • _asyncMaxDeposit()

  • _asyncMaxMint()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

_isRedeemAsync() → bool internal

Returns true if the redeem flow is asynchronous (Request-based). When false, withdraw and redeem behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async redeem fulfillment strategy.

_consumeClaimableWithdraw(uint256, address) → uint256 internal

Consumes assets from the claimable redeem and returns the proportional shares (rounded up).

_consumeClaimableRedeem(uint256, address) → uint256 internal

Consumes shares from the claimable redeem and returns the proportional assets (rounded down).

_pendingRedeemRequest(uint256, address) → uint256 internal

Returns the amount of shares in Pending state for controller with the given requestId.

_claimableRedeemRequest(uint256, address) → uint256 internal

Returns the amount of shares in Claimable state for controller with the given requestId.

_asyncMaxWithdraw(address) → uint256 internal

Returns the maximum assets that can be claimed via withdraw for an async owner.

_asyncMaxRedeem(address) → uint256 internal

Returns the maximum shares that can be claimed via redeem for an async owner.

ERC7540EpochDeposit

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540EpochDeposit.sol";

Epoch-based batch fulfillment strategy for asynchronous deposits.

Extends ERC7540 with a deposit flow where requests submitted during the same epoch are batched together and settled at a single exchange rate when the admin closes the epoch via _fulfillDeposit. All controllers within a fulfilled epoch receive the same pro-rata conversion from assets to shares.

Production equivalents: Cove, Amphor, Lagoon.

The requestId returned by requestDeposit is the epoch ID. By default, epochs are weekly (block.timestamp / 1 weeks); override currentDepositEpoch to change the cadence or use manually-bumped epoch counters.

Each account tracks its epoch memberships via a {DoubleEndedQueue} capped at _depositRequestQueueLimit entries (default: 32) to bound the O(n) loops in _asyncMaxDeposit and _asyncMaxMint. Users that hit the limit should claim fulfilled epochs to free up space.

Claims pay each controller’s pro-rata share floor-rounded against the remaining epoch totals. With very small fulfillment values (e.g. an epoch settling 3 assets for 2 shares across 3 equal claimants), rounding can leave one controller with up to 1 "wei" of unclaimable residue. At realistic ERC-20 token decimals this is sub-unit and economically immaterial. Unlike ERC-4626’s inflation-attack surface, the per-epoch totalAssets and totalShares cannot be inflated by donation (they only change via requestDeposit and _fulfillDeposit); deployers wanting finer per-claim granularity can set _decimalsOffset to scale share precision relative to assets.
Functions
  • _isDepositAsync()

  • currentDepositEpoch()

  • totalDepositAssets(epochId)

  • totalDepositShares(epochId)

  • depositEpochs(controller, start, end)

  • _pendingDepositRequest(requestId, controller)

  • _pendingAvailableDepositRequest(requestId, controller)

  • _claimableDepositRequest(requestId, controller)

  • _asyncMaxDeposit(owner)

  • _asyncMaxMint(owner)

  • _convertToDepositShares(epochId, assets, rounding)

  • _convertToDepositAssets(epochId, shares, rounding)

  • _requestDeposit(assets, controller, owner, )

  • _fulfillDeposit(epochId, totalShares)

  • _consumeClaimableDeposit(assets, controller)

  • _consumeClaimableMint(shares, controller)

  • _depositRequestQueueLimit()

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _requestRedeem(shares, controller, owner, requestId)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isRedeemAsync()

  • _pendingRedeemRequest(, )

  • _claimableRedeemRequest(, )

  • _consumeClaimableWithdraw(, )

  • _consumeClaimableRedeem(, )

  • _asyncMaxWithdraw()

  • _asyncMaxRedeem()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
  • ERC7540EpochDepositFulfilled(epochId, totalAssets, totalShares)

IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
  • ERC7540EpochDepositTooEarly(epochId)

  • ERC7540EpochDepositEmptyEpoch(epochId)

  • ERC7540EpochDepositAlreadyFulfilled(epochId)

  • ERC7540EpochDepositQueueLimitExceeded(controller)

ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

_isDepositAsync() → bool internal

Returns true if the deposit flow is asynchronous (Request-based). When false, deposit and mint behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async deposit fulfillment strategy.

currentDepositEpoch() → uint256 public

Returns the current epoch ID. Defaults to block.timestamp / 1 weeks + 1.

totalDepositAssets(uint256 epochId) → uint256 public

Returns the total assets queued in epochId. Equals the sum of all deposit requests during the Pending phase; decreases as claimants consume their pro-rata share once the epoch is fulfilled, and reaches 0 once the epoch is fully claimed.

totalDepositShares(uint256 epochId) → uint256 public

Returns the shares allocated to epochId at fulfillment. Zero before _fulfillDeposit is called; decreases as claimants consume their pro-rata share. Together with totalDepositAssets it encodes the locked epoch rate.

depositEpochs(address controller, uint256 start, uint256 end) → uint256[] epochIds public

Returns the deposit epoch IDs that controller has open requests in, in queue order (oldest first). Fully claimed epochs are popped from the queue and no longer appear.

Using start = 0 and end = type(uint64).max will return the entire set of epochs.

_pendingDepositRequest(uint256 requestId, address controller) → uint256 internal

A request is pending if its epoch has not yet been fulfilled (totalShares == 0) and still has assets queued (totalAssets > 0).

_pendingAvailableDepositRequest(uint256 requestId, address controller) → uint256 internal

Returns the controller’s stored deposit request for requestId, or 0 if the epoch has been fully claimed (totalAssets == 0).

_claimableDepositRequest(uint256 requestId, address controller) → uint256 internal

A request is claimable if its epoch has been fulfilled (totalShares > 0).

_asyncMaxDeposit(address owner) → uint256 assets internal

Sums claimable assets from `owner’s fulfilled epochs oldest-first, stopping at the first Pending epoch. Fulfilled epochs behind a Pending one are not counted until the Pending one is fulfilled. Matches _consumeClaimableDeposit.

O(n) in owner’s epochs, bounded by _depositRequestQueueLimit (default 32). Per-account, so an attacker creating many small requests can only inflate their own queue, not other users'. Cross-controller DoS is not possible because epoch fulfillment via _fulfillDeposit is O(1) (it sets `totalShares for the entire epoch in a single write).

_asyncMaxMint(address owner) → uint256 shares internal

Sums claimable shares across all fulfilled epochs the owner participates in. Same as _asyncMaxDeposit.

_convertToDepositShares(uint256 epochId, uint256 assets, enum Math.Rounding rounding) → uint256 internal

Converts assets to shares at epochId’s locked rate. Returns 0 if `totalAssets is 0.

_convertToDepositAssets(uint256 epochId, uint256 shares, enum Math.Rounding rounding) → uint256 internal

Converts shares to assets at epochId’s locked rate. Returns 0 if `totalShares is 0.

_requestDeposit(uint256 assets, address controller, address owner, uint256) → uint256 internal

Records the request in the current epoch and enqueues the epoch ID for controller if not already present. A zero-amount request is a no-op at the epoch layer (it emits the event via super but does not create an unfulfillable epoch or occupy a queue slot).

Requirements:

  • _msgSender() must be controller or an approved operator of controller. This is on top of the base’s owner authentication and prevents third-party queue spam.

  • The controller’s epoch queue must not exceed _depositRequestQueueLimit.

_fulfillDeposit(uint256 epochId, uint256 totalShares) internal

Fulfills a past epoch by setting its totalShares. All requests within the epoch become claimable at the rate totalShares / totalAssets.

When epoch transition is manual, the caller should bump the epoch before calling this.
Pending vs. fulfilled is distinguished by totalShares == 0. Admins are assumed not to fulfill at zero (a confiscation event with no economic purpose); if 0 is passed by accident, the call is a no-op and the admin can re-fulfill. This recovery only holds as long as derived contracts preserve the no-side-effect semantics of this function — if not, derived contracts should restrict totalShares != 0. A genuine total-loss settlement is not representable in this encoding; deployers needing that SHOULD override to encode the fulfilled state separately (e.g. an explicit boolean).
Out-of-order fulfillment is permitted, but each controller’s claims stay gated on their oldest Pending epoch (see _consumeClaimableDeposit). Funds are not lost; a later fulfilled epoch simply waits until the older one is fulfilled. Derived contracts wanting strict FIFO settlement should enforce it here.

Requirements:

  • epochId must be a past epoch (less than currentDepositEpoch).

  • The epoch must have pending assets and must not have been fulfilled already.

_consumeClaimableDeposit(uint256 assets, address controller) → uint256 internal

Consumes assets from controller’s epochs oldest-first at each epoch’s locked rate, dequeueing fully consumed ones. Breaks early when the oldest epoch is still Pending: consuming from it would burn `assets for zero shares. Claims are therefore gated on the oldest Pending epoch, matching _asyncMaxDeposit.

Wrappers wanting stricter FIFO semantics should consider overriding to revert when the oldest epoch is Pending.
When the epoch’s locked rate skews the assets : shares ratio (either direction), a small assets input can round batchShares to zero; the epoch’s asset pool moves without minting shares to the caller. At realistic ERC-20 decimals the per-call drift is sub-cent. Deployers with non-standard decimals or a zero-drift requirement SHOULD override to revert when batchAssets > 0 && batchShares == 0.

_consumeClaimableMint(uint256 shares, address controller) → uint256 internal

Same as _consumeClaimableDeposit but iterates by shares instead of assets.

When the epoch’s locked rate skews the assets : shares ratio (either direction), a small shares input can round batchAssets to zero; the epoch’s share pool moves without consuming the caller’s asset entitlement. At realistic ERC-20 decimals the per-call drift is sub-cent. Deployers with non-standard decimals or a zero-drift requirement SHOULD override to revert when batchShares > 0 && batchAssets == 0.

_depositRequestQueueLimit() → uint256 internal

Maximum number of epoch entries in a controller’s queue. Defaults to 32. Prevents unbounded iteration in _asyncMaxDeposit and _asyncMaxMint.

ERC7540EpochDepositFulfilled(uint256 indexed epochId, uint256 totalAssets, uint256 totalShares) event

Emitted when a deposit epoch transitions from Pending to Claimable via _fulfillDeposit.

ERC7540EpochDepositTooEarly(uint256 epochId) error

Attempted to fulfill a deposit epoch that has not yet ended.

ERC7540EpochDepositEmptyEpoch(uint256 epochId) error

Attempted to fulfill a deposit epoch with no pending requests.

ERC7540EpochDepositAlreadyFulfilled(uint256 epochId) error

Attempted to fulfill a deposit epoch that has already been fulfilled.

ERC7540EpochDepositQueueLimitExceeded(address controller) error

Attempted to enqueue an epoch for controller past _depositRequestQueueLimit.

ERC7540EpochRedeem

import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC7540EpochRedeem.sol";

Epoch-based batch fulfillment strategy for asynchronous redemptions.

Extends ERC7540 with a redeem flow where requests submitted during the same epoch are batched together and settled at a single exchange rate when the admin closes the epoch via _fulfillRedeem. All controllers within a fulfilled epoch receive the same pro-rata conversion from shares to assets.

Production equivalents: Cove, Amphor, Lagoon.

The requestId returned by requestRedeem is the epoch ID. By default, epochs are weekly (block.timestamp / 1 weeks); override currentRedeemEpoch to change the cadence or use manually-bumped epoch counters.

Each account tracks its epoch memberships via a {DoubleEndedQueue} capped at _redeemRequestQueueLimit entries (default: 32) to bound the O(n) loops in _asyncMaxWithdraw and _asyncMaxRedeem. Users that hit the limit should claim fulfilled epochs to free up space.

Claims pay each controller’s pro-rata share floor-rounded against the remaining epoch totals. With very small fulfillment values (e.g. an epoch settling 3 shares for 2 assets across 3 equal claimants), rounding can leave one controller with up to 1 "wei" of unclaimable residue. At realistic ERC-20 token decimals this is sub-unit and economically immaterial. Unlike ERC-4626’s inflation-attack surface, the per-epoch totalShares and totalAssets cannot be inflated by donation (they only change via requestRedeem and _fulfillRedeem); deployers wanting finer per-claim granularity can set _decimalsOffset to scale share precision relative to assets.
Functions
  • _isRedeemAsync()

  • currentRedeemEpoch()

  • totalRedeemShares(epochId)

  • totalRedeemAssets(epochId)

  • redeemEpochs(controller, start, end)

  • _pendingRedeemRequest(requestId, controller)

  • _pendingAvailableRedeemRequest(requestId, controller)

  • _claimableRedeemRequest(requestId, controller)

  • _asyncMaxWithdraw(owner)

  • _asyncMaxRedeem(owner)

  • _convertToRedeemAssets(epochId, shares, rounding)

  • _convertToRedeemShares(epochId, assets, rounding)

  • _requestRedeem(shares, controller, owner, )

  • _fulfillRedeem(epochId, totalAssets)

  • _consumeClaimableWithdraw(assets, controller)

  • _consumeClaimableRedeem(shares, controller)

  • _redeemRequestQueueLimit()

ERC7540
  • supportsInterface(interfaceId)

  • isOperator(controller, operator)

  • setOperator(operator, approved)

  • _setOperator(controller, operator, approved)

  • _checkOperatorOrController(async, controller, operator)

  • decimals()

  • asset()

  • share()

  • vault(asset_)

  • totalAssets()

  • totalSupply()

  • pendingDepositRequest(requestId, controller)

  • claimableDepositRequest(requestId, controller)

  • pendingRedeemRequest(requestId, controller)

  • claimableRedeemRequest(requestId, controller)

  • totalPendingDepositAssets()

  • totalPendingRedeemShares()

  • convertToShares(assets)

  • convertToAssets(shares)

  • maxDeposit(owner)

  • maxMint(owner)

  • maxWithdraw(owner)

  • maxRedeem(owner)

  • previewDeposit(assets)

  • previewMint(shares)

  • previewWithdraw(assets)

  • previewRedeem(shares)

  • requestDeposit(assets, controller, owner)

  • deposit(assets, receiver)

  • deposit(assets, receiver, controller)

  • mint(shares, receiver)

  • mint(shares, receiver, controller)

  • requestRedeem(shares, controller, owner)

  • withdraw(assets, receiver, ownerOrController)

  • redeem(shares, receiver, ownerOrController)

  • _convertToShares(assets, rounding)

  • _convertToAssets(shares, rounding)

  • _requestDeposit(assets, controller, owner, requestId)

  • _mintSharesOnDepositFulfill(assets, shares)

  • _deposit(callerOrController, receiver, assets, shares)

  • _burnSharesOnRedeemFulfill(, shares)

  • _withdraw(caller, receiver, owner, assets, shares)

  • _transferIn(from, assets)

  • _transferOut(to, assets)

  • _decimalsOffset()

  • _depositShareOrigin()

  • _redeemShareDestination()

  • _isDepositAsync()

  • _pendingDepositRequest(, )

  • _claimableDepositRequest(, )

  • _consumeClaimableDeposit(, )

  • _consumeClaimableMint(, )

  • _asyncMaxDeposit()

  • _asyncMaxMint()

ERC20
  • name()

  • symbol()

  • balanceOf(account)

  • transfer(to, value)

  • allowance(owner, spender)

  • approve(spender, value)

  • transferFrom(from, to, value)

  • _transfer(from, to, value)

  • _update(from, to, value)

  • _mint(account, value)

  • _burn(account, value)

  • _approve(owner, spender, value)

  • _approve(owner, spender, value, emitEvent)

  • _spendAllowance(owner, spender, value)

Events
  • ERC7540EpochRedeemFulfilled(epochId, totalShares, totalAssets)

IERC4626
  • Deposit(sender, owner, assets, shares)

  • Withdraw(sender, receiver, owner, assets, shares)

IERC20
  • Transfer(from, to, value)

  • Approval(owner, spender, value)

IERC7540Redeem
  • RedeemRequest(controller, owner, requestId, sender, shares)

IERC7540Deposit
  • DepositRequest(controller, owner, requestId, sender, assets)

IERC7540Operator
  • OperatorSet(controller, operator, approved)

Errors
  • ERC7540EpochRedeemTooEarly(epochId)

  • ERC7540EpochRedeemEmptyEpoch(epochId)

  • ERC7540EpochRedeemAlreadyFulfilled(epochId)

  • ERC7540EpochRedeemQueueLimitExceeded(controller)

ERC7540
  • ERC4626ExceededMaxDeposit(receiver, assets, max)

  • ERC4626ExceededMaxMint(receiver, shares, max)

  • ERC4626ExceededMaxWithdraw(owner, assets, max)

  • ERC4626ExceededMaxRedeem(owner, shares, max)

  • ERC7540InvalidOperator(controller, operator)

  • ERC7540SyncDeposit()

  • ERC7540AsyncDeposit()

  • ERC7540SyncRedeem()

  • ERC7540AsyncRedeem()

  • ERC7540MissingAsync()

  • ERC7540UnauthorizedMintSharesOnDepositFulfill()

  • ERC7540UnauthorizedBurnSharesOnRedeemFulfill()

IERC20Errors
  • ERC20InsufficientBalance(sender, balance, needed)

  • ERC20InvalidSender(sender)

  • ERC20InvalidReceiver(receiver)

  • ERC20InsufficientAllowance(spender, allowance, needed)

  • ERC20InvalidApprover(approver)

  • ERC20InvalidSpender(spender)

_isRedeemAsync() → bool internal

Returns true if the redeem flow is asynchronous (Request-based). When false, withdraw and redeem behave as standard synchronous ERC-4626 operations.

Override to return true in extensions that provide an async redeem fulfillment strategy.

currentRedeemEpoch() → uint256 public

Returns the current epoch ID. Defaults to block.timestamp / 1 weeks + 1.

totalRedeemShares(uint256 epochId) → uint256 public

Returns the total shares queued in epochId. Equals the sum of all redeem requests during the Pending phase; decreases as claimants consume their pro-rata share once the epoch is fulfilled, and reaches 0 once the epoch is fully claimed.

totalRedeemAssets(uint256 epochId) → uint256 public

Returns the assets allocated to epochId at fulfillment. Zero before _fulfillRedeem is called; decreases as claimants consume their pro-rata share. Together with totalRedeemShares it encodes the locked epoch rate.

redeemEpochs(address controller, uint256 start, uint256 end) → uint256[] epochIds public

Returns the redeem epoch IDs that controller has open requests in, in queue order (oldest first). Fully claimed epochs are popped from the queue and no longer appear.

Using start = 0 and end = type(uint64).max will return the entire set of epochs.

_pendingRedeemRequest(uint256 requestId, address controller) → uint256 internal

A request is pending if its epoch has not yet been fulfilled (totalAssets == 0) and still has shares queued (totalShares > 0).

_pendingAvailableRedeemRequest(uint256 requestId, address controller) → uint256 internal

Returns the controller’s stored redeem request for requestId, or 0 if the epoch has been fully claimed (totalShares == 0).

_claimableRedeemRequest(uint256 requestId, address controller) → uint256 internal

A request is claimable if its epoch has been fulfilled (totalAssets > 0).

_asyncMaxWithdraw(address owner) → uint256 assets internal

Sums claimable assets from `owner’s fulfilled epochs oldest-first, stopping at the first Pending epoch. Fulfilled epochs behind a Pending one are not counted until the Pending one is fulfilled. Matches _consumeClaimableWithdraw.

O(n) in owner’s epochs, bounded by _redeemRequestQueueLimit (default 32). Per-account, so an attacker creating many small requests can only inflate their own queue, not other users'. Cross-controller DoS is not possible because epoch fulfillment via _fulfillRedeem is O(1) (it sets `totalAssets for the entire epoch in a single write).

_asyncMaxRedeem(address owner) → uint256 shares internal

Sums claimable shares across all fulfilled epochs the owner participates in. Same as _asyncMaxWithdraw.

_convertToRedeemAssets(uint256 epochId, uint256 shares, enum Math.Rounding rounding) → uint256 internal

Converts shares to assets at epochId’s locked rate. Returns 0 if `totalShares is 0.

_convertToRedeemShares(uint256 epochId, uint256 assets, enum Math.Rounding rounding) → uint256 internal

Converts assets to shares at epochId’s locked rate. Returns 0 if `totalAssets is 0.

_requestRedeem(uint256 shares, address controller, address owner, uint256) → uint256 internal

Records the request in the current epoch and enqueues the epoch ID for controller if not already present. A zero-amount request is a no-op at the epoch layer (it emits the event via super but does not create an unfulfillable epoch or occupy a queue slot).

Requirements:

  • _msgSender() must be controller or an approved operator of controller. This is on top of the base’s owner allowance/operator check and prevents third-party queue spam.

  • The controller’s epoch queue must not exceed _redeemRequestQueueLimit.

_fulfillRedeem(uint256 epochId, uint256 totalAssets) internal

Fulfills a past epoch by setting its totalAssets. All requests within the epoch become claimable at the rate totalAssets / totalShares.

When epoch transition is manual, the caller should bump the epoch before calling this.
Pending vs. fulfilled is distinguished by totalAssets == 0. Admins are assumed not to fulfill at zero (a confiscation event with no economic purpose); if 0 is passed by accident, the call is a no-op and the admin can re-fulfill. This recovery only holds as long as derived contracts preserve the no-side-effect semantics of this function — if not, derived contracts should restrict totalAssets != 0. A genuine total-loss settlement is not representable in this encoding; deployers needing that SHOULD override to encode the fulfilled state separately (e.g. an explicit boolean).
Out-of-order fulfillment is permitted, but each controller’s claims stay gated on their oldest Pending epoch (see _consumeClaimableWithdraw). Funds are not lost; a later fulfilled epoch simply waits until the older one is fulfilled. Derived contracts wanting strict FIFO settlement should enforce it here.

Requirements:

  • epochId must be a past epoch (less than currentRedeemEpoch).

  • The epoch must have pending shares and must not have been fulfilled already.

_consumeClaimableWithdraw(uint256 assets, address controller) → uint256 internal

Consumes assets from controller’s epochs oldest-first at each epoch’s locked rate, dequeueing fully consumed ones. Breaks early when the oldest epoch is still Pending: consuming from it would burn `assets for zero shares. Claims are therefore gated on the oldest Pending epoch, matching _asyncMaxWithdraw.

Wrappers wanting stricter FIFO semantics should consider overriding to revert when the oldest epoch is Pending.
When the epoch’s locked rate skews the shares : assets ratio (either direction), a small assets input can round batchShares to zero; the epoch’s asset pool moves without burning shares from the caller’s request. At realistic ERC-20 decimals the per-call drift is sub-cent. Deployers with non-standard decimals or a zero-drift requirement SHOULD override to revert when batchAssets > 0 && batchShares == 0.

_consumeClaimableRedeem(uint256 shares, address controller) → uint256 internal

Same as _consumeClaimableWithdraw but iterates by shares instead of assets.

When the epoch’s locked rate skews the shares : assets ratio (either direction), a small shares input can round batchAssets to zero; the epoch’s share pool moves without paying assets to the caller. At realistic ERC-20 decimals the per-call drift is sub-cent. Deployers with non-standard decimals or a zero-drift requirement SHOULD override to revert when batchShares > 0 && batchAssets == 0.

_redeemRequestQueueLimit() → uint256 internal

Maximum number of epoch entries in a controller’s queue. Defaults to 32. Prevents unbounded iteration in _asyncMaxWithdraw and _asyncMaxRedeem.

ERC7540EpochRedeemFulfilled(uint256 indexed epochId, uint256 totalShares, uint256 totalAssets) event

Emitted when a redeem epoch transitions from Pending to Claimable via _fulfillRedeem.

ERC7540EpochRedeemTooEarly(uint256 epochId) error

Attempted to fulfill a redeem epoch that has not yet ended.

ERC7540EpochRedeemEmptyEpoch(uint256 epochId) error

Attempted to fulfill a redeem epoch with no pending requests.

ERC7540EpochRedeemAlreadyFulfilled(uint256 epochId) error

Attempted to fulfill a redeem epoch that has already been fulfilled.

ERC7540EpochRedeemQueueLimitExceeded(address controller) error

Attempted to enqueue an epoch for controller past _redeemRequestQueueLimit.