Common Errors
Revert selectors and the role table for the errors most likely to block a Fusion vault deployment.
Revert selectors and setup errors an agent or developer is likely to hit while deploying or operating a Fusion vault (see Deploy your first Fusion vault), with the cause and the fix for each. The SDK README's Common errors and the MCP resource fusion://invariants carry the same deployment checks.
Revert table
| Selector | Revert | What happened | Fix |
|---|---|---|---|
0x8745fbfd | DaoFeePackagesArrayEmpty() | clone() was sent to IporFusionFactoryImpl | Send it to IporFusionFactoryProxy for that chain — see Addresses |
0x9996b315 | AddressEmptyCode(address) | execute() touched a market with no balance fuse | add_balance_fuse(market_id, balance_fuse) before the first execute on that market |
0x4bf4de4e | HandlerNotFound() | A protocol called back into the vault during execute() (a flash loan) and no handler is registered for its address and selector | update_callback_handler(handler, sender, selector) before the first execute that triggers the callback |
0x068ca9d8 | AccessManagedUnauthorized(address) | The caller lacks the role the function requires: FUSE_MANAGER for add_fuses, grant_market_substrates, add_balance_fuse, update_callback_handler; ALPHA for execute; WHITELIST for deposit and mint on a private vault | Check first with AccessManager.can_call(caller, target, selector).call(ctx) (a closed target or a missing role answers immediate=False, delay=0; a role with an execution delay answers its delay). AccessManager.grant_role(role, account, Period(0)) from the role's admin (OWNER grants ATOMIST, ATOMIST grants the rest); for a reverting deposit, whitelist the depositor or convert the vault to public |
ValueError: Private key required for sending transactions | SDK, before any transaction | .send(ctx) on a Web3Context without a key | Web3Context(w3, chain_id, signer=..., private_key=...) or Web3Context.from_url(url, private_key=...) |
A positive can_call(...).call(ctx).delay means the call must be scheduled, not that another role grant is missing: AccessManager.read_call_plan(ctx, call, caller=...) tells a missing schedule from a waiting or a ready one. For withdrawals, redemptions and share transfers also read get_account_lock_time(account).call(ctx): a deposit or mint locks its caller, which can differ from the share receiver. Neither check replaces a simulation.
For an execute batch, fusion vault preflight <vault> --calldata <hex> --sender <account> --chain-id base (SDK: preflight_execute, MCP: vault_preflight) checks the permission, the registered fuses, the balance dependencies and a simulated execution together. Market limits take Wad fractions of total assets: Wad(6 * 10**17) is 60%, and a token amount such as 1_000_000 sets a near-zero ceiling; a limit outside 0..10**18 is rejected before encoding.
Role table
| Role | ID | Grants | Granted by |
|---|---|---|---|
OWNER_ROLE | 1 | Everything below, via the access manager | Set at vault creation from the owner argument to clone() |
ATOMIST_ROLE | 100 | Vault configuration authority; administers FUSE_MANAGER, ALPHA and WHITELIST | OWNER |
ALPHA_ROLE | 200 | Can call execute | ATOMIST |
FUSE_MANAGER_ROLE | 300 | Can call add_fuses, grant_market_substrates, add_balance_fuse, update_callback_handler | ATOMIST |
WHITELIST_ROLE | 800 | Can deposit / mint into a private vault | ATOMIST |
A fresh clone grants the owner only OWNER_ROLE — every configuration and execute call from that account reverts AccessManagedUnauthorized until ATOMIST_ROLE is granted first (it administers the rest), matching step 2 of Deploy your first Fusion vault.
Related invariants
- Clone from
IporFusionFactoryProxy, never fromIporFusionFactoryImpl— only the proxy carries the fee configuration. - Register all three of
add_fuses,grant_market_substratesandadd_balance_fusebefore the firstexecuteon a market. Any order between them works; what is not optional is that all three precedeexecute. - A fresh clone is private:
depositandmintrevert until either the depositor holdsWHITELIST_ROLE(keeps the vault private) or an Atomist callsconvert_to_public_vault()(opens deposits to every address, and cannot be reverted). - A flash-loan fuse also needs
update_callback_handler(handler, sender, selector)before its firstexecute: Morpho Blue calls backonMorphoFlashLoan(uint256,bytes), the Euler EVConEulerFlashLoan(bytes), and an unregistered callback revertsHandlerNotFound(). - Fuses are immutable and addresses are per chain — resolve
(chain, name)inipor-abi(opens in new tab); never reuse an address across chains.