ERC-4626 is an ERC-20 extension that defines a standard API for tokenized vaults holding a single underlying ERC-20 asset. The vault itself is a token whose shares represent a proportional claim on the vault's assets, and the standard fixes the deposit, mint, withdraw, redeem, and conversion functions that integrators can rely on. This article explains the share/asset mental model, walks through a runnable OpenZeppelin-based vault, shows how to verify a deployment over JSON-RPC, and covers the inflation attack and the extension standards (ERC-7540, ERC-7575) that handle asynchronous and multi-asset cases. It separates what the specification requires from what you must measure on your own deployment.
What ERC-4626 Standardizes and What It Leaves to You
ERC-4626, finalized as EIP-4626, is a standard API for tokenized vaults that represent shares of a single underlying ERC-20 token. The vault contract must itself implement ERC-20, so the vault's units — called shares — are transferable tokens whose holders have a claim on the vault's underlying holdings. The standard defines the functions for depositing and withdrawing assets, minting and redeeming shares, and reading the conversion rates between the two.
What the standard does not define is the strategy. A compliant vault can lend into a money market, hold tokenized treasuries, or simply sit idle; ERC-4626 only fixes the interface and the accounting semantics around totalAssets, totalSupply, and the conversion functions. Fees, caps, access control, and yield source are implementation choices. That is why two ERC-4626 vaults with the same interface can behave very differently under stress.
This guide assumes you can read Solidity, have a working knowledge of ERC-20 approvals, and can reach an EVM JSON-RPC endpoint. For endpoint selection rather than interface design, the Ethereum RPC API overview and the RPC endpoint guide cover connectivity; this page focuses on the vault contract itself.
- The vault MUST implement ERC-20 and its optional metadata extensions (name, symbol, decimals).
- The vault manages exactly one underlying
asset()token. - Conversion functions must be consistent with
totalAssets()andtotalSupply()at the time of the call. - Strategy, fees, caps, and pause behavior are outside the standard.
The Full Interface in One Pass
The standard groups its functions into four write paths and a set of read paths. The four write paths exist because users can specify either the asset amount or the share amount, and because the caller and the receiver can differ. deposit(assets, receiver) pulls a fixed amount of assets and issues shares to receiver. mint(shares, receiver) issues a fixed number of shares and pulls whatever assets are required. withdraw(assets, receiver, owner) burns enough shares from owner to release a fixed asset amount. redeem(shares, receiver, owner) burns a fixed number of shares and releases the corresponding assets.
Each write path has a matching max* view — maxDeposit, maxMint, maxWithdraw, maxRedeem — that reports the limit for a given address. Integrators must check these before submitting a transaction, because a vault may cap deposits, pause withdrawals, or restrict certain addresses. A transaction that ignores maxDeposit will revert and waste gas.
The read paths include asset, totalAssets, the two conversion functions, and the four preview functions. The standard also defines Deposit and Withdraw events, which indexers and accounting systems use to reconstruct share ownership without replaying every transfer.
- Write paths: deposit, mint, withdraw, redeem.
- Limit views: maxDeposit, maxMint, maxWithdraw, maxRedeem.
- Rate views: convertToShares, convertToAssets.
- Preview views: previewDeposit, previewMint, previewWithdraw, previewRedeem.
- Events: Deposit(sender, owner, assets, shares) and Withdraw(sender, receiver, owner, assets, shares).
Building a Minimal Compliant Vault with OpenZeppelin
OpenZeppelin Contracts ships an ERC4626 base that implements the interface on top of ERC20 and SafeERC20. The base handles share math, the preview functions, and the virtual-offset defense against the inflation attack. A minimal vault only needs to supply the underlying asset address and a name and symbol for the shares.
The example below is a starting point, not a production vault. It holds assets idle; a real vault would add a strategy that increases totalAssets over time and would override totalAssets() to report the strategy's value. It also inherits OpenZeppelin's default virtual offset, which is the recommended baseline defense discussed later.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SimpleVault is ERC4626 {
constructor(IERC20 asset_)
ERC20("Simple Vault Share", "svSHARE")
ERC4626(asset_)
{}
// Idle vault: totalAssets() defaults to asset.balanceOf(address(this)).
// Override totalAssets() when a strategy holds the assets elsewhere.
}Verifying a Deployment Over JSON-RPC
Before integrating a vault, confirm its reported state matches the chain. The procedure below uses eth_call against a vault address and a known holder. Replace RPC_URL with your provider endpoint and VAULT_ADDRESS with the deployed vault. The selectors shown are the standard four-byte selectors for asset(), totalAssets(), and totalSupply(); you can regenerate them with cast sig or any ABI encoder.
Run the calls in order and record the results in a table. The expected verification is that asset() returns the underlying token you expect, totalAssets() is at least the sum of assets you can account for, and convertToShares(totalAssets()) is close to totalSupply() within rounding. A large mismatch between the two is a signal to inspect the vault's accounting overrides before depositing.
curl -s RPC_URL \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [
{"to": "VAULT_ADDRESS", "data": "0x38d52e0f"},
"latest"
]
}'
# 0x38d52e0f = asset()
# 0x01e1d114 = totalAssets()
# 0x18160ddd = totalSupply()Recording Integration Measurements
Interface compliance does not tell you whether a vault is safe or liquid. Build a small measurement sheet and fill it from live calls rather than from documentation. The table below is a template; every value must come from your own eth_call results or from an indexer you trust, and you should re-run it at each block you care about.
| Check | Method | Expected result |
|---|---|---|
| Underlying asset | asset() | Matches the token you intend to deposit |
| Assets under management | totalAssets() | Consistent with strategy balances you can verify |
| Share supply | totalSupply() | Non-zero after first deposit |
| Deposit quote | previewDeposit(amount) | Shares rounded down, non-zero |
| Redeem quote | previewRedeem(shares) | Assets rounded down, at most totalAssets() |
| Deposit limit | maxDeposit(you) | At least your intended amount |
| Withdraw limit | maxWithdraw(you) | At least your intended amount |
| Round-trip loss | deposit then redeem preview | Small and explainable by rounding and fees |
The round-trip check is the most useful single number. Preview a deposit of a realistic size, then preview redeeming the shares it would issue, and compare the assets returned to the assets deposited. A large gap that is not explained by documented fees or rounding is a reason to pause. Do not assume a value; measure it at the block you will transact against.
The Inflation Attack and the Virtual Offset Defense
The best-known ERC-4626 pitfall is the inflation attack, also called the donation attack. An attacker deposits a tiny amount to receive one share, then donates a large amount of the underlying directly to the vault, inflating the asset value per share. A subsequent depositor whose assets are smaller than the inflated share price can be rounded down to zero shares, and the attacker redeems the donation plus the victim's deposit.
OpenZeppelin's recommended defense is a virtual offset: the share math adds a small virtual asset amount and a small virtual share amount to the numerator and denominator, so the first depositor cannot make one share arbitrarily expensive. The base contract applies this by default. Vaults that override the conversion internals must preserve an equivalent defense or document why their design makes the attack unprofitable.
The attack is not the only pitfall. Fee-on-transfer underlying tokens break the assumption that the vault receives exactly the deposited amount. Rebasing tokens change totalAssets without a transfer. Vaults that report strategy value must handle a strategy loss without letting early redeemers exit at the old rate. Each of these is a reason to read the vault's overrides rather than trusting the interface alone.
- Keep the virtual offset unless you have a reviewed alternative.
- Reject or specially handle fee-on-transfer and rebasing underlying tokens.
- Cap deposits until the strategy and accounting are audited.
- Test the first-depositor path explicitly in a fork test.
Asynchronous and Multi-Asset Extensions
ERC-4626 assumes atomic deposits and redemptions up to a limit. That assumption fails for real-world asset protocols, cross-chain lending, liquid staking with unbonding queues, and insurance modules where settlement is delayed. ERC-7540 extends the standard for asynchronous vaults: requests are submitted, then claimed later, while the existing deposit, mint, withdraw, and redeem functions are reused to claim the settled request.
ERC-7575 addresses vaults with multiple assets or entry points, such as liquidity provider tokens. Because ERC-4626 requires the vault itself to be an ERC-20, multi-asset vaults are awkward or non-compliant under the base standard; ERC-7575 externalizes the share token from the vault implementation so the vault can manage several assets while still exposing a compatible surface.
If you are integrating a vault that advertises ERC-4626 but settles asynchronously, check whether it implements ERC-7540 and read the request lifecycle before building a UI around it. Treating an asynchronous vault as atomic will produce incorrect quotes and failed claims.
Troubleshooting Common Integration Failures
Most ERC-4626 integration bugs fall into a few patterns. A deposit reverts because the caller did not approve the vault for the asset amount, or approved the wrong spender. A withdraw reverts because maxWithdraw was zero due to a pause or a cap, or because the caller passed an owner they do not control and had no allowance. A quote is wrong because the integrator used convertToShares instead of previewDeposit, ignoring rounding and fees.
A subtler failure is stale state. Quotes computed at one block can be invalid by the time the transaction lands, especially in vaults whose totalAssets moves with a lending rate. Use a deadline or a slippage bound where the vault supports one, and re-check limits immediately before submitting. If a vault's totalAssets jumps without a corresponding Deposit or Withdraw event, investigate donations or strategy reporting before treating the rate as reliable.
When a call fails at the RPC layer rather than in the contract, confirm the endpoint is synced and the chain ID matches the deployment. A vault address from one network will return empty data on another. The network directory lists supported chains, and dedicated node access is worth considering when you need consistent state reads across many vaults.
- Check allowance and spender before every deposit or mint.
- Read max* limits in the same block as the transaction.
- Prefer preview functions over raw conversion for quotes.
- Confirm chain ID and endpoint sync before debugging contract logic.
Limitations and Tradeoffs of the Standard
ERC-4626 buys composability at the cost of hiding strategy risk behind a uniform interface. Two vaults with identical ABIs can have completely different risk profiles, and the standard gives integrators no way to express that difference. Aggregators that route across many vaults must add their own risk scoring, caps, and monitoring on top of the interface.
The standard also assumes a single underlying ERC-20 and atomic settlement. Projects that need multiple assets or delayed settlement must adopt extensions, and projects with non-standard tokens must handle the edge cases themselves. None of this is a defect in the standard; it is the boundary of what a minimal interface can promise.
Finally, compliance is necessary but not sufficient. A vault can pass every interface check and still be economically unsafe. Treat the interface as the entry ticket and the measurement sheet as the real due diligence.
Next Steps for Builders and Integrators
If you are building a vault, start from the OpenZeppelin base, keep the virtual offset, add a strategy with an explicit totalAssets override, and write fork tests for the first-depositor path, fee-on-transfer assets, and strategy loss. If you are integrating, build the measurement sheet into your monitoring so it runs at every block you quote against, and alert on unexplained changes in totalAssets.
For the infrastructure side, review RPC pricing to size your read volume, and use the API service when you need managed access rather than running your own nodes. For broader protocol context, the Learn library covers related standards and operational guides. Concrete endpoint and provider choices are best handled by the RPC Assistant, which keeps short, current recommendations separate from this implementation guide.