CoreVault capabilities
Vault capabilities
Inspect one vault with ERC-165 supportsInterface to read the full capability matrix. Shared capability interfaces live under contracts/shared/interfaces/vault/. Concrete Creator and Agent vaults keep their lane identities.
IOVault4626 stays a lean wiring ABI. Do not look there for share externalization or async requests.
How to inspect
Call supportsInterface(bytes4) with the official ERC-165 ids (also in OVaultCapabilityIds). Live CreatorOVault / AgentOVault do not advertise 7575 yet — AgentOVault runtime exceeds EIP-170 if those methods are added to the shared facade. OVaultAsyncRequestModule still attaches: if the vault has no supportsInterface / share(), it treats the vault itself as the ERC-4626 share token. Tests also cover the intended 7575 ABI via Test7575OVault.
| Capability | Interface ID | Intended sync OVault | Live sync OVault | Async request module |
|---|---|---|---|---|
| ERC-165 | 0x01ffc9a7 | yes | no | yes |
| ERC-7575 vault | 0x2f0a18c5 | yes | no | yes |
| ERC-7575 share | 0xf815c03d | yes | no | no (share is the underlying vault) |
| ERC-7540 operator | 0xe3bc4e65 | no | yes | |
| ERC-7540 deposit | 0xce3bbe50 | no | yes | |
| ERC-7540 redeem | 0x620ee8e4 | no | yes | |
| ERC-7887 cancel deposit (Draft) | 0x8bf840e3 | no | yes | |
| ERC-7887 cancel redeem (Draft) | 0xe76cffc7 | no | yes | |
| ERC-8161 transfer deposit | 0x53b3bb0a | no | yes | |
| ERC-8161 transfer redeem | 0x7846f5bd | no | yes |
The advertised ERC-7575 vault id is IERC-4626 (excluding ERC-20) XOR share(). It is not type(IOVault7575).interfaceId, which is only the share() selector (0xa8d5fd65).
Integrator rule: require supportsInterface(0x2f0a18c5) and read share(). Never infer the accounting share from shareOFT(), IShareOFT4626.vault(), or type(IOVault7575).interfaceId. Advertised IDs are centralized in OVaultCapabilityIds / OVaultCapabilitiesLib.
ERC-7575 share token
Intended synchronous vaults implement (test harness today; live facades deferred for EIP-170):
function share() external view returns (address); // address(this)
function vault(address asset_) external view returns (address);
share() returns the vault itself ( / ◇). That is the ERC-4626 accounting share and the EIP-allowed share() MAY return address(this) path.
ShareOFT ( / ◆) is not share(). It is a LayerZero wrapper minted by the OVault wrapper at a fixed 1000:1 normalization, with trade fees and lottery behavior. Discover it via IOVaultWrapper4626.shareOFT() or Registry4626, not ERC-7575.
A true external-share ERC-7575 vault (share != address(this)) is not fully ERC-4626 compatible: the vault itself must not be the ERC-20, and entry functions must mint the external share 1:1. That shape is reserved for a future implementation. Do not map share() to ShareOFT on the current vaults.
Async request module (ERC-7540)
OVaultAsyncRequestModule sits in front of a compatible sync OVault, including a live CreatorOVault that does not advertise ERC-7575. It does not change CreatorOVault / AgentOVault.
Flow: requestDeposit / requestRedeem → fulfill* (Pending → Claimable) → deposit / redeem claim against the underlying vault. requestId is always 0. previewDeposit, previewMint, previewRedeem, and previewWithdraw revert. share() is the underlying vault ( / ◇), not the module and not ShareOFT.
fulfill* is the controller or that controller's operator — not the module owner and not a random caller. Owner pause is instant (emergency) and does not lock claims. controller must equal owner on request. A new request or pending transfer is blocked while the destination controller still has cancel-claimable outstanding. Claims emit Deposit / Withdraw on this module (controller, receiver) so indexers are not forced to watch the sync vault.
Cancel (ERC-7887, Draft) skips straight to cancel-claimable so users can recover pending or claimable assets or shares. Fulfill is only a bookkeeping flip — it cannot trap funds. Transfer (ERC-8161) still moves pending balances only.
asset and share are cached at construct. totalAssets is escrowed request value (not donation-inclusive AUM) and must not revert. Claims measure the token/share delta and revert if the vault spends more than reserved or mints zero shares. Cancel-claims measure the paid delta and revert if the module cannot cover the booked amount. request* credits the received delta and reverts if a surplus arrives (rebase-up / reflection). maxDeposit is min(claimable, vault.maxDeposit(controller)) — the argument is the share receiver (EIP-4626), so controller is the claim-to-self proxy; do not pass the module. maxMint inverts previewMint against that deposit cap and vault.maxMint(controller) so a mint-disabled vault (AgentOVault) advertises 0. maxWithdraw inverts previewWithdraw against maxRedeem and vault.maxWithdraw(address(this)) (the module owns the escrowed shares). mint(maxMint()) and withdraw(maxWithdraw()) succeed.
The existing large-withdrawal queue on the sync vault is an MEV / flash-loan gate, not ERC-7540.
Learnings from other ERC-7540 stacks
These are production mistakes we copied the fix for, not new product scope:
| Source | Failure | Locked by |
|---|---|---|
| Centrifuge / OZ ERC7540 | Public or owner fulfill griefs ERC-8161 pending transfers | testLesson_centrifuge_strangerFulfillCannotGriefPendingTransfer, testLesson_oz_operatorCanFulfillOwnController, testOwnerCannotFulfill |
| OpenZeppelin ERC7540 natspec | Dead controller locks the request | testRejectsModuleOrVaultAsController, testCancelAfterFulfillDeposit |
| Superform v2 (SUP-14312) | controller != owner on redeem desyncs share buckets | testLesson_superform_redeemControllerMustEqualOwner |
| Lagoon V1–V5 | Hybrid sync+async on one vault | testAsyncSelectorsAreAbsentOnSyncVaults, testSyncVaultPreviewsStillWork |
| ERC-7887 | New request while cancel is outstanding | testRequestBlockedWhileCancelClaimable, testLesson_eip7887_redeemBlockedWhileCancelClaimable |
| EIP-7540 / Centrifuge claim events | Indexers miss claims if only the inner vault emits | testLesson_eip7540_claimEmitsDepositWithController, testLesson_eip7540_claimEmitsWithdrawWithController |
| ERC-8161 | Transfer must not move claimable | testLesson_eip8161_transferDoesNotMoveClaimable |
Run the lock: forge test --match-test testLesson_ --no-match-path 'test/vault/strategies/CreatorOVaultStrategies.Rebalance.*'. If any of those fail, we unlearned the corresponding production bug.
Epoch note
share(), vault(address), and supportsInterface are additive ABI. Live vaults keep prior bytecode until a size-safe reseal. This source change does not cut a new greenfield shell or reseal DEPLOY_BYTECODE. Do not add these methods to the shared CreatorOVault facade until AgentOVault runtime is back under 24,576 bytes.
Prev: CreatorOVault · Next: CreatorOVaultWrapper