Making 7540 Fungible

Now that ERC7540 is increasing traction, its a good time for new primitives to be built on top. What I am looking to address is the ability to allow the 7540 async requests to become fungible and have outlined my implementation in the following spec.

If there is anyone that is deep in the 4626/7540 trenches then it would be good to hear feedback.

A big shoutout to @jeroen for his valuable feedback so far.

ERC7540Fungibility

TODO: think of a better name

Overview

ERC7540Fungibility converts pending ERC-7540 async vault requests into fungible ERC-6909 claim tokens. A user wraps their pending deposit or redeem request, receives fungible tokens representing their share of the position, and burns those tokens to claim settled assets directly from the vault once the request is fulfilled.

The contract is vault-agnostic — it works with any ERC-7540 vault, whether or not the vault supports ERC-8161 transferable requests.

How It Works

A pending vault request is non-fungible — it’s tied to a specific controller, vault, and request ID. Wrapping it into ERC-6909 tokens makes it fungible. Each token represents an equal share of the underlying position. Token holders can transfer, trade, or use their shares in other protocols.

Each wrapped request gets a unique tokenId (issued sequentially). The underlying vault position is held by an isolated delegate clone that acts as the controller on the vault. When the vault fulfills the request, token holders burn their ERC-6909 shares to claim their portion directly from the vault.

Function Reference

Function Description
transferDeposit Wrap an existing ERC-8161 pending deposit request into fungible tokens
transferRedeem Wrap an existing ERC-8161 pending redeem request into fungible tokens
requestDeposit Pull assets from owner, originate a deposit request, and mint fungible tokens
requestRedeem Pull vault shares from owner, originate a redeem request, and mint fungible tokens
pending Check whether the vault request is still pending
cancel Transfer the vault request back to a specified controller via ERC-8161
redeem Burn shares and claim the corresponding portion directly from the vault

Wrapping Modes

ERC-8161 Transfer (transferDeposit / transferRedeem)

For vaults that support transferable requests. The user has an existing pending request and transfers its controller to a delegate clone. Requires this contract to be set as an operator on the vault for the controller.

Direct Request (requestDeposit / requestRedeem)

For vaults without ERC-8161. The contract pulls assets or shares from the owner, deploys a delegate clone, and originates a new request on the vault through the delegate. The delegate becomes the controller on the vault.

In both modes, the receiver gets ERC-6909 tokens equal to the request amount.

Lifecycle

wrap (transfer* or request*) → [pending] → redeem (burn shares, claim from vault)
                                   ↓
                                cancel (transfer request back to controller)

Wrap — User wraps a vault position. Delegate clone deployed. ERC-6909 tokens minted to the receiver.

Pending — The vault is processing the request. Tokens are freely transferable.

Redeem — Token holders burn shares and claim their portion directly from the vault via deposit() (for deposit requests) or redeem() (for redeem requests). Each call executes a partial claim against the vault. The return amount is determined by the vault’s exchange rate at execution time.

Cancel — Owner-only. Transfers the vault request back to a specified controller via ERC-8161. All ERC-6909 tokens are burned and the token is deleted. Only allowed if the owner still holds all shares.

Settlement Model

There is no separate claim() step. When a holder calls redeem(), the contract delegates directly to the underlying vault’s deposit() or redeem() function through the delegate clone. The vault determines the exchange rate at execution time. Each partial redemption is an independent vault interaction.

This means the exchange rate may differ between redeemers if the vault reprices between calls. This is an intentional design choice — the fungibility contract is a thin wrapper and does not attempt to override or snapshot the vault’s settlement mechanics. If the vault reprices, that’s the vault behaving as designed.

A consequence of this model is that previewRedeem() is not possible as a native view function. ERC-7540 async vaults return 0 from previewRedeem and there is no view function that exposes the final conversion rate until execution. Off-chain consumers can simulate the result via eth_call. On-chain consumers that need a preview can use the Uniswap Quoter revert-call pattern — execute the vault call inside a subcall that reverts with the result encoded in the revert data, then decode the result in the parent call.

Custody Model

Each wrapped request gets its own delegate clone deployed via EIP-1167 minimal proxy. The delegate is a thin executor contract (~77 bytes) that forwards calls from the fungibility contract to the vault. Assets never touch the fungibility contract itself.

The delegate clone exists primarily to distinguish requests. For vaults with requestId = 0, the only differentiator is the controller address, so we need a unique controller per wrapped request. Deploying a lightweight clone gives us that — each clone is its own address and acts as the controller on the underlying vault.

The clone also provides asset isolation. When a request is fulfilled and a holder redeems, the vault sends settled assets through the delegate. If assets were pooled in the main contract instead, anyone could deploy a fake ERC-7540 vault using the same underlying token, create a request that “resolves” to arbitrary amounts, and drain funds that came from a legitimate request on a different vault. With per-request delegates, a malicious vault can only touch its own clone — there is no shared pool to steal from.

Authorization

ERC-6909 Operators — Global approval via setOperator(). An operator can act on behalf of the owner across all token IDs. Used for cancellation, redeem-on-behalf, and delegated wrapping.

ERC-6909 Allowances — Per-token-ID approval via approve(). Controls share transfers only. Does not grant access to cancel or redeem.

Vault Operator — For transferDeposit / transferRedeem, this contract must be set as an operator on the vault for the controller. This is a separate approval on the vault contract.

Cancellation

Cancellation transfers the vault request back to a specified controller via ERC-8161. This is a synchronous operation — no two-step async cancel pattern. The contract:

  1. Verifies the owner still holds all shares (no transfers have occurred)
  2. Burns all ERC-6909 tokens and zeros the supply
  3. Calls transferDepositRequest or transferRedeemRequest on the vault, moving the request from the delegate back to the specified controller
  4. Deletes the token

The vault must support ERC-8161 for cancellation to work. Requests wrapped against vaults without ERC-8161 (created via requestDeposit / requestRedeem) cannot be cancelled through this mechanism.