Every page on https://ipor.io is available as plain Markdown by appending .md to its URL. Arriving from the documentation, read Fusion for agents (https://ipor.io/docs/build-on-fusion/fusion-for-agents.md) first. Core vocabulary: - Plasma Vault: the ERC-4626 vault contract that holds depositor funds. Every interaction with an external protocol goes through the vault. Vault shares use the underlying asset's decimals plus 2 (DECIMALS_OFFSET = 2 in PlasmaVaultLib.sol), which decimals() returns despite its NatSpec; do not assume a 1:1 share-to-asset scale. - Fuse: a stateless adapter contract encoding one action on an external protocol (supply, withdraw, borrow, swap, claim). The Plasma Vault executes fuses via delegatecall, so the vault itself holds the resulting positions. Fuses are immutable. New strategies are composed from already-registered fuses; a new fuse is needed only for an action no registered fuse covers. - Balance Fuse: a fuse that reports the value of the vault's position in one market, used for share price calculation. - Market: a numeric ID that groups the fuses and the balance fuse for one protocol integration. The IDs are defined in the IporFusionMarkets library (contracts/libraries/IporFusionMarkets.sol) and mirrored in the SDK as ipor_fusion.IporFusionMarkets. - Substrate: a per-market allowlist of assets, pools or parameters a fuse is permitted to touch. Substrates are bytes32 values with market-specific layouts: for Aave V3 the substrate is the asset address right-aligned in 32 bytes, other markets use typed layouts. The SDK decodes them in ipor_fusion.substrates; the layouts are documented in the fuse libraries of the contracts repository. - Atomist: the vault curator. Configures fuses, substrates, fees, limits, price oracle and roles. Holds ATOMIST_ROLE. - Alpha: the strategist address that rebalances the vault. Holds ALPHA_ROLE and calls PlasmaVault.execute. - Access Manager: an OpenZeppelin AccessManager based contract (IporFusionAccessManager) that owns all role assignments and execution delays for a vault. Roles are numeric IDs defined in the Roles library (contracts/libraries/Roles.sol): OWNER_ROLE = 1, GUARDIAN_ROLE = 2, ATOMIST_ROLE = 100, ALPHA_ROLE = 200, FUSE_MANAGER_ROLE = 300, CLAIM_REWARDS_ROLE = 600, WHITELIST_ROLE = 800, UPDATE_MARKETS_BALANCES_ROLE = 1000, PRICE_ORACLE_MIDDLEWARE_MANAGER_ROLE = 1200, PUBLIC_ROLE = type(uint64).max. Subset; see the library. - Withdraw Manager: contract every factory-created vault has (a vault cannot be initialized without one); it runs withdrawal requests, their release by the Alpha, and withdrawal fees. - Rewards Claim Manager: contract that claims and holds protocol rewards before they are swapped or vested into the vault. - Fee Manager: contract that accrues and distributes the vault's management and performance fees. - Context Manager: contract that lets an approved address execute vault operations on behalf of another via signature-based authentication. - Price Oracle Middleware: the price source a vault uses to value its assets. - Pre-hooks: optional checks executed before selected vault functions. Rules for agents interacting with IPOR Fusion: - Do not call a fuse contract directly. Encode the action as a FuseAction struct { address fuse; bytes data; } (interface IPlasmaVault) and call PlasmaVault.execute(FuseAction[] calldata calls_) from an address holding ALPHA_ROLE. - An action on an asset or pool outside the market's granted substrates reverts inside the fuse with that fuse's own error; the vault checks only that the fuse is registered. Fuses with hard-coded targets take no substrate. - Roles are not granted by default. Read the vault's Access Manager before assuming an address can execute, configure, deposit or withdraw. - deposit, mint, withdraw and redeem are restricted functions; a freshly cloned vault is private, so deposit and mint work only when the caller holds WHITELIST_ROLE, until an Atomist calls convertToPublicVault, which maps them to PUBLIC_ROLE and, on a vault created with clone, cannot be reverted. For a vault operated by its owner's own bot, grant WHITELIST_ROLE instead of going public. Do not trust maxDeposit, maxMint, maxWithdraw or maxRedeem: they ignore roles, liquidity and redemption locks, so simulate the call instead. - Market balances are refreshed by PlasmaVault.updateMarketsBalances(uint256[] calldata marketIds_), callable by UPDATE_MARKETS_BALANCES_ROLE, which initialization also grants to the vault itself. Total assets and share price depend on those balances being current. execute refreshes only its fuses' markets plus dependencies; it and updateMarketsBalances mint the performance fee above the high-water mark, so refresh all markets before measuring rebalances. A reverting balance fuse blocks its market: refreshes and executes touching it revert; test one market ID at a time. - Total assets = the vault's own balance of the underlying asset + the cached balance of every market + the Rewards Claim Manager's balanceOf when one is set. The ERC20 balance fuse (market 7, ERC20_VAULT_BALANCE) skips the underlying asset and prices every other substrate token, held or not. - Value each holding through exactly one balance fuse: a holding counted by two markets inflates total assets and share price while every on-chain check passes. Known double counts: a receipt token (Aave aToken, ERC-4626 share, Midas mToken) listed as an ERC20 balance substrate next to its own market; the same Morpho Blue market id granted to both the Morpho market (14) and the Morpho liquidity-only market (41); the same ERC-4626 vault or token granted to two markets. - Match substrates to the balance fuse: Erc4626BalanceFuse reverts on a plain token such as USDC; CurveChildLiquidityGaugeBalanceFuse reverts AssetNotFoundInCurvePool when the pool lacks the vault's asset. Some reads skip zero positions, so an empty market's clean refresh proves little. - Every token a balance fuse prices needs an oracle price, or its market blocks. That includes ERC20 substrates, the asset of granted ERC-4626, Euler and Silo vaults, and both tokens of every Morpho market granted to market 14, where supply and supplyCollateral accept any onBehalf. - Withdrawals: withdraw and redeem pay from the vault's idle balance not reserved for released requests, plus what its instant-withdrawal fuses unwind (set by configureInstantWithdrawalFuses, which needs CONFIG_INSTANT_WITHDRAWAL_FUSES_ROLE = 900, held by no address after clone); without them a shortfall reverts in the underlying token's transfer. Otherwise request with WithdrawManager.requestShares; after the Alpha releases funds, redeem with PlasmaVault.redeemFromRequest within the withdraw window. Separately, the Access Manager locks the calling account for the vault's redemption delay after each deposit or mint. - Take contract addresses only from the ipor-abi repository or from the app. Always state the chain name and chain ID next to an address. mainnet/addresses.json lists vaults, fuses, prehooks and price_oracles per chain but no factory; the per-chain files mainnet/mainnet--fusion/addresses.json are flat name-to-address maps that add the infrastructure contracts, including the vault factory. ABIs are under mainnet/mainnet--fusion/abis. - To deploy a vault, use the address under the IporFusionFactoryProxy key, never IporFusionFactoryImpl: calling clone on the implementation reverts DaoFeePackagesArrayEmpty. - Fuse registry names differ per chain for the same role (Base publishes the Aave V3 balance fuse as BalanceFuseAaveV3 and AaveV3WithPriceOracleMiddlewareBalanceFuse, Arbitrum only as the latter), and SDK class names differ from registry names (the SDK's AaveV3SupplyFuse is the registry's SupplyFuseAaveV3). Resolve names and addresses per chain from ipor-abi; do not port a configuration to another chain by substituting addresses. - Supported chains and chain IDs: Ethereum (1), Arbitrum (42161), Base (8453), Unichain (130), TAC (239), Ink (57073), Plasma (9745), Avalanche (43114), Katana (747474), Botanix (3637), HyperEVM (999), Robinhood (4663), Monad (143) and Flare (14). ipor-abi holds the current list; "supported" means a Fusion deployment, factory included, exists there. Live vaults run on a subset: enumerate them with GET /v2/fusion/vaults (chainId per vault). - Python: pip install ipor-fusion gives the SDK (Web3Context, PlasmaVault and fuse classes such as AaveV3SupplyFuse). pipx install 'ipor-fusion[cli]' adds the fusion CLI (for example fusion vault info
--chain-id ethereum). The SDK README lists Ethereum, Arbitrum and Base as supported networks. Notes for generating code with the Python SDK (ipor-fusion): - Amounts are raw on-chain integers with no decimal scaling: 1 USDC (6 decimals) is 1_000_000. Use the types in ipor_fusion.types (Amount, Shares and others) instead of bare ints. - Fuse methods are stateless encoders: each returns a FuseAction (fuse address plus calldata) and touches no chain. Nothing happens until PlasmaVault.execute([actions]) runs the batch atomically. - Every wrapper method returns a Call: .call() runs a read-only eth_call on a view and returns a typed value (it raises on a write-only call); .send() signs locally and needs a private key in the Web3Context; .build_transaction() returns the unsigned transaction dict (needs the signer address and an RPC, no key, no value field); .calldata gives the raw bytes for an external signer. - VaultSimulator runs an execute batch plus reads through eth_simulateV1 with no broadcast and no local node. - FusionFactory deploys new vaults with clone; use the IporFusionFactoryProxy address of the chain. After sending clone, read the created addresses from the receipt with decode_clone_receipt(receipt): a .call() preview only predicts the next CREATE addresses, which another transaction can take first, so use a preview only inside one atomic simulation. Do not use clone_supervised (contract function cloneSupervised): only the factory's MAINTENANCE_MANAGER_ROLE may call it, and it gives the caller ADMIN_ROLE, full control of the new vault's Access Manager. - A fresh clone grants its owner only OWNER_ROLE. Before configuring or executing, grant the operating roles through the vault's Access Manager with grant_role(role, account, 0): ATOMIST_ROLE first (it administers the others), then FUSE_MANAGER_ROLE for configuration and ALPHA_ROLE for execute. - On each market, add_fuses, grant_market_substrates and add_balance_fuse must all happen before the first execute, in any order among themselves, and the vault's price oracle must price the underlying asset, or execute reverts. - Common reverts on this path: 0x8745fbfd DaoFeePackagesArrayEmpty means clone was sent to the factory implementation, use the proxy; 0x9996b315 AddressEmptyCode means execute or updateMarketsBalances reached a missing or code-less balance fuse; 0x24a01144 UnsupportedAsset and 0xb73dbe63 UnexpectedPriceResult mean the price oracle cannot price an asset; 0x068ca9d8 AccessManagedUnauthorized means the caller lacks the role the function requires, including deposit or mint on a private vault without WHITELIST_ROLE. MCP (Model Context Protocol) servers. Two options, both named ipor-fusion, with different tool sets (vault listing is vaults_list hosted, vault_list local): - Hosted, no install: https://mcp.ipor.io/mcp is public, unauthenticated and read-only (HTTP transport). Claude Code: claude mcp add --transport http ipor-fusion https://mcp.ipor.io/mcp. Generic client config: {"mcpServers": {"ipor-fusion": {"type": "http", "url": "https://mcp.ipor.io/mcp"}}}. Tools: vaults_list, vault_info, vault_validate, vault_oracle_mapping, fusion_addresses_list, fusion_address_names, fusion_address_lookup, market_morpho_blue, market_meta_morpho. Start with vaults_list and vault_info; to judge whether a vault is healthy, call vault_validate: a verdict (critical, warning or ok) with each finding, the totalAssets reconciliation and lending health. Read the fusion://invariants resource before writing code that deploys or configures a vault. Caveats: unfiltered vaults_list returns every vault in one payload, large enough to exhaust a context window; narrow it (parameters: chain_id, sort_by, descending, limit, fields); APYs are percent strings ("4.30%"), never multiply by 100; vault_info supports Ethereum (1), Arbitrum (42161), Base (8453), HyperEVM (999), Unichain (130), Plasma (9745), Avalanche (43114), Katana (747474) and Monad (143) only; vault_validate covers the same chains. - Local, from the SDK: pipx install 'ipor-fusion[mcp]' installs the fusion-mcp server (stdio transport) that exposes the CLI against your own RPC providers and local config. Client config: {"mcpServers": {"ipor-fusion": {"command": "fusion-mcp", "type": "stdio"}}}. Tools: server_info, config_show, config_set_provider, config_set_etherscan_key, vault_info, vault_role_accounts, vault_oracle_mapping, vault_list, vault_add, vault_remove, market_morpho_blue, market_meta_morpho. Configure providers and vaults first (fusion config or the config tools). - Both servers serve the SDK guide as MCP resources fusion://glossary, fusion://architecture, fusion://invariants and fusion://quickstart, and the prompts quickstart, deploy_vault, analyze_vault, trace_oracle_pricing and explain_fuse. - Agent skill: skills/ipor-deploy-vault/SKILL.md in the ipor-fusion.py repository teaches the full deploy walk with its invariants and revert selectors. IPOR Fusion API at https://api.ipor.io (read-only vault and market statistics): - No API key, no authentication, no query parameters: every input is encoded in the URL path. Responses are pre-rendered JSON files served from a CDN and regenerated on a schedule; short history buckets (7d) are refreshed most often, the all bucket least often. - Responses are gzip-encoded; send Accept-Encoding: gzip (curl --compressed). - Most numbers are decimal strings, some fields are JSON floats or null; parse per field with a decimal library. Token amounts and TVL are decimal-scaled human units, not raw base units. Timestamps are ISO-8601 UTC. - Enumerate vaults with GET /v2/fusion/vaults: per vault chainId, address, name, asset, netApy, grossApy, rewardsApy, totalAssets, tvl, publicDepositOpened (access posture), managedByIporAlpha (IPOR Labs runs the Alpha; off-chain) and lastReadBlockTimestamp (freshness). Markets: GET /v2/fusion/markets (chainId, protocol, marketId, supply/borrow APYs). - Time series: GET /v2/fusion/vault-history/{chainId}/{vaultAddress}-{period} and GET /v2/fusion/market-history/{chainId}/{protocol}/{substrate}-{period}. Addresses in these paths must be lowercase; a checksummed address returns 404. Creating a vault with an AI agent (Fusion Vault Launchpad, https://github.com/IPOR-Labs/fusion-vault-launchpad): - Clone the repository and follow its AGENTS.md instead of assembling a deployment by hand. One JSON strategy file describes the vault (chain, underlying, fuses, substrates, price feeds, fees, roles and more); the pipeline turns it into a configured vault in idempotent steps and verifies the result from the vault's own contracts. - A dry-run needs no key. A fork rehearsal on a local anvil fork uses anvil's public test key and then exercises the vault: deposit, every fuse, accounting, withdrawal. A live deployment needs the human's own funded key in a local .env file. - Never ask a human for a private key in a conversation. Never broadcast to a live chain without a clean fork rehearsal, a clean plan/run diff, the three sign-offs in the strategy file (roles reviewed, hardening and front-end listing acknowledged) and an explicit human "yes" for that exact file in that session. - Supported chains: Ethereum (1) and Base (8453). Two worked examples ship: a USDC vault on Aave V3 and a leveraged wstETH/USDC loop on Morpho Blue, both on Base.# Fusion Introduction > What IPOR Fusion is, who this documentation is for, and how onchain and offchain responsibilities split between vaults and Alphas. ## What is Fusion? IPOR Fusion is an [institutional-grade](https://ipor.io/docs/fusion-for-institutions.md) asset management infrastructure for automated execution. It is an unopinionated and customizable framework that asset managers can use to deploy assets onchain while implementing custom algorithms offchain. Functioning as a universal integration layer, the protocol utilizes a modular [vault architecture](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fusion-vault.md) to connect with the entire DeFi ecosystem. Complex cross-protocol interactions are routed through isolated, standardized adapter contracts known as "Fuses." This composable design allows builders to bundle multiple operations into a single, atomic execution, abstracting the technical overhead of managing diverse protocol interfaces. It can be considered the _Prime Brokerage Protocol of DeFi_. From a TradFi asset manager's perspective, Fusion is a single entry point for the integration, coordination, monitoring, execution, and accounting of sophisticated DeFi strategies. The protocol clearly separates concerns between onchain state and offchain computation: - **Onchain components:** Smart contracts enforce transparent asset allocation, strict risk limits, and verifiable accounting. - **Offchain execution:** External agents (called "[Alphas](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-alpha.md)") execute strategy operations based on parameters and logic configured by vault managers (called "[Atomists](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-atomist.md)"). ## Who is this documentation for? This section provides technical specifications and integration guides for anyone building on or interacting with the Fusion ecosystem. The primary audience includes: - **Vault Managers (Atomists) & Risk Managers** configuring market substrates, exposure limits, and role-based access controls. - **Backend Developers** building offchain execution agents (Alphas). - **Smart Contract Engineers** integrating new protocols via the Fuse architecture. - **Integrators** connecting external applications to Fusion's ERC4626 vault interfaces. If you require technical support or have specific integration questions, please open a ticket on the IPOR Labs Discord server. > **Note:** To stay up to date on frontend, MCP and SDK feature updates, join the [**announcement channel on Telegram**](https://t.me/fusionsdk)# General > High-level map of the Fusion architecture: how vaults, fuses, Atomists and Alphas fit together. > IPOR Fusion's architecture is modular, built with flexibility and composability in mind. It consists of three core elements: - [**Vaults**](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fusion-vault.md) (ERC4626 compliant) accept deposits and put capital to work by deploying it in a series of strategies that will be dynamically managed by Alpha Keepers (Alphas for short); - [**Fuses**](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fuse.md) are small contracts that connect vaults with specific actions within DeFi protocols; - [**Atomists**](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-atomist.md) are curators that design the strategy of the vault; - [**Alphas**](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-alpha.md) are the off-chain agents or bots responsible for maintaining the strategy operations of a specific vault. They run off-chain automated software to optimize asset allocation by calling functions on Fuses within the limits set by the Atomist. ![Diagram of operational vaults with fuses and alpha connected](https://ipor.io/docs/assets/build-on-fusion/chart-3-22fc2a.jpg) _Diagram of operational vaults with fuses and alpha connected_ ## Asset Ownership All deposited assets are held by the vault and can not be shared in any way between multiple vaults. That makes things like taking loans against deposited assets possible so that the strategies like leveraged looping are possible on Fusion. ![Image](https://ipor.io/docs/assets/build-on-fusion/screenshot-2024-09-12-at-9-27-00-am-cbbaa5.png) __ ## Upgradability and Configuration Vaults and fuses are not upgradable. Their functionality, however, can be extended by connecting new fuses, or removing unused fuses. Vaults allow for a certain degree of configuration (see more here: [Configuration](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step.md)) ## Supported Blockchains Fusion is currently available on the following blockchains. - Ethereum - Base - Arbitrum - Unichain - Ink - Avalanche - Plasma - TAC Depending on demand, Fusion will also be deployed to other blockchains.# What is a Fusion vault? > The Plasma Vault's implemented standards (ERC-4626, ERC-20, OZ Votes, Access Manager), redemption delay, rewards, fees and configuration surface. ## Architecture Vaults in IPOR Fusion are called Plasma Vaults and are the central part of the Fusion system. Plasma Vaults implement a Diamond Proxy pattern and delegate calls to fuses and attached contracts to manage this such as fees and rewards collection. ![A Plasma Vault's relation to other elements of the system](https://ipor.io/docs/assets/build-on-fusion/chart-2-10db03.jpg) _A Plasma Vault's relation to other elements of the system_ ### Implemented Standards Plasma Vaults implement multiple standards that enable an array of desired functionalities. ### ERC4626 All Plasma Vaults implement the ERC4626 standard. ### ERC20 All Plasma Vaults implement the ERC20 standard. Standard functionality can be restricted at the time of the vault creation, namely: - **Minting and redemption** can be restricted by the vault creator so that only the whitelisted addresses have access to those methods. This setting can only be lifted, not added later. That means if the vault is created as closed/whitelisted it can later become an open vault, however if it is already unrestricted then it cannot be moved to a restricted mode - Transferability can be restricted upon vault creation. This setting can only be lifted making transferability possible but not the other way around ### Open Zeppelin Votes Plasma Vaults implement OpenZeppelin Votes.sol to enable integration with DAOs and DAO Protocols such as [Aragon](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/vault-governance-and-depositor-led-security-best-practices.md). This implementation allows for counting shares for any given block and allows voting on proposals without the need for locking or wrapping the share tokens. OpenZeppelin Votes add to gas cost when dealing with shares, and Vault Creators can opt-out of this integration at the time of vault creation. ### Open Zeppelin Access Manager Plasma Vaults rely on [OpenZeppelin Access Manager](https://docs.openzeppelin.com/contracts/5.x/api/access#AccessManager). This system is used by the vault to validate access to all the restricted methods. More information about the access manager configuration can be found in the [dedicated documentation](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md). ## Redemption Delay Fusion allows Atomists to set a mandatory delay between deposit and redemptions of assets from the vault. In other words, if configured to a value higher than 0 the user would not be able to deposit and withdraw assets in the same block. That helps with preventing malicious attacks to sandwich a vault and take advantage of the events when the exchange of the vault token changes. By default the vaults are configured so that flash loans can't be used, but the Atomists can set the timeout to higher value to operate the Vault according to a specific strategy. ## Handling of Rewards As the vault delegates assets it may also happen, that it will accrue rewards or points from the protocols it connects to. The strategy and how to deal with those incentives may vary depending on the Atomist's strategy and the Plasma Vault allows for great flexibility in this regard.\ \ Plasma Vaults utilize a dedicated sub-module called `Rewards Manager` to handle the process of counting, claiming, passing rewards for further processing, and handling the proceeds from of the rewards for example auto compounding pw- or ve- rewards, automatic sale, or TWAP. - **Accounting** - vaults don't account for the accrued rewards by design. Usually, rewards would be issued in the reward token other than the vault's accounting token and it could lead to issues with accounting and vulnerabilities when calculating the exchange rate of the share token. - Dealing with claimed rewards - After the rewards have been claimed, they can be requested by another contract or an entity. - Phasing rewards back into the vault ## Fees There are two types of fees that can be charged in a Fusion Vault: - Performance fee - charged as a percentage of the increase of the exchange rate between vault's underlying asset and the vault token. A [high-water mark method](https://www.investopedia.com/terms/h/highwatermark.asp) is used. - Management fee - charged on the vault's annualized TVL. The Atomists configure both fees. The IPOR DAO also earns part of both fees to support its operations. See [here](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step.md). ## Configuration Basic configuration: - Name and token ticker - immutable - Underlying asset - immutable - Total supply cap - configurable by the Atomist ### Timelocks - configurable Each role (check the [access management](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md)) has a timelock attached. For every action taken the role holder is subject to a timelock. The timelock value can be changed by granting the role again by authorized user. ### Restricting minting and redemptions (whitelist) - partially configurable by the Atomist Atomists can launch the vault with the whitelist, they can amend the addresses on the whitelist and also turn off the whitelist altogether. A whitelist can not be added to vaults already launched or made public. ### Restricting transfers - partially configurable by the Atomist Vault can be launched with restrictions on transfers of vault tokens. This restriction can be lifted by the Atomist after the launch of the vault. However if the restriction on transfers has already been lifted, or vault was launched without the restriction then the transferability can not be imposed. ### Withdrawing assets - immutable Depending on the trading strategy of the Vault, it can configured to have either instant or "scheduled" withdrawals. All vaults are launched hybrid as default, meaning they can support both instant and scheduled withdrawals. ### Fees - configurable Fees changes are subject to a timelock and caps hardcoded in the vault contract. ### [Access management](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md) - configurable ## Operation ### Creating a Plasma Vault Vaults can be [created](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step.md) manually by deploying a configured version of the Fusion Vault using the repository or by using the factory. The detailed instruction on how to use the factory and it's UI can be found in the [Quick Start Guide](https://ipor.io/docs/build-on-fusion/developer-guide/quick-start-guide.md) ### Editing a Vault A vault's configuration can be edited by authorized users - see [Access Management](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md) for more information. ### Creating transactions Step 1 : Define data structures ```solidity struct AaveV2SupplyFuseEnterData { address asset; uint256 amount; } struct Erc4626SupplyFuseEnterData { address vault; uint256 vaultAssetAmount; } struct FuseAction { address target; bytes data; } ``` Step 2: Prepare Data ```solidity address usdcAddress = 0xFF970A61A04b1cA14834A43f5de4533eBDDB5CC8; // Arbitrum USDC Address AaveV2SupplyFuseEnterData memory aaveData = AaveV3SupplyFuseEnterData({ asset: usdcAddress, amount: 1000 // amount to supply }); Erc4626SupplyFuseEnterData memory erc4626Data = Erc4626SupplyFuseEnterData({ vault: 0xabcdefabcdefabcdefabcdefabcdefabcdef, // Adres vaulta vaultAssetAmount: 500 // amount to supply }); FuseAction[] memory actions = new FuseAction[](2); actions[0] = FuseAction({ target: address(aaveV3SupplyFuse), // AaveV3SupplyFuse data: abi.encodeWithSignature("enter((address,uint256))", aaveData) // params to call the method }); actions[1] = FuseAction({ target: address(erc4626SupplyFuse), // Erc4626SupplyFuse contract address data: abi.encodeWithSignature("enter((address,uint256))", erc4626Data) // params to call the method }); ``` Step 3: Invoke method ```solidity plasmaVault.execute(actions); ```# What is a Fuse? > What a Fuse is, how it adapts vault calls to an external protocol, and the fuse whitelist registry. > Fuses are one of three pillars on which Fusion stands. Fuses are the remedy providing permissionlessness, transparency, security, and speed of development. **Fuses** are small contracts that are attached to vaults to perform atomic actions on integrated protocols. All Fuses must meet the following requirements: - **Stateless** — They have no memory, no configuration, and they store no data. Thanks to that they can be used by multiple vaults at the same time without the risk of interfering with one another. - **Non-upgradable** — Fuses, by nature, are simple contracts that can not be altered. That assures a Plasma Vault’s actions can be trusted and Fuses can be shared without additional risk. Should the destination market’s ABI change, a new Fuse needs to be deployed.\* - **Standard ABI** — One of the tasks of the Fuse is to bring the interface of the market down to a standard ABI. Fuses implement two core functions: Enter and Exit that, depending on context deploy or withdraw assets, take a loan or repay it, etc. On top of that some Fuses can implement instantWithdraw — a function with predefined params to pull the assets from the market without the need for Alpha’s interaction. This is relevant if the Vault allows for instant redemptions of shares. - **Configurability** — Because Fuses are stateless, any configuration is delegated to the vault. The Fuse is built to accept the calls with the configuration. ## Types of Fuses Fuses, by design, are limited to specific uses. One protocol may allow many use cases such as providing liquidity, trading derivatives, staking, etc. Outside of functions that can be performed, there are other kinds of actions that need to be performed such as checking balance or claiming incentives. Below is the detailed list of kinds of fuses used by Fusion. ### Functional fuses These bi-directional fuses allow for action and its reversion. What that means depends on the protocol but some examples would include: - **Provide** and **withdraw** liquidity from a DEX or a Credit Market - **Borrow** against collateral and **repay** the loan - **Open** and **close** a derivative such as an option, an interest rate swap, or a perpetual future ### Balance Fuses Each project may require slightly different steps to price assets that are deposited into it by the vault. Vault may be depositing liquidity and at the same time borrowing against collateral. Balance fuse provides a standard way to account for the balance that is then taken when calculating the exchange of the vault's shares. When supporting a given protocol, a balance fuse is the mandatory fuse to provide. See also [here](https://ipor.io/docs/build-on-fusion/developer-guide/balance-fuses.md). ### Rewards Fuses Each Vault comes standard with a Rewards module. It is responsible for claiming the rewards on behalf of the Vault and allows to pass the claimed incentives to a dedicated agent for processing. If the rewards are sold, the proceeds can be deposited back to the rewards manager and vested into the vault. Claiming of the rewards is done via a specialized fuse, that when invoked, will handle the logic of claiming the rewards. ### Swap Fuses Swapping in the context of Fusion is not reversible. Therefore the swap fuses have slightly different interfaces. They allow for the swapping of two approved assets. ### Custom Fuse Besides the typical fuses listed above, custom fuses can be created and used by the vault. Since fuses follow a delegate call, the fuse custom function can also be executed. This can extend the functionality of the vault to accommodate new workflows. It is important to note that custom fuses must be thoroughly tested, and possibly audited before use. ## Developing a Fuse The Fusion DAO is dedicating efforts to provide lots of quality fuses. However, if you would like to see a fuse developed that is not on the DAO's backlog, you may want to develop it yourself. If you're the protocol administrator and wish Fusion to integrate with you, this is the fastest way to integrate. You can find detailed instructions on the [dedicated section](https://ipor.io/docs/build-on-fusion/integrating-a-new-market.md). ### Currently available fuses developed by IPOR Labs The readme of below repository has the latest list of deployed fuses ## Fuse Whitelist Registry The IPOR DAO maintains a global Fuse Whitelist Registry that tracks the status of all Fuses (`ACTIVE`, `DEPRECATED`, `REMOVED`). The Whitelist is [here](https://app.ipor.io/fusion-terminal/fuse-whitelist/ethereum/fuses) available in Fusion Terminal.# What is an Atomist? > The Atomist role: who configures a vault's risk and market boundaries. > An Atomist is a strategy designer. Atomists are curators that design the strategy of the vault. They construct their investment universe by selecting assets, actions, and markets via Fuses and substrates. Atomists can commission an external [Alpha](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-alpha.md) or run their own internal Alpha using their own optimization and risk parameters to automate the Vault.\ \ An Atomist might be one of the following profiles: - Risk Curators - Asset Managers - Crypto native Funds - TradFi Funds - Protocols - DAO Treasuries - and so on Fusion is particularly suitable for [institutional](https://ipor.io/docs/fusion-for-institutions.md) use.# What is an Alpha? > The Alpha role: who executes strategy transactions within an Atomist's configured boundaries. Most fundamentally, the Alpha is a role granted by the [Atomist](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-atomist.md). You can think of the Alpha as the active management function of a fund. The privilege that an Alpha has is preparing and executing transactions on behalf of the Fusion Vault. Alphas can be smart contracts, AI agents, EOAs, or automated services that hold their private keys. Each vault can have multiple Alphas so it is important that, if multiple Alphas are connected to a particular vault, they should be coordinated so that they don't interfere with one another. The transactions an Alpha can perform are defined and limited by the vault's strategy. An Alpha can only execute transactions for which the necessary fuses have been connected to the vault. This ensures that an Alpha cannot misuse the vault's assets.# Fusion for agents > Router page for AI agents: which Fusion interface (MCP, SDK, API) to use for which task, plus the minimal vault-creation example. ### Fusion for agents #### Fusion for Agents — Start Here Use this page to choose the correct machine interface for working with IPOR Fusion. Start here if you are using Codex, Claude or another AI agent to inspect Fusion vaults, prepare transactions or build integrations. This page is a router to the canonical Fusion interfaces. It does not replace the detailed technical documentation. **Choose the right interface** **Fusion MCP** Use Fusion MCP to inspect deployed Fusion state and research existing vaults, including available vault configuration, Fuses, Balance Fuses, substrates, roles, oracle configuration, registered contracts and other information exposed by the server. **Hosted MCP connection** Transport: `Streamable HTTP` Server URL: `https://mcp.ipor.io/mcp` The server URL is an MCP transport endpoint. Add it through the MCP settings of the relevant AI client. Do not open it as a normal webpage. Full tool list, resources, prompts, client setup commands and a worked example: MCP For a locally installed alternative, use the MCP server included in the Python SDK: [Install the SDK-bundled Fusion MCP server](https://github.com/IPOR-Labs/ipor-fusion.py#mcp-server) **Python SDK** Use the official Python SDK to create Fusion vaults, construct contract calls, encode calldata and submit transactions through a configured signer. - [Python SDK repository](https://github.com/IPOR-Labs/ipor-fusion.py) - [FusionFactory implementation](https://github.com/IPOR-Labs/ipor-fusion.py/blob/main/src/ipor_fusion/core/fusion_factory.py) - [SDK installation and quickstart](https://github.com/IPOR-Labs/ipor-fusion.py#quickstart) The executed, transaction-by-transaction SDK walk is on Deploy your first Fusion vault. Factory addresses per chain are on Addresses; revert selectors and the role table are on Common Errors. **Fusion Vault Launchpad** Use the Fusion Vault Launchpad when the task is a complete vault deployment: create, configure, verify and rehearse a Plasma Vault from one JSON strategy file, with a human in the loop. The pipeline runs 17 idempotent steps on top of the Python SDK and verifies the result from the vault's own contracts. A dry-run needs no key; a fork rehearsal on a local anvil fork deposits, executes every declared fuse, checks accounting and withdraws; a live broadcast needs the human's own funded key and an explicit sign-off. - [Fusion Vault Launchpad repository](https://github.com/IPOR-Labs/fusion-vault-launchpad) - [AGENTS.md — operating guide for agents, read first](https://github.com/IPOR-Labs/fusion-vault-launchpad/blob/main/AGENTS.md) - [Using the repository with your agent](https://github.com/IPOR-Labs/fusion-vault-launchpad/blob/main/docs/09-using-with-your-agent.md) Supported chains: Ethereum and Base. Two worked examples ship: a USDC vault on Aave V3 and a leveraged wstETH/USDC loop on Morpho Blue, both on Base. **Fusion API** Use the Fusion API for public vault, product and historical data exposed through HTTP endpoints. [Fusion API documentation](https://ipor.io/docs/build-on-fusion/developer-guide/api.md) **Machine-readable documentation** Use the machine-readable documentation indexes to discover and retrieve Fusion documentation: - [llms.txt](https://ipor.io/docs/llms.txt) - [llms-full.txt](https://ipor.io/docs/llms-full.txt) - [Fusion documentation homepage](https://ipor.io/docs.md) - [ipor.io/llms.txt](https://ipor.io/llms.txt): protocol-wide index across the app, the docs, the SDK and the Fusion Vault Launchpad Pages in this guide written for agents carry the `agents` tag: MCP, Deploy your first Fusion vault, Addresses, Common Errors and SDK. GitBook also exposes individual documentation pages as Markdown. If a raw Markdown URL cannot be retrieved, do not assume the page is unavailable. Retry through the normal HTML page, `llms.txt`, `llms-full.txt` or the linked GitHub source. **Deployments and ABIs** Use the canonical IPOR ABI and deployment repository to resolve supported contract names, chain-specific deployment addresses and ABIs. [IPOR deployments and ABIs](https://github.com/IPOR-Labs/ipor-abi) An address or ABI does not, by itself, prove that a Fuse or other component is compatible with a particular vault, market or strategy. **Creating a Fusion vault** Vault creation is available through `FusionFactory.clone()` in the Python SDK. ```python from web3 import Web3 from ipor_fusion import Web3Context from ipor_fusion.core import FusionFactory ctx = Web3Context.from_url( url=RPC_URL, private_key=PRIVATE_KEY, ) factory = FusionFactory( ctx, Web3.to_checksum_address(FUSION_FACTORY_ADDRESS), ) clone_call = factory.clone( asset_name="Example Fusion Vault", asset_symbol="exampleFV", underlying_token=Web3.to_checksum_address(ASSET_ADDRESS), redemption_delay_seconds=0, owner=Web3.to_checksum_address(OWNER_ADDRESS), dao_fee_package_index=0, ) preview = clone_call.call() calldata = clone_call.calldata receipt = clone_call.send() ``` The returned call supports three workflows: - `clone_call.call()` performs a read-only `eth_call` preview and returns the predicted Fusion deployment. - `clone_call.calldata` returns unsigned calldata for an external signer or multisig workflow. - `clone_call.send()` submits the transaction using the signer configured in `Web3Context`. Resolve the chain-specific `IporFusionFactoryProxy` address through the canonical deployment registry: [IPOR deployments and ABIs](https://github.com/IPOR-Labs/ipor-abi) Preview creation again immediately before submission because the predicted deployment depends on the factory's current state. **Creation is not configuration** Creating a vault deploys the Fusion vault infrastructure. It does not make the vault strategy-ready. Depending on the intended strategy, post-deployment configuration can include: - access roles and execution permissions; - Functional Fuses and Balance Fuses; - substrates and dependencies; - price feeds and oracle configuration; - limits and withdrawal configuration. Use the existing configuration documentation for the detailed process: - [Vault configuration step-by-step](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step.md) - [Access Management](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md) - [Substrates](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/substrates.md) - [Price Oracle Middleware](https://ipor.io/docs/build-on-fusion/developer-guide/price-oracle-middleware.md) - [What is a Fuse?](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fuse.md) **Rules for agents** - Use Fusion MCP for deployed-state inspection and research. - Use the Python SDK for vault creation and call construction. - For a complete vault deployment, use the Fusion Vault Launchpad instead of assembling the steps by hand. Never broadcast to a live chain without a clean fork rehearsal and the human's explicit "yes" for that exact strategy file. - Resolve production addresses through the canonical deployment registry. - Do not infer production configuration from tests or an arbitrary existing vault. - If an authoritative integration or configuration requirement cannot be found, return `UNKNOWN` rather than guessing. - Preview and prepare transactions by default. Signing or broadcasting requires explicit authorization.# Quick Start Guide > Video walkthrough of creating and configuring a vault via the webapp, plus the SDK/agent path for programmatic deployment. > **Warning:** The Fusion Terminal UI was rebuilt in August 2026 (new Vault Wizard, reorganized vault management: General Settings, Access Manager, Integrations, Scheduled Ops, Withdrawals, Fees, Price Oracle, Prehooks, Allocations, Alpha Config, and vault customization moved to its own Brand & Info section). The video walkthroughs below predate this rebuild — the concepts still apply, but menus, labels and page layout in the live app no longer match the videos step-by-step. ### SDK / agent path Building programmatically, or an AI agent? Skip the videos below and go straight to Deploy your first Fusion vault — the executed, copy-pasteable eleven-transaction walk (clone, roles, market setup, access posture, deposit, execute) using the Python SDK, plus factory addresses per chain. ### Creating a vault To create a vault use the vault factory. This is the first step in preparing your strategy. Once the vault is created it still needs to be whitelisted if you want to have it included in the IPOR webapp interface. [VIDEO](https://www.youtube.com/watch?v=_3wjQf9iMY4&list=PLTda9D8MCj4QafXFkFPR_i27CakEUdBeg&index=1) ### Vault Setup Documentation Playlist: 1. Create vault [VIDEO](https://youtu.be/xxdiy6j75VQ?si=LIfnjHqNFgHNGY5S) 2. General settings [VIDEO](https://www.youtube.com/watch?v=NL7lMRXSjf4&list=PLTda9D8MCj4QafXFkFPR_i27CakEUdBeg&index=2) 3. Access manager [VIDEO](https://youtu.be/vi7bEMcnbbs?si=VNgrXGpjTJQg883w) 4. Integrations [VIDEO](https://youtu.be/0DL98lOzBvs?si=W_gspKW5qrwtaHFI) 1. Fuses [VIDEO](https://youtu.be/lHBWjRYRa2o?si=KSPEjUF8NreNIBa9) 2. Substrates [VIDEO](https://youtu.be/zYXwfeUp7Og?si=M9ImdV-Yhdi0LaGp) 1. for the information as to what are the specific substrates for each fuse it is still best to check the test files for a specific fuse. The interface will be improved to include that information directly in the front end. 5. Scheduled operations [VIDEO](https://youtu.be/tfoUAhiKJBA?si=DzLHsI-XT_IZ2dt0) 6. Withdrawals 1. Instant [VIDEO](https://youtu.be/p2WXHJHMMrg?si=I9wW1v-OAu7q0-Yl) 2. Scheduled [VIDEO](https://youtu.be/7Aoke_3iMNQ?si=KSAv_Z4EZ0-eCllH) 7. Fees [VIDEO](https://youtu.be/cZPpg96R9bE?si=gjjBAadAU85hof5P) 8. Price Oracle [VIDEO](https://youtu.be/DiKjA-dQ7GE?si=RC_iTfTMsbZuq6vb) 9. Prehooks - [VIDEO](https://youtu.be/DhtYHEmO4Ek?si=2PSttK-ZnOGeWFi-), [Documentation](https://ipor.io/docs/build-on-fusion/developer-guide/configuring-pre-hooks.md) 10. Price feeds [VIDEO](https://youtu.be/8UZ6Yp512Ng?si=GBDPE9g-DwlOAKOZ) ### Running Alpha This section describes how to set up and run the "Alpha" bot, which interacts with the smart contracts of your vault in the IPOR Fusion protocol. The bot uses the official IPOR Fusion SDK for Python. To run and develop the bot, you will need two main repositories: - IPOR Fusion Python SDK (ipor-fusion.py): This is the official Software Development Kit (SDK) for interacting with the protocol.\ - IPOR Fusion Alpha Example (ipor-fusion-alpha-example): This is a ready-to-run example bot that demonstrates how to use the SDK. This is the best starting point.\ #### How to run the bot as fast as possible 🚀 To run a basic version of the bot in the shortest possible time, follow the steps below using the ipor-fusion-alpha-example repository. Clone the example repository: ``` $ git clone https://github.com/IPOR-Labs/ipor-fusion-alpha-example.git $ cd ipor-fusion-alpha-example ``` Configure environment variables: Copy the .env.example file to a new file named .env. ``` $ cp .env.example .env ``` Then, open the .env file and fill it with your credentials: ``` PROVIDER_URL: Your blockchain node provider URL (e.g., from Infura or Alchemy) PRIVATE_KEY: The private key of your wallet that will manage the vault. PLASMA_VAULT_ADDRESS: The smart contract address of your IPOR Plasma vault. ``` Install project dependencies: Make sure you have Python (version 3.11 or newer) and poetry installed. ``` $ poetry install ``` Run the bot: ``` $ poetry run python main.py ``` The bot will connect to the blockchain network, initialize the connection to your vault, and start executing the default strategy every 60 seconds. #### How to customize the bot for your own strategy? 🛠️ The example bot is designed for easy modification. To implement your own logic: - Edit the main strategy logic: Open the alpha_bot.py file and modify the do_fusion() method. This is where you should place the code for your custom strategy (e.g., conditions for opening/closing positions, flashloan logic, looping). - Change the execution frequency: Open the main.py file and change the interval value in the scheduler section to adjust how often the bot executes its strategy. - Add your own variables: If your strategy requires additional parameters, add them to the .env file and read them in the bot's code (e.g., in alpha_bot.py). #### Where to find information during development? 📖 While creating your own strategy or extending the bot, use the following resources: - Official IPOR Documentation: The main knowledge base about the protocol, smart contracts, and their functions. - SDK Source Code (ipor-fusion.py): Analyzing the SDK's code will help you understand what functions and classes are available under the hood and how to use them. - Example Repository (ipor-fusion-alpha-example): The best source for practical usage patterns of the SDK. #### How to debug? 🐛 Debugging is crucial for creating a stable and secure bot. Here are some recommended methods: - Local Testing with Anvil: The example repository includes a Docker Compose configuration that runs a local blockchain instance (Anvil). This allows you to test interactions with smart contracts without incurring real gas costs. Start the local environment with the command: ``` $ docker-compose up -d ``` The bot will automatically connect to the local Anvil instance. - Unit and Integration Tests: The repository includes a test suite. Run them using poetry run pytest. Write your own tests for new features to ensure they work correctly. - Logging: Add more detailed logs in the do_fusion() method in the alpha_bot.py file using the built-in logging module. This will help you trace what your bot is doing and what decisions it's making step-by-step. - Interactive Debugger: Use a debugger, such as the one built into VS Code or IntelliJ IDA, to set breakpoints in the code and analyze the application's state in real-time. #### Frontend If you would like the vault to be listed on the Fusion frontend, please contact IPOR Labs via Discord ticket.# Smart Contracts > DeepWiki-hosted architecture reference for the ipor-fusion smart contracts. An AI-generated documentation based on the IPOR Labs GitHub repository is available on [DeepWiki](https://deepwiki.com/IPOR-Labs/ipor-fusion).# Open-source Repository > Where the Fusion smart-contract source lives and how it's licensed. Fusion's codebase is fully open source and can be accessed in this GitHub repository: # Developing a Fuse > Anatomy of an action fuse and its balance fuse, shown on the ERC4626 supply fuse: data structs, enter, exit, transient variants, instant withdraw, events and errors. > **Note:** If you are integrating a new market, start with [Integrating a New Market](https://ipor.io/docs/build-on-fusion/integrating-a-new-market.md). It describes the whole process and the rules for the fuses you write. This page explains how a single fuse is built. A fuse is a small stateless contract that the vault runs by `delegatecall`. This page walks through the generic ERC4626 fuses of the public [`IPOR-Labs/ipor-fusion`](https://github.com/IPOR-Labs/ipor-fusion) repository, read at commit `632350d`: [`Erc4626SupplyFuse`](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/erc4626/Erc4626SupplyFuse.sol) (an action fuse) and [`Erc4626BalanceFuse`](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/erc4626/Erc4626BalanceFuse.sol) (its balance fuse). Excerpts are short and link to the exact lines. Where the example differs from what the [Coding Standard](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/coding-standard.md) asks of new fuses, this page says so. ## The shape of an action fuse An action fuse moves the vault into or out of a position on an external venue. The [Alpha](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-alpha.md) (the role that executes strategy transactions on the vault) runs it through `PlasmaVault.execute`. The vault runs the call only for a fuse address that was added to that vault with `addFuses(address[])`, reads `MARKET_ID()` from the fuse and then delegates the encoded call. Because of the `delegatecall`, `address(this)` inside the fuse is the vault, and storage reads see the vault's storage. ```solidity contract Erc4626SupplyFuse is IFuseCommon, IFuseInstantWithdraw { ``` Source: [`Erc4626SupplyFuse.sol#L37`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L37) `IFuseCommon` has one function, `MARKET_ID()`. `IFuseInstantWithdraw` is optional. The reference fuses do not implement `IFuse` (`enter(bytes)` and `exit(bytes)`); they expose typed functions, such as `enter(Erc4626SupplyFuseEnterData)`. The contract holds two immutables, and nothing else is stored: ```solidity address public immutable VERSION; uint256 public immutable MARKET_ID; constructor(uint256 marketId_) { VERSION = address(this); MARKET_ID = marketId_; } ``` Source: [`Erc4626SupplyFuse.sol#L81-L87`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L81-L87) - `VERSION` is the address of the fuse contract itself, not of the vault. It is the first parameter of every event and the key of the transient storage. - `MARKET_ID` identifies the market the fuse belongs to. It is immutable, so a fuse deployed with a wrong id must be redeployed. This constructor does not reject `marketId_ == 0`. IPOR requires new fuses to do so, because the vault engine uses market id `0` as an empty slot. The guard is shown on `AaveV4SupplyFuse`: ```solidity /// @notice Constructor for AaveV4SupplyFuse /// @param marketId_ The Market ID associated with the Fuse constructor(uint256 marketId_) { if (marketId_ == 0) { revert AaveV4SupplyFuseInvalidMarketId(); } VERSION = address(this); MARKET_ID = marketId_; } ``` Source: [`AaveV4SupplyFuse.sol#L123-L131`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/aave_v4/AaveV4SupplyFuse.sol#L123-L131) ## Data structs `enter` and `exit` take a struct, so the Alpha encodes the call data once and the fuse decodes it without manual parsing. ```solidity /// @notice Data structure for entering - supply - the ERC4626 vault struct Erc4626SupplyFuseEnterData { /// @dev ERC4626 vault address address vault; /// @dev amount to supply, this is amount of underlying asset in the given ERC4626 vault uint256 vaultAssetAmount; /// @dev minimum amount of shares expected to receive from the deposit uint256 minSharesOut; } ``` Source: [`Erc4626SupplyFuse.sol#L15-L23`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L15-L23) ```solidity /// @notice Data structure for exiting - withdrawing - the ERC4626 vault struct Erc4626SupplyFuseExitData { /// @dev ERC4626 vault address address vault; /// @dev amount to withdraw, this is amount of underlying asset in the given ERC4626 vault uint256 vaultAssetAmount; /// @dev maximum number of vault shares that can be burned during the withdraw, 0 means no limit uint256 maxSharesBurned; } ``` Source: [`Erc4626SupplyFuse.sol#L25-L33`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L25-L33) Both structs carry a caller-supplied limit: `minSharesOut` on enter and `maxSharesBurned` on exit, where `0` means no limit. ## enter `enter` returns early, without an event, when the requested amount is `0`. Next it checks that the vault is a granted substrate of the market: ```solidity if (!PlasmaVaultConfigLib.isSubstrateAsAssetGranted(MARKET_ID, data_.vault)) { revert Erc4626SupplyFuseUnsupportedVault("enter", data_.vault); } ``` Source: [`Erc4626SupplyFuse.sol#L94-L96`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L94-L96) The substrates of this fuse are the addresses of the ERC4626 vaults the atomist allowed. The check comes before any approval or deposit. Then `enter` limits the amount to the asset balance of the vault, again returning `0` without an event if nothing is left, and moves the funds: ```solidity ERC20(underlyingAsset).forceApprove(data_.vault, finalVaultAssetAmount); uint256 shares = IERC4626(data_.vault).deposit(finalVaultAssetAmount, address(this)); if (shares < data_.minSharesOut) { revert Erc4626SupplyFuseInsufficientShares(shares, data_.minSharesOut); } emit Erc4626SupplyFuseEnter(VERSION, underlyingAsset, data_.vault, finalVaultAssetAmount); ``` Source: [`Erc4626SupplyFuse.sol#L109-L117`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L109-L117) In this excerpt, `underlyingAsset` is the asset of the ERC4626 vault and `finalVaultAssetAmount` is the requested amount limited to the balance of the vault. The fuse approves exactly the amount it deposits, deposits to `address(this)` (the vault), reverts with `Erc4626SupplyFuseInsufficientShares` when fewer shares than `minSharesOut` are received and emits the event with the amount that was actually supplied. This example does not set the allowance back to zero after the deposit. IPOR requires new fuses to do that; the pattern is shown on `AguaSupplyFuse` (`forceApprove(vault, amount)`, deposit, `forceApprove(vault, 0)`). ## exit `exit` and `instantWithdraw` share one internal function, `_exit`. It does the following: - returns `0` without an event when the requested amount is `0`; - reverts with `Erc4626SupplyFuseUnsupportedVault` when the vault is not a granted substrate, on the instant-withdraw path too; - limits the amount to `maxWithdraw(address(this))` of the vault, and returns `0` without an event when that is `0`. A zero `maxWithdraw` is therefore a silent no-op: it is neither a revert nor a failure event. The `maxWithdraw` read is not inside a `try` block, so a revert of that call propagates. When called as `exit`, the Alpha decides the limit: a non-zero `maxSharesBurned` makes the fuse revert with `Erc4626SupplyFuseExcessiveSharesBurned` if more shares were burned. A failed vault `withdraw` is not caught on this path either, so the whole action reverts. ## Transient variants `enterTransient()` and `exitTransient()` take no arguments. They read their inputs from transient storage under the key `VERSION`, call the same `enter` or `exit` logic and write the result back to transient storage. ```solidity function enterTransient() external { bytes32[] memory inputs = TransientStorageLib.getInputs(VERSION); address vault = TypeConversionLib.toAddress(inputs[0]); uint256 amount = TypeConversionLib.toUint256(inputs[1]); uint256 minSharesOut = TypeConversionLib.toUint256(inputs[2]); uint256 suppliedAmount = enter(Erc4626SupplyFuseEnterData({vault: vault, vaultAssetAmount: amount, minSharesOut: minSharesOut})); bytes32[] memory outputs = new bytes32[](1); outputs[0] = TypeConversionLib.toBytes32(suppliedAmount); TransientStorageLib.setOutputs(VERSION, outputs); } ``` Source: [`Erc4626SupplyFuse.sol#L120-L131`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L120-L131) `exitTransient` follows the same pattern with `maxSharesBurned` as the third input. ## instantWithdraw `instantWithdraw` is optional. The vault calls it when a user withdrawal needs more assets than the vault holds, for every fuse configured with `configureInstantWithdrawalFuses`, in the configured order, until the missing amount is covered. The first parameter is always the amount in the underlying asset; the other parameters belong to the fuse. For the ERC4626 fuse `params[1]` is the vault address: ```solidity /// @dev params[0] - amount in underlying asset, params[1] - vault address function instantWithdraw(bytes32[] calldata params_) external override { uint256 amount = uint256(params_[0]); address vault = PlasmaVaultConfigLib.bytes32ToAddress(params_[1]); _exit(Erc4626SupplyFuseExitData(vault, amount, 0), true); } ``` Source: [`Erc4626SupplyFuse.sol#L150-L157`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L150-L157) The vault does not wrap this call in `try`/`catch`, so a revert inside `instantWithdraw` reverts the user withdrawal. That is why the example catches the one call that can fail because of the venue, the vault `withdraw`, and reports the failure with an event instead: ```solidity if (catchExceptions_) { try IERC4626(vault_).withdraw(finalVaultAssetAmount_, address(this), address(this)) returns ( uint256 shares_ ) { if (maxSharesBurned_ > 0 && shares_ > maxSharesBurned_) { revert Erc4626SupplyFuseExcessiveSharesBurned(shares_, maxSharesBurned_); } shares = shares_; emit Erc4626SupplyFuseExit(VERSION, IERC4626(vault_).asset(), vault_, finalVaultAssetAmount_, shares); } catch { /// @dev if withdraw failed, continue with the next step emit Erc4626SupplyFuseExitFailed(VERSION, IERC4626(vault_).asset(), vault_, finalVaultAssetAmount_); } ``` Source: [`Erc4626SupplyFuse.sol#L186-L198`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L186-L198) Three things to note about this example: - Only the vault `withdraw` is inside `try`. The substrate check and the `maxWithdraw` read in `_exit`, and the `asset()` reads for the events in both branches, are outside the `catch`, so this path can still revert. The `Erc4626SupplyFuseExcessiveSharesBurned` check cannot fire here, because `instantWithdraw` passes `maxSharesBurned = 0`. - `instantWithdraw` passes `maxSharesBurned = 0`, so on the automatic instant-withdraw path the limit on burned shares is off. This differs from `exit`, where the Alpha can set a limit. - A failed withdraw emits `Erc4626SupplyFuseExitFailed` and continues; a zero amount or a zero `maxWithdraw` returns `0` without any event. ## Events and errors Events carry the fuse `VERSION` as the first parameter and are named with the fuse name as prefix. Errors are custom errors with the same prefix. The example declares no `indexed` parameters; IPOR requires the same of new fuses. ```solidity event Erc4626SupplyFuseEnter(address version, address asset, address vault, uint256 vaultAssetAmount); ``` Source: [`Erc4626SupplyFuse.sol#L46`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L46) ```solidity error Erc4626SupplyFuseUnsupportedVault(string action, address asset); ``` Source: [`Erc4626SupplyFuse.sol#L69`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L69) The enter event reports the amount after limiting it to the available balance, so it shows what happened and not only what was requested. The example also declares `Erc4626SupplyFuseExit`, `Erc4626SupplyFuseExitFailed`, `Erc4626SupplyFuseInsufficientShares` and `Erc4626SupplyFuseExcessiveSharesBurned`. The NatSpec on the `enter`, `exit` and transient functions of this example is minimal; the Aave V4 fuses show the level of NatSpec described in the [Coding Standard](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/coding-standard.md). ## Substrates and vault configuration A [substrate](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/substrates.md) is a `bytes32` value that an atomist grants to a market with `grantMarketSubstrates(marketId, substrates)`. Each call replaces the whole set of substrates of that market. A fuse checks the substrate before it moves funds. A plain address is checked with `PlasmaVaultConfigLib.isSubstrateAsAssetGranted`, and a richer encoding with `isMarketSubstrateGranted`. When a substrate carries more than an address, put the encoding and decoding in a substrate library, as `AaveV4SubstrateLib` does. The usual order for an atomist is: add the fuses with `addFuses`, set the balance fuse, grant the substrates, make sure the vault's price manager or the shared [Price Oracle Middleware](https://ipor.io/docs/build-on-fusion/developer-guide/price-oracle-middleware.md) has a price source for every asset, add a claim fuse, if any, to the vault's `RewardsClaimManager` with `addRewardFuses`, and then optionally configure instant withdraw and the [dependency balance graph](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/dependency-graphs.md) with `updateDependencyBalanceGraphs`. The [What a Market Integration Contains](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/market-components.md) section lists these components in detail. ## The balance fuse Each market needs a balance fuse. It implements `IMarketBalanceFuse`: ```solidity interface IMarketBalanceFuse { /// @notice Get the balance of the Plasma Vault in the market in USD /// @dev Notice! Every Balance Fuse have to implement this exact function signature, because it is used by Plasma Vault engine /// @return balanceValue The balance of the Plasma Vault in the market in USD, represented in 18 decimals function balanceOf() external returns (uint256 balanceValue); } ``` Source: [`IMarketBalanceFuse.sol#L5-L10`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/IMarketBalanceFuse.sol#L5-L10) `balanceOf()` returns the balance of the vault in the market in USD with 18 decimals (WAD). Every balance fuse must use this exact signature, because the vault engine calls it. The interface does not declare `balanceOf()` as `view`, although the reference implementations do. You may declare it `view`, as they do. The vault calls it by `delegatecall` and does not catch a revert, so a reverting balance fuse blocks the balance update. `Erc4626BalanceFuse` reads the substrates with `PlasmaVaultConfigLib.getMarketSubstrates(MARKET_ID)`. For each vault it converts the shares held by the vault to assets, prices the asset through the Price Oracle Middleware and adds the value in WAD. It returns `0` immediately when the market has no substrates. ```solidity for (uint256 i; i < len; ++i) { vault = IERC4626(PlasmaVaultConfigLib.bytes32ToAddress(vaults[i])); vaultAssets = vault.convertToAssets(vault.balanceOf(plasmaVault)); asset = vault.asset(); (price, priceDecimals) = IPriceOracleMiddleware(priceOracleMiddleware).getAssetPrice(asset); balance += IporMath.convertToWad(vaultAssets * price, IERC20Metadata(asset).decimals() + priceDecimals); } return balance; ``` Source: [`Erc4626BalanceFuse.sol#L70-L78`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626BalanceFuse.sol#L70-L78) This fuse does not check for a zero price itself; the middleware rejects a zero or negative price. `convertToAssets` ignores withdrawal limits, as the valuation rule asks, but it also leaves out fees that an ERC4626 vault charges on withdrawal (EIP-4626). For a venue that charges an exit fee, value the position at what the exit pays; see [Security Requirements](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/security-requirements.md). `ZeroBalanceFuse` is a balance fuse that always returns `0`. It is only for a market that creates no separate position of its own, with the position valued on another market. Never use it for a position that you cannot value, because it hides the position from the vault balance. ## Where to go next - [Integrating a New Market](https://ipor.io/docs/build-on-fusion/integrating-a-new-market.md): the process, from writing the fuses to the Fuse Whitelist Registry. - [What a Market Integration Contains](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/market-components.md): every contract a market integration can contain. - [Coding standard](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/coding-standard.md) and [security requirements](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/security-requirements.md): the rules for the fuses you write. - [Whitelist Review](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/whitelist-review.md): what to prepare for the review of the deployed code.# Balance Fuses > What a Balance Fuse tracks and how it pairs with a Functional Fuse for a market. #### Short Summary **Balance fuses shift accounting and validation from opaque, manual, and mutable off-chain spreadsheets to small, auditable, onchain modules that read vault state and market data, enforce whitelists/constraints, and produce verifiable outputs.** That architectural change gives permissionless verification, immutable audit trails, and automated enforcement — properties that Excel cannot provide. --- > How do Balance Fuses in IPOR Fusion work, and why do they offer more security than Excel spreadsheets in offchain accounting (as seen in other protocols like Stream Finance)? IPOR Fusion’s **balance fuses** are a concrete example of how onchain, composable guards can replace fragile off-chain bookkeeping (like Excel) and give vaults provable, auditable accounting and behavior. Below is explained what they are, how they work (at a technical level you can audit), and exactly why that is materially more secure than maintaining off-chain spreadsheets. ## What a “balance fuse” is (short) A **balance fuse** in IPOR Fusion is an onchain, market-specific fuse module whose job is to _determine and/or validate balances for a market or integration_ and to enforce constraints the vault relies on when routing assets through external protocols. Balance fuses are one type of “fuse” in the Fusion architecture — small, purpose-built, immutable onchain components that the vault calls when performing actions. ## How they work — mechanics you can audit 1. **Onchain interface** — balance fuses implement a defined interface (e.g. `IMarketBalanceFuse`) exposing functions such as a market-balance query (get user/market balance in USD or shares). That means external actors (and auditors) can call and inspect exactly how balances are derived. 2. **Vault-level accounting remains authoritative** — The fuse itself _never holds funds or persistent accounting state_; the vault remains the single source of truth for tokens/shares. The fuse is a verification/adapter layer that reads vault state, reads market/substrate data (oracles, onchain positions), and returns validated numbers or allows/blocks actions. This reduces complexity and centralizes accounting to an auditable smart contract. It allows also for instant withdrawals (if configured for the vault) with accurate share valuation. 3. **Market-specific logic & whitelisting** — a balance fuse is written for a particular market/substrate and can include checks like allowed assets, allowed method signatures, and validation constraints. Vaults can be configured only to accept [whitelisted](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fuse.md)/verified fuses and markets, and non-whitelisted custom fuses trigger alerts in the Fusion UI. That restricts what external integrations can do. 4. **Price oracles & middleware tie-ins** — balance fuses integrate with Fusion’s price oracle middleware and market substrate configs so the numeric balances they return are grounded in onchain price feeds and standardized market identifiers (not an off-chain spreadsheet cell someone updated). 5. **Immutable, auditable code** — fuses are immutable (non-upgradable) pieces of code: you can review their logic onchain or in the project repo and reason about exactly how a balance was computed. Every call and event is logged on the blockchain. ## Why this is more secure than Excel/off-chain accounting (concrete comparisons) **1) Immutability vs. silent edits** - Excel: a spreadsheet cell can be changed, copied, or deleted without an immutable audit trail. A malicious or accidental edit can silently change reported balances. - Balance fuse: logic lives onchain and is immutable (or at least upgrade paths are explicit and auditable). Any change requires a transaction and is visible onchain. **2) Single source of truth vs. manual reconciliation** - Excel: reconciliation requires manual imports/exports from multiple protocols and human consolidation — error-prone and inconsistent. - Fusion: the vault is the authoritative contract; fuses read the vault and onchain markets directly and return validated numbers. No manual patching of “what the ledger says.” **3) Permissioned behavior vs. ad-hoc scripts** - Excel: operators often use ad-hoc scripts or spreadsheets to decide swaps/allocations; those scripts can call unsafe contracts or be manipulated. - Balance fuses: constrain what external protocols/methods are allowed (whitelists, allowed signatures) and can refuse actions that would break rules. Non-whitelisted fuses trigger UI alerts. **4) Onchain verifiability vs. opaque off-chain proofs** - Excel: even with csv exports, a counterparty or auditor must trust that the exported file was correct; proving the file maps to onchain state is extra work. - Balance fuse: anyone can call the same onchain functions, re-derive balances from onchain data, and verify results match what the vault used — cryptographically verifiable. **5) Event logs & monitoring vs. manual logs** - Excel: change history depends on systems (file history, emails); detection of tampering or mistakes is slow. - Balance fuse + vault: every call, rebalancing, or state change emits events onchain. The system can emit alerts when a non-whitelisted fuse is used or when constraints are violated, enabling automated monitoring. ## Practical examples (what a balance fuse prevents) - A bad agent tries to report inflated collateral or misprice an Illiquid token in a spreadsheet to justify an unsafe leverage step → onchain fuse uses oracle data and vault accounting and rejects the action. - A manager wants to route funds into a non-whitelisted market with custom call signatures → vault/fuse whitelists prevent the call or trigger alarms before funds move.# General share price calculation rules > How a Plasma Vault's share price is computed and what can move it. ### Core calculation architecture The foundation of asset calculation begins with the \_getGrossTotalAssets() function, which aggregates three primary components. The first component is the direct vault balance, representing the raw underlying asset balance held directly in the vault contract, retrieved via IERC20(asset()).balanceOf(address(this)) and representing immediately available liquidity. The second component encompasses market-specific assets, which represent the total value of all positions across integrated DeFi protocols, calculated through PlasmaVaultLib.getTotalAssetsInAllMarkets() where each market maintains its own balance tracking via specialized Balance Fuses. The third component, which is optional, includes reward claims representing the value of claimable rewards from integrated protocols, retrieved via IRewardsClaimManager(rewardsClaimManagerAddress).balanceOf() and only included if a rewards claim manager is configured. All asset valuations are standardized to USD using a hierarchical price oracle system. The primary layer consists of the PriceOracleMiddlewareManager, which manages custom price feed sources for specific assets, provides fallback to PriceOracleMiddleware when custom feeds are unavailable, converts all prices to standardized 18-decimal USD format, and implements gas-efficient price caching mechanisms. The secondary layer is the PriceOracleMiddleware, which handles Chainlink Feed Registry integration, provides fallback pricing for assets without custom feeds, ensures consistent 18-decimal USD price format, and validates price feed data integrity and freshness. The price conversion process converts all external prices to 18-decimal USD using IporMath.convertToWad(), maintains precision through standardized decimal handling, and implements price validation to prevent zero or negative values. Each integrated DeFi protocol is represented by a unique market ID with dedicated balance tracking through the Balance Fuse system. Each market employs a specialized Balance Fuse contract such as Erc4626BalanceFuse, which calculates market-specific asset values in USD, implements protocol-specific balance calculation logic, and returns standardized 18-decimal USD values. Markets are defined by protocol-specific identifiers called substrates, which include asset addresses, pool identifiers, and protocol parameters, enabling complex multi-asset market structures and supporting various DeFi protocol types including lending, AMMs, and yield farming. The balance update mechanism updates market balances through the updateMarketsBalances() function, implements dependency resolution for interconnected markets, ensures atomic balance updates across related protocols, and calculates deltas to minimize storage operations. ### Gas optimization and efficiency The system implements several mechanisms to reduce gas consumption and improve efficiency. Market balance storage ensures that market values are stored in underlying token amounts rather than USD, with price conversions only occurring during balance updates, thereby reducing gas costs for simple operations like deposits and withdrawals. Batch market updates allow multiple market balances to be updated in a single transaction, with dependency resolution preventing redundant calculations and delta-based updates minimizing storage writes. The delegatecall pattern enables Balance Fuses to use delegatecall for gas-efficient execution, eliminating external contract call overhead while maintaining security through controlled execution context. All market values are ultimately converted to the vault's underlying asset denomination through a precise conversion process. The USD to underlying asset conversion divides market USD values by the underlying asset USD price using the formula (USD_Value \* 10^18) / underlying_asset_price, maintaining precision through careful decimal handling. Decimal standardization ensures that all calculations use consistent decimal precision, with the underlying asset decimals offset applied (DECIMALS_OFFSET = 2) to ensure compatibility across different asset types. ### Balance update workflow The complete balance update process follows a carefully orchestrated sequence. Market dependency resolution identifies all markets requiring balance updates, resolves market dependencies to ensure proper update order, and prevents circular dependency issues. Price oracle integration retrieves the current USD price for the underlying asset, validates price feed data integrity, and handles price feed failures gracefully. Balance Fuse execution runs each market's Balance Fuse via delegatecall, collects USD-denominated market values, and implements protocol-specific calculation logic. The final conversion and storage phase converts USD values to underlying asset amounts, updates market-specific storage, calculates and applies total asset deltas, and triggers asset distribution protection checks. ### Security and validation The system implements multiple security layers to ensure data integrity and system reliability. Price validation ensures that all prices are validated for positive values, includes price feed staleness checks, and provides fallback mechanisms for price feed failures. Balance integrity is maintained through asset distribution protection limits, market balance consistency checks, and dependency validation. Access control is enforced through role-based access to balance update functions, controlled execution context for delegatecalls, and comprehensive audit trails through event emissions.# Accounting and PPS Protection > How the vault protects its price-per-share against manipulation and accounting edge cases. A critical challenge for multi-protocol yield aggregators is maintaining an accurate and manipulation-resistant Price Per Share (PPS). This page explains the risks associated with "Auto-Accrual" accounting and how IPOR Fusion's architecture provides robust protection against these attack vectors. ### The Pitfalls of Auto-Accrual Accounting Some early vault designs utilized an "Auto-Accrual" model. In this model, the vault automatically updates its `totalAssets()` on every transaction (deposit, withdrawal, or transfer) by looping through and querying every integrated market. While this ensures that yield is always "up-to-date," it introduces major vulnerabilities: 1. **Intra-block PPS Manipulation**: Since `totalAssets()` updates on every "touch," an attacker can donate assets to an underlying strategy, observe an immediate rise in the vault's PPS, and exploit this higher valuation (e.g., by borrowing against the inflated shares) all within the same block. 2. **The "Weakest Link" Problem**: The entire vault's security is tied to its most manipulable strategy. If a single integrated market can be manipulated, the entire vault's accounting is affected instantly. 3. **PPS Inflation via Donation**: Anyone can artificially pump the PPS by directly transferring assets to external protocols the vault uses, causing immediate and uncontrolled revaluation of all vault shares. ### The Fusion Solution: Explicit Reporting IPOR Fusion solves these issues by decoupling user transactions from yield accrual. The `PlasmaVault` uses an **Explicit Reporting** model. #### 1. Cached Balances instead of Live Loops In a PlasmaVault, the `totalAssets()` call does **not** loop through integrated markets on-the-fly. Instead, it aggregates: - The direct `IERC20(asset).balanceOf(vault)` (idle assets). - **Cached values** from storage (`PlasmaVaultLib.getTotalAssetsInAllMarkets()`). - Optionally, pending rewards. Because the market balances are cached, an external donation to an external protocol or a change in interest rates does not affect the vault's PPS until those balances are explicitly updated and written to storage. Conversely, a direct donation to the vault contract itself is visible but protected by mathematical safeguards. #### 2. Controlled Balance Updates Balance updates in Fusion are deliberate and triggered only by authorized operations or roles via `_updateMarketsBalances()`: - **Alpha Strategy Execution**: Whenever an Alpha executes a strategy via `execute()`, the vault automatically updates the balances of all affected markets at the end of the transaction. This ensures that the vault's accounting reflects the new position state immediately after a deliberate change. - **Instant Withdrawals**: If an instant withdrawal requires the vault to source liquidity from external markets, the system triggers a balance update for those markets to reflect the exit and ensure accurate share pricing for the redeeming user. - **Alpha-Driven Reporting**: For passive yield (interest accrual), accounts with the `UPDATE_MARKETS_BALANCES_ROLE` (Alphas) periodically trigger balance refreshes. This is asynchronous to user deposits, eliminating intra-block manipulation vectors. #### 3. Oracle-Based Isolation Unlike designs that rely on raw balances of LP tokens or receipt tokens, Fusion's **Balance Fuses** return values converted through the `PriceOracleMiddleware`. Manipulating a raw balance in an external protocol does not translate 1:1 to the vault's `totalAssets` unless the oracle price also reflects that change. ### Additional Safeguards #### Redemption Delay (Withdraw Delay) To further neutralize flash loan attacks and sandwiching, Fusion vaults utilize a **Redemption Delay**. Configured via the `IporFusionAccessManager`, this delay enforces a minimum time (e.g., 24 hours) that must pass between a deposit and the subsequent ability to withdraw or redeem those shares. This ensures that capital is "at risk" and prevents attackers from entering and exiting the vault within the same block or price window. See also: [managing-redemption-delays.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/managing-redemption-delays.md "mention") #### Mathematical Safeguards (Virtual Offsets) For direct donation attacks on the vault contract itself, Fusion implements ERC-4626 virtual offsets. The internal share calculation uses a multiplier and an offset to prevent "inflation attacks": `shares = assets \cdot \frac{supply + 10^k}{totalAssets + 1}` Where `10^k` represents the `_SHARE_SCALE_MULTIPLIER` (typically 100). This makes the cost of manipulating the PPS via small-scale donations prohibitively expensive. ### Comparison of Accounting Models | **Aspect** | **Auto-Accrual** | **Explicit (IPOR Fusion)** | | ----------------------- | ---------------------------------- | ---------------------------------- | | **Yield Accrual** | Automatic, every transaction | Triggered (Alpha, Withdraw) | | **PPS Update** | Per-transaction, via loops | Per-operation/reporting | | **Donation Protection** | Vulnerable | **Resistant** | | **Flash Loan Exploit** | Possible | **Prevented (via Delay & Cache)** | | **Security Scope** | Weakest link across all strategies | Isolated via Oracles & Triggers | | **Withdrawal Safety** | Vulnerable to intra-block swings | **Protected via Redemption Delay** | ### Implementation Summary By utilizing **cached balances**, **operation-specific triggers**, and a **redemption delay**, Fusion ensures that the vault's accounting is a product of verified protocol state rather than side effects of user actions. This architecture makes the Fusion Vault significantly more robust against sophisticated MEV and price manipulation attacks.# Price Oracle Middleware > How the price oracle middleware resolves asset prices and how to add or override a feed. ## Purpose and scope The Price Oracle Middleware system provides standardized asset valuation and conversion for the PlasmaVault ecosystem. It is centered around the `PriceOracleMiddlewareWithRoles` contract, which acts as a centralized price hub, and the `PriceOracleMiddlewareManager`, which provides a vault-specific interface for price queries and validation. The system is responsible for: - Providing asset prices in a unified quote currency (USD) with 18 decimals [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol42-46](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L42-L46) - Managing custom price feed sources (e.g., Pendle PT, Curve, ERC4626) [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol125-135](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L125-L135) - Falling back to the Chainlink Feed Registry when custom sources are not defined [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol19-21](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L19-L21) - Implementing price change validation to protect vaults from extreme volatility or oracle manipulation [contracts/managers/price/PriceOracleMiddlewareManagerLib.sol43-48](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/managers/price/PriceOracleMiddlewareManagerLib.sol#L43-L48) ## System architecture The architecture separates global price discovery from vault-specific price management and validation. ### Logic flow and code entities ![Image](https://ipor.io/docs/assets/build-on-fusion/image-2-bb20c5.png) __ **Architecture description**: 1. **PlasmaVault** and its **Balance Fuses** interact with the `PriceOracleMiddlewareManager` assigned to the vault [contracts/vaults/PlasmaVault.sol75](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/vaults/PlasmaVault.sol#L75-L75) 2. The **PriceOracleMiddlewareManager** handles vault-specific configuration, such as custom asset sources and price validation logic stored via `PriceOracleMiddlewareManagerLib` [contracts/managers/price/PriceOracleMiddlewareManagerLib.sol64-75](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/managers/price/PriceOracleMiddlewareManagerLib.sol#L64-L75) 3. **PriceOracleMiddlewareWithRoles** serves as the protocol-wide price aggregator, managing specialized feeds like **PtPriceFeed** for Pendle tokens [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol140-150](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L140-L150) ## Core components ### 1. PriceOracleMiddlewareWithRoles This contract provides the "Source of Truth" for asset prices. It normalizes all outputs to 18 decimals (WAD) [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol76-77](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L76-L77) - **Quote Currency**: Hardcoded to the Chainlink USD address `0x...0348` [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol42](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L42-L42) - **Custom Sources**: Allows administrators to map specific assets to custom `IPriceFeed` implementations [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol112-114](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L112-L114) - **Pendle Integration**: Features specialized logic to deploy and manage `PtPriceFeed` instances for Pendle Principal Tokens [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol140-160](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L140-L160) ### 2. PriceOracleMiddlewareManager A per-vault manager that wraps the global middleware with validation and vault-specific overrides. - **getAssetPrice**: The primary entry point. It checks for a vault-specific source in `PriceOracleMiddlewareManagerLib` before falling back to the global middleware [contracts/managers/price/PriceOracleMiddlewareManager.sol103-118](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/managers/price/PriceOracleMiddlewareManager.sol#L103-L118) - **Validation**: Implements `validatePriceChange` which compares the current price against a `lastValidatedPrice`. If the delta exceeds `maxPriceDelta`, the transaction reverts [contracts/managers/price/PriceOracleMiddlewareManagerLib.sol43-48](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/managers/price/PriceOracleMiddlewareManagerLib.sol#L43-L48) ## Specialized price feeds The system supports various specialized feeds to handle complex DeFi assets. ### Pendle PT Price Feed (`PtPriceFeed`) Calculates the price of Pendle Principal Tokens using Pendle's TWAP oracle and the price of the underlying asset from the middleware [contracts/price_oracle/price_feed/PtPriceFeed.sol12-18](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/price_feed/PtPriceFeed.sol#L12-L18) | Feature | Implementation Detail | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TWAP Window | Minimum 5 minutes, recommended 15 minutes [contracts/price_oracle/price_feed/PtPriceFeed.sol23](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/price_feed/PtPriceFeed.sol#L23-L23) | | Calculation | `(PtToAssetRate * UnderlyingAssetPrice) / scalingFactor` [contracts/price_oracle/price_feed/PtPriceFeed.sol128](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/price_feed/PtPriceFeed.sol#L128-L128) | | Metadata | Returns Chainlink-compatible `latestRoundData` [contracts/price_oracle/price_feed/PtPriceFeed.sol109-113](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/price_feed/PtPriceFeed.sol#L109-L113) | ### Feed factory pattern Factories like `PtPriceFeedFactory` and `CurveStableSwapNGPriceFeedFactory` are used to deploy standardized feed instances that the middleware can then consume [contracts/factory/price_feed/PtPriceFeedFactory.sol6-10](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/factory/price_feed/PtPriceFeedFactory.sol#L6-L10) ## Price validation mechanism The validation system prevents the vault from transacting at "stale" or manipulated prices by enforcing a maximum allowed deviation. ![Image](https://ipor.io/docs/assets/build-on-fusion/image-3-4ccd77.png) __ - **Configuration**: Managed via `updatePriceValidation` which sets the `maxPriceDelta` for an asset [contracts/managers/price/PriceOracleMiddlewareManagerLib.sol139-152](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/managers/price/PriceOracleMiddlewareManagerLib.sol#L139-L152) - **Execution**: Usually triggered via a Pre-Hook (`ValidateAllAssetsPricesPreHook`) before vault actions like `execute` or `deposit` [contracts/handlers/pre_hooks/pre_hooks/ValidateAllAssetsPricesPreHook.sol18-23](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/handlers/pre_hooks/pre_hooks/ValidateAllAssetsPricesPreHook.sol#L18-L23) ## Access control and roles The system utilizes a hierarchy of roles defined in `Roles.sol` to secure configuration. | Role | Entity | Permission | | -------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ADMIN_ROLE` | `AccessManager` | Highest level; manages all roles [contracts/libraries/Roles.sol11-13](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/libraries/Roles.sol#L11-L13) | | `ATOMIST_ROLE` | `PlasmaVault` | Can update the `priceOracleMiddleware` address [contracts/libraries/Roles.sol44-45](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/libraries/Roles.sol#L44-L45) | | `PRICE_ORACLE_MIDDLEWARE_MANAGER_ROLE` | `PriceOracleMiddlewareManager` | Manages asset price sources and validation deltas [contracts/libraries/Roles.sol102-105](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/libraries/Roles.sol#L102-L105) | | `SET_ASSETS_PRICES_SOURCES` | `PriceOracleMiddlewareWithRoles` | Global permission to set price sources [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol27](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L27-L27) | | `ADD_PT_TOKEN_PRICE` | `PriceOracleMiddlewareWithRoles` | Permission to deploy Pendle PT feeds [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol28](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L28-L28) | ## Data normalization All prices returned by the middleware system are normalized to 18 decimals (WAD) to ensure consistency across the IPOR Fusion protocol. - **Normalization Formula**: If a feed has `N` decimals, the price is scaled by `10^{(18-N)}` [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol76-77](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L76-L77) - **Batch Processing**: `getAssetsPrices` allows fetching multiple asset prices in a single call, returning arrays of prices all in 18-decimal format [contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol92-107](https://github.com/IPOR-Labs/ipor-fusion/blob/c26a0a9d/contracts/price_oracle/PriceOracleMiddlewareWithRoles.sol#L92-L107)# Configuring Pre-hooks > How to attach pre-execution validation hooks (e.g. rate limits, price checks) to vault functions. Pre-hooks are very powerful feature of the Fusion Vaults. They allow an execution of an additional functions before invoking a primary action. Because the pre-hooks are very powerful functionality their management should **always be done by user with a long timelock.** IPOR web app will be alerting liquidity providers about the use of unverified pre-hooks. ## What is a pre-hook Fusion vault allows for an execution of a code when invoking restricted methods on the core vault. Pre-hooks are not available to methods on managers. Pre-hooks are smart contracts with an access to the memory of the contract and authority to execute functions. They can be used for various applications such as: - updating balance cache on markets - raising exceptions (effectively pausing functions) - triggering auto-withdrawals from complex strategies - etc. > Due to that power and flexibility it is important that the user allowed to modify the prehooks has timelock configured To add pre-hook to the vault use `setPreHookImplementaions` ```solidity PlasmaVaultGovernance(PLASMA_VAULT) .setPreHookImplementations(selectors, preHooks, substrates); ``` where `selectors` are the signatures of restricted methods on the vault, pre-hooks are addresses of implementations and substrates are the params required by the pre-hooks. Example implementation can be found in below test case: . It demonstrates the use of the pre-hook that rebalances the cache of the vault before deposit and withdrawal while ignoring balances with dust. List of the deployed prehooks: ### Bundled Pre-hooks IPOR Fusion web-app comes with some bunled prehooks that bring some valuable functionalities to the vaults. This list may not be exhaustive as there are new components being developed but below you can find some pre-hooks available at the time of writing this documentation and use cases for them. #### Function Pausing Pre-hook Under the prehooks section of the administrative panel `/edit` you can find `Pause Functions` . You can use this interface to [pause](https://ipor.io/docs/build-on-fusion/developer-guide/pause-functionality.md) individual functions of the fusion vaults. Underneeth a special pre-hook is used that simply raises an exception. By attaching it before a restricted method you essentially render it unable to execute. ![Image](https://ipor.io/docs/assets/build-on-fusion/function-pausing-7bd98f.png) __ #### Auto Rebalance Pre-hook Another prehook provided via the app.ipor.io interface is the auto rebalance prehook. Because rebalancing of the vault before deposit and withdrawals would cost additional gas, vaults for gas optimisation rely on cached balances that are refreshed each time the vault interacts with connected markets. You can force rebalancing before every deposit/withdraw action by using this pre-hook. ![Image](https://ipor.io/docs/assets/build-on-fusion/auto-rebalance1-32de48.png) __ ![Image](https://ipor.io/docs/assets/build-on-fusion/auto-rebalance2-6d46a7.png) __ #### Exchange rate validator Another example of what pre-hooks can be used for is validating of the changes in the exchange rate since the last rebalance. When this is enabled then pre-hook will keep track of the exchange rates and will block selected functions is the jump of the share exchange rate exeeds the defined threshold. This is useful in case you want to restrict access in the situaltions where vault is misconfigured or price oracles are experiencing issues. ![Image](https://ipor.io/docs/assets/build-on-fusion/exchange-rate-validator-2-950b20.png) __ ![Image](https://ipor.io/docs/assets/build-on-fusion/https-files-gitbook-com-v0-b-gitbook-x-prod-appspot-com-o-spaces-2fr8w71un6csi2mpkvqqk9-2fuploads-2f1ppbek7q4xmgkmojakry-2fexchange-20rate-20validator-201-pngalt-media-token-2053d134-3df3-412c-9274-dc0e7e001bc0-f23a34.avif) __# API > Base URL, endpoints, address-casing and error-format details for calling the public Fusion API directly. ## IPOR Static API — Developer Guide Public, read-only JSON API serving IPOR Fusion vault data, including the historical time series behind the charts on [app.ipor.io](https://app.ipor.io/). **Base URL:** `https://api.ipor.io` No API key. No authentication. No rate limit. No query parameters — every input is encoded in the URL path. > **Important:** the long-range history buckets (`1y`, `all`) are **intentionally stale** by days to weeks. A request for `-all` alone returns a series whose most recent point may be around three weeks old, with no error to indicate this. See Period buckets and freshness. This behaviour is by design and will not change; clients are expected to merge buckets. --- ### Quick start Chart a vault's APY over the last 30 days: ```bash # Note the lowercase address. Anything else 404s. curl --compressed \ 'https://api.ipor.io/v2/fusion/vault-history/1/0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2-30d' \ | jq '.history[-1] | {blockTimestamp, apy, tvl}' ``` ```json { "blockTimestamp": "2026-07-08T13:14:47Z", "apy": "3.2864762350246543", "tvl": "490462.27822644341523" } ``` Note that the most recent point is already approximately 21 hours old. Obtaining a series that extends to the present requires additionally fetching `-7d` and merging the two. The reference client performs this automatically. Discover vault addresses first: ```bash curl --compressed 'https://api.ipor.io/dapp/plasma-vaults-list' \ | jq '.plasmaVaults[] | {chainId, address, name, apr}' ``` --- ### Conventions **Addresses must be lowercase.** The API is static JSON objects on S3 behind CloudFront, so paths are object keys and are case-sensitive. A checksummed (mixed-case) address returns `404`. ``` 200 /v2/fusion/vault-history/1/0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2-7d 404 /v2/fusion/vault-history/1/0x43Ee0243eA8CF02f7087d8B16C8D2007cC9c7cA2-7d ``` This requires care, because **`/dapp/plasma-vaults-list` returns most of its addresses checksummed** (mixed-case). An address taken from the vault list and used unmodified in a history path will return 404. Apply `.toLowerCase()` before constructing any path. **Numbers are decimal strings, not JSON numbers.** `"tvl": "490462.27822644341523"`. The precision exceeds that of an IEEE-754 double. Parse them with a decimal library (`decimal.js`, `big.js`, `bignumber.js`). Passing them through `Number()` will discard low-order digits. Token amounts (`totalAssets`, `balance`) are raw base units and must be scaled by the asset's `assetDecimals`. **Timestamps are ISO-8601 UTC**, e.g. `"2026-07-09T09:14:47Z"`. **Every history point carries a `blockNumber`**, which is the correct key for joining, deduplicating, and ordering series. Prefer it over the timestamp. **Errors are XML, not JSON.** A miss — unknown vault, unsupported period, or a non-lowercase address — returns the raw S3 error document with `content-type: application/xml`: ```xml NoSuchKeyThe specified key does not exist.... ``` A client that calls `.json()` on any non-2xx response will throw a parse error rather than surface a clean 404. Branch on `response.status` before parsing. **Responses are gzipped** (`content-encoding: gzip`). Send `Accept-Encoding: gzip` (`curl --compressed`); browsers and `fetch` do this automatically. The uncompressed payloads are much larger — see Payload sizes. **CORS is open** (`access-control-allow-origin: *`, `GET` only). The header is returned when an `Origin` request header is present, so browser calls work directly with no proxy. --- ### Period buckets and freshness Both history endpoints take a period suffix on the last path segment: | Period | Coverage | | ------ | ------------------- | | `7d` | Trailing 7 days | | `30d` | Trailing 30 days | | `1y` | Trailing 365 days | | `all` | Full vault lifetime | Points are spaced roughly **hourly**. #### Why the data is spliced this way The API is not a database query layer. Every response is a **pre-rendered file written to S3** and served through a CDN. This is a deliberate design decision made for security and performance: there is no query surface to exploit or overload, and reads remain fast and inexpensive at any traffic level. The trade-off is that a file's contents are only as current as its last write, and rewriting a multi-megabyte object is costly. Consequently, **the older a file's data, the less frequently it is regenerated**. The `7d` file is small and is rewritten frequently. The `all` file is large and consists mostly of immutable history, so it is rewritten rarely. This is the reason the most recent point in a bucket lags real time by an amount that grows with the size of the bucket. Measured against vault `0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2` on Ethereum at `2026-07-09T10:00Z`: | Period | Points | Newest point | Trails real time by | | ------ | ------ | ------------------- | ------------------- | | `7d` | 168 | `2026-07-09T09:14Z` | **\~45 minutes** | | `30d` | 720 | `2026-07-08T13:14Z` | **\~21 hours** | | `1y` | 8,714 | `2026-07-06T13:14Z` | **\~2.9 days** | | `all` | 14,726 | `2026-06-17T13:14Z` | **\~22 days** | The exact lag varies by vault and by regeneration cycle. Do not depend on the specific values above. Depend instead on the ordering, which is invariant: `7d` is always the most current bucket and `all` is always the least current. **This is intended behaviour and will not change.** Rewriting the multi-megabyte `all` object every hour would provide no benefit, because its stale tail can be reconstructed inexpensively from the smaller, more current buckets. Clients are expected to perform this reconstruction. #### The merge rule To render a period accurately, fetch the requested bucket **plus every more current bucket that covers its stale tail**, then merge the point sets: | Requested | Fetch | | --------- | ------------------ | | `7d` | `7d` | | `30d` | `30d`, `7d` | | `1y` | `1y`, `30d`, `7d` | | `all` | `all`, `30d`, `7d` | Merge by `blockNumber`, sort ascending, and on a duplicate `blockNumber` retain the point from the more current bucket. The buckets overlap, so duplicates are expected and must be collapsed. Otherwise any aggregate computed over the series (averages, integrals, point counts) will double-count the overlap region. Applying this, all four periods become current to within \~1 hour: ``` 7d pts= 168 first=2026-07-02T11:14:47Z last=2026-07-09T10:14:47Z trails=0.2h 30d pts= 741 first=2026-06-08T14:14:47Z last=2026-07-09T10:14:47Z trails=0.2h 1y pts= 8783 first=2025-07-06T14:14:47Z last=2026-07-09T10:14:47Z trails=0.2h all pts= 15249 first=2024-09-30T21:15:11Z last=2026-07-09T10:14:47Z trails=0.2h ``` Tolerate a `404` on an individual bucket. A vault younger than a year has no `1y` object, but its `all` and `30d` objects exist. Treat the merge as successful if at least one bucket resolves. Propagate any non-404 failure rather than rendering a partial series without indication. --- ### Endpoints #### `GET /dapp/plasma-vaults-list` A compact vault directory (\~100 KB). Use this endpoint to enumerate vaults. It is preferable to `/fusion/vaults`, which is approximately 44 MB. ```jsonc { "timestamp": "2026-07-09T10:01:15.819Z", "version": "…", "plasmaVaults": [ { "name": "IPOR USDC Lending Optimizer Ethereum", "chainId": 1, "address": "0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2", // may be MIXED-CASE "apr": "3.286746235024654313", "totalAssets": "463267371354", // raw base units "assetAddress": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "assetSymbol": "USDC", "assetDecimals": 6, "marketIds": ["1", "11", "3", "4", "14"], "fuses": ["0x465D639EB964158beE11f35E8fc23f704EC936a2", "…"] } ] } ``` This is the authoritative list of vaults the API serves. Remember to lowercase `address` before using it in a history path. --- #### `GET /v2/fusion/vault-history/{chainId}/{vaultAddress}-{period}` Per-vault time series: APY, TVL, and allocation across markets. **This is the primary charting endpoint.** Subject to the merge rule. `vaultAddress` **must be lowercase**. ```jsonc { "history": [ { "blockNumber": 25444165, "blockTimestamp": "2026-07-02T10:14:47Z", "apy": "3.2864762350246543", "rewardsApy": "0.104", "vestingApy": null, "underlyingAssetApy": "3.11", "assetsToSharesRatio": "1.0412…", "tvl": "490462.27822644341523", // USD "totalBalance": "490648.346799", // underlying asset units "marketBalances": [ { "protocol": "aave-v3", "marketId": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "balanceType": "DEPOSIT", "balance": "11096.690979", "balanceUsd": "11092.48278088003383" } ] } ] } ``` `marketBalances` provides the data for the historical-allocation (stacked area) chart: each point records how the vault's assets were distributed across underlying protocols at that block. `apy` is net of fees; `underlyingAssetApy` and `rewardsApy` decompose it. Some vaults additionally expose `dexPositionBalances` (LP positions, with `token0`/`token1` USD amounts) and `rewardsVestingApy`. Treat all of these as optional and nullable. --- #### `GET /v2/fusion/market-history/{chainId}/{protocol}/{substrate}-{period}` Per-market time series: supply and borrow rates and market size for one market on one protocol. Use it to chart the yield of the venues a vault allocates into. Subject to the merge rule. - `protocol` — a protocol slug, e.g. `aave-v3`. - `substrate` — identifies the market within the protocol, **lowercase**. For lending pools this is the underlying asset address; for vault-shaped markets (`erc4626`, `meta-morpho`, `morpho-blue`) it is the market or vault address. ```jsonc { "points": [ { "blockNumber": 25444165, "timestamp": "2026-07-02T10:14:47Z", "supplyApy": "4.1034406162952586", "supplyBaseApy": "4.1034406162952586", "supplyRewards": [ { "assetAddress": "0xc00e94cb662c3520282e6f5717214004a7f26888", "rewardApy": "0.1008130660673186", "type": "INSTANT" } ], "borrowApy": "4.9405590449566654", "borrowBaseApy": "4.9405590449566654", "borrowRewards": [], "totalSupply": "2017881149.188354", "totalBorrow": "1869968394.182922" } ] } ``` `supplyApy` = `supplyBaseApy` + the sum of `supplyRewards[].rewardApy`. Point density is at least hourly and may be finer than the vault history. Two protocols use a substrate convention that differs from their market id: `spark` markets are keyed by the **DAI address**, and Harvest vaults use the protocol slug `harvest-erc4626`. --- #### `GET /fusion/vaults` The full vault and market snapshot, **\~44 MB uncompressed** (\~4 MB gzipped). It embeds a `history` array within every vault object, which accounts for most of the payload size. ```jsonc { "creationDate": "2026-07-09T10:58:00.019119154Z", "vaults": [ { "chainId": 1, "address": "…", "name": "…", "apy": "…", "apr30d": "…", "historicalApr": "…", "history": [ /* … */ ], "tvl": "…", "managedByIporAlpha": false, "alphaDisabled": false } ], "markets": [ { "chainId": 1, "protocol": "euler-v2", "marketId": "0xdc1a…", "supplyApy": "…", "supplyBaseApy": "…", "supplyRewards": [], "borrowApy": "…", "supplyApr30d": "…", "borrowApr30d": "…" } ] } ``` **Do not fetch this endpoint to read a single vault.** Use `/dapp/plasma-vaults-list` for enumeration and the `-history` endpoints for time series. Its principal use is the `markets[]` array, which provides current rates for every market on every chain in a single request. This is the sole reason the IPOR frontend retrieves it. --- #### `GET /fusion/vaults-customization-list` Atomist-supplied presentation metadata for vaults that have opted in. Only a minority of vaults appear; absence from this list is normal and not an error. ```jsonc [ { "chainId": 1, "vaultAddress": "0x8f5855D30610b176c972F844B93A28c6D1553416", "description": "x*y=k", "vaultLogoUrl": "https://api.ipor.io/fusion/vaults-customization/1/0x8f58…/vault-logo", "announcements": [] } ] ``` --- #### `GET /fusion/markets/{chainId}/{protocol}/{marketId}` _(v1)_ The v1 market-history endpoint. Returns market metadata alongside a points array of the same shape as v2: ```jsonc { "chainId": 1, "protocol": "aave-v3", "marketId": "0xa0b8…", "createdAt": "…", "points": [ /* same shape as market-history */ ] } ``` **v1 is limited to a trailing 30-day window.** It provides no period selector and returns no data older than 30 days. **Prefer v2 for new integrations.** `/v2/fusion/market-history/…` exposes the full history from vault creation through the `{period}` suffix, up to and including `all`. v1 remains appropriate only where a fixed 30-day window is sufficient and the merge rule is not required. --- ### Supported chains `chainId` appears in every history path. | Chain | `chainId` | | --------- | --------- | | Ethereum | `1` | | Arbitrum | `42161` | | Base | `8453` | | Unichain | `130` | | Ink | `57073` | | Plasma | `9745` | | Avalanche | `43114` | Chain support expands over time. Rather than hardcoding this table, derive the live set from `GET /dapp/plasma-vaults-list → plasmaVaults[].chainId`. --- ### Protocol slugs The `{protocol}` path segment takes a slug such as `aave-v3`, `compound-v3`, `morpho-blue`, `meta-morpho`, `euler-v2`, `spark`, `moonwell`, `gearbox-v3`, `fluid-instadapp`, `pendle`, or `erc4626`. **Do not hardcode the set.** New protocols and yield-bearing assets are added continuously, so any list reproduced here becomes inaccurate over time. Derive it at runtime: ```bash curl --compressed 'https://api.ipor.io/fusion/vaults' \ | jq -r '[.markets[].protocol] | unique | .[]' ``` Two slugs follow non-obvious conventions: `spark` markets use the **DAI address** as their substrate, and Harvest vaults use the slug `harvest-erc4626` rather than `erc4626`. --- ### Payload sizes and caching Orders of magnitude, uncompressed, for a mature Ethereum vault. Payloads grow with vault age and market count, so treat these as a guide to relative cost, not as fixed values: | Endpoint | `7d` | `30d` | `1y` | `all` | | ---------------- | -------- | -------- | ------ | ----------- | | `vault-history` | \~200 KB | \~850 KB | \~8 MB | **\~14 MB** | | `market-history` | \~100 KB | \~700 KB | \~5 MB | \~6 MB | | Endpoint | Size | | ----------------------------------- | ---------------------------- | | `/dapp/plasma-vaults-list` | \~100 KB (\~20 KB gzipped) | | `/fusion/vaults-customization-list` | \~15 KB | | `/fusion/markets/…` (v1) | \~400 KB | | `/fusion/vaults` | **\~44 MB** (\~4 MB gzipped) | Responses are served from CloudFront and carry `etag` and `last-modified`. Use conditional requests (`If-None-Match`) to avoid re-downloading unchanged objects. **Cache the long buckets.** An application that re-fetches `-all` at a short interval re-downloads approximately 14 MB to obtain data that is regenerated only rarely. The appropriate polling strategy follows from the freshness table: - Fetch `all` and `1y` **once** and cache them for hours or days. - Re-poll **only `7d`** (and `30d`) at your refresh interval. These objects are small and contain all recently added points. - Re-run the merge against the cached long bucket on each refresh. Revalidate the cached long buckets periodically with a conditional request rather than caching them indefinitely. Historical data is occasionally recomputed to correct defects, so an `all` object can change without gaining new points. See Data accuracy and corrections. --- ### Reference TypeScript client Dependency-free. Handles bucket merging, address normalisation, and the XML error case. Verified against the live API. ```ts /** * Minimal client for the IPOR static API (https://api.ipor.io). */ const BASE_URL = 'https://api.ipor.io'; export type Period = '7d' | '30d' | '1y' | 'all'; /** * Buckets to fetch for a requested period, ordered least-current to most-current. * On a duplicate blockNumber the last bucket takes precedence, so the most * current bucket must be listed last. */ const BUCKETS_TO_MERGE: Record = { '7d': ['7d'], '30d': ['30d', '7d'], '1y': ['1y', '30d', '7d'], all: ['all', '30d', '7d'], }; /** Numeric fields are decimal strings, not numbers — parse with care. */ type Decimal = string; export interface MarketBalance { marketId: string | null; protocol: string | null; balance: Decimal; balanceUsd: Decimal; balanceType: string | null; } export interface VaultHistoryPoint { blockNumber: number; blockTimestamp: string; // ISO-8601, e.g. "2026-07-09T09:14:47Z" apy: Decimal | null; rewardsApy: Decimal | null; vestingApy: Decimal | null; underlyingAssetApy: Decimal | null; assetsToSharesRatio: Decimal | null; tvl: Decimal; totalBalance: Decimal; marketBalances: MarketBalance[]; } export interface VaultHistoryResponse { history: VaultHistoryPoint[]; } export interface RewardApy { assetAddress: string; rewardApy: Decimal; type: string; } export interface MarketHistoryPoint { blockNumber: number; timestamp: string; supplyApy: Decimal | null; supplyBaseApy: Decimal | null; supplyRewards: RewardApy[]; borrowApy: Decimal | null; borrowBaseApy: Decimal | null; borrowRewards: RewardApy[]; totalSupply: Decimal | null; totalBorrow: Decimal | null; } export interface MarketHistoryResponse { points: MarketHistoryPoint[]; } /** A missing object. The API answers with an S3 XML body, not JSON. */ export class IporNotFoundError extends Error { constructor(readonly path: string) { super(`No data at ${path}`); this.name = 'IporNotFoundError'; } } async function getJson(path: string, signal?: AbortSignal): Promise { const response = await fetch(`${BASE_URL}${path}`, { signal }); // Unknown vault, unsupported period, or a non-lowercase address all land here. if (response.status === 404) throw new IporNotFoundError(path); if (!response.ok) { throw new Error(`GET ${path} failed: ${response.status}`); } // Guard against XML slipping through on a non-404 error status. const contentType = response.headers.get('content-type') ?? ''; if (!contentType.includes('application/json')) { const body = await response.text(); throw new Error(`GET ${path} returned ${contentType}: ${body.slice(0, 200)}`); } return (await response.json()) as T; } /** S3 keys are case-sensitive; `plasma-vaults-list` returns mixed-case addresses. */ const normalizeAddress = (address: string): string => address.toLowerCase(); /** * Concatenate buckets and sort by blockNumber ascending, retaining the LAST * occurrence of any repeated blockNumber. Callers pass buckets least-current * first, so an overlapping point is taken from the most current bucket * containing it. */ function mergeByBlockNumber( buckets: T[][], ): T[] { const byBlock = new Map(); for (const bucket of buckets) { for (const point of bucket) byBlock.set(point.blockNumber, point); } return [...byBlock.values()].sort((a, b) => a.blockNumber - b.blockNumber); } /** * Fetch every bucket for `period` and merge. Buckets are fetched concurrently; * a missing one is tolerated as long as at least one resolves, since not every * vault has a full `1y`/`all` history. */ async function fetchMerged< TPayload extends object, T extends { blockNumber: number }, >( period: Period, buildPath: (bucket: Period) => string, extract: (payload: Awaited) => T[], signal?: AbortSignal, ): Promise { const settled = await Promise.allSettled( BUCKETS_TO_MERGE[period].map((bucket) => getJson(buildPath(bucket), signal), ), ); const rejection = settled.find( (r): r is PromiseRejectedResult => r.status === 'rejected' && !(r.reason instanceof IporNotFoundError), ); if (rejection) throw rejection.reason; const resolved = settled .filter( (r): r is PromiseFulfilledResult> => r.status === 'fulfilled', ) .map((r) => extract(r.value)); if (resolved.length === 0) throw new IporNotFoundError(buildPath(period)); return mergeByBlockNumber(resolved); } /** * Vault APY / TVL / per-market allocation over time, hourly. * * The returned series is current to within ~1h regardless of `period`, because * the stale tail of the long buckets is backfilled from `30d` and `7d`. */ export function fetchVaultHistory(args: { chainId: number; vaultAddress: string; period: Period; signal?: AbortSignal; }): Promise { const address = normalizeAddress(args.vaultAddress); return fetchMerged( args.period, (bucket) => `/v2/fusion/vault-history/${args.chainId}/${address}-${bucket}`, (payload) => payload.history, args.signal, ); } /** * Supply/borrow APY and size for one market, hourly. */ export function fetchMarketHistory(args: { chainId: number; protocol: string; substrate: string; period: Period; signal?: AbortSignal; }): Promise { const substrate = normalizeAddress(args.substrate); return fetchMerged( args.period, (bucket) => `/v2/fusion/market-history/${args.chainId}/${args.protocol}/${substrate}-${bucket}`, (payload) => payload.points, args.signal, ); } export interface PlasmaVaultListItem { chainId: number; /** Mixed-case as returned; lowercase before using it in a history path. */ address: string; name: string; apr: Decimal | null; totalAssets: Decimal; assetAddress: string; assetSymbol: string; assetDecimals: number; marketIds: string[]; } export interface PlasmaVaultsList { timestamp: string; version: string; plasmaVaults: PlasmaVaultListItem[]; } /** ~100 KB. Preferred for enumerating vaults; `/fusion/vaults` is ~44 MB. */ export function fetchPlasmaVaultsList( signal?: AbortSignal, ): Promise { return getJson('/dapp/plasma-vaults-list', signal); } ``` #### Usage ```ts // Full lifetime APY series, current to within the hour. const history = await fetchVaultHistory({ chainId: 1, vaultAddress: '0x43Ee0243eA8CF02f7087d8B16C8D2007cC9c7cA2', // casing handled period: 'all', }); const series = history .filter((p) => p.apy !== null) .map((p) => ({ t: Date.parse(p.blockTimestamp), apy: Number(p.apy) })); // Underlying market rates. const aaveUsdc = await fetchMarketHistory({ chainId: 1, protocol: 'aave-v3', substrate: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', period: '30d', }); ``` --- ### Data accuracy and corrections **Vault APY figures are not guaranteed to be exact.** Two causes account for most discrepancies: - **Strategy changes.** A vault's strategy can be reconfigured over its lifetime. Historical points computed under a previous configuration will not always be directly comparable with later ones. - **Indexing bugs.** The pipeline that computes APY, TVL, and market balances is complex, and defects occur from time to time. **Corrections are applied retroactively.** When a bug is identified, the affected history is recomputed and the underlying S3 objects are rewritten. A series you fetched previously may therefore differ from the same series fetched later, and the corrected values are the authoritative ones. If you cache history, treat `etag` and `last-modified` as the signal to re-read rather than assuming historical points are immutable. This applies to the long buckets as well: an `all` object that has not gained new points may still have been rewritten to fix older ones. **Please report anything that looks wrong.** If you observe an APY, TVL, or allocation figure that does not match your own calculation or on-chain observation, report it in the IPOR Discord: **** Including the vault address, chain id, period, and the affected timestamps or block numbers will make the issue substantially faster to diagnose. Reports from integrators are a meaningful source of the corrections described above. --- ### Known limitations **`1y` and `all` are stale by design.** Described in Period buckets and freshness. Without merging, a chart will terminate weeks before the present. **APY values may be revised.** Historical points are not immutable; see Data accuracy and corrections. **Overlapping buckets contain duplicate `blockNumber` values.** Deduplicate during the merge. Note that the IPOR frontend's own implementation does not deduplicate. This has no visible effect on a line chart but produces incorrect results for any aggregate computation, so that implementation should not be treated as a reference for numeric work. **Addresses are case-sensitive in paths but returned checksummed in payloads.** Addresses obtained from the vault list will return 404 if used without normalisation. **404 responses return XML.** Check `response.status` before parsing the body. **No pagination, filtering, or date-range selection.** Buckets are retrieved in full. There is no mechanism to request a value between two arbitrary dates; fetch the enclosing bucket and slice the series client-side. **Two market-history versions coexist.** v1 (`/fusion/markets/…`) is limited to a trailing 30-day window. v2 (`/v2/fusion/market-history/…`) serves the full history from vault creation, selected by the `{period}` suffix. Use v2 unless a 30-day window is sufficient. **No published SLA or changelog.** This API serves app.ipor.io. It is public and stable in practice, but response shapes may acquire additional fields. Parse defensively: ignore unknown fields, and treat every documented field other than `blockNumber`, `blockTimestamp`/`timestamp`, `tvl`, and `totalBalance` as potentially null. --- ### Provenance Endpoint inventory derived from the IPOR webapp at `origin/develop` commit `6a734ab9d` — principally `src/config/constants.ts`, `src/fusion/api/vault-history/`, `src/fusion/api/markets/`, and `src/fusion/pages/list/list/usePlasmaVaultsList.ts`. All shapes, sizes, status codes, freshness figures, and the merge behaviour were verified against the live API on **2026-07-09**. Figures in the freshness and size tables are point measurements against vault `0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2` (Ethereum) and the `aave-v3` / `compound-v3` USDC markets. They characterise the regeneration cadence; they are not guarantees. Counts that move — how many vaults, markets, protocols, or chains exist — are deliberately omitted from this document. Query them from `/dapp/plasma-vaults-list` and `/fusion/vaults` instead# Security and Audits > IPOR's internal QA process, AI-assisted audit tooling, and the list of completed third-party audits with scope and reports. ## Code Security and Quality Assurance ### Internal Development Process IPOR Labs employs a multi-layered quality assurance process covering every stage of the feature lifecycle: 1. Unit and integration testing — every new feature and code modification requires test coverage. 2. Internal audits using specialized LLM models — as part of our code review process, we leverage proprietary auditing tools powered by state-of-the-art large language models (LLMs). These tools are specifically designed for smart contract analysis and enable detection of subtle logic bugs, state inconsistencies, and potential attack vectors that may escape traditional code review. 3. Code Review — every change undergoes a peer review process by team members, providing an additional layer of quality and security verification. ### Proprietary Audit Tools IPOR Labs developes in-house security audit tools powered by the latest AI models. These tools specialize in: - Deep business logic analysis of smart contracts (Feynman technique — questioning every line of code, operation ordering, and implicit assumptions) - State inconsistency detection — identifying situations where an operation mutates one piece of coupled state without updating dependent components - Combined multi-layer analysis — fusing results from different audit techniques in a feedback loop to discover bugs at intersections that no single technique would catch alone ### External Audit Tool Evaluation IPOR Labs actively tests and evaluates third-party automated smart contract security audit tools for potential inclusion in the pipeline: | Tool | Status | | --------------------------- | ---------------- | | Wake Arena BETA (Ackee) | Under evaluation | | AI Agent (Spearbit/Cantina) | Under evaluation | | MixBytes Audit Tools | Under evaluation | | Olympix | Under evaluation | | TestMachine | Under evaluation | ### Production Instance Auditing Beyond source code audits, IPOR Labs conducts systematic verification of production instances, covering: - Code — verifying deployed code matches previously audited versions - Configuration — reviewing vault configuration parameters, role permissions, and module settings - Markets — validating market configurations, limits, and risk parameters of active strategies ## Audits ### BlocSec Date: February 28, 2025 (v 1.0) _**Covers the currently live contracts**_ Report (Google Docs PDF): **Scope** - Updated IPOR Fusion: - Fusion Vault - Base Fuses - Rewards Manager - Access Management - Price Oracle Middleware - Prehooks - Context Manager - Withdraw Manager ### Protofire Date: September 6, 2024 (v 1.1) _**Covers the currently live contracts**_ Report (Google Docs PDF): [https://drive.google.com/file/d/1UZE7J-pTfHY-XtgZtVYMAOh4tHXTCCN2/view](https://drive.google.com/file/d/1UZE7J-pTfHY-XtgZtVYMAOh4tHXTCCN2/view?usp=sharing) **Scope** - IPOR Fusion: - Fusion Vault - Base Fuses - Rewards Manager - Access Management - Price Oracle MiddlewareSee https://github.com/IPOR-Labs/fusion-vault-launchpad# AGENTS.md — operating guide for AI agents You are an AI agent helping a **human operator** create an IPOR Fusion PlasmaVault with this repository. The human may be a quant trader, a DeFi curator, a treasury manager or an engineer. This file is written for you, whichever model or tool runs you (Claude Code, OpenAI Codex, xAI, or another). Read it fully before doing anything else. Everything it references is public. ## 1. Your job in one paragraph Turn the human's intent into a schema-valid strategy file, prove on a dry-run and a fork that the configuration does what they intend, and hand them a clear go/no-go before anything touches a live chain. You do the mechanical work; the human decides intent, addresses, money and every irreversible step. You can simulate everything without a private key. You cannot create a vault without one, and the key is theirs to provide, never yours to ask for in chat. ## 2. Read these, in this order 1. `docs/01-concepts.md` — vaults, fuses, substrates, roles, the lifecycle. 2. `docs/05-human-in-the-loop.md` — what only the human decides and where you must stop. 3. `docs/02-setup.md` — then run `python tools/doctor.py` (or `make doctor`): it tells you which of the three modes (dry-run, fork rehearsal, live) the current environment supports, without printing any secret. 4. `docs/03-strategy-json.md` — the strategy file, field by field. 5. `docs/04-deploy.md` — dry-run, fork rehearsal (deploy *and use* the vault), live broadcast, verification. 6. `docs/10-hardening.md` — what comes after a verified vault; read it with the human before the sign-offs. If you resume work on an existing strategy, also read `strategies/.json`, `strategies/.md`, and `.deploy-state/.json` if it exists. Everything else in the repository, and the public sources outside it, is listed in section 10. ## 3. Private key and RPC: the rules | Situation | What you do | |---|---| | The human wants to *see what would happen* | Run the dry-run. It needs no key and no `.env`. Never suggest a key is needed for this. | | The human wants to *rehearse* | Start an anvil fork; use anvil's default account #0 key (public, in `.env.example`). Never use a real key against a fork. | | The human is about to broadcast (fork or live) | Ask first: "Do you want to sign in your browser wallet (Rabby, MetaMask) through the local signing page, or with a key in `.env`?" Recommend the page for any live run: it lists every signature up front, shows where you are (vault map, step list) and never needs the key on disk. If they choose the page, run with `--signer browser` (`make sign`), give them the URL (`http://127.0.0.1:8789`) and wait while they sign; do not click or sign for them. | | The human wants to *create the vault for real* | Offer the two ways to sign, the page first: "You can sign every transaction in your browser wallet (Rabby, MetaMask) on a local page I serve, or I can use a deployer key you put in `.env`. The page needs no key on disk. Either way I need a private RPC endpoint in `.env` as `RPC_URL`, and the deployer account funded with gas." For the page, also ask for the deployer address (`DEPLOYER_ADDRESS`) so the page refuses any other account. For the key: "Put it in `.env` as `DEPLOYER_PRIVATE_KEY` (see `.env.example`) and tell me when done. Do not paste the key here." Then wait. | | `DEPLOYER_PRIVATE_KEY` is missing and the human asked for a live broadcast | Run the dry-run anyway so the plan is ready, then offer the signing page (section 7): it needs no key. Stop only if they want the key route and have not set it. | | The human pastes a private key into the conversation | Do not repeat it, do not write it into any file other than `.env`, and tell them the key should be considered exposed and rotated after the deployment. | | The human asks you to generate a key | You can, but say plainly that a key generated in a chat is not safe custody for real funds and should be used only for a trial with funds they can lose. | You never print the contents of `.env`, never commit it, and never copy the key into logs, state files or specs. `.gitignore` already excludes `.env` and `.deploy-state/`. ## 4. Operating rules 1. **English for every artifact.** Talk to the human in their language; write specs, JSON, reports and commit messages in English. 2. **Never invent on-chain facts.** Fuse addresses, market ids, whitelist status, role holders, prices, feed addresses: read them from the chain (the FuseWhitelist), from the ipor-abi snapshot a context names (`contexts/ipor-abi/`), or from the public sources in `docs/07-resources.md`. When you cannot verify something, write `unverified` and say what would verify it. 3. **Ask the human only what only the human knows.** Intent, chain, asset, venues, addresses, risk appetite, fees, whitelist and transferability choices. Resolve everything else yourself and show a short confirmation summary. 4. **One question at a time, with a recommended default marked as such.** Always accept a custom value. 5. **Persist as you go.** After every stage, write the answers into `strategies/.md` and `.json`. Never hold state only in the conversation. Edit specs; do not overwrite them wholesale. 6. **Tag provenance.** Every fact in the human spec is `[client]` (the human said so) or `[agent: ]`. 7. **Read the chain, not the labels.** Verification means reading the vault's own contracts; `--verify-only` does this. Logs and step names are not evidence. 8. **Report faithfully.** If a check failed, show the failure. If a step was skipped, say so. Never mark something ✅ on data you did not fetch. 9. **Results are not guarantees.** A clean dry-run, rehearsal or verification means the known checks passed. Say so plainly, and point the human to `DISCLAIMER.md` once at the start of a session. 10. **Do not edit `contexts/` or `schema/` silently.** Propose the change with its source and ask, unless the human explicitly asked you to make it. Never write an IPOR contract address into a context: name its ipor-abi key, or add a `pins` entry with the reason the registries are not enough. ## 5. Autonomy boundary | You may do this on your own | The human must do this | |---|---| | Drive the intake conversation and write `strategies/.md` | Answer the `[client]` questions (`docs/05-human-in-the-loop.md`) | | Resolve fuse availability, market ids, feed choices, dependency graph, queue params | Confirm chain, underlying, venues, target size, fee tier | | Write `strategies/.json`; run `python tools/spec_lint.py` | Provide every address that will hold a role, and say which are multisigs | | Run dry-runs as often as useful | Decide whitelist-at-launch and transferability-at-launch | | Start anvil, run a fork rehearsal with `--broadcast --rehearse`, run `tools/plan_diff.py` | Provide and fund the deployer account (browser wallet or key); choose the RPC endpoint | | Serve the signing page (`--signer browser`) and give the human its URL | Accept the page's disclaimer, connect the wallet, and click Sign on every transaction | | Propose a token holder and deposit size for the rehearsal, read from the chain | Say in their own words that they reviewed the role holders, understand hardening comes before production, and know that front-end listing needs the IPOR Labs team (`signoff.*`) | | Run `--verify-only` at any time | Say an explicit **"yes, deploy to "** after reviewing the dry-run plan and the fork results | | Re-run `--broadcast` **without** `--force-restart` to resume an interrupted live run, after confirming receipts | Approve the resumed run if any recorded transaction is missing on-chain | Two hard rules sit above the table: - **No live broadcast without all of these:** a clean fork rehearsal *including the rehearsal stage* (the vault deposited into, every fuse executed, accounting checked, withdrawal paid), a `plan_diff` with no `run_only` actions, the three `signoff` flags set after the human's own words, and an explicit human go-ahead in the current session for this exact file. Approval for an earlier version of the file does not carry over. - **No irreversible switch without an explicit acknowledgement.** Share transferability (`transferability.enabled_at_launch: true`) cannot be undone. The file must carry `irreversible_ack: true`, and you set that only after the human has said in their own words that they understand it is one-way. Whitelist removal is not something this pipeline does at all. ## 6. The lifecycle at a glance ``` intent ──► strategies/.md ──► strategies/.json ──► dry-run ──► fork rehearsal ──► plan diff │ verified live vault ◄── live broadcast ◄─┘ human "yes" ``` | Step | Command | Output | Human gate before it? | |---|---|---|---| | Intake | conversation + `templates/strategy-spec.md` | `strategies/.md` | answers to `[client]` items | | Machine spec | write `strategies/.json`; `python tools/spec_lint.py strategies/.json` | validated JSON | no | | Dry-run | `python -m deploy strategies/.json` | `.deploy-state/.plan.json` | no | | Fork rehearsal | anvil fork + `RPC_URL=http://127.0.0.1:8546 DEPLOYER_PRIVATE_KEY= python -m deploy … --broadcast --rehearse` | `.deploy-state/.json`, `.run.json`, `.rehearsal.json`, verification + rehearsal reports | no | | Plan diff | `python tools/plan_diff.py .deploy-state/.plan.json .deploy-state/.run.json` | exit 0/1 | no | | Clean state | delete `.deploy-state/.json` (fork addresses) or pass `--force-restart` on the live run | — | no | | Live | `python -m deploy … --broadcast --i-understand-this-is-live` with real `RPC_URL` and funded key | live vault, state file | **yes** | | Live, wallet-signed | same with `--signer browser` (or `make sign`): the human signs each transaction on the local page in their wallet | live vault, state file | **yes**, and ask whether they want the page (section 3) | | Alpha roles, later | once the alpha (bot) exists: `python -m deploy --broadcast --signer browser --assign-alpha` (`make assign-alpha`); the base run defers ALPHA and the balance/rewards updater roles | role grants on the deployed vault | **yes** | | Several connected vaults | write a campaign file (`docs/04-deploy.md` §5c), dry-run it with `python -m deploy.campaign `, then `--broadcast --signer browser` (or `make sign-set`): one page, children first, connections signed after each parent | every vault's state file, `.deploy-state/.campaign.json` | **yes**, for the whole set | | Verify | `python -m deploy … --verify-only` | report | no | | Harden | `docs/10-hardening.md`: edit roles/delays/fees/limits, re-run the affected steps, verify | hardened vault | **yes**, per run | Details, flags and failure modes: `docs/04-deploy.md`. Every row also exists as a make target (`make help`), so you can run `make dry-run STRATEGY=strategies/.json`, `make fork`, `make rehearse`, `make diff`, `make verify` instead of remembering flags. Prefer the plain commands when you need a non-default flag. ## 7. How to talk to the human - Start a session by saying what you will and will not do without them (section 5), and where the disclaimer is. - Before a live broadcast, show: the chain, the vault name and symbol, the underlying, every role holder, the fee tier, whitelist and transferability settings, the gas estimate from the rehearsal, and the exact command you will run. Then ask for the yes. - **Presenting the signing page.** When the human chooses the page: (1) start the run in the background with `make sign` or `python -m deploy --broadcast --signer browser` (plus `--i-understand-this-is-live` on a live chain); the page is up once the log prints `[browser-signer] open http://127.0.0.1:8789/`. (2) Give them the URL and say what they will see: a disclaimer to accept, a *Connect wallet* step, then one card per transaction with a Sign button (and a Skip button on optional role grants), a map of the vaults at the top and a step list on the right marking "You are here", and a status bar while the next transaction is prepared. (3) Say they have 60 minutes to connect, and that rejecting a transaction stops the run at that step (re-run the same command to resume). (4) Watch the log and report progress and the verification result. Never accept the disclaimer, connect, or sign for them. On a fork, remind them to point the wallet's network at the fork URL the page shows, and switch it back afterwards. - When the chain, the spec and the human's memory disagree, show all three and ask. - Echo an answer back before persisting anything that is hard to undo. - Give the reason for a recommendation in one or two sentences ("a whitelist can be removed later but never re-added, so launching closed loses nothing"). ## 8. Things that have gone wrong before Each of these produced a check that now exists. Know why. - A vault priced off the shared chain middleware in the logs, but its **own oracle** was unconfigured. Only `getAssetPrice(asset)` on the vault's own price manager counts; step `05_price_feeds` refuses to finish a broadcast while any asset is unpriceable. - Lending markets shipped **without dependency-graph edges** to the idle balance market, so the vault's accounting did not cascade. Edges are now derived from `balance_fuses`; you cannot omit them. - A supply cap was computed in underlying decimals instead of **share decimals** and shipped 100× too small. Use `total_supply_cap_underlying`; the pipeline converts with the vault's own `decimals()`. - A dry-run once persisted a preview clone address, so a later broadcast **skipped the real clone**. Dry-runs no longer write state; still, clear state before the first live run. - A wallet cancel on the clone left the clone's **preview** addresses in the state file; the re-run skipped the clone and sent role grants to an access manager that did not exist. Three checks now: the clone step saves addresses only after the clone is mined (and discards unsent previews), a resumed run refuses a state whose clone transaction is not mined on the connected chain or whose vault has no code (this also catches fork state reused against another fork or live, which share the chain id), and every send is refused when its target has no code (`deploy/chain_guards.py`). - A fork run was signed in a wallet still pointed at the **live RPC**: fork and live share the chain id, so the page accepted it and a transaction landed on mainnet. The page now checks a marker account that exists only on the fork, through the wallet, on connect and before every signature. - A spec with `deployer_temporary_owner: false` and an appointed owner other than the signer was **cloned on a fork**, then every role grant failed: the signer did not own the vault. The clone step now refuses before sending when the clone owner is not the signer. - A universal swapper fuse was granted substrates in a **layout the fuse ignores**; every config check was green and the venue was dead. The loader enforces fuse family → encoding. - A public RPC stalled a live broadcast mid-sequence and hand-sent follow-ups raced nonces. Use a private RPC, run detached, and resume without `--force-restart`. - Every read-back check can be green on a vault that cannot be used: a substrate the fuse cannot act on, a market valued twice, a withdrawal path that cannot pay. The rehearsal stage (`--rehearse`) now deposits, executes every declared fuse, checks NAV after each batch and withdraws, on the fork, before anything is called verified. - A flash-loan vault passed every configuration check and reverted `HandlerNotFound()` on its first loop: nothing had registered the **callback handler** that routes Morpho's `onMorphoFlashLoan` back into the vault. The loader now refuses a `MorphoFlashLoanFuse` without its `callback_handlers[]` entry, step `03b` writes it, and verification reads it back from vault storage. ## 9. When you are unsure Stop and ask. A blocked deployment costs an hour. A mis-deployed vault can cost the human their capital and their reputation. ## 10. Map of the repository and related sources This section replaces a separate `llms.txt`: this file is the entry point. **Manual (`docs/`)** | File | Contents | |---|---| | `README.md` | What the toolkit does, the three modes, the quick start, what has been verified | | `docs/01-concepts.md` | Vaults, fuses, substrates, roles, one-way switches, lifecycle, trust order | | `docs/02-setup.md` | Prerequisites, `.env`, anvil fork, checking the installation | | `docs/03-strategy-json.md` | Field reference for `strategies/.json` and the chain contexts | | `docs/04-deploy.md` | Dry-run, fork rehearsal, live broadcast, vault sets, verification, recovery | | `docs/05-human-in-the-loop.md` | What only the human decides and how to ask | | `docs/06-troubleshooting.md` | Symptoms, causes, fixes | | `docs/07-resources.md` | Public sources and common on-chain reads | | `docs/08-glossary.md` | Terms | | `docs/09-using-with-your-agent.md` | How each agent tool finds this file, a starter prompt | | `docs/10-hardening.md` | Role handover, delays, the deployer's renounce, front-end listing | | `DISCLAIMER.md`, `CONTRIBUTING.md`, `SECURITY.md` | Terms of use; adding chains, fuses and steps; reporting a vulnerability | **Reference files** | Path | Contents | |---|---| | `strategies/example-usdc-aave-base.json` + `.md` | Placeholder single-venue USDC vault on Base; every address is an anvil test account, so the pipeline refuses it on a live chain | | `strategies/example-wsteth-usdc-loop-base.json` + `.md` | Placeholder leveraged wstETH/USDC loop on Morpho Blue (Base): collateral, borrow and flash-loan fuses, the callback handler, the legacy swapper, scheduled withdrawals, and what the alpha does after deployment | | `rehearsals/` | The SDK fuse actions the fork rehearsal runs for each example | | `templates/strategy-spec.md` | The shape of a human spec, with provenance tags | | `schema/strategy.schema.json` | Validation schema for strategy files | | `contexts/ipor-abi/` | Vendored ipor-abi snapshots: every IPOR contract address the pipeline uses (`make ipor-abi` refreshes them) | | `contexts/.json` | Optional per-chain overrides: public RPC, pins with reasons, aliases, external addresses, tokens; `make check-context CONTEXT=` checks a chain | **Outside this repository:** `docs/07-resources.md` lists the public sources (docs.ipor.io, ipor-abi, the SDK, its deploy skill and worked examples, the MCP server, the alpha example bot) and the on-chain reads you will do often.# 09 — Using this repository with your AI agent This repository does not depend on any particular AI product. Every step is a plain command with a documented flag set, and the agent instructions live in one file, `AGENTS.md`. This page tells you how each common tool finds that file, and what to say to your agent to get started. ## 1. How your tool finds the instructions | Tool | Reads automatically | Provided in this repo | |---|---|---| | OpenAI Codex (CLI and cloud), Cursor, GitHub Copilot (agent mode, coding agent), Windsurf, xAI Grok CLI | `AGENTS.md` | `AGENTS.md` | | Claude Code | `CLAUDE.md` | `CLAUDE.md`, which imports `AGENTS.md` | | Google Gemini CLI | `GEMINI.md` | `GEMINI.md` → points to `AGENTS.md` | | Aider | files passed with `--read` | run `aider --read AGENTS.md` | | Anything else, or a chat interface without file access | nothing | paste `AGENTS.md` into the conversation, or use the prompt below | `CLAUDE.md` and `GEMINI.md` exist only because those two tools look for their own file name; each one says: read `AGENTS.md` first. If your tool is not listed, tell the agent to read `AGENTS.md`; that is all it needs. ## 2. A prompt to start any agent Paste this as your first message, adjusting the last line: ``` You are working in the fusion-vault-launchpad repository. Before anything else, read AGENTS.md and follow its reading order. Then run `python tools/doctor.py` (or `make doctor`) and tell me which modes are available in this environment. Follow the autonomy boundary in AGENTS.md exactly: never ask me for a private key in this conversation, never broadcast to a live chain without my explicit "yes" for the exact file, and report failures as failures. My goal: . ``` ## 3. What to expect from a well-behaved agent 1. It reads `AGENTS.md`, the concepts page and the human-in-the-loop page before touching anything. 2. It runs the doctor and the unit tests, then a dry-run of the example, and shows you the plan. 3. It asks you the `[client]` questions one at a time, with a recommended default, and writes your answers into `strategies/.md` and `.json` as it goes. 4. It dry-runs your strategy until the plan reads right, then rehearses on a local fork and shows you the verification report and the plan/run diff. 5. Only then does it ask how you want to sign: in your browser wallet (Rabby, MetaMask) on a local signing page it serves at `http://127.0.0.1:8789`, or with a key you put into `.env` yourself. Either way you put the private RPC into `.env`, and it asks for an explicit "yes" before running the live command, which it shows you in full first. With the page, it hands you the URL and waits: you accept the disclaimer, connect the wallet and click Sign on each transaction while the vault map and the step list show where you are. If your agent skips a step, asks for your key in chat, or proposes a live broadcast before a fork rehearsal, stop it and point it at `AGENTS.md` §5. The pipeline itself also refuses a live broadcast that lacks the explicit flag or still contains placeholder addresses, but the agent's discipline is your first line of defence. ## 4. Commands the agent will use All are plain commands; `make help` lists the wrapped versions. | Purpose | Command | |---|---| | Environment check | `python tools/doctor.py [strategy.json]` | | Unit tests | `python -m pytest` | | Reconcile `.md` with `.json` | `python tools/spec_lint.py strategies/.json` | | Dry-run | `python -m deploy strategies/.json` | | Start a fork | `anvil --fork-url --port 8546` | | Rehearse on the fork | `RPC_URL=http://127.0.0.1:8546 DEPLOYER_PRIVATE_KEY= python -m deploy strategies/.json --broadcast` | | Plan vs run diff | `python tools/plan_diff.py .deploy-state/.plan.json .deploy-state/.run.json` | | Live deployment | `python -m deploy strategies/.json --broadcast --i-understand-this-is-live` with `.env` filled in | | Verify a live vault | `python -m deploy strategies/.json --verify-only` | ## 5. Smaller or less capable models The repository is designed so that correctness does not depend on the model's judgement: - Validation is in code (schema, semantic checks, whitelist gate, guards), not in the instructions. - Every failure message says what to do next. - The dangerous action needs a flag the agent has to type on purpose, a key the human has to place, and passes only if no placeholder address remains. That said, a less capable model will need more of your attention during intake, where the questions are open-ended. Review `strategies/.md` yourself before the fork rehearsal; it is short and written for you.# 03 — The strategy file: `strategies/.json` and chain contexts The deployer reads exactly two files: the strategy JSON and the chain context it names. Nothing strategy-specific lives in code. The schema is `schema/strategy.schema.json`. Two worked examples ship: `strategies/example-usdc-aave-base.json` (single venue, instant withdrawals) and `strategies/example-wsteth-usdc-loop-base.json` (collateralised Morpho loop with a flash-loan callback handler, legacy swapper, scheduled withdrawals), each with its human spec `.md`. ## 1. Two files per strategy | File | Purpose | Written by | |---|---|---| | `strategies/.md` | The human spec: intent, decisions, provenance tags, sign-off. Start from `templates/strategy-spec.md`. | agent, from the human's answers | | `strategies/.json` | The machine spec the deployer executes | agent, from the `.md` | Keep them reconciled: ```bash python tools/spec_lint.py strategies/.json # exit 1 if the vault name/symbol in the JSON is not in the .md; warns on address mismatches python -m deploy strategies/.json # schema + semantic checks at load, then the dry-run plan ``` ## 2. Validation the loader enforces `deploy/config.py` rejects the file at load time if: | Rule | Error | |---|---| | JSON Schema violation | `jsonschema.ValidationError` | | `fees.recipients[].split_bps` do not sum to 10000 | `split_bps must sum to 10000` | | whitelist disabled at launch but `initial_accounts` non-empty | `whitelist disabled at launch but initial_accounts is non-empty` | | `transferability.enabled_at_launch` without `irreversible_ack: true` | `requires irreversible_ack=true` | | the underlying has no `price_feeds` entry | `underlying missing from price_feeds` | | a Universal Token Swapper fuse family does not match its substrate encoding, or families are mixed on one market | `universal token swapper: …` | | a fuse that needs a callback handler (`MorphoFlashLoanFuse`) has no matching `callback_handlers[]` entry | `callback handlers: …` | Before a **live** broadcast, `deploy/guards.py` additionally refuses any configuration in which the signer, `vault.initial_owner_override`, a `roles.grants[].account`, a `whitelist.initial_accounts[]` entry or a `fees.recipients[].address` is one of anvil's ten default accounts. ## 3. Field reference ### `meta` | Key | Type | Notes | |---|---|---| | `name` | `^[a-z0-9-]+$` | the slug; also the state file stem | | `client` | string | organisation | | `contact` | string | optional | | `spec_version` | string | `v2` today | | `spec_source` | string | path to the `.md` | | `status` | `DRAFT` / `READY-FOR-REVIEW` / `APPROVED` / `SHIPPED` | | ### `chain` | Key | Notes | |---|---| | `id` | numeric chain id (1, 8453, …) | | `context` | file stem under `contexts/`. Shipped: `mainnet-ethereum-fusion`, `base-fusion`, `arbitrum-fusion`, `hyperevm-fusion`. | ### `vault` | Key | Notes | |---|---| | `name`, `symbol` | ERC-20 name and symbol (symbol ≤ 11 chars) | | `underlying` | token address; must also appear in `price_feeds` | | `decimals` | underlying decimals (informational) | | `total_supply_cap_underlying` | **preferred.** Human amount of the underlying, e.g. `"100000"` for 100,000 USDC. Converted at deploy using the vault's own `decimals()` (share decimals = underlying + offset). | | `total_supply_cap` | escape hatch: raw cap in **share** units. Avoid. | | `redemption_delay_seconds` | anti-flash-loan guard between deposit and withdraw/transfer; default 1; **immutable after clone** | | `dao_fee_package_index` | **immutable.** Index into the factory's fee packages. Read the current packages with `getDaoFeePackage(uint256)` on the FusionFactory and confirm with the human. | | `initial_owner_override` | the appointed **final** OWNER. `null` = the deployer. | | `deployer_temporary_owner` | `true`: clone under the deployer so it keeps a temporary OWNER role alongside the appointed owner (useful when an agent will later run hardening steps and renounce). `false`/`null`: clone under the appointed owner only. Make it an explicit decision. | If `deployer_temporary_owner` is `true` and `initial_owner_override` differs from the deployer, `roles.grants` **must** include an `OWNER_ROLE` grant for the appointed owner. Step `01_clone` warns otherwise. ### `roles` | Key | Notes | |---|---| | `execution_delay_seconds` | default per-grant delay applied by `11_roles` (usually 0 at launch) | | `grants[]` | `{role, account, execution_delay_seconds?}`; role names as in `Roles.sol`: `OWNER_ROLE`, `GUARDIAN_ROLE`, `ATOMIST_ROLE`, `ALPHA_ROLE`, `FUSE_MANAGER_ROLE`, `PRE_HOOKS_MANAGER_ROLE`, `CLAIM_REWARDS_ROLE`, `TRANSFER_REWARDS_ROLE`, `WHITELIST_ROLE`, `CONFIG_INSTANT_WITHDRAWAL_FUSES_ROLE`, `WITHDRAW_MANAGER_REQUEST_FEE_ROLE`, `WITHDRAW_MANAGER_WITHDRAW_FEE_ROLE`, `UPDATE_MARKETS_BALANCES_ROLE`, `UPDATE_REWARDS_BALANCE_ROLE`, `PRICE_ORACLE_MIDDLEWARE_MANAGER_ROLE` | How grants are used: `01b_bootstrap_roles` grants **the deployer** every role listed here so one signer can run the pipeline; `11_roles` grants the listed **accounts**; `10_whitelist` handles `WHITELIST_ROLE` from `whitelist.initial_accounts`. One account holding everything is the normal trial posture. The Guardian should still be a different account from the Owner. ### `fuses[]`, `balance_fuses[]` | Key | Notes | |---|---| | `fuses[].name` | a `FuseWhitelist` fuse-type name (`EulerV2SupplyFuse`, `ERC20BalanceFuse`, `Erc4626SupplyFuse`, …) or a key of the context's `fuses` map; the address is **resolved on the whitelist** before any step runs (see below) and then gated | | `fuses[].market` | optional; the market to resolve the fuse on. Needed only when the same fuse type is active on more than one of the strategy's balance-fuse markets (e.g. `Erc4626SupplyFuse` on `ERC4626_0001` and `ERC4626_0002`) | | `balance_fuses[].market` | market name (`AAVE_V3`, `MORPHO`, `ERC20_VAULT_BALANCE`, …) resolved via the context `markets` map or the SDK's `IporFusionMarkets` | | `balance_fuses[].fuse` | balance fuse name from the context | **Resolution order** (`deploy/fuse_resolver.py`, run once per pipeline start): the name is mapped to a whitelist fuse type (by type name, or by the type of the context address for that key), then `getFusesByTypeAndMarketIdAndStatus(type, market, active)` is read on the chain's `FuseWhitelist`. Exactly one active address wins, even when the context lists another one (the context is a cache and goes stale when IPOR ships a new fuse version; the run prints `update the context`). Several active addresses: the context's pin is used if it is one of them, otherwise the run stops and names them. No active address: the context address is used with a warning, and the whitelist gate in `02_add_fuses` decides. Names that are neither a type name nor a context key stop the run. Every market you act on needs a balance fuse. Include `ERC20_VAULT_BALANCE` with `ERC20BalanceFuse` in virtually every vault. The factory injects standard fuses (`BurnRequestFeeFuse`) on its own; do not list them; `02b_standard_fuses` upgrades them to the latest version from the context's `standard_fuses`. ### `substrates[]` `{market, encoding, values}`. Encodings (`deploy/encoders/substrates.py`): | `encoding` | `values` items | Used for | |---|---|---| | `address` | `"0x…"` | Aave V3 / Spark assets, ERC-4626 vaults, tracked tokens on market 7, legacy Universal Swapper | | `morpho_market_id` | `"0x…"` (bytes32) | Morpho Blue markets | | `raw_bytes32` | `"0x…"` (32 bytes) | anything already encoded | | `odos_substrate` | `{"kind":"token","address"}` / `{"kind":"slippage","slippage_bps"}` | Odos swapper | | `universal_typed_substrate` | `{"kind":"token","address"}`, `{"kind":"target","address"}`, optional one `{"kind":"slippage",…}` | `UniversalTokenSwapperFuse` / `…EthFuse` / `…V2` | | `universal_selector_substrate` | `{"kind":"token","address"}` for every token; `{"kind":"target","target","selector"}` per allowed `(contract, function)` | `UniversalTokenSwapperWithVerificationFuse` family | | `euler_substrate` | `{euler_vault, is_collateral, can_borrow, sub_accounts}` | Euler V2 | | `aave_v4_substrate` | `{spoke, reserve_id, is_collateral, can_borrow}` | Aave V4 | The **Universal Token Swapper encoding is dictated by the fuse family, not the market**. A word in the wrong layout is silently ignored by the fuse and every swap reverts. `deploy/swapper.py` enforces name → encoding at load; `--verify-only` fails on non-canonical words. ### `callback_handlers[]` `{handler, sender, signature}`. Some fuses make an external protocol call the vault **back** in the middle of `execute()`; a Morpho flash loan calls `onMorphoFlashLoan(uint256,bytes)`. The vault only accepts such a call if a handler is registered for that `(sender, selector)` pair; otherwise it reverts `HandlerNotFound()` (`0x4bf4de4e`) on the first flash loan, after every other check has passed. Nothing else in the configuration reveals the gap, which is why the loader enforces it. | Key | Notes | |---|---| | `handler` | name from the context's `callback_handlers` map (`CallbackHandlerMorpho`, …), as in the `ipor-abi` addresses file | | `sender` | the contract that calls back: an address, or a top-level context key such as `morpho_blue` | | `signature` | canonical callback signature, no spaces or parameter names: `onMorphoFlashLoan(uint256,bytes)` | Required pairs live in `deploy/callbacks.py::REQUIRED_CALLBACKS`; today: `MorphoFlashLoanFuse` → `CallbackHandlerMorpho` for `morpho_blue` on `onMorphoFlashLoan(uint256,bytes)`. Step `03b_callback_handlers` writes the entries (`FUSE_MANAGER_ROLE`), skipping any the vault already routes, and `--verify-only` reads them back from vault storage (the contract has no getter). See `strategies/example-wsteth-usdc-loop-base.json`. ### `dependency_graph[]` `{from, to: [...]}`: explicit extra edges only. The deployer **derives** the mandatory edges itself: every balance-fuse market except `ERC20_VAULT_BALANCE` → `ERC20_VAULT_BALANCE`. You cannot opt out of the derived edges. ### `market_limits[]` `{market, limit}`: per-market allocation caps. Optional at launch. ### `price_feeds[]` `{asset, feed_type, feed?, params?}`. The underlying **must** be listed. | `feed_type` | `feed` / `params` | Behaviour | |---|---|---| | `prebuilt` | `feed`: a name from the context's `price_feed_factories` | registers an already-deployed feed | | `literal` | `feed`: an aggregator address (Chainlink-compatible `latestRoundData`) | registers that address directly | | `DualCrossReferencePriceFeedFactory` | `params.feed_a`, `params.feed_b` | deploys `A/B × B/USD` | | `CollateralTokenOnMorphoMarketPriceFeedFactory` | `params.morpho_oracle`, `collateral_token`, `loan_token` | prices a token off a Morpho market oracle | | `ERC4626PriceFeedFactory` | `params` per factory | `convertToAssets × underlying` | Feeds are registered on the **vault's own** price manager, not the shared middleware. If the vault's oracle already prices an asset through the shared middleware, the step is a no-op and shows as `plan_only` in the plan/run diff. Before using a `literal` feed, read `latestRoundData()` on it and check `updatedAt` is recent; stale aggregators exist. ### `withdraw_manager` | Key | Notes | |---|---| | `window_seconds` | scheduled-withdrawal window. **`0` means instant-only**: the deployer skips the call (setting 0 on-chain reverts). | | `request_fee_bps`, `withdraw_fee_bps` | contributions burned on the request path / instant path; `null` if unset | | `fees_managed_by` | informational | For an instant-only vault also add a `PauseFunctionPreHook` on `request(uint256)` `0xd845a4b3` and `requestShares(uint256)` `0xfc415e9c`. ### `fees` | Key | Notes | |---|---| | `tier`, `performance_bps` | informational; the on-chain tier is `vault.dao_fee_package_index` | | `management_supplemental_bps`, `performance_supplemental_bps` | supplemental fees on top of the DAO package, paid to `recipients` | | `recipients[]` | `{address, split_bps}` summing to 10000 | ### `instant_withdrawal.order[]` `{fuse, params: [{encoding, value}, …]}` in queue order. `params[0]` is the amount slot filled at runtime (pass `{"encoding":"uint256","value":0}`), followed by the fuse's remaining params. Minimum counts per family are enforced (`deploy/fuses.py`). Never put a collateralised market on the queue. ### `pre_hooks[]` `{name, selector, params}`; names resolved via the context `pre_hooks` map. | Hook | Selector | Params | |---|---|---| | `ExchangeRateValidatorPreHook` | `deposit` `0x6e553f65` | `{"threshold_bps": 200}` share-price drift guard | | `PauseFunctionPreHook` | `request` `0xd845a4b3`, `requestShares` `0xfc415e9c` | `{}` instant-only vaults | | `UpdateBalancesPreHook` | deposit/withdraw selectors | refresh cached balances before user actions | ### `rewards`, `whitelist`, `transferability` | Key | Notes | |---|---| | `rewards.enabled` | informational today; reward fuses and claim roles are configured through `fuses` and `roles` | | `whitelist.enabled_at_launch` | `true` recommended for the trial | | `whitelist.initial_accounts[]` | accounts granted `WHITELIST_ROLE`; must be empty if disabled | | `transferability.enabled_at_launch` | `true` calls `enableTransferShares()`: **irreversible** | | `transferability.irreversible_ack` | must be `true` when enabling; set only after the human acknowledged | ### `execution` | Key | Notes | |---|---| | `fork_chain_id_allowlist[]` | chain ids other than `chain.id` a `--broadcast` may target (`31337` for a bare anvil). A mismatch outside the list is a **hard fail** on broadcast. | | `gas_multiplier` | default 1.25 | | `state_file` | `.deploy-state/.json` | | `broadcast`, `max_priority_fee_wei`, `tx_confirmations` | informational | ## 4. Chain contexts `chain.context` (e.g. `base-fusion`) selects the vendored ipor-abi snapshot `contexts/ipor-abi/mainnet-.json`, a copy of ipor-abi's `mainnet//addresses.json` pinned to an ipor-abi commit. Every IPOR contract address comes from there; nothing is hand-copied: | Contract | Looked up as | |---|---| | FusionFactory, oracle middleware, FuseWhitelist | `IporFusionFactoryProxy`, `PriceOracleMiddlewareUsdWithRolesProxy`, `IporFusionFuseWhitelistProxy` (same keys on every chain) | | Standard (factory-injected) fuses | `BurnRequestFeeFuse`, `BurnRequestFeeFuseV2`, oldest first, those the chain lists | | Price-feed factories | `Proxy`, e.g. `ERC4626PriceFeedFactory` → `ERC4626PriceFeedFactoryProxy` | | Pre-hooks, callback handlers | the name itself (`ExchangeRateValidatorPreHook`, `CallbackHandlerMorpho`); the legacy names `PauseFunctionPreHook`, `UpdateBalancesPreHook`, `UpdateBalancesIgnoreDustPreHook` map to `PreHook…` keys | | Fuses | the `FuseWhitelist` decides (below); ipor-abi under the same name, or an alias, is the fallback and cross-check | | `morpho_blue`, `middleware_owner` (fork impersonation only) | `Morpho`, `IporFusionPriceOracleMiddlewareWithRolesOwner` | `contexts/.json` is **optional** (schema `schema/deploy-context.schema.json`) and holds only what cannot be derived: | Key | Content | |---|---| | `public_rpc` | read-only public endpoint used by dry-runs when `RPC_URL` is unset | | `pins{}` | a fuse or pre-hook pinned to one address, with `reason`, `checked` (date) and optional `revisit`; only when several versions are active or ipor-abi lacks the contract | | `aliases{}` | a name strategies use → the ipor-abi key, when the name is neither a `FuseWhitelist` type nor an ipor-abi key (e.g. `MorphoFlashLoanBalanceFuse` → `BalanceFuseFlashLoanMorpho`) | | `external_addresses{}` | third-party contracts ipor-abi does not list, with their `source` (e.g. the Aave V3 pool) | | `tokens{}` | token symbol → address, for symbols a strategy uses (e.g. a rehearsal token holder) | | `markets{}` | market ids the SDK's `IporFusionMarkets` does not know (e.g. `HYPERCORE` on HyperEVM) | | `contracts{}`, `standard_fuses[]`, `chain_id`, `ipor_abi_deployment` | rare overrides when a chain differs from the defaults above | How a run resolves addresses, before any step: 1. **Fuses** (`deploy/fuse_resolver.py`): the `FuseWhitelist` gives the active address for the fuse type on its market. A pin must be among the active addresses, or the run fails. Without a pin: one active address wins; several need a pin (the ipor-abi entry is used with a warning when it is one of them); none falls back to ipor-abi with a warning, and the whitelist gate decides. The report names each chosen address's ipor-abi key, or says it is not in ipor-abi. 2. **Everything else** (`deploy/address_book.py`): the chain id must match; roots, the pre-hooks and callback handlers the strategy names, feed factories and external addresses must have code; `factory.getPriceOracleMiddleware()` must equal ipor-abi's middleware; the Aave pool must equal `AaveV3PoolAddressesProvider.getPool()`. Warnings: the newest standard fuse is not active, a token's `symbol()` differs, the snapshot is older than 45 days. ipor-abi entries at the zero address mean "not on this chain". `make check-context CONTEXT=` (or `python tools/check_context.py --all`) prints the full table without a strategy. `make ipor-abi` refreshes the snapshots from ipor-abi `main` and prints every added, removed or changed key; review that diff before committing it, because a changed key moves every vault deployed afterwards to the new contract. Adding a chain = a context file with its `public_rpc`, then `make ipor-abi` (vendors `mainnet-`) and `make check-context CONTEXT=`.# 04 — Deploying: dry-run, fork rehearsal, live broadcast `python -m deploy` turns `strategies/.json` into a configured PlasmaVault. It is generic (no strategy-specific code), idempotent (completed steps are skipped from a state file), resumable, and produces machine-readable artifacts that can be diffed. ## 1. The pipeline Steps run in this order. The **index** is what `--from-step` and `--only-step` take. | Idx | Step | What it does | Signer needs | |---|---|---|---| | 0 | `00_dry_run_report` | prints the plan header | — | | 1 | `01_clone` | `FusionFactory.clone(...)` → vault, access manager, fee/rewards/withdraw/context/price managers. Previews the deterministic addresses via `eth_call` first. | gas | | 2 | `01b_bootstrap_roles` | grants the **deployer** every role listed in `roles.grants` so one signer can run the pipeline | OWNER (from clone) | | 3 | `01c_total_supply_cap` | sets the cap from `total_supply_cap_underlying` using the vault's own `decimals()` | ATOMIST | | 4 | `02_add_fuses` | `addFuses` for every `fuses[].name`, gated on the on-chain `FuseWhitelist` | FUSE_MANAGER | | 5 | `02b_standard_fuses` | upgrades factory-injected standard fuses to the newest in the context | FUSE_MANAGER | | 6 | `03_grant_substrates` | `grantMarketSubstrates` per market with the declared encoding | FUSE_MANAGER / ATOMIST | | 7 | `03b_callback_handlers` | `updateCallbackHandler` for every `callback_handlers[]` entry (flash-loan callbacks); skips entries the vault already routes, read from its storage | FUSE_MANAGER | | 8 | `04_balance_fuses` | `addBalanceFuse` per market and `updateDependencyBalanceGraphs` with the **derived** graph | FUSE_MANAGER | | 9 | `05_price_feeds` | deploys factory feeds, registers sources on the **vault's own** price manager, skips assets it already prices; refuses to finish while any asset is unpriceable | PRICE_ORACLE_MIDDLEWARE_MANAGER on the vault's manager | | 10 | `06_withdraw_manager` | `updateWithdrawWindow`; skipped when `window_seconds == 0` | ATOMIST | | 11 | `07_fees` | supplemental fees and recipients | fee roles | | 12 | `08_instant_withdrawal` | `configureInstantWithdrawalFuses` in queue order | CONFIG_INSTANT_WITHDRAWAL_FUSES | | 13 | `09_pre_hooks` | `setPreHookImplementations` | PRE_HOOKS_MANAGER | | 14 | `10_whitelist` | grants `WHITELIST_ROLE` to `whitelist.initial_accounts` | ATOMIST | | 15 | `11_roles` | grants every `roles.grants[]` account | role admins | | 16 | `12_transferability` | `enableTransferShares()` if enabled at launch: irreversible | ATOMIST | After the last step a broadcast runs the verification report (§6) and prints the deployed addresses. With `--rehearse` on a local fork it then runs the rehearsal stage (§4b): the vault is used, not only read. ## 2. Command reference ``` python -m deploy [flags] ``` | Flag | Effect | |---|---| | *(none)* | **Dry-run.** Loads and validates, previews the clone, prints every intended call with decoded args, writes `.deploy-state/.plan.json`. No signer, no state written, nothing sent. | | `--broadcast` | Sends transactions. Requires `RPC_URL` and `DEPLOYER_PRIVATE_KEY`. Writes `.deploy-state/.json` after every step and `.run.json` at the end. | | `--i-understand-this-is-live` | Required when the node is **not** a local fork (anvil, hardhat). Applies to every live chain, not only Ethereum. | | `--verify-only` | Reads the chain against the JSON using the recorded `fusion_instance`. Needs a state file. Re-runnable at any time. | | `--rehearse` | After a `--broadcast` on a **local fork only**: funds the deployer from `rehearsal.token_holder`, deposits, executes the rehearsal script's batches, checks the accounting after each, withdraws through the configured path. Exits non-zero if any check fails. | | `--rehearse-only` | Runs only that stage against the vault in the recorded state (local fork, `RPC_URL` and the fork key). | | `--from-step N` | Resume from index N. | | `--only-step N` | Run a single step (for example re-run `05_price_feeds`). | | `--force-restart` | Discard the state file for this strategy. Use before the **first** live broadcast if a rehearsal wrote state; **never** after an interrupted live run. | Safety checks at session open: - `RPC_URL` must be set for any `--broadcast`; a dry-run may fall back to the context's `public_rpc`. - The RPC's chain id must equal `chain.id` or be listed in `execution.fork_chain_id_allowlist`; on `--broadcast` a mismatch is a **hard fail**, on a dry-run a warning. - On a live node, any anvil default account among the signer, role holders, whitelist or fee recipients is a **hard fail**. - A public RPC host with `--broadcast` prints a warning: use a private endpoint and run detached. - Resume check: before a run resumes from a state file, the recorded clone transaction must be mined with success on the connected chain and the vault and its access manager must have code. Otherwise the run stops before sending anything; `--force-restart` if the state belongs to an old fork or a failed attempt, or fix `RPC_URL` if the vault should exist. A clone that was never mined (a wallet cancel, a failed send) leaves no addresses behind. - No send without a contract: every transaction the pipeline sends, with any signer, is refused when its target has no code on the connected chain. - Config drift: if the state file's `config_hash` differs from the current JSON, the run aborts. Reconcile the JSON with what was deployed, or `--force-restart` if nothing real was broadcast. ## 3. Dry-run (simulation) ```bash python -m deploy strategies/.json ``` Needs no key and no `.env`. Read the output end to end. Every step prints what it would call: fuse addresses, substrate words, feed params, role grants, window, fees, queue. Anything that looks off is a JSON or context error; fix and re-run. Output: `.deploy-state/.plan.json`, a `manifest` (chain, RPC source, signer, gas multiplier, `config_hash`, SDK version) and an ordered `actions[]` list with `(step, action, key, args)`. Limits of a dry-run: it reads the live chain for whitelist status and address previews, but the vault does not exist, so nothing can be verified on-chain and factory-minted feed addresses are unknown. That is what the fork rehearsal is for. ## 4. Fork rehearsal — mandatory before any live broadcast ```bash # terminal A — fresh fork of the target chain lsof -ti :8546 | xargs -r kill anvil --fork-url --port 8546 # terminal B — broadcast against the fork with anvil's default account #0, then use the vault RPC_URL=http://127.0.0.1:8546 \ DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ python -m deploy strategies/.json --broadcast --rehearse ``` Expected outcome: - all 17 steps execute; - the verification report is green: underlying matches, fuses match (declared + standard factory-injected), window matches or "instant-only", all role grants present, **all assets price via the vault's own oracle**, **all dependency-graph edges present**, queue params satisfy every fuse family, substrate sets match and are canonical, cap correct; - the rehearsal report is green (§4b): deposit credited 1:1, every declared fuse executed, NAV consistent after each batch, withdrawal paid; - `.deploy-state/.json` holds the `fusion_instance` and every tx hash; `.run.json` holds the actions with gas; `.rehearsal.json` holds the observations. Then diff the plan against the run: ```bash python tools/plan_diff.py .deploy-state/.plan.json .deploy-state/.run.json ``` Exit 0 means the broadcast did nothing the plan did not declare. `plan_only` entries are usually benign: an already-priceable asset skipped, or the standard-fuse upgrade found nothing to do because the factory already injects the latest version. Any `run_only` entry is a hard fail; investigate before going further. A `config_hash` mismatch between the two artifacts means the JSON changed between dry-run and rehearsal; redo the dry-run. Notes: - If a step fails, fix the JSON or the context and re-run the same command. Completed steps are skipped from state. - When the rehearsal is clean, **delete `.deploy-state/.json`** (it holds fork addresses) or pass `--force-restart` on the live run. - Sum `gas_used` in `.run.json` to estimate what to fund the live deployer with. The shipped example used about 10.4M gas on a Base fork (the clone alone is about 8.9M); Ethereum gas prices make the same deployment far more expensive. ## 4a. Rehearsing as the production deployer, and preparing the fork Two options make a fork rehearsal reproduce the live run instead of a stand-in: - `--signer impersonate` (fork only) makes anvil sign for `DEPLOYER_ADDRESS`, so the clone owner, every role grant, the whitelist entries and the factory's per-client fee package are exactly the ones the live run will use. No key is needed. The pipeline refuses this mode against a live node. - A rehearsal script may define `prepare(env) -> list[str]`. It runs once, before the deposit, only on the fork, for third-party state the strategy depends on but does not own: a market parameter the venue's governor has not set yet, a pool that does not exist, liquidity to borrow, a fee package the DAO still has to approve. Every returned line is printed as a warning and stored under `observations.prepare`, so nobody mistakes it for chain state. The env offers `impersonated_call(sender, to, signature, arg_types, args)`, `impersonated_send(sender, to, data)`, `deploy_contract(sender, bytecode, constructor_args)` and `advance_time(seconds)`; impersonated calls estimate gas first. ```bash RPC_URL=http://127.0.0.1:8546 DEPLOYER_ADDRESS=0xYourDeployer \ python -m deploy strategies/.json --broadcast --rehearse --signer impersonate ``` A rehearsal that needed `prepare` is only as good as the prepared assumptions; list them in the strategy's `.md` (§13) next to what still has to happen on the live chain. ## 4b. The rehearsal stage — what "verified" means Reading configuration back proves the vault is configured. It does not prove the vault works: a granted substrate the fuse cannot act on, a market counted twice, or a withdrawal path that cannot pay all pass every read-back check. The rehearsal stage (`deploy/rehearsal.py`, rules in `deploy/rehearsal_rules.py`) uses the vault on the fork: | Step | What happens | Fails when | |---|---|---| | Roles | the deployer receives `ALPHA`, `WHITELIST` and balance-updater roles for the rehearsal (fork only) | a grant reverts | | Fund | `rehearsal.token_holder` is impersonated and sends `rehearsal.deposit_underlying` to the deployer | the holder is short | | Deposit | `approve` + `deposit` | `totalAssets` does not rise by exactly the deposit (share rounding aside), or no shares are minted | | Coverage | the script's batches are collected | a declared fuse is exercised by no batch (unless listed in `rehearsal.allow_unexercised`) | | Execute | each batch is sent; then `updateMarketsBalances` on every market | NAV moves more than `rehearsal.max_nav_drift_bps` (up = a market counted twice, down = value not tracked or overpaid); cached NAV ≠ refreshed NAV (dependency graph incomplete); any market worth more than the vault | | Unwind | the script's `unwind` batches, for leveraged vaults | same checks | | Withdraw | instant: `withdraw(min(half the deposit, maxWithdraw))`; scheduled: `requestShares` → +1h → `releaseFunds(timestamp, shares)` by the Alpha → `redeemFromRequest` | the payout is short, or NAV does not drop by the payout | The batches come from `rehearsal.script`, a Python file built on the SDK fuse wrappers (`rehearsals/.py`; copy the nearest SDK walk from `ipor-fusion.py/tests/test_simulate_*.py`). The stage never runs against a live chain: it impersonates accounts and moves time. Re-running the stage on a used fork vault accumulates deposits; for a clean claim, `make clean-state`, restart anvil, and broadcast again. ## 5. Live broadcast Pre-flight, every item: - [ ] Fork rehearsal clean and `plan_diff` exit 0. - [ ] `vault.initial_owner_override` is the real appointed owner; `vault.deployer_temporary_owner` is an explicit decision. - [ ] Every `roles.grants[].account`, `whitelist.initial_accounts[]` and `fees.recipients[].address` is a production address. (The pipeline checks for anvil defaults; it cannot check for typos.) - [ ] `whitelist.initial_accounts` contains the first depositor. - [ ] `RPC_URL` in `.env` is a **private** endpoint for the target chain. - [ ] `DEPLOYER_PRIVATE_KEY` is in `.env` and the account is funded on the target chain. - [ ] The state file for this strategy is gone or `--force-restart` is passed. - [ ] `signoff.roles_reviewed`, `signoff.hardening_ack` and `signoff.frontend_listing_ack` are `true`, each set after the human's own words (`docs/05-human-in-the-loop.md` §4). - [ ] **The human has said "yes" to this exact file in this session.** Broadcast, detached, with logs: ```bash mkdir -p .deploy-state nohup python -m deploy strategies/.json --broadcast --i-understand-this-is-live --force-restart \ > .deploy-state/.broadcast.log 2>&1 & tail -f .deploy-state/.broadcast.log ``` Post-broadcast: 1. Read the verification report at the end of the log. 2. `python -m deploy strategies/.json --verify-only`; repeat any time. 3. `python tools/plan_diff.py .deploy-state/.plan.json .deploy-state/.run.json`. 4. Copy the deployed addresses (vault, access manager, withdraw manager, fee manager, price manager) into the `.md` spec and commit the `.md` and `.json`. 5. Make the first deposit from a whitelisted account and confirm `totalAssets()` moves. 6. Hand the vault to the Alpha. Operating it (executing fuse actions, simulating a step before sending it) is the SDK's job, not this repository's; the pointers are in `docs/07-resources.md` §"After deployment". ## 5b. Signing with a browser wallet `--signer browser` replaces the private key with a wallet in your browser (Rabby, MetaMask, any EIP-6963 wallet): ```bash DEPLOYER_ADDRESS=0xYourDeployer python -m deploy strategies/.json --broadcast --i-understand-this-is-live --signer browser # or: make sign STRATEGY=strategies/.json RPC_URL= DEPLOYER_ADDRESS=0xYourDeployer ``` 1. Before the first transaction the pipeline loads the dry-run plan for this exact strategy file (`.deploy-state/.plan.json`); when it is missing or stale it runs the dry-run first. The page therefore lists **every signature of the run, per step**, before you sign anything. 2. It starts a local page on `http://127.0.0.1:8789` (`--signer-port` changes it) and waits until a wallet connects with the right chain and, when `DEPLOYER_ADDRESS` is set, the right account. After connecting, the page shows a pre-flight: the deployer's gas balance and whether the factory holds a custom DAO fee package for this deployer. The package is fixed when the vault is cloned, so a missing custom package has to be fixed before the first signature. Before any wallet can connect, the page asks you to accept a short disclaimer: the page is provided as is, with no guarantee that it is fully reliable, and your wallet is the source of truth for what you sign. The pipeline refuses a wallet connection without it and logs the time of acceptance. 3. The top of the page is a map of the vaults being deployed (one card for a single vault; the whole set and its connections with `deploy.campaign`, §5c). The step list on the right shows the stage being signed: done steps are filled, the step marked **You are here** is waiting for you, the rest are ahead. Click any finished step or vault to review its transactions (hashes, gas, arguments); a *Back to "You are here"* button and a banner bring you back. 4. The page moves on by itself: after you connect and after every signature it opens the next transaction as soon as the pipeline has prepared it, so each signature is one click on **Sign**. A status bar under the header says what is happening (preparing the transaction, waiting for the wallet, waiting for the block, verifying) with an elapsed-time counter, so a slow RPC never looks like a frozen page. 5. Every step builds its transaction as with a key (gas estimate, revert check), then hands the unsigned transaction to the page: contract (named: FusionFactory, PlasmaVault, AccessManager …), function, arguments, gas limit and raw calldata. Confirm it in the wallet; nonce and fees are set by the wallet. The page never offers the same transaction twice. After a page reload, the signature card offers to reconnect the wallet in place. 6. The page returns the hash; the pipeline waits for the receipt, records state and moves on. Rejecting in the wallet or pressing *Reject* on the page sends nothing and does not stop the run: the transaction keeps waiting and *Sign* asks the wallet again. Stop the run with Ctrl+C in the terminal; re-running the same command resumes, and steps finished by an earlier run appear as *signed earlier*. 7. After the last step the page shows the verification report and the deployed contracts, and keeps serving for `--signer-linger` minutes (default 30) so the record stays browsable. *Close signing session* stops it. **Roles.** The base setup (step `01b`) gives the deployer ATOMIST and FUSE_MANAGER, plus a role only when a configured step needs it: PRICE_ORACLE_MIDDLEWARE_MANAGER for price feeds, CONFIG_INSTANT_WITHDRAWAL_FUSES for an instant-withdrawal order, PRE_HOOKS_MANAGER for pre-hooks (`deploy/role_plan.py`). In the final roles step (`11`), OWNER, ATOMIST and FUSE_MANAGER grants are required; every other grant is optional and has a **Skip** button on the page (mark a grant `"required": true` in `roles.grants` to make it mandatory). Skipped grants are recorded in the state file and reported by the verification as missing, not failed. The alpha roles (ALPHA, UPDATE_MARKETS_BALANCES, UPDATE_REWARDS_BALANCE, CLAIM_REWARDS, TRANSFER_REWARDS) are not granted by the base run at all; once the alpha exists, grant the ones listed in `roles.grants` with `python -m deploy --broadcast --signer browser --assign-alpha` (`make assign-alpha`), which runs only that grant step on the deployed vault. **On a fork.** The page works against an anvil fork too, which is the way to rehearse the signing itself. Point the wallet's network at the fork first (Rabby: More → Modify RPC URL; MetaMask: Networks → Edit → add the RPC URL); the page shows the fork's URL and reminds you. A fork has the live chain's id, so the chain id cannot show where the wallet sends. The pipeline therefore funds a random marker account on the fork only, and the page reads its balance through the wallet's RPC when you connect and again before every signature: a wallet still on the live RPC is refused and nothing reaches it. Fund the deployer on the fork with `cast rpc anvil_setBalance 0x56BC75E2D63100000 --rpc-url `. The rehearsal stage (`--rehearse`) still runs with `--signer impersonate` or the fork key: it impersonates accounts and moves time, which a wallet cannot do. The page is `tools/browser_signer/index.html`: plain HTML, no build step, Fusion app design tokens, wallet discovery through EIP-6963 (falls back to `window.ethereum`). It binds to 127.0.0.1 only, refuses requests addressed to any other host name, and every POST carries a per-run token embedded in the page, so another site open in the browser cannot answer for you. Nothing leaves the machine except the transactions you confirm. `DEPLOYER_PRIVATE_KEY` is ignored in this mode. ## 5c. Deploying a set of connected vaults A strategy is sometimes several vaults: an index vault that holds shares of per-asset vaults, which in turn hold other vaults. `deploy.campaign` deploys the whole set in one run from a small campaign file: ```json { "name": "my-vault-set", "vaults": [ {"id": "leaf", "strategy": "leaf.json"}, {"id": "parent", "strategy": "parent.json"}, {"id": "remote", "strategy": "remote.json", "blocked": "why it cannot be deployed yet"} ], "links": [ {"parent": "parent", "child": "leaf", "kind": "erc4626", "placeholder": "0x"}, {"parent": "parent", "child": "remote", "kind": "cross-chain", "blocked": "no cross-chain fuse yet"} ] } ``` ```bash python -m deploy.campaign my-vault-set.campaign.json # dry-run every vault DEPLOYER_ADDRESS=0x… python -m deploy.campaign my-vault-set.campaign.json --broadcast --signer browser # or: make sign-set CAMPAIGN=my-vault-set.campaign.json RPC_URL=… DEPLOYER_ADDRESS=0x… ``` - **Order.** Children deploy before their parents; the page numbers the vaults in that order. - **Stand-ins.** A parent's strategy is rehearsed alone against a stand-in ERC4626 (any vault with the same asset). Before the parent deploys, every occurrence of the link's `placeholder` in its strategy (substrates, price feeds, instant-withdrawal queue) is replaced by the child's deployed vault address; the resolved file is written to `.deploy-state//.resolved.json`. - **Connections.** Right after a parent deploys, each `erc4626` link is one signature on the child: its access manager grants the parent `WHITELIST_ROLE`, so the parent may deposit. Already granted means nothing is sent. `cross-chain` links are drawn only. - **Blocked.** A vault marked `blocked`, or a parent that holds a blocked child through an `erc4626` link, is skipped and shown as blocked with its reason. A failed stage stops the run; everything after it is marked blocked. Re-running resumes: finished steps are skipped. - **Chains.** `RPC_URL_` overrides `RPC_URL` per chain for a set that spans chains. - **Page.** The top of the page becomes a map of the vaults and connections (status, order, signatures, a "You are here" marker). The right-hand list shows the steps of the stage being signed; click a finished vault or connection on the map to review it. Keep campaign files next to the strategies they reference (outside this repository for real clients, like the strategies themselves). ## 6. What the verification report checks `deploy/verification.py`, run at the end of every broadcast and by `--verify-only`: | Check | Fail means | |---|---| | underlying matches | wrong vault | | fuses: declared ⊆ on-chain; standard factory fuses recognised; anything else warned | a declared fuse missing, or an unexpected fuse present | | withdraw window | mismatch (instant-only = expected factory default) | | every `roles.grants` present | a grant failed or RPC lag | | **every `price_feeds` asset returns a price from the vault's own oracle** | **the vault is broken** (deposits, withdrawals and accounting revert) | | **every derived dependency-graph edge present on-chain** | accounting does not cascade to the idle balance | | **every `callback_handlers[]` entry registered, read from vault storage** | the first flash loan reverts `HandlerNotFound()` | | instant-queue params long enough for each fuse family | instant withdrawals through that entry revert | | substrate sets: expected ⊆ on-chain, every word canonical for typed encodings | the fuse ignores the word; the venue is dead | | total supply cap in underlying terms | cap off by the decimals offset | The three bold rows are the **critical hazards**. They are read from the vault's own contracts, never from the shared middleware or the JSON. The rehearsal stage (§4b) adds the behavioural checks: deposit credited, every fuse executed, no double accounting, withdrawal paid. ## 7. Recovery and re-runs | Situation | Do | |---|---| | A step failed on the fork | fix, re-run the same command; state skips completed steps | | A step failed on a live chain | read `.deploy-state/.json`, confirm every recorded tx receipt on-chain, fix the cause, re-run `--broadcast --i-understand-this-is-live` **without** `--force-restart` | | The live run was interrupted (timeout, network) | same as above; never restart from scratch, the vault is already cloned | | Someone changed configuration by hand on-chain | update the JSON to match, then reconcile the state file's `config_hash` (`deploy/state.py::hash_config`) instead of discarding state | | Need to redo one step | `--only-step N --broadcast …` | | Wrong chain by accident | the chain-id guard stops it; fix `RPC_URL` |# 10 — Hardening a vault for production A vault that passed the fork rehearsal is **verified**, not **production-ready**. During the trial one operator key holds every role, the whitelist is closed to a handful of accounts, and nothing is timelocked. Hardening is the set of changes that turn that trial posture into one a stranger's money can sit behind. This page says what those changes are, which ones this repository performs for the agent today, and which are manual. The human acknowledges having read this page by setting `signoff.hardening_ack: true` in the strategy file. The pipeline refuses a live broadcast without it. ## 1. What "hardened" means | Area | Trial posture (what the pipeline deploys) | Production posture | |---|---|---| | Owner | the deployer, or one appointed EOA | a multisig; the deployer's `OWNER_ROLE` renounced | | Guardian | a second EOA | an independent multisig or the security team's key; can pause | | Atomist, Fuse manager, Pre-hooks manager, oracle manager | the operator EOA | a multisig, with an execution delay on the sensitive functions | | Alpha | the bot's EOA | the bot's EOA only; nothing else | | Whitelist | closed, a few accounts | closed with a managed list, or opened deliberately (one-way) | | Transferability | off | off, or on with the acknowledgement recorded (one-way) | | Execution delays | 0 | non-zero on role grants and configuration changes (`roles.execution_delay_seconds`, per-grant `execution_delay_seconds`) | | Fees | recipients = operator | recipients = treasury multisig | | Instant-withdrawal queue | as rehearsed | reviewed against the production market limits | | Market limits | none | `market_limits[]` sized to the mandate | ## 2. What this repository does for the agent | Change | How | Status | |---|---|---| | Grant production roles to multisigs | edit `roles.grants[]`, re-run `--only-step 15 --broadcast …` (`11_roles`) | works today | | Add execution delays | `roles.execution_delay_seconds` and per-grant `execution_delay_seconds`; `11_roles` applies them | works today | | Move fee recipients | `fees.recipients[]`, re-run `07_fees` | works today | | Set market limits | `market_limits[]` | works today | | Whitelist changes | `whitelist.initial_accounts[]`, re-run `10_whitelist` (add only) | works today; removal is manual | | Verify the result | `--verify-only` reads every role holder and delay back from the access manager | works today | | **Renounce the deployer's roles** | not a pipeline step yet; `OWNER_ROLE` renounce is the last hardening action and must come after the multisig confirmed it holds OWNER | manual (`app.ipor.io` or `cast send` on the access manager) | | Revoke a role, remove a whitelisted account, open the whitelist, enable transfers | one-way or destructive; deliberately outside the pipeline | manual, owner-signed | Ownership model matters here. With `vault.deployer_temporary_owner: true` (Mode A) the deployer keeps a temporary `OWNER_ROLE` next to the appointed owner and can run the whole hardening list autonomously before renouncing. With Mode B every change is signed by the appointed owner. ## 3. The hardening walk 1. Rehearse the hardened configuration on a fork first: copy the strategy, replace every EOA with the production address, set the delays, and run `--broadcast --rehearse` against anvil. The rehearsal's role step and `--verify-only` prove the grants; the deposit and withdrawal prove the whitelist still admits the intended depositors. 2. Apply the same edits to the live strategy file and re-run the affected steps with `--only-step` on the live chain, after the human's "yes" for each run. 3. Confirm every role holder with `--verify-only`, then hand the human the exact renounce transaction for the deployer's roles. The human, or the multisig, sends it. 4. Record the final role table in the `.md` spec and set `meta.status` to `SHIPPED`. ## 4. Before the vault is shown to anyone Listing a vault on the IPOR front end (`app.ipor.io`) is not something this repository or the agent can do. It requires contacting the IPOR Labs team through the channels on [docs.ipor.io](https://docs.ipor.io); the team reviews the configuration and the fuse whitelist status before listing. The human acknowledges this with `signoff.frontend_listing_ack: true`. Until then the vault is reachable only by its address, through the SDK, a block explorer, or a custom front end.# Vault configuration step-by-step > The full vault setup guide: fuses, substrates, price oracle, pre-hooks, scheduled withdrawals, instant withdrawal order and fees. Below best practices and vault setup algorithm is a guide you can use to configure your own Fusion Vault. All settings described below can be accomplished by using the [**Build on Fusion web application**](https://app.ipor.io/build-on-fusion)**.** There is also a [Quick Start Guide](https://ipor.io/docs/build-on-fusion/developer-guide/quick-start-guide.md) and a [Video Documentation](https://www.youtube.com/playlist?list=PLTda9D8MCj4QafXFkFPR_i27CakEUdBeg) available. ## Access Management See [here](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md). ## Strategy Strategy Configuration: Defining the vault’s strategy is the primary responsibility of Atomists and Fuse Managers. The following instructions provide a step-by-step guide to configuring the most critical aspects of the strategy settings. ![Image](https://ipor.io/docs/assets/build-on-fusion/screenshot-2025-06-05-at-2-40-13-pm-830af1.png) __ **Setting up Fuses:** Configuring fuses is the most critical process in defining the vault’s strategy and must be executed carefully. A list of available fuses for supported blockchains is available in the [whitelist](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fuse.md) contract or the IPOR Labs repository [(https://github.com/IPOR-Labs/ipor-fusion)](https://github.com/IPOR-Labs/ipor-fusion). Below is a step-by-step guide to configuring the key aspects of fuse settings. ### 1. Balance Fuses Fuses are paired as "Balance Fuses" and "Functional Fuses." Balance Fuses track the vault’s asset balances after transactions, while Functional Fuses perform actions. Each fuse is identified by a unique marketId, assigned during deployment. All Functional Fuses must be paired with a Balance Fuse. If a Balance Fuse reads no balance, use the ZeroBalanceFuse [(https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/ZeroBalanceFuse.sol)](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/ZeroBalanceFuse.sol). - ERC20 Balance Fuse: Tracks ERC20 token balances held directly by the vault. Include this fuse in most vaults to monitor ERC20 assets. - Standard Balance Fuses: Market-specific fuses, including: - ERC4626 Balance Fuses: Configurable in two ways: - Separate ERC4626 per Market: Tracks each market’s balance individually for easier monitoring. - Single ERC4626 for All Markets: Simplifies integration and reduces gas costs but tracks balances as a single sum.\ Best Practices: - Always pair Functional Fuses with a corresponding Balance Fuse or ZeroBalanceFuse. - Use separate ERC4626 fuses for complex strategies requiring granular balance tracking. ### 2. Functional Fuses Each market integration requires at least one Balance Fuse and typically one or more Functional Fuses. Some markets support multiple Functional Fuses for additional functionality (e.g., Morpho supports Deposit, Borrow, and Flash Loan fuses, plus an ERC4626 fuse for MetaMorpho deposits). Non-standard fuses include: - Universal Swapper Fuse: A flexible interface for complex transactions (e.g., aggregators, staking, DEX interactions). It validates: - Whitelisted assets (only substrate-defined assets). - Whitelisted markets (e.g., DEXes, aggregator proxy contracts, staking contracts). - Beneficiaries (default: the vault). - Allowed method signatures. - ERC4626 Fuse: Enables deposits to any ERC4626 vault, including IPOR Fusion vaults. - Custom Fuses: Built for unique scenarios (e.g., airdrops). Non-whitelisted custom fuses trigger alerts in the IPOR Fusion interface. - Rewards Fuses: Used by the rewards manager to harvest token incentives.\ Best Practices: - Whitelist all assets and markets for Universal Swapper transactions to ensure safety. - Add custom fuses to the [whitelist](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fuse.md) to avoid user alerts. ### 3. Fuse Configuration - Substrates: Configure substrates (e.g., asset or market addresses) based on fuse requirements. Consult individual fuse documentation if managing substrates manually (outside the IPOR Fusion interface). Common substrates include asset addresses (e.g., for DEXes, Universal Swapper) or market addresses (e.g., for Morpho, ERC4626). - Price Feeds: All Balance Fuses, except ZeroBalanceFuse, rely on the Price Oracle and its feeds. Ensure matching price feeds are configured (see "Price Oracle" section below). Price feeds can be set via the IPOR Fusion interface. - Dependency Graph: Fusion caches asset balances for gas efficiency, updated when Alpha adjusts allocations or balances are manually refreshed. For some markets (e.g., DEXes), update related balances (e.g., ERC20 after a swap). Configure the dependency graph by selecting a market and defining additional balances to update.\ Best Practices: - Verify substrate compatibility with each fuse to avoid misconfiguration. - Configure the dependency graph for markets requiring multi-balance updates (e.g., DEX swaps). ### 4. Price Oracle Vaults include a standard Price Oracle middleware provided by Fusion, with verified price feeds from reputable sources (e.g., Chainlink). Additional price feeds or custom middleware can be added as needed.\ Best Practices: - Ensure price feeds match the assets and markets used by Balance Fuses. - Use verified price feeds to maintain reliability and security. ### 5. Pre-Hooks See the dedicated pre-hooks documentation for configuration details [here](https://ipor.io/docs/build-on-fusion/developer-guide/configuring-pre-hooks.md). ### 6. Scheduled Withdrawals Fusion vaults support two withdrawal types: Scheduled and Instant (both can be used at the same time). To disable scheduled withdrawals, set the withdrawal window to 0. Otherwise, define the withdrawal window (in seconds) before requests expire. Alpha should prepare assets well before expiration to allow LPs time to claim.\ Best Practices: - Set a withdrawal window that balances LP convenience and vault stability. - Ensure Alpha prepares assets early to avoid expiration issues. ### 7. Instant Withdrawal Order Unallocated assets and specific fuses configured for instant withdrawals are available immediately. Define the order of markets for instant withdrawals via the IPOR Fusion interface. Important: Avoid including markets where withdrawals could cause liquidations (e.g., if assets are used as collateral for borrowing).\ Best Practices: - Exclude collateralized markets from instant withdrawals to prevent liquidations. - Use timelocks for changes to instant withdrawal configurations. ### 8. Fees There are two types of fees: - Performance fee - charged as a percentage of the increase of the exchange rate between vault's underlying asset and the vault token. A [high-water mark method](https://www.investopedia.com/terms/h/highwatermark.asp) is used. - Management fee - charged on the vault's annualized TVL. The fees can also be split and sent to several different addresses. By default, each vault charges a fixed management fee and performance fee that goes to the Fusion DAO. The Atomist can choose between 3 fee tiers during the vault creation process: | Tier | Management Fee | Performance Fee | | ---- | -------------- | --------------- | | A | 0.05% | 10% | | B | 0.3% | 2% | | C | 0.5% | 0% | > **Note:** The fee tier cannot be changed after the vault has been created. Atomists can define additional fees by specifying addresses and rates, ensuring total fees do not exceed 5% (management) or 50% (performance). Fees are displayed in the vault overview for LPs. Best Practices: - Use a multisig wallet for fee management to enhance security. - Clearly communicate fee structures to LPs to maintain transparency. ### 9. On- and Offboarding Contributions [What are On- and Offboarding Contributions?](https://ipor.io/docs/fusion-for-depositors/user-guide/vault-fees/onboarding-and-offboarding-contributions.md) [Decision aid for Atomists](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/on-and-offboarding-contributions.md)# Access Management > How OpenZeppelin Access Manager governs vault roles, and how to grant, revoke and time-lock them. ## Overview IPOR Fusion utilizes OpenZeppelin's AccessManager to restrict function access through a role-based system. The roles below are ordered from highest to lowest authority. (Developer note: The list of roles is available in the GitHub repository: ) ![Image](https://ipor.io/docs/assets/build-on-fusion/screenshot-2025-06-05-at-2-37-40-pm-a6bee3.png) __ ![Image](https://ipor.io/docs/content-images/08856daef7ac.jpg) __ ## Grant Delays (The Waiting Room) When [timelocks](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/timelocks-and-execution-delays.md) are enabled, adding a new account to an important role follows a strict procedure to prevent the sudden injection of foreign keys into governance. When a new account is granted an important role, its membership does not activate immediately — it waits out that role's grant delay first (recommended: 14 days for `OWNER`, 3 days for the configuration tier). This closes the "inject a key into governance" path: even a grant that completes successfully produces an account that cannot act yet. The rule itself is protected too. Changing a grant delay via `setGrantDelay` arms only after a hardcoded 5-day minimum setback (`minSetback`) — and reducing one takes the larger of 5 days and the size of the reduction. Nobody, including a legitimate administrator, can shorten the waiting room and walk a new key straight in. Note that grant delays are `ADMIN`-gated: they must be configured before `ADMIN` is renounced, or they become permanently unavailable. _Developer Note:_ Re-granting an existing member — for example raising their execution delay — is not subject to the grant delay. This is deliberate: it keeps hardening instantaneous. ## Admin Role An account with this role has the rights to manage the IporFusionAccessManager in general. It is the highest role, capable of managing all roles including `ADMIN_ROLE` and `OWNER_ROLE`. This is a technical role used during the bootstrapping phase of a vault. In a fully secured, timelocked vault deployment, the `ADMIN` role is ultimately **renounced**. _Note:_ When a vault is created in supervised mode, the `ADMIN` role must be **manually renounced** (after making the `OWNER` self-administered) to remove the "skeleton key" that could override all security mechanisms instantly. ## Owner Role The highest administrative role, defined during the vault's bootstrapping process. The account with this role has rights to manage Owners, Guardians, Atomists, and configure execution delays (timelocks). - Self-managed (if `getRoleAdmin(OWNER) == OWNER`, the Owner can change itself to another Owner) - Single user (recommended use of multisig for this role) **Best practices** - **14-Day Timelock:** The Owner role should be configured with a 14-day execution delay to ensure all top-level administrative changes are publicly visible well in advance. - **Strict Key Separation:** Use a highly secure, dedicated multisig wallet. The accounts holding the Owner role must be strictly separated from accounts holding operational roles (like Atomist or Alpha) and must be completely distinct from Guardian keys. - **Cold Wallet Usage:** Treat the Owner multisig effectively as a cold wallet used only for structural vault changes. ## Atomist Role This role serves as the primary managing accounts for the vault, overseeing most day-to-day settings, such as configuring Fuse Managers, Alphas, fees, and other parameters. - Configurable by the Owner - Allows multiple users #### Best practices - Use multiple Atomists to ensure redundancy. - Apply timelocks to selected Atomist actions whenever feasible. ## Alpha The account with this role has rights to execute the strategy on the vault using execute method, typically as an automated strategy manager. - Configurable by the Atomist - Allows multiple users Depending on the level of automation, the following roles can be assigned to the same wallet managed by Alpha in the IPOR Fusion system: ### CONFIG_INSTANT_WITHDRAWAL_FUSES_ROLE (900) This role configures fuses, such as credit market deposit fuses, to enable instant withdrawals, allowing liquidity providers (LPs) to withdraw assets from the vault immediately. The role holder defines the order of configured markets to ensure asset availability for LPs. A key requirement is that the market (a collection of fuses linked to an external platform) must be fully liquid from the vault’s perspective. If liquidity is used as collateral for borrowing, instant withdrawals could trigger liquidation, so such markets should not be used. For complex vault strategies involving adding or removing credit positions, assign this role to Alpha for efficiency. Alternatively, a dedicated address or Atomist can manage it. #### Best Practices - Ensure markets selected for instant withdrawals are highly liquid to avoid liquidation risks. - Use timelocks for changes to fuse configurations to allow review by Guardians or LPs. ### WITHDRAW_MANAGER_REQUEST_FEE_ROLE (901) This role sets the request fee, charged when an LP submits a withdrawal request from a scheduled withdrawal vault. The fee is applied at the time of the request, not redemption. For strategies with algorithmically adjusting fees, assign this role to Alpha. Alternatively, Atomists can manage it based on the vault’s strategy. #### Best Practices - Use a multisig wallet for managing this role to enhance security. - Monitor fee adjustments to align with the vault’s strategy and LP expectations. ### WITHDRAW_MANAGER_WITHDRAW_FEE_ROLE (902) This role sets the withdrawal fee, analogous to the request fee but charged during instant withdrawals. #### Best Practices - Align withdrawal fees with the vault’s operational strategy. - Use a multisig wallet for secure management. ### UPDATE_MARKETS_BALANCES_ROLE (1000) This role manually updates cached market balances for deposits or withdrawals if automation (e.g., via pre-hooks) is not enabled. This is necessary for less active vaults to ensure accurate balance reporting. For efficiency, assign this role to an automated service. #### Best Practices - Automate balance updates with pre-hooks when possible to reduce manual intervention. - Grant this role to a secure, automated service to ensure timely updates. ### UPDATE_REWARDS_BALANCE_ROLE (1100) Similar to the previous role, this role updates the balances of incentives claimable by the vault through the rewards manager. For automated workflows, assign this role to Alpha. #### Best Practices - Integrate with automated reward tracking systems to streamline updates. - Use a secure wallet or service for this role to prevent unauthorized access. ### CLAIM_REWARDS_ROLE (600) This role allows claiming rewards on behalf of the vault. For practical reasons, assign this role to Alpha to automate reward collection. #### Best Practices - Use a secure, automated system like Alpha to ensure timely reward claims. - Monitor reward claims to verify correct distribution. ### TRANSFER_REWARDS_ROLE (700) This role enables the transfer of claimed rewards to the rewards claim manager. In the IPOR Fusion Plasma Vault, reward management (e.g., selling, staking, or transferring) can be handled by a dedicated module. If no such module exists, Alpha can manage rewards manually. Assigning this role to Alpha allows it to transfer rewards to itself for processing. #### Best Practices - Use a dedicated rewards management module when available to streamline operations. ## Guardian Role This role serves as the emergency brake for the vault. The Guardian has the authority to reject time-locked actions and to [pause](https://ipor.io/docs/build-on-fusion/developer-guide/pause-functionality.md) or unpause the vault in case of an emergency. - Configurable by the Owner - Allows for multiple users - **Cannot be timelocked:** Guardians must always retain a 0-day execution delay to function as an instant emergency brake. **Guardian Capabilities & Limitations:** - **Veto Configuration Changes:** The Guardian can unilaterally cancel pending operational and configuration transactions scheduled by the Owner, Atomist, or Fuse Manager (any function gated through the access manager's target-function-role map). - **Emergency Pause:** The Guardian can instantly pause the vault, freezing all execution actions (including user deposits and withdrawals). - **LIMITATION - No Role Management Veto:** Crucially, role-management calls (`grantRole`, `revokeRole`, `setGrantDelay`) are OpenZeppelin admin functions. **Guardians CANNOT cancel these actions.** Only the scheduler themselves or an `ADMIN` can cancel role changes. For permission changes, the timelock provides a _detection-and-exit window_ for depositors, not a Guardian veto. #### **Best Practices** - **Instant Reaction:** Guardians must hold a **0-day execution delay** so they can react instantly to emergencies. - **Key Separation:** Guardian keys must be independent and separate from Owner keys. The emergency brake must fail independently of the roles it is meant to brake. - **Monitoring is Mandatory:** Because Guardians cannot veto role changes, robust on-chain monitoring for `RoleGranted` and `RoleRevoked` events is required to alert depositors of unauthorized permission changes during the exit window. - If using automated risk monitoring, consider granting the Guardian role to an emergency mechanism to enable automatic vault pausing. ## Fuse Manager Role The account with this role has rights to manage the FuseManager contract, add or remove fuses, balance fuses and reward fuses. - Configurable by the Atomist - Allows for multiple users #### Best Practices - For a more decentralized setup, use a dedicated, time-locked account to manage fuses, as they impact the strategy. ## Claim Rewards Role Account with this role has rights to claim rewards from the PlasmaVault using and interacting with the RewardsClaimManager contract.If PlasmaVault has a dedicated smart contract to handle the harvested rewards, the role should be granted to that contract. - Configurable by the Atomist - Allows for multiple users ## Transfer Rewards Role An account with this role has rights to transfer rewards from the PlasmaVault to the RewardsClaimManager.If PlasmaVault has a dedicated smart contract to handle the harvested rewards, the role could be granted to that contract. - Configurable by the Atomist - Allows for multiple users ## Rewards Claim Manager Role Technical role for the RewardsClaimManager contract. Account with this role has rights to claim rewards from the PlasmaVault. For practical reasons, it can be granted to the same entity holding either an Alpha or Atomist role.Configurable by the AtomistAllows for multiple usersPerformance Fee Manager RoleThe account with this role has rights to manage the performance fee, define the performance fee rate, and manage the performance fee recipient.Self managedAllows for multiple usersManagement Fee Manager RoleThe account with this role has rights to manage the management fee, define the management fee rate, and manage the management fee recipient.Self-managedAllows for multiple usersWhitelist RoleThe account with this role has rights to deposit/mint and withdraw/redeem assets from the Plasma Vault.Configurable by the AtomistAllows for multiple usersConfig Instant Withdrawal Fuses RoleAn account with this role has rights to configure instant withdrawal fuses order. ## Technical Roles (Must remain at 0-day delay) Certain roles act as the internal plumbing of the IPOR Fusion system. These roles are held by system contracts (or automated keepers) and are essential for the fundamental operation of the vault. Applying an execution delay (timelock) to any of these roles will break the vault's core mechanics. The following roles **must always remain at a 0-day execution delay** and should never have a minimal execution delay (floor) set: - `TECH_PLASMA_VAULT` (3) - `TECH_CONTEXT_MANAGER` (5) - `TECH_WITHDRAW_MANAGER` (6) - `TECH_VAULT_TRANSFER_SHARES` (7) - `TECH_REWARDS_CLAIM` (601) - `TECH_PERF_FEE` (400) and `TECH_MGMT_FEE` (500) _(Only when held by the internal FeeManager/plumbing on factory vaults. If held by governance, these require a 3-day delay)._ ## **PreHooksManager** Role As outlined in the [pre-hook configuration](https://ipor.io/docs/build-on-fusion/developer-guide/configuring-pre-hooks.md), all changes made by the PreHooksManager must be subject to a timelock. This delay allows liquidity providers (LPs) and other administrators to respond if pre-hooks are set incorrectly. #### Best practices - Implement a sufficiently long timelock for PreHooksManager actions. - Use a multisig wallet to administer pre-hooks for enhanced security. ## **Whitelist / Open Vault** By default, each new vault in the IPOR Fusion system is initialized with a whitelist, restricting minting and redeeming of shares to whitelisted addresses only. Atomists can add addresses to the whitelist or disable it entirely, making the vault open to all users. Once the whitelist is disabled, it cannot be re-enabled. #### Best Practices - Retain the whitelist during the testing period, even if you plan to open the vault to all users. - Use a secure multisig wallet for Atomists managing the whitelist to enhance security. ## Cross-Chain Access Management For Fusion vaults that utilize cross-chain strategies, the access management model is extended to secure the asynchronous nature of remote capital deployment. This includes dedicated attestation and emergency recovery roles: - **Attestation Roles:** Independent Proposer and Approver roles are required to validate and recognize remote Net Asset Value (NAV) on the source chain. - **Emergency Roles:** Rescue Admin and Rescue Guardian roles provide a bifurcated, time-delayed break-glass mechanism for severe bridge or transport failures. To maintain maximum security, the protocol strictly enforces that these paired roles (Proposer/Approver and Admin/Guardian) must be held by separate, independent addresses. For a complete breakdown of these specialized roles and their operational responsibilities, please refer to the [**Governance and Operating Roles**](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/cross-chain-vault-integration.md) section in the Cross-Chain Vault Integration: Business & Operations Guide.# Timelocks and Execution Delays > How execution delays protect depositors from unilateral configuration changes, and how to size them. This guide explains the conceptual model, governance rules, and operational safeguards of **timelocks (execution delays)** in IPOR Fusion. ## 1. Executive Summary: What is a Timelock? A **timelock** (technically referred to as an **execution delay**) is a mandatory, onchain "cooling-off" period enforced between the time an administrative action is proposed and the time it actually takes effect on the blockchain. In IPOR Fusion, no single administrative key can immediately alter critical vault parameters (such as changing performance fees, updating assets, or modifying Fuses) if a timelock is active. This design enforces complete transparency and provides a vital window for depositors and Guardians to react before any high-impact change is executed. ## 2. The Core Value Proposition Timelocks are not meant to slow down routine operations, but rather to establish a robust line of defense for depositors and operators. They solve three primary security problems: | **Risk Mitigated** | **How Timelock Solves It** | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Admin Key Compromise** | If an Atomist or Owner private key is stolen, the attacker cannot drain or hijack the vault instantly. They must schedule their malicious transaction, triggering public alerts. | | **Human Error** | If an operator inputes an incorrect fee percentage (e.g., 50% instead of 5%), the mistake is caught while sitting in the queue and can be cancelled before execution. | | **User Transparency** | Depositors are never surprised by sudden policy shifts. If a vault owner increases performance fees, depositors have the entire timelock window to withdraw their funds if they disagree. | ## 3. The Three Shields of Vault Security Timelocks turn governance from something that _can_ happen instantly into something that _must_ happen slowly and in the open. To achieve this, the system relies on three distinct defensive shields working together. **Shield 1: The Notice Period (Execution Delay)** A protected role cannot act on the spot. It must publicly announce an operation and may carry it out only after its delay elapses (e.g., 14 days for the Owner, 3 days for the Atomist). The pending operation is visible on-chain throughout this period. **Shield 2: The Waiting Room (Grant Delay)** When a new account is granted an important role, its membership does not activate immediately — it waits out that role's grant delay first (recommended: 14 days for `OWNER`, 3 days for the configuration tier). This closes the "inject a key into governance" path: even a grant that completes successfully produces an account that cannot act yet. The rule itself is protected too. Changing a grant delay via `setGrantDelay` arms only after a hardcoded 5-day minimum setback — and reducing one takes the larger of 5 days and the size of the reduction. Nobody, including a legitimate administrator, can shorten the waiting room and walk a new key straight in. Note that grant delays are `ADMIN`-gated: they must be configured before `ADMIN` is renounced, or they become permanently unavailable. Re-granting an existing member — for example raising their execution delay — is not subject to the grant delay. This is deliberate: it keeps hardening instantaneous. **Shield 3: The Minimum Threshold (Floors)** Without a minimum threshold, a timelock could theoretically be bypassed in minutes by removing a role and granting it back with a zero-day waiting period. The Access Manager prevents this by enforcing a hard floor (a minimum execution delay) for critical roles. The contract will reject any attempt to grant a role with a delay shorter than its established floor. Crucially, lowering this floor is itself an Owner action, meaning it also requires a 14-day public notice. _Developer Note:_ Setting a floor is **not retroactive**. It constrains future grants only. If an account already holds a role, setting a new floor does not increase their current delay. To apply the new floor to existing accounts, they must be explicitly re-granted their roles. > **The Golden Rule:** Tightening security is fast; loosening security is slow. Raising any protection (e.g., increasing a delay) takes effect immediately, allowing a vault to be hardened the moment a problem is suspected. Conversely, every weakening of security is visible long before it lands. ## 4. Timelock Levels and Configuration Timelocks are highly granular and can be configured at two distinct levels to fit your organization's security posture: ### A. Account-Role Level (Individual Delays) You can apply different timelock durations to different accounts, even if they hold the exact same administrative role. - **Operational Example**: A vault owner might grant the Atomist role to a **multi-signature cold wallet** with a 0**-second delay** (for rapid emergency adjustments), while granting the same Atomist role to a **single-signature hot wallet** or automated keeper with a 2**-day delay** for routine rebalancing. ### B. Minimal Role Delay (The Safety Floor) To prevent key administrators from overriding security thresholds, the system supports a "Minimal Role Delay". Once set, no account can be granted that specific role with a delay lower than the established floor. - **Operational Example**: If the Minimal Role Delay for the Atomist role is set to 1 day, any attempt by a compromised Owner account to grant an instant, 0-day Atomist role will be automatically rejected by the access manager. ## 5. Who Can Set, Edit, and Remove Timelocks? The editing and removal of timelocks is governed by the **Role Admin Hierarchy**. Under this model, only the designated "Admin" of a specific role has the authority to grant that role or modify its execution delay: ``` ┌───────┐ │ Admin │ ┌──────────────────────┐◄──────┘ │ OWNER ROLE │ └──────────┬───────────┘ │ Admin of │ ├──────────────────────────────────────┐ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ │ ATOMIST ROLE │ │ GUARDIAN ROLE │ │ PRE HOOKS ROLE │ └────────┬────────┘ └─────────────────┘ └──────────────────┘ │ Admin of ├───────────────────┐ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ ALPHA ROLE │ │FUSE MANAGER ROLE│ └─────────────────┘ └─────────────────┘ ``` - **Owners Only**: The Owner administers the Atomist and Guardian . Only Owners can set, edit, or remove timelocks on Atomists or Guardians. - **Atomists Only**: The Atomist administers operational roles like Alpha or Fuse Manager. Only the Atomists can configure timelocks for those roles. - **Self-Governance**: The Owner role is self-administered. Only existing Owner accounts can modify the delay settings of other Owner accounts. ## 6. The "Admin Timelock" Constraint A common security vulnerability in DeFi occurs when an administrator with a timelock simply uses their admin power to change their own timelock to 0 seconds, bypassing the safety window. IPOR Fusion closes this loophole using **Self-Referential Security**: > **Rule**: Administrators are strictly bound by their own timelocks. If a vault Owner is subject to a 14-day timelock (which is the strictly recommended default for this role), they cannot instantly change their own timelock or the timelock of any other user. Any attempt to modify a timelock, grant a role, or change minimal role delays must itself be scheduled as a proposed transaction and wait out the 14-day cooling-off period before taking effect. ## 7. The Guardian: The Emergency Brake While a timelock prevents malicious changes from occurring instantly, it is only effective if there is a mechanism to stop a malicious transaction while it sits in the queue. This is the responsibility of the [**Guardian**](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md). The Guardian acts as the vault’s emergency security sentinel: - **The Sentinel Power**: Guardians have the unilateral authority to **cancel** any pending scheduled operational and configuration transaction in the access manager's queue. - **Instant Cancellation**: Guardians are granted roles with a 0**-day delay**, enabling them to intervene instantly. - **Safety Isolation**: Guardians can _only_ cancel pending actions; they cannot schedule new administrative configurations or authorize movements of capital. If a monitoring bot or offline cold wallet detects an unauthorized transaction in the queue, the Guardian executes a cancellation. The malicious transaction is instantly deleted, and the compromised key is rendered harmless while the operators coordinate a key rotation. ## 8. Enabling Timelocks on an Existing Vault (High-Level) Activating timelocks is a configuration change, not a redeployment. No funds move, no migration is required, and the vault keeps operating normally throughout the process. Before enabling timelocks, the Vault Owner must make several key decisions: - **Owner Notice Period:** Starting at 14 days is the recommended default. Raising a delay is always immediate, so starting lower and tightening later costs nothing, but lowering it takes time. - **Guardian Line-up:** Guardians may be individual keys held by people who react within hours. They may also be third-party security providers. Providers that monitor the mempool can react within the same block. Guardians should hold no other role, and their keys must be strictly separate from the Owner's keys. - **Key Separation:** The Owner should operate via a dedicated multisig that is rarely used (effectively a cold wallet). Timelocks protect against rushed malicious actions; they do not protect against a stolen key. Separation limits the damage if a single key is compromised. - **Monitoring:** Someone must actively watch for on-chain alerts (`RoleGranted`, `OperationScheduled`) and know how to respond. Without active monitoring, the 3-day and 14-day notice windows provide no actual protection. - **Re-granting is Required:** Remember that simply setting minimum thresholds (floors) does not magically update the delays of accounts that already hold those roles. During setup, every existing account must be explicitly re-granted its role to apply the target execution delay. ## 9. Frequently Asked Questions (FAQ) **Are everyday user operations delayed by timelocks?** No. Everyday operations such as deposits, withdrawals, and redemptions remain immediate. Timelocks govern rule changes and administrative parameters, not user activity. **Can a change be announced and then quietly executed much later?** No. An announced operation expires 7 days after its earliest execution date. If it is not carried out within that execution window, it lapses entirely. The whole announcement process must then be restarted from scratch. **Why does the automated Alpha (Keeper) operator have no timelock?** The Alpha operator is fast _because_ it is strictly boxed in by rules that only the slow roles can change. It moves funds only within the limits, substrates, and protocols explicitly pre-approved by the Atomist and Fuse Manager. Delaying the Alpha would break daily rebalancing and yield harvesting without adding protection, as the dangerous power lies in changing the boundaries, not operating within them. **What happens if Guardians pause a vault?** The emergency [pause](https://ipor.io/docs/build-on-fusion/developer-guide/pause-functionality.md) is a full freeze. It halts all execution actions, including user deposits and withdrawals. It is designed as an emergency brake for genuine incidents, not a convenience switch, and it is lifted once the situation is resolved. **What stops an attacker from removing a role and re-granting it instantly?** The Minimum Threshold (Shield 3). The contract rejects any grant below the established floor. Lowering that floor first costs the Owner their own 14-day notice period, in plain sight.# Substrates > What substrates are and how they scope which assets and markets a fuse is allowed to touch. In IPOR Fusion, the Atomist defines the "walled garden" in which the strategy logic (Alpha) can operate. While Fuses define _how_ to interact with a protocol (e.g., "supply to Aave"), Substrates define _where_ and with _what_ specifically those Fuses are allowed to interact. #### What are Substrates? Substrates are specific identifiers or permissioned parameters granted to a Market within a Fusion Vault. They act as the granular configuration that tells the vault which underlying components of an external protocol are "visible" and "authorized." Think of a Market (like Aave V3) as a building. The Fuse is the door you use to enter. Substrates are the specific rooms inside that building that the Atomist has unlocked for the Alpha. Common examples of Substrates include: - Asset Addresses: Specific ERC20 tokens allowed for supply/borrow. - Sub-Market IDs: Specific pool IDs or market identifiers within a larger protocol (e.g., a specific Morpho Blue market). - Target Limits: Constraints on asset amounts or specific function selectors allowed for execution. #### Why are Substrates necessary? Substrates serve two primary purposes: 1. Security: They prevent an Alpha from interacting with unauthorized, risky, or unverified pools within a protocol. 2. Accounting: The Plasma Vault uses Substrates to know which balances it needs to track. If a Substrate isn't configured, the vault won't "see" the assets held in that sub-market, ensuring the vault's Net Asset Value (NAV) and share price remain accurate and protected. #### Configuration Step-by-Step As an Atomist, you configure Substrates after adding a Fuse to your vault. This is done via the `grantMarketSubstrates` method. **1. Identify the Market ID** Every external protocol integration in Fusion has a unique `uint256` Market ID defined in the `IporFusionMarkets` library. - _Example:_ `AAVE_V3 = 1`, `COMPOUND_V3_USDC = 2`, `MORPHO_BLUE = 5`. **2. Define the Substrate Data** The data required for a substrate varies depending on the Market type. - For Lending Markets (Aave, Spark, etc.): The substrate is typically the address of the underlying asset (e.g., the USDC token address). - For Complex Markets (Morpho Blue, Uniswap V3): The substrate might be a specific Market ID or a pool address. - For Async Actions: Substrates can include encoded limits (`ALLOWED_AMOUNT_TO_OUTSIDE`) or permitted function selectors (`ALLOWED_TARGETS`). **3. Grant Permissions** Using the Vault's management interface (or directly via the `PlasmaVault` contract), the Atomist calls: `grantMarketSubstrates(uint256 marketId, bytes[] calldata substrates)` This registers the list of authorized IDs for that specific market. #### Technical Example: Morpho Blue If you are setting up a vault to supply USDC to a specific Morpho Blue market, you wouldn't just grant access to "Morpho." You would: 1. Identify the Morpho Market ID (the unique hash for the specific collateral/loan pair). 2. Add this ID as a Substrate to the Morpho Blue Market in your vault. 3. The vault now knows to track the balance of the `Id` and allows the Alpha to call `enter` or `exit` for that specific pool. #### Summary for Atomists - Fuses = Actions (Supply, Withdraw, Swap). - Markets = Protocols (Aave, Morpho, Gearbox). - Substrates = Specifics (USDC, Market ID `0x123...`, Function selectors). Without the correct Substrates, your Alpha will not be able to execute trades, even if the Fuse is correctly installed. --- #### Related Resources - [Video: Explaining Substrates (YouTube)](https://www.google.com/search?q=https://youtube.com/watch%3Fv%3DzYXwfeUp7Og)# Curating a Fusion vault > Ongoing vault-curation topics: share price, contributions, performance, oracle choice, caps and market impact. This section will focus on some topics that are important for designing the strategy and determining the necessary parameters: - [share-price-dynamics.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/share-price-dynamics.md "mention") - [on-and-offboarding-contributions.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/on-and-offboarding-contributions.md "mention") - [understanding-performance-of-a-vault.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/understanding-performance-of-a-vault.md "mention") - [choosing-the-right-price-oracle.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/choosing-the-right-price-oracle.md "mention") - [asynchronous-reward-compounding.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/asynchronous-reward-compounding.md "mention") - [dependency-graphs.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/dependency-graphs.md "mention") - [managing-redemption-delays.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/managing-redemption-delays.md "mention") - [deposit-caps](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/deposit-caps.md "mention") - [yield-scalability-and-market-impact.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/yield-scalability-and-market-impact.md "mention") - [integrating-pendle-pt-tokens.md](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/integrating-pendle-pt-tokens.md "mention")# Managing Redemption Delays > How the mandatory deposit-to-redemption delay works and how to configure it against sandwich attacks. A critical decision for any Atomist when curating a Fusion vault is whether to implement a **Redemption Delay**. While this feature is a powerful security tool, it introduces significant trade-offs regarding how the vault interacts with the broader DeFi ecosystem. ### What is a Redemption Delay? A redemption delay is a mandatory waiting period enforced between a user's deposit and their ability to withdraw or transfer their shares. In IPOR Fusion, this can be configured from 0 seconds (disabled) up to a maximum of 7 days. When enabled, the vault records the timestamp of a user's last deposit. Any attempt to `withdraw`, `redeem`, or `transfer` shares before the delay period has passed will result in an `AccountIsLocked` revert. The redemption delay parameter is set during the vault creation process in the Fusion Factory. Once the vault is created, this value is **immutable**. Atomists cannot change the delay period later, so careful consideration of the integration trade-offs is required before deployment. ### The Security Motivation: Breaking Atomicity In DeFi, an **atomic transaction** is a sequence of operations that must all succeed or fail together within a single block. Flash loan attacks rely entirely on this property: an attacker borrows funds, manipulates a vault's share price, and repays the loan—all in one block. By requiring even a 1-second delay, the vault forces the user to wait at least until the next block. This makes flash loan attacks impossible because the loan cannot be repaid in the same transaction it was taken. #### Pros and Cons at a Glance | Feature | Pros | Cons | | ------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------- | | **Security** | Hard protection against flash loans and price manipulation. | Does not prevent "sandwich attacks" that span across blocks. | | **Integrations** | Simple to understand for retail users. | Breaks "Money Lego" composability; causes issues with Zaps and Liquidators. | | **User Experience** | Prevents "in-and-out" gaming of the vault. | Can be frustrating for users expecting instant liquidity. | ### Integration Issues with DeFi Protocols Implementing a redemption delay, even one as short as 1 second, can cause "silent" failures for other protocols treating your Fusion Vault as a building block. #### 1. Zaps and Aggregators Many DEX aggregators and "Zap" contracts bundle multiple actions. A user might swap ETH to USDC and deposit into your vault in one click. If the Zap contract needs to "pull back" a tiny amount of shares for fee coverage or slippage adjustment in the same transaction, the entire sequence will fail. #### 2. Liquidation Friction and Collateral Utility If a lending protocol accepts your vault shares as collateral, a redemption delay creates a major risk. During a liquidation, the protocol needs to seize and redeem those shares for the underlying asset immediately to cover debt. If the vault enforces a delay, the liquidation fails, potentially leading to **bad debt** for the lending protocol. While bad debt is a risk for the lender, the indirect consequence for the Fusion vault is **reduced collateral utility**. Risk managers at lending protocols are likely to reject vault shares with redemption delays or apply much higher "haircuts" (lower LTVs), making your vault less attractive for power users and institutional integrators. #### 3. Yield Optimizers and Bots Automated rebalancing bots often deposit into a vault and immediately check solvency or re-calculate a strategy. If these bots need to move funds out in the same block (common in volatile markets), they will get stuck in a revert loop. ### The Alternative: On-boarding and Off-boarding Contributions If your goal is to protect the vault from manipulation without breaking its composability, consider using **On-boarding and Off-boarding Contributions** (Deposit and Withdrawal fees). In IPOR Fusion, these are shares minted or burned during the entry/exit process. #### How Fees Protect the Vault Instead of a time-based lock, you can implement a small fee (e.g., 0.1%). This "tax" on atomicity makes flash loan attacks and sandwich attacks economically unviable because the cost of the fee typically exceeds the potential profit from the manipulation. - **Composability stays intact**: Zaps and Liquidators can still interact with the vault instantly. - **Protection remains**: Flash loaners are deterred by the upfront cost. ### Recommendation for Atomists - **For Private/Curated Vaults**: If you expect your vault to be used primarily by retail users through a frontend, a **short redemption delay (e.g., 1 hour)** is a robust security measure. - **For Ecosystem Building Blocks**: If you want your vault to be used as collateral in lending markets or included in yield aggregators, set the **Redemption Delay to 0** and utilize **On-boarding/Off-boarding Contributions** to provide security while maintaining high composability.# Deposit Caps > Why and how to cap total vault deposits. A Deposit Cap (technically referred to as the **Total Supply Cap**) is a core security and risk management parameter that defines the maximum number of shares a Fusion Vault can issue. It serves as a vital circuit breaker to limit protocol exposure and protect vault participants from specific attack vectors. ## Overview In IPOR Fusion, the Deposit Cap is enforced at the share-minting level. By limiting the total supply of shares, the Atomist indirectly controls the maximum Total Value Locked (TVL) in the vault. Every vault should ideally be configured with a deposit cap that aligns with the strategy's current liquidity capacity and the desired risk profile. ## Security Rationale Setting an appropriate cap is a primary defense mechanism against several DeFi risks: ### 1. Mitigating Flash Loan and Share Price Attacks Vaults that offer **instant deposits and redemptions** without charging an onboarding/offboarding contribution (entry/exit fees) are potentially vulnerable to share price manipulation. - **The Vector:** An attacker could use a flash loan to significantly inflate the vault's assets or manipulate external price feeds used by balance fuses. If they can enter and exit in the same block for free, they can exploit temporary discrepancies in share price calculation. - **The Defense:** While [Redemption Delays](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/managing-redemption-delays.md) are the primary defense against same-block attacks, a **Deposit Cap** bounds the maximum potential impact. It ensures that even if a manipulation is attempted, the attacker cannot use an unlimited amount of capital to exploit the vault's share price calculation. ### 2. Strategy Capacity and Liquidity Management Many DeFi protocols have finite liquidity. If a Fusion vault grows too large for its underlying protocol integrations, it may suffer from excessive slippage during rebalancing or be unable to exit positions quickly during market stress. A cap ensures the vault stays within the "sweet spot" of the underlying strategy's liquidity. ### 3. Risk Contamination In the early stages of a new strategy, a cap allows the Atomist to observe the performance and oracle stability with a controlled amount of capital before scaling up. ## Technical Implementation ### Role Requirements Only accounts with the `ATOMIST_ROLE` are authorized to modify the supply cap. ### Setting the Deposit Cap The cap is set using the `setTotalSupplyCap` function within the `PlasmaVaultGovernance` contract: ``` function setTotalSupplyCap(uint256 cap_) external restricted; ``` **Important Note on Denomination:** Unlike many other protocols, the cap in IPOR Fusion is denominated in **Shares**, not underlying assets. Because the share price increases over time as yield accrues, the asset-equivalent value of a fixed share cap will also increase. The precision of shares is calculated as: `Share\ Decimals = Underlying\ Asset\ Decimals + 2` ### Management Interface Atomists can manage these limits through the IPOR Fusion Web App in the Vault Management section. 1. Navigate to the **Curation** tab of your vault. 2. Locate the **Vault Limits** section. 3. Enter the new cap value in the underlying asset denomination (the UI automatically handles the conversion to share decimals for the transaction). 4. Execute the transaction. ## Best Practices for Atomists ### 1. The Inverse Relationship with Delays The importance of a strict deposit cap is inversely proportional to your **Redemption Delay**. If your vault has a `redemptionDelay` of 0 and no entry/exit contributions, a tight cap is your absolute primary line of defense against share price manipulation. > For more information on managing waiting periods, see [Managing Redemption Delays](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/managing-redemption-delays.md). ### 2. Frequency of Balance Updates The enforcement of the cap happens during the `deposit` and `mint` flows. However, the calculation of available "headroom" depends on an accurate share price. **Reminder:** Atomists must ensure that `updateMarketsBalances` is called regularly (manually or via keepers). If market balances are stale, the share price might be incorrect, leading to a cap enforcement that doesn't reflect the true value of the vault. ### 3. Strategy Capacity Monitoring Regularly monitor the capacity of the underlying strategy. If the integrated protocols or pools reach their own liquidity limits, the Fusion vault's cap should be adjusted accordingly. While the cap can be increased as the strategy demonstrates deeper liquidity, it should be lowered if market conditions change or if protocol limits are reached to maintain desired slippage parameters.# Public Vault Listing > How a vault gets listed publicly on the IPOR webapp. While the IPOR Fusion protocol is entirely permissionless - allowing anyone to deploy a vault and share its address directly with users - the official Fusion DApp frontend lists a public registry of vaults. To appear on this default interface, a vault must successfully pass a public listing review. This review process is conducted directly between the Atomist and the IPOR Fusion DAO. Its primary purpose is to assess whether a vault exposed to the public meets baseline security, transparency, and operational standards. ## Listing Requirements To be approved for the public registry, an Atomist must demonstrate that their vault adheres to a comprehensive set of criteria. The DAO conducts a systematic review of its configuration. ### 1. Security and Governance Concepts The vault's access control and security architecture must align with the [Vault Governance and Depositor-Led Security Best Practices](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/vault-governance-and-depositor-led-security-best-practices.md). Alternatively, the Atomist must implement a comparable and robust security framework utilizing components such as: - Multisig wallets for the `OWNER_ROLE` and `ATOMIST_ROLE`. - Timelocks for sensitive configuration changes. - A dedicated `GUARDIAN_ROLE` for emergency interventions. ### 2. Comprehensive Vault Description Transparency is critical. The vault must provide a complete and accessible description covering the following areas: - **Strategy:** A clear explanation of how the vault generates yield, the protocols it interacts with, and the underlying mechanics. - **Vault Governance:** Explicit disclosure of who holds the `OWNER_ROLE`, `ATOMIST_ROLE`, and `GUARDIAN_ROLE`, including a description of the multisig setups (e.g., threshold requirements and signers). - **Contribution Policy:** If the vault charges onboarding (deposit) or offboarding (withdrawal) contributions, the description must clearly explain their purpose and calculation methodology. For example, stating whether the contributions are fixed or dynamically calculated. - **Atomist Profile:** Background information on the vault creator/manager and reliable contact information. ### 3. Legal Compliance The Atomist must confirm that the vault, its strategy, and its marketing comply with the laws that apply to the Atomist in each jurisdiction where they operate. The Atomist must hold any license their activities require. ## The IPOR Fusion DAO Setup Check As part of the listing process, the IPOR Fusion DAO conducts a high-level technical review of the Vault's configuration. **The setup check verifies:** - **Contract Configuration:** Fuses, Dependency Balance Graphs, and Price Oracle Middleware are correctly assigned and logically structured. - **Strategy Sanity Check:** A high-level review of the strategy's execution path to check for obvious logical flaws or broken integration loops. **What is NOT checked:** The review does **not** include a granular analysis of the strategy's risk parameters. The DAO does not evaluate e.g. the safety of the target Loan-to-Value (LTV) ratios, the depth of liquidity in the target pools, or the broader market risks associated with the underlying protocols. ## Frontend Visibility Passing the DAO review makes a vault eligible to be added to the official frontend registry. There is no strict minimum Total Value Locked (TVL) required to be listed. However, to optimize the user experience and surface established strategies, the IPOR Fusion frontend implements a default visibility filter. Vaults with a TVL below $100,000 are hidden by default. Users who wish to explore newly listed or smaller vaults can manually toggle this filter off within the interface to view the complete list of DAO-approved vaults.# Running an Alpha > Manual vs. automated Alpha operation, the SDK-driven transaction path, and handling accrued points. ## Manual Alpha Operations The simplest case of Alpha is appointing an EOA to run the operations to manually using the interface. The app.ipor.io website has an interface for interacting with smart contracts via widgets. Widgets abstract the fuse functionality and provide a consistent interface for executing transactions. If the interface is unavailable for the fuse you want to interact with, you can always build the transaction by hand. Fusion implements a diamond proxy pattern so that the vault implements the function calls from the connected fuses. ## Automated Alpha The core philosophy of IPOR Fusion is to build the vaults in such a way as to enable automation without sacrificing transparency and security. In principle, there is no difference between a manual and an automated Alpha. They both have the same privileges and from the perspective of the vault so there is no difference whatsoever. The main difference is the interface through which management is done. Since the automated Alpha doesn't need UI it needs to build all its transactions via the provided [SDK](https://ipor.io/docs/build-on-fusion/alpha/sdk.md), or by communicating directly with smart contracts. When operating an Alpha there are certain security precautions one needs to take. Because the Alpha has direct access to the vault, albeit limited by the fuses and validations, it can still manage the assets freely. Natively, the Alpha architecture does not require any form of consensus. Of course, if this is your choice, you may implement the logic the have the decision-making process decentralised and more sophisticated. ## Handling Points One particular scenario when operating vaults as Alpha is accounting for points. Thanks to the structure of Fusion, all the points are accrued by the vault. If you need to distribute those points or rewards based on those points it has to be handled by your backend logic. Service providers like Merkl can also forward points to the users of the vault. If you need to show points on your vault's page on the IPOR web app, and this particular type of points is not yet supported, reach out to the IPOR DAO via Discord, where you can request adding that integration. It's up to the individual Atomist/Alpha.# Best Practices for Alphas > Gas efficiency, leveraged-looping risk, accounting, withdrawal orchestration and security practices for an Alpha bot. The **Alpha** role (assigned via `ALPHA_ROLE`) is the operational engine of a Fusion Vault. As an Alpha, your primary responsibility is the execution of yield strategies and the management of vault liquidity. Unlike the Atomist, who configures the "physical" constraints of the vault, the Alpha operates within those constraints to maximize risk-adjusted returns for depositors. This guide provides technical tips and best practices for running an efficient Alpha bot or manual strategy. ## 1. Optimize for Atomicity and Gas Efficiency The `execute(FuseAction[] calldata calls_)` function is designed for atomic batching. Every action within the `calls_` array is executed via `delegatecall` in the vault's context. - **Batching for Gas Efficiency:** Combine related operations (e.g., swapping tokens, then supplying them to Aave V3) into a single `execute` call. This reduces overhead and ensures that the vault never holds intermediary assets across blocks. - **Optimal Transaction Sizing:** Avoid overly long execution chains that might exceed block gas limits. Estimate gas fees for execution and evaluate them against the likelihood of sustained rate changes. - **All-or-Nothing Execution:** If any `FuseAction` fails—due to slippage or substrate violations—the entire transaction reverts. Use this to build complex "Zaps" or arbitrage loops with zero principal risk to the vault. ## 2. Leveraged Looping Strategies Leveraging can significantly boost yield but introduces specific risks to the vault's Net Asset Value (NAV). - **Slippage Management:** Slippage is the primary "performance killer." - Prefer **native staking/unstaking** or **wrapping** over DEX swaps whenever possible. - Use **aggregators** to find the best routes. The `UniversalSwapper` fuse provides additional validation for aggregator-provided transactions. - For large positions, **TWAP in and out**. Building a massive leveraged position in a single block can cause high slippage. Smaller, incremental steps allow the market depth to recover. - **Simulate Market Depth:** Before executing, understand the current market depth and rate dynamics. Simulate slippage on-chain or via off-chain forks to ensure you aren't leaving "free money" for MEV sandwich bots. - **Avoid Swaps via Flash Loans:** When moving between positions with similar assets, use a **Flash Loan** (e.g. via `MorphoFlashLoanFuse`) to transition atomically without incurring swap fees or slippage. - **Flash Loan Capacity Limits:** Sometimes the liquidity available for a single flash loan is insufficient to move an entire position. In these cases, split the position into smaller operations and execute the transaction using two or more flash loans to bypass the liquidity constraints. - **Cross-Asset Volatility:** Be highly cautious when leverage looping with different underlying assets (e.g., supplying one stablecoin as collateral while borrowing a different stablecoin). The leverage multiplier amplifies any price divergence between the two assets (leverage × stable_1_price / stable_2_price), which will cause abrupt jumps in the vault's share price. - **Off-Chain Calculation Buffer:** If calculating state changes off-chain, add a buffer to account for the time elapsed between calculation and on-chain execution. Additional interest will accrue during this delay. This is particularly critical in markets with high utilization. Keep in mind that moving a leveraged position (e.g., 10x leverage) out of such a market will accrue 10x more borrow interest during the delay than the base rate suggests. ## 3. Managing Accounting and Performance Fees Performance fees are calculated on realized gains during `execute()` and `updateMarketsBalances()`. - **Understand the High-Water Mark:** IPOR Fusion utilizes High-Water Mark (HWM) logic. Performance fees are only minted when `TotalAssets_{after} > HighWaterMark`. - **Strategic Balance Updates:** Call `updateMarketsBalances(uint256[] marketIds)` periodically. This synchronizes internal accounting with external protocol interest. - **Prevent Inflation Attacks:** Update the share price (`\frac{TotalAssets}{TotalSupply}`) frequently enough to ensure accuracy and prevent share price inflation attacks, especially in public vaults. - **Oracle Selection and Share Price Jumps:** Prefer fundamental oracles (e.g., wstETH-ETH) whenever possible to avoid artificial share price jumps. If utilizing market-priced oracles, be aware that deleveraging costs will be immediately reflected in the share price, making it inherently more volatile. If an asset is new or has limited DEX liquidity but maintains a 1:1 redemption peg, consider suggesting a hardcoded price oracle to the Atomist. ## 4. Liquidity and Withdrawal Orchestration - **Unallocated Buffer:** Maintain a buffer of assets in an instantly liquid strategy (e.g., Lending Optimizers). This allows the vault to fulfill smaller withdrawals instantly without the gas cost of deleveraging a complex position. - **Scheduled Withdrawal Flexibility:** Do not ignore the flexibility of scheduled withdrawals. Sometimes it is more economical to use the next incoming deposit to pay down vault debt rather than manually deleveraging via a swap. - **The `releaseFunds` Responsibility:** For scheduled withdrawals, Alphas must call `releaseFunds()` on the `WithdrawManager`. Monitor the queue and rebalance the vault _before_ releasing funds to ensure liquidity is present. - **Burn Request Fees Proportionally:** When preparing funds for scheduled withdrawals and utilizing the `BurnRequestFeeFuse`, ensure that you burn the offboarding (request) fees in proportion to the funds being prepared. - **Dynamic Fees:** If granted the role, the Alpha can manage the **Request Fee** and **Withdrawal Fee**. Set these dynamically to match your expected cost of unwinding positions, preventing users from "socializing" the cost of their exits. ## 5. Deleveraging and Risk Execution The Alpha executes deleveraging actions based on market conditions, but must always operate within the risk framework established by the Atomist. - **Bite the Bullet:** Deleveraging often incurs trade costs (swaps/fees). In high-risk scenarios (e.g., price volatility approaching an Atomist-defined threshold), it is better to "bite the bullet" and deleverage at a loss than to risk a liquidation event that threatens the vault's principal. - **Cost vs. Performance:** Decisions to deleverage must balance execution costs (slippage and gas) against the cumulative negative carry of an inverted position. While using new deposits to pay down debt is an efficient way to deleverage, it is unlikely to occur when rates are inverted, as smart depositors will avoid a vault with negative performance. Alphas must proactively calculate the "breakeven" time—where the cost of a forced swap today is lower than the projected loss from holding the inverted position—and execute accordingly while remaining within the Atomist's safety bounds. ## 6. Security and Risk Mitigation - **Comprehensive Simulation:** Always simulate the transaction off-chain and verify the final balances before broadcasting. Specifically, simulate share price changes to ensure the execution does not cause unintended share price jumps. - **Strict Slippage:** Always populate `minOutAmount` or `maxSharesBurned` when making a swap. Never use `0` in production. When building automated bots, it is highly recommended to configure tightly bound, low slippage tolerances. - **Oracle Pre-Hooks:** Use the `ValidateAllAssetsPricesPreHook` before `execute()` to block operations if the `PriceOracleMiddleware` detects a price deviation beyond your threshold (e.g., 1-2%). - **Monitoring Health Factors:** For borrow positions (e.g. Euler V2, Morpho), monitor the Health Factor (`HF`) of sub-accounts using the `balanceOf` query in the corresponding Balance Fuse. ## 7. Technical Role Constraints - **Governance Separation:** Internal functions (fee % changes, adding fuses) are restricted to the Atomist. Focus on capital allocation and execution logic. - **Bot Robustness:** Ensure your automated Alpha has robust error handling, monitors gas price spikes, and maintains a sufficient ETH balance for operations. **Summary Checklist for Alpha Operations** | **Task** | **Frequency** | **Target Contract** | | ------------------------------ | -------------------- | --------------------- | | `updateMarketsBalances` | e.g. Hourly/Daily | `PlasmaVault` | | `releaseFunds` | Per Withdrawal Cycle | `WithdrawManager` | | `execute` (Rebalance/Compound) | As needed | `PlasmaVault` | | `claimRewards` | Based on Accrual | `RewardsClaimManager` |# Vault Fees Detailed information about each vault's fee structure can be found on the "Vault Info" page (next to "Overview") for each Fusion vault. ![Image](https://ipor.io/docs/assets/fusion-for-depositors/image-22e796.png) __ Fusion vaults aim to set a standard for transparency in DeFi. The "Vault Info" page includes essential information in that respect, including the wallets of vault-related roles and contracts, as well as, all fees, contributions, and other essential information. ![Image](https://ipor.io/docs/assets/fusion-for-depositors/image-64-db7798.png) __ It's recommended that users check the "Vault Info" page before depositing in a vault, so they are informed about fees and the deposit/withdrawal policy. ## Fee Accounting Schedule Fees are not charged on a fixed calendar schedule (such as monthly or annually). Instead: - **Automated Deductions**: The fee is calculated and deducted automatically whenever the vault's share price updates—such as during user deposits, withdrawals, or strategy rebalances. - **Real-Time Share Pricing**: Because fees are processed in parallel with share price updates, the price displayed on your dashboard is always fully up-to-date and net of fees, ensuring no hidden charges at withdrawal. ## Where Do the Fees Go? When performance fees are collected, they are held in a secure contract and split between: 1. **The IPOR DAO**: A portion goes to the protocol to support the network. 2. **The Vault Curator**: A portion goes directly to the strategists and partners who build and manage the vault.See https://github.com/IPOR-Labs/ipor-fusionSee https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/libraries/Roles.solSee https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/libraries/IporFusionMarkets.solSee https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/interfaces/IPlasmaVault.solSee https://github.com/IPOR-Labs/ipor-abiSee https://github.com/IPOR-Labs/ipor-abi/blob/main/mainnet/addresses.jsonSee https://github.com/IPOR-Labs/ipor-abi/blob/main/mainnet/mainnet-base-fusion/addresses.jsonSee https://github.com/IPOR-Labs/ipor-fusion.pySee https://pypi.org/project/ipor-fusion/--- name: ipor-deploy-vault description: Deploy, configure, fund and operate an IPOR Fusion Plasma Vault with the ipor-fusion Python SDK. Use when the user wants to create or deploy a Fusion vault, add fuses or a balance fuse, grant vault roles, whitelist depositors or open the vault to the public, deposit, or execute a strategy through a fuse — or hits DaoFeePackagesArrayEmpty, AddressEmptyCode or AccessManagedUnauthorized. Read it before writing any vault code. license: BSD-3-Clause metadata: homepage: https://docs.ipor.io/build-on-fusion repository: https://github.com/IPOR-Labs/ipor-fusion.py --- # Deploy and operate an IPOR Fusion vault IPOR Fusion is a protocol for on-chain asset-management vaults on EVM chains. A **Plasma Vault** is an ERC-4626 vault whose strategy is expressed as calls through **fuses**, immutable adapters to external protocols (Aave V3, Morpho, Uniswap, ...). Fusion is a separate product from IPOR's interest-rate derivatives; when a user says "IPOR vault", "Fusion vault" or "automated strategy vault", they mean this. Two sections follow, both shipped unchanged inside the `ipor-fusion` package and served by its MCP server as `fusion://invariants` and `fusion://quickstart`: the rules whose violation is a revert, then the executed walk from nothing to a vault with a live position. Read the rules, then copy the walk. For anything beyond this skill — the public API, vault operations, the wider vocabulary — the canonical agent index is https://ipor.io/llms.txt. ## Invariants Rules that hold for every Fusion vault. Breaking one is a revert; the revert name and selector are listed with each rule so a failed transaction is greppable. 1. **Clone from `IporFusionFactoryProxy`, never from `IporFusionFactoryImpl`.** Only the proxy carries the fee configuration. `clone()` on the implementation reverts `DaoFeePackagesArrayEmpty()` (`0x8745fbfd`). The two are published side by side in the registry under those exact names; resolve the proxy by name, do not pattern-match a suffix. 2. **A fresh clone grants the owner only `OWNER_ROLE` (1).** Every configuration and execute call from that account reverts `AccessManagedUnauthorized(address)` (`0x068ca9d8`) until the roles are granted through the vault's access manager: `ATOMIST_ROLE` (100) first, because it administers the others, then `FUSE_MANAGER_ROLE` (300) for configuration and `ALPHA_ROLE` (200) for `execute`. 3. **Register all three before the first `execute` on a market:** `add_fuses([...])`, `grant_market_substrates(market_id, [...])` and `add_balance_fuse(market_id, balance_fuse)`. Any order between them works; what is not optional is that all three precede `execute`. `execute` on a market with no balance fuse reverts `AddressEmptyCode(address)` (`0x9996b315`) with the zero address; an action outside the granted substrates reverts inside the fuse. A flash-loan fuse needs one more step: the lender calls back into the vault mid-`execute`, which reverts `HandlerNotFound()` (`0x4bf4de4e`) unless `update_callback_handler(handler, sender, selector)` registered a handler for that caller and selector. Morpho Blue (`MorphoFlashLoanFuse`) calls `onMorphoFlashLoan(uint256,bytes)`, served by `CallbackHandlerMorpho`; the Euler EVC calls `onEulerFlashLoan(bytes)` from an `EulerV2BatchFuse` batch, served by the Euler callback handler. Take the handler address from the chain's `addresses.json` in ipor-abi. 4. **A fresh clone is private.** `deposit` and `mint` revert `AccessManagedUnauthorized(address)` (`0x068ca9d8`) until either the depositor holds `WHITELIST_ROLE` (800), which keeps the vault private, or an Atomist calls `convert_to_public_vault()`, which opens deposits to every address and cannot be reverted. A vault run by its owner's own bot whitelists the depositor; only a vault that takes outside money goes public. 5. **Fuses are immutable and addresses are per chain.** A fuse cannot be upgraded; new strategies are composed from already-registered fuses, and a new fuse is needed only for an action no registered fuse covers. Factory, fuse and token addresses differ on every chain and so may the registry names for the same role: Base publishes both `BalanceFuseAaveV3` and `AaveV3WithPriceOracleMiddlewareBalanceFuse`, Arbitrum only the latter, so a name that resolves on one chain may not exist on the next. Resolve `(chain, name)` in `ipor-abi`; never reuse an address across chains. 6. **`.send()` signs locally and needs a private key in the `Web3Context`; `.call()` previews without one.** Sending through a context built with `signer=` alone raises `ValueError("Private key required for sending transactions")`. ## Common errors | Selector | Revert | What happened | Fix | |---|---|---|---| | `0x8745fbfd` | `DaoFeePackagesArrayEmpty()` | `clone()` was sent to `IporFusionFactoryImpl` | Send it to `IporFusionFactoryProxy` for that chain | | `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 | `AccessManager.grant_role(role, account, 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()` on a `Web3Context` without a key | `Web3Context(w3, chain_id, signer=..., private_key=...)` or `Web3Context.from_url(url, private_key=...)` | ## Quickstart The path from nothing to a vault with a live position is eleven transactions, all signed by the account that will own the vault. There is no shortcut in the SDK: every one of them is mandatory. The invariants (`fusion://invariants`) say what reverts when a step is skipped. Copy the walk and change only the addresses. ## Install ```bash pip install ipor-fusion # SDK (Python 3.11+) curl -L https://foundry.paradigm.xyz | bash && foundryup # anvil, for fork tests anvil --fork-url --chain-id 8453 # then point the SDK at :8545 ``` The hosted read-only server at `https://mcp.ipor.io/mcp` and the bundled `fusion-mcp` server inspect vaults; neither deploys, configures, deposits or executes. Those go through the SDK below. ## The walk: clone, roles, market, access posture, deposit, execute Base, USDC into Aave V3. Executed end to end on a Base fork with this SDK: all eleven transactions land, the vault's idle USDC goes from 0 to the deposit through `deposit()`, and its aToken balance rises after `execute`. Swap the four addresses for another chain or market; keep the order. ```python import os from eth_account import Account from web3 import Web3 from ipor_fusion import ( ERC20, AccessManager, FusionFactory, IporFusionMarkets, PlasmaVault, Roles, Web3Context, ) from ipor_fusion.fuses import AaveV3SupplyFuse # A Base RPC, or an anvil fork of Base: anvil --fork-url --chain-id 8453 w3 = Web3(Web3.HTTPProvider("http://localhost:8545")) OWNER_PRIVATE_KEY = os.environ["OWNER_PRIVATE_KEY"] # .send() signs locally; .call() previews need no key owner = Account.from_key(OWNER_PRIVATE_KEY).address # owns, configures and operates the vault ctx = Web3Context(w3, chain_id=8453, signer=owner, private_key=OWNER_PRIVATE_KEY) FACTORY_PROXY = "0x1455717668fA96534f675856347A973fA907e922" # IporFusionFactoryProxy, Base USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" SUPPLY_FUSE = "0x26fD6EF391E98C78CfCA27e00c3d15be4D941625" # SupplyFuseAaveV3, Base BALANCE_FUSE = "0xf53f3EaFfDf67539256365cA7299540A98b60BA9" # BalanceFuseAaveV3, Base # 1. Deploy, then read the addresses actually created from the mined receipt. # Do not use a prior .call() preview: another clone can consume those CREATE # addresses before this transaction lands. factory = FusionFactory(ctx, FACTORY_PROXY) clone = factory.clone( asset_name="My Vault", asset_symbol="MV", underlying_token=USDC, redemption_delay_seconds=0, owner=owner, ) receipt = clone.send(ctx) instance = factory.decode_clone_receipt(receipt) vault_address = instance.plasma_vault access_manager_address = instance.access_manager # 2. Roles. The clone grants `owner` only OWNER_ROLE. ATOMIST first: it administers the rest. am = AccessManager(ctx, access_manager_address) am.grant_role(Roles.ATOMIST_ROLE, owner, 0).send(ctx) am.grant_role(Roles.FUSE_MANAGER_ROLE, owner, 0).send(ctx) am.grant_role(Roles.ALPHA_ROLE, owner, 0).send(ctx) # 3. Market, in this order: fuses -> substrates -> balance fuse. vault = PlasmaVault(ctx, vault_address) vault.add_fuses([SUPPLY_FUSE]).send(ctx) usdc_substrate = bytes(12) + bytes.fromhex(USDC[2:]) # address right-aligned in bytes32 vault.grant_market_substrates(IporFusionMarkets.AAVE_V3, [usdc_substrate]).send(ctx) vault.add_balance_fuse(IporFusionMarkets.AAVE_V3, BALANCE_FUSE).send(ctx) # 4. Access posture. A fresh clone is private and deposit() reverts until one of these. am.grant_role(Roles.WHITELIST_ROLE, owner, 0).send(ctx) # (a) own bot: whitelist the depositor # vault.convert_to_public_vault().send(ctx) # (b) outside money: ATOMIST-only, one-way # 5. Fund and run the first strategy step. The depositor must already hold the USDC. amount = 1_000 * 10**6 ERC20(ctx, USDC).approve(vault_address, amount).send(ctx) vault.deposit(amount, owner).send(ctx) vault.execute([AaveV3SupplyFuse(SUPPLY_FUSE).supply(asset=USDC, amount=amount)]).send(ctx) ``` Notes on the walk: - `clone` versus `clone_supervised`: same arguments; the supervised variant is gated by a maintenance role. Use `clone`. - The balance fuse above is the `BalanceFuseAaveV3` entry of the registry for Base and is verified working on a fresh clone. Vaults already in production may register a different one for the same market; to match them, read `PlasmaVault(ctx, address).get_balance_fuses()` on such a vault. - Substrate layouts are market-specific. For Aave V3 the substrate is the asset address right-aligned in 32 bytes; other markets use typed layouts, decoded by `ipor_fusion.substrates` and documented in the fuse libraries of the contracts repository. - Give the bot its own key: grant `ALPHA_ROLE` to the bot's address instead of `owner`, and keep the owner key offline. ## Factory proxies per chain `clone()` goes to the proxy column. The implementation column is listed only so you recognise and avoid it. Verify against `ipor-abi` before use; the registry is the source of truth and is regenerated on release. | Chain | Chain id | `IporFusionFactoryProxy` (use) | `IporFusionFactoryImpl` (reverts) | Verified | |---|---|---|---|---| | Ethereum | 1 | `0xcd05909C4A1F8E501e4ED554cEF4Ed5E48D9b852` | `0xf19C1E9f6616F6056AF1e322A86fDaAaAf0263f5` | implementation slot | | Unichain | 130 | `0xC9b4E839e86284C558791C13Bda97999fc49aF63` | `0xf5Bc40C192D51ED93ADD6557884766772FA72589` | implementation slot | | TAC | 239 | `0x26b5561E2E0e7d7977431E92740903Ef3cB7194b` | `0x7DDd0bBB59eFB4b7791cE5E1Cf1587F083aE4Ef5` | implementation slot | | Botanix | 3637 | `0x7c9857f22D0cc6523C8e88BFDCbf411AB8300fF5` | `0xddb0EFB2a7a5C02e01b5A1f85C0a1110d68EeC26` | registry only | | Base | 8453 | `0x1455717668fA96534f675856347A973fA907e922` | `0x610152A79BE7F2Aa3aA70520c9331c18fe8D33b7` | clone executed on a fork | | Plasma | 9745 | `0x70d41759FBF90fB63c88197217cB08F93a1c65CB` | `0x3A42bD6fEa94421025Ae2ed01CE6f137ac785abD` | implementation slot | | Arbitrum | 42161 | `0x134fCAce7a2C7Ef3dF2479B62f03ddabAEa922d5` | `0x87f94ac9aF79261F0BC73582114f805F55Cd0b25` | clone executed on a fork | | Avalanche | 43114 | `0xa00b6379833D77fA9C9497b32dae00dF39Ac751e` | `0x399596cFccFEbBb60f8652bece8586a6Bb4199ab` | implementation slot | | Ink | 57073 | `0xEC53f69Bd1D991a2F99e96DE66E81D0E42A61D8D` | `0xe7372BC46b79e0c87AfE5286c41c20687018E425` | implementation slot | | Katana | 747474 | `0xc29b8D591d6a3f109Ca7ba384F2e00162866D37B` | `0x4a4De6e86AD0d0546F55B595cA75f55803D940bA` | registry only | "Implementation slot" means the proxy's EIP-1967 implementation slot was read and matches the registry's `IporFusionFactoryImpl`; "registry only" means the pair was taken from `ipor-abi` without an on-chain read. Fuse addresses are not in the SDK. Resolve them by registry name and chain in `ipor-abi` (`SupplyFuseAaveV3`, `BalanceFuseAaveV3`, ...), or with the hosted server's `fusion_address_lookup(name, chain_id=...)`. The SDK class for a registry name swaps the parts: `SupplyFuseAaveV3` is `AaveV3SupplyFuse`. ## Pointers - Documentation: https://docs.ipor.io/build-on-fusion - SDK: https://github.com/IPOR-Labs/ipor-fusion.py (PyPI `ipor-fusion`); `tests/test_simulate_vault_from_scratch_base.py` runs this walk in one `eth_simulateV1` batch - Contract ABIs and per-chain addresses: https://github.com/IPOR-Labs/ipor-abi - Contracts: https://github.com/IPOR-Labs/ipor-fusion - A strategy bot that operates an existing vault: https://github.com/IPOR-Labs/ipor-fusion-alpha-example - Read-only inspection of any live vault, no key needed: `https://mcp.ipor.io/mcp`. The `fusion-mcp` server bundled with the SDK (`pip install 'ipor-fusion[mcp]'`) serves this guide as the resources `fusion://glossary`, `fusion://architecture`, `fusion://invariants` and `fusion://quickstart`# SDK > Install, connect and execute with the Python SDK; the CLI, MCP server, agent skills, examples and module reference. The official Python SDK for IPOR Fusion (`pip install ipor-fusion`; [PyPI](https://pypi.org/project/ipor-fusion/), [source](https://github.com/IPOR-Labs/ipor-fusion.py)). Use it to create vaults, construct contract calls, encode calldata and submit transactions through a configured signer — the only one of Fusion's public interfaces that can write, not just read (see MCP and the API for read-only surfaces). For the full first-vault walkthrough, see Deploy your first Fusion vault. For revert selectors and the role table, see Common errors. ### Install ```bash pip install ipor-fusion ``` ### Connect and execute ```python from ipor_fusion import Web3Context, PlasmaVault, AaveV3SupplyFuse from web3 import Web3 # 1. Create a Web3 context with your provider and private key ctx = Web3Context.from_url( url="https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY", private_key="0x...", ) # 2. Wrap the PlasmaVault contract vault = PlasmaVault(ctx, Web3.to_checksum_address("0xVAULT_ADDRESS")) # 3. Build a fuse action (e.g. supply USDC to Aave V3) fuse = AaveV3SupplyFuse(Web3.to_checksum_address("0xFUSE_ADDRESS")) action = fuse.supply( asset=Web3.to_checksum_address("0xUSDC_ADDRESS"), amount=1_000_000, # 1 USDC (6 decimals) ) # 4. Execute on-chain (execute() returns a Call; .send() signs and submits it) receipt = vault.execute([action]).send() ``` Fuse, factory and manager addresses per chain are published in [`ipor-abi`](https://github.com/IPOR-Labs/ipor-abi) (`mainnet/mainnet--fusion/addresses.json`) — see also Addresses. A fuse must also be registered on the vault; `vault.get_fuses().call()` lists the registered ones. Amounts are raw on-chain integers (`Amount`, `Shares` in `ipor_fusion.types`); the SDK never scales by decimals. ### Read, send, or simulate Every wrapper method returns a `Call` instead of executing immediately. The same `Call` powers all three modes — this is the SDK's read-only-vs-transaction-sending surface: ```python from ipor_fusion import VaultSimulator total = vault.total_assets().call() # eth_call -> Amount, no key needed receipt = vault.execute([action]).send() # signed tx -> TxReceipt, needs a private key payload = vault.execute([action]).calldata # raw bytes for an external signer # Simulate first via eth_simulateV1 (no local node); alpha is the account allowed to call execute() sim = VaultSimulator(ctx.web3, vault=vault.address, alpha=Web3.to_checksum_address("0xALPHA")) sim.observe("before", vault.total_assets()) sim.execute([action]) sim.observe("after", vault.total_assets()) result = sim.run() if result.all_success: print(result.get("after") - result.get("before")) ``` `.send()` signs locally and requires a private key in the `Web3Context`; `.call()` and `.calldata` need none — sending through a keyless context raises `ValueError`. ### CLI: `fusion` The SDK ships a `fusion` CLI for **inspecting** Plasma Vaults from the terminal — it does not deploy, configure or execute; that's the SDK's job above. ```bash # Install with CLI extras (pipx keeps dependencies isolated) pipx install 'ipor-fusion[cli]' # Configure an RPC provider (auto-detects chain ID) fusion config set-provider https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY # Inspect a vault (auto-saves to config on first use) fusion vault info 0xB8a451107A9f87FDe481D4D686247D6e43Ed715e --chain-id ethereum # List saved vaults fusion vault list # Inspect a Morpho Blue market or MetaMorpho vault fusion market morpho-blue 0xMARKET_ID --chain ethereum fusion market meta-morpho 0xVAULT_ADDRESS --chain ethereum ``` ### MCP: `fusion-mcp` Two ways to reach Fusion from an MCP-compatible client — see MCP for the hosted server's full tool/resource/prompt reference. - **Hosted, no install:** `https://mcp.ipor.io/mcp` — public, unauthenticated, read-only. - **Local, from the SDK:** `pipx install 'ipor-fusion[mcp]'` ships a `fusion-mcp` server that exposes the same CLI over MCP, against your own RPC providers and local config, plus the resources `fusion://glossary`, `fusion://architecture`, `fusion://invariants` and `fusion://quickstart`, and the prompts `quickstart`, `deploy_vault`, `analyze_vault`, `trace_oracle_pricing` and `explain_fuse`. ```json { "mcpServers": { "ipor-fusion": { "command": "fusion-mcp", "type": "stdio" } } } ``` ### Agent skills [`skills/ipor-deploy-vault/SKILL.md`](https://github.com/IPOR-Labs/ipor-fusion.py/blob/main/skills/ipor-deploy-vault/SKILL.md) teaches a coding agent the full clone -> roles -> market -> access posture -> deposit -> execute walk, with the invariants and the revert selectors, before it writes vault code — the same text as `fusion://invariants` / `fusion://quickstart` and Common errors, one text with several delivery paths. One install per machine: ```bash # Claude Code -- plugin with the skill and the hosted MCP server /plugin marketplace add IPOR-Labs/ipor-fusion.py /plugin install ipor-fusion@ipor-fusion # Codex CLI, Gemini CLI, Cursor -- the skill; add the MCP server as shown above npx skills add IPOR-Labs/ipor-fusion.py -g -a codex # or gemini-cli, cursor ``` ### Vault construction examples Runnable, canonical examples for building and configuring a vault from scratch live in [`examples/`](https://github.com/IPOR-Labs/ipor-fusion.py/tree/main/examples). They preview and simulate through `eth_simulateV1` — nothing is ever signed or broadcast. - [Simple Aave V3 supply vault](https://github.com/IPOR-Labs/ipor-fusion.py/blob/main/examples/simple_aave_v3_supply_base.py) — start here for vault creation, role bootstrap, and configuring a single supported market. - [Advanced Euler V2 credit-market vault](https://github.com/IPOR-Labs/ipor-fusion.py/blob/main/examples/advanced_euler_v2_credit_market_base.py) — continue here for composing multiple functional fuses, typed Euler substrates and sub-accounts, and an ordered collateral -> borrow -> repay -> unwind lifecycle. For a strategy bot that operates an existing vault end to end, see [`ipor-fusion-alpha-example`](https://github.com/IPOR-Labs/ipor-fusion-alpha-example). ### Architecture The SDK uses a **fuse adapter pattern**: Fuses encode protocol-specific calls into `FuseAction` objects (pure calldata, no state); `PlasmaVault` batches and executes `FuseAction` sequences on-chain via `execute()` (atomically); `Web3Context` manages provider connections, signing and transaction dispatch; `Call` is the lazy result of every wrapper method. ``` Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call --> .send() / simulate ``` #### Core modules (`ipor_fusion.core`) | Module | Purpose | | -------------------------------------------------------- | ------------------------------------------------------------------------------------ | | `Web3Context` | Provider connection, signing, tx dispatch, gas estimation | | `Call` | Pre-encoded contract call; `.call()`, `.send()`, `.calldata`, `.build_transaction()` | | `PlasmaVault` | ERC-4626 vault — execute, deposit, withdraw, fuse and market configuration | | `VaultSimulator` | Batch `execute` + reads through `eth_simulateV1`, multi-block, no local node | | `FusionFactory` | Deploy a new vault (`clone`, `clone_supervised`) | | `AccessManager` | Role-based access control | | `RewardsManager` | Claim and vest rewards | | `WithdrawManager` | Time-windowed withdrawal requests | | `FeeManager` | Deposit, performance, and management fee configuration | | `PriceOracleMiddleware` / `PriceOracleMiddlewareManager` | Asset price feeds and per-vault overrides | | `ERC20` | Token reads and approvals | #### Supported protocols (`ipor_fusion.fuses`) Aave V3, Morpho, Euler V2, Uniswap V3, Ramses V2, Compound V3, Gearbox V3, ERC-4626, Fluid Instadapp, Merkl, plus a Universal Token Swapper fuse. Full class list in the [README](https://github.com/IPOR-Labs/ipor-fusion.py#supported-protocols-ipor_fusionfuses). #### Supported networks Ethereum, Arbitrum, Base, Unichain, Plasma, Avalanche, Katana, Monad and HyperEVM. ### Development and contributing Contributor and coding-agent instructions (commands, conventions, invariants, domain rules) live in [`AGENTS.md`](https://github.com/IPOR-Labs/ipor-fusion.py/blob/main/AGENTS.md) in the repository.See https://github.com/IPOR-Labs/typescript-examplesSee https://app.ipor.io/fusion# Glossary of Terms | Term | Description | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | AMM (Automated Market Maker) | A dynamic pricing mechanism that acts as the counterparty for all trades. It uses the IPOR Index and market demand to price interest rate derivatives while managing risk for liquidity providers. | | Liquidity Pool (LP) | A collective pool of assets provided by depositors. These assets serve as the counterparty for swaps. LPs earn yield from contract fees, net payouts, and leveraged returns from underlying money markets. | | IPOR Index | The benchmark reference interest rate for DeFi. It is sourced from various credit protocols and published on-chain. It serves as the "heartbeat" of DeFi, providing a risk-free rate for assets like USDT, USDC, DAI, and ETH | | SOAP (Sum of All Payoffs) | A valuation metric used by the AMM to represent the net value of all outstanding derivative contracts in the pool. | | IRS (Interest Rate Swap) | The primary derivative of the IPOR protocol. It allows users to exchange a fixed interest rate for a floating interest rate (or vice versa) without exchanging the underlying principal. | | Payer (Pay Fixed, Receive Floating) | A swap position where the user pays a fixed rate and receives the floating IPOR Index rate. This is typically used to hedge against rising interest rates. | | Receiver (Receive Fixed, Pay Floating) | A swap position where the user receives a fixed rate and pays the floating IPOR Index rate. This is typically used to hedge against falling interest rates. | | IBT (Interest Bearing Token) | A tokenized representation of a position that accumulates interest over time based on the underlying protocol rates. | | Spread | The difference between the fixed rate offered by the AMM and the current IPOR Index rate, used to compensate the pool for taking on risk. | | Liquidation | The process of closing out a position when the collateral no longer meets the required maintenance margin, ensuring the protocol remains solvent. | | IPOR Oracle | The system that delivers the IPOR Index data to the smart contracts on-chain. | | Snapshot | A tool used by the IPOR DAO for off-chain voting and community sentiment tracking. | | Atomist | A strategy designer or curator who designs the vault's investment logic. They define the "investment universe" by selecting specific assets, markets, and actions via Fuses. Typical Atomists include risk curators, asset managers, DAO treasuries, and protocols. See also [here](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-atomist.md). | | Alpha | An off-chain agent (often an automated bot or specialized strategist) responsible for executing a vault's strategy. Alphas monitor market conditions and trigger the rebalancing or movement of assets by calling specific Fuses. They act as the "brain" of the vault but are constrained by the security parameters set in the smart contracts. See also [here](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-alpha.md). | | Fuse | Modular, single-purpose smart contracts that allow a Plasma Vault to interact with external DeFi protocols (e.g., Aave or Curve). Each Fuse is designed to perform one atomic action— such as "deposit," "withdraw," or "claim rewards" — making the system highly composable and easy to extend without upgrading the core vault. See also [here](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fuse.md). | | Substrate | The underlying target market or liquidity venue where capital is actually deployed. While a Fuse is the "connector" (the logic that executes the transaction), the Substrate is the "destination" (the specific financial environment)._Example:_ If a Vault wants to earn yield on Morpho, the Fuse is the smart contract code that knows how to talk to Morpho's functions, while the Substrate is the specific Morpho Market (e.g., the USDC/wETH market) where the funds reside. |# Cross-Chain Vault Integration - Technical Overview > Technical detail behind cross-chain vault integration: NAV attestation and emergency recovery roles. ## 1. Architecture Overview The IPOR Fusion Cross-Chain Vault system extends the core `PlasmaVault` architecture to enable asynchronous, secure, and transport-agnostic capital allocation across multiple blockchain networks. By leveraging the existing modular Fuse system, source-chain vaults can permissionlessly deploy liquidity to destination-chain strategies while maintaining a unified ERC4626 interface for end-users. ### 1.1. Core Design Principles The cross-chain architecture is built upon three foundational pillars: 1. **Hub and Spoke Execution:** User interactions (deposits, withdrawals, and share minting) are strictly isolated to the Source Vault (the Hub). Destination networks host Remote Vaults (the Spokes) which are dedicated exclusively to local yield generation and cannot accept direct user deposits. 2. **Asynchronous Accounting (Fail-Closed):** Cross-chain messaging is inherently asynchronous and subject to transport delays or execution failures. The system implements a "fail-closed" state machine utilizing Epochs and State Versions to track pending transactions. If a message fails, the vault protects user funds by temporarily locking state transitions until the failure is repaired or rescued by a Guardian. 3. **Transport Agnosticism:** The bridging layer is structurally abstracted. The core cross-chain logic does not depend on a specific bridge, allowing the protocol to securely route liquidity using the optimal transport layer (e.g., Stargate or Chainlink CCIP) based on the asset type and destination network. ### 1.2. Component Roles & Relationships The cross-chain flow involves a precise choreography between source and destination components. This separation of concerns ensures that the core `PlasmaVault` logic remains pristine, offloading cross-chain complexity to dedicated adapters. - **Source Vault (`PlasmaVault`):** The primary ERC4626 vault where liquidity providers deposit assets. It holds the canonical state of total assets (NAV), issues shares, and acts as the central command center for the `ALPHA_ROLE` strategist. - **Crosschain Fuses:** Operation and balance fuses natively attached to the Source Vault. For example, a `CrosschainBalanceFuse` tracks the value of assets deployed on remote chains, while a `CrosschainClaimFuse` initiates the harvesting of remote yields. - **Dispatcher:** The remote operator contract deployed on the destination chain. It holds the tracked remote assets and executes authenticated commands. - **Executor:** The vault-specific gateway deployed on the source chain. It acts as the origin point for all cross-chain commands and maintains the source-side accounting ledger. - **Remote Vault (`PlasmaVault`):** A streamlined vault deployed on the destination chain. It receives liquidity from the Dispatcher and utilizes its own local Fuse system to deploy capital into destination-native DeFi protocols. ### 1.3. Transport Layers: Stargate vs. CCIP To maximize security and routing efficiency, the IPOR Fusion Cross-Chain architecture supports multiple arbitrary messaging bridges (AMBs). The initial implementation provides native integration for **Stargate** (built on LayerZero V2) and **Chainlink CCIP**. - **Stargate Transport:** Optimized for highly liquid stablecoin transfers and native asset routing. It utilizes LayerZero's messaging primitives to provide fast, cost-efficient execution. The `StargateCrosschainFactory` handles the deployment. - **Chainlink CCIP Transport:** Utilized for scenarios requiring Programmable Token Transfers combined with maximum security guarantees. CCIP benefits from Chainlink's Risk Management Network, making it the preferred transport for large-scale operations. Configured via the `CcipCrosschainFactory`. ## 2. Smart Contract Components The cross-chain architecture relies on a suite of specialized smart contracts designed to strictly segregate bridging logic from core vault accounting. This modularity ensures that the `PlasmaVault` remains secure and unaware of transport-specific complexities. #### 2.1. Factories (`StargateCrosschainFactory`, `CcipCrosschainFactory`) Factories serve as the shared platform infrastructure responsible for the deterministic deployment and configuration of cross-chain components. There is a distinct factory implementation for each supported transport layer. - **Deterministic Deployment (`CREATE3`):** Factories utilize `CREATE3` to ensure that an Executor deployed on the source chain and its corresponding Dispatcher deployed on the destination chain share the exact same contract address. This same-address pairing is a foundational security guarantee for cross-chain authentication. - **Route and Asset Governance:** Factories maintain the canonical configuration of supported source-to-destination routes, cross-chain gas policies, fee ceilings, and allowed bridge tokens. - **Access Control:** Factory configurations are governed by the platform team (often behind a timelock) and can enforce creator restrictions, allowing only approved addresses to deploy new Executor instances. ### 2.2. Executors The **Executor** is the vault-specific gateway deployed on the source chain. It acts as the origin point for all cross-chain commands and maintains the source-side accounting ledger. - **Immutable Binding:** An Executor is not a shared omnibus account. Upon deployment, it is permanently bound to a single `MANAGER` (the source `PlasmaVault`), a specific asset, and a specific transport layer. - **Source Accounting:** The Executor tracks the business state of capital through different phases: - `idle`: Assets returned to the source chain but not yet claimed by the vault. - `inFlight`: Outbound assets awaiting authenticated settlement on the destination chain. - `settled`: The approved, canonical value of assets deployed remotely. - **Recovery Boundary:** Each Executor maintains its own isolated incident state, ensuring that a failure in one cross-chain lane does not halt operations in another vault's Executor. ### 2.3. Dispatchers The **Dispatcher** is the remote operator contract deployed on the destination chain. It receives instructions exclusively from its same-address Executor counterpart on the source chain. - **Remote Custody:** The Dispatcher holds the "tracked idle" assets on the destination chain as well as the shares issued by remote ERC-4626 vaults. - **Execution Engine:** It decodes authenticated commands (e.g., `DEPOSIT`, `REDEEM`, `REQUEST_SHARES`) and executes them against the allowlisted remote vaults. - **Canonical State Publication:** The Dispatcher exposes a public, monotonic state version and accounting epoch. Off-chain observers read this state to attest to the remote NAV back on the source chain. ### 2.4. Fuses Fuses act as the connective tissue between the source `PlasmaVault` and the Executor, translating standard portfolio manager intents into cross-chain operations. - **Transport Supply Fuses:** Validates vault liquidity and transfers fresh assets to the Executor, initiating an outbound bridging message to the destination Dispatcher. It is also used to trigger asynchronous `RECALL` requests. - **Transport Command Fuses:** Dispatches authenticated business actions (e.g., allocating tracked idle assets into a specific remote vault) to the destination chain. - **`CrosschainClaimFuse`:** A shared, transport-agnostic fuse that pulls returning `idle` assets from the Executor back into the source `PlasmaVault`'s active balance. - **`CrosschainBalanceFuse`:** Calculates the tracked market value of one or more Executors. It aggregates `idle`, `inFlight`, and `settled` ledgers, prices the Executor's asset using the vault's `PriceOracleMiddleware`, and returns the USD-WAD market value for the vault's NAV calculation. ### 2.5. Hooks (`CrosschainNavValidationPreHook`) To protect the integrity of the source vault's share price during cross-chain operations, the system introduces the `CrosschainNavValidationPreHook`. - **Fail-Closed Protection:** If cross-chain messaging stalls, observations expire, or a state-version gap is detected, the `CrosschainBalanceFuse` will revert to protect against pricing inaccuracies. - **Share-Price Gating:** The `CrosschainNavValidationPreHook` catches this "fail-closed" state and blocks all share-price-sensitive operations on the `PlasmaVault` (e.g., `deposit`, `mint`, `withdraw`, `redeem`). - This ensures users cannot enter or exit the vault at a distorted valuation when the remote state is unverified or compromised. ### 2.6. Attestation & Rescue Modules Because cross-chain bridges are asynchronous and cannot directly return synchronous return values, the architecture relies on robust attestation and recovery modules. - **Attestation Library:** Implements a two-role approval process (`PROPOSER` and `APPROVER`) to recognize remote yield. An off-chain observer reads the Dispatcher's state and proposes it on the source chain. The approver validates the state version and freshness before the Executor's `settled` ledger is updated. - **Rescue Module:** A generic, delayed break-glass mechanism built into both Executors and Dispatchers. It allows a `RESCUE_ADMIN` to schedule arbitrary recovery transactions (e.g., recovering stuck tokens). To prevent abuse, all rescue actions are subject to a mandatory 24-hour timelock and can be unilaterally canceled by an independent `RESCUE_GUARDIAN`. ## 3. Substrates & Configuration The IPOR Fusion architecture uses "substrates" as granular permission identifiers. The cross-chain integration extends this system by introducing specific typed substrates that authorize remote execution pathways. This ensures that the portfolio manager (the `ALPHA_ROLE`) cannot arbitrarily bridge funds or interact with unvetted remote contracts. ### 3.1. EXECUTOR Substrates Before a source `PlasmaVault` can interact with an Executor, the vault's governance (`ATOMIST_ROLE`) must explicitly grant it permission using an `EXECUTOR` substrate. - **Structure:** An `EXECUTOR` substrate is a `bytes32` identifier generated by encoding the specific Executor contract address. - **Validation:** Both the `CrosschainSupplyFuse` and the `CrosschainCommandFuse` validate that the target Executor is granted as a substrate on the source vault. - **Purpose:** This prevents the portfolio manager from routing funds to unauthorized bridging contracts or rogue Executors, effectively locking the outbound capital flow to verified pathways. ### 3.2. REMOTE_VAULT Substrates To control where capital is deployed on the destination chain, the source vault uses `REMOTE_VAULT` substrates. - **Structure:** A `REMOTE_VAULT` substrate binds a specific destination `chainId` to a specific `remoteVaultAddress`. This prevents the ambiguity of using the same vault address on the wrong network. - **Source-Side Enforcement:** When the portfolio manager submits a command (e.g., `DEPOSIT`) via the `CrosschainCommandFuse`, the fuse verifies that the target `(chainId, remoteVaultAddress)` is an approved `REMOTE_VAULT` substrate on the source `PlasmaVault`. - **Destination-Side Enforcement:** The remote Dispatcher only executes authenticated commands. Because the command originated from the source vault (which already validated the substrate), the Dispatcher implicitly trusts the target. However, as a secondary defense, the Dispatcher also maintains its own local allowlist of approved remote vaults. ### 3.3. Market ID Assignments In the IPOR Fusion architecture, every integration is associated with a specific `MARKET_ID`. The cross-chain integration utilizes dedicated market IDs to group related Executors and define their dependencies. - **Cross-Chain Market ID:** A unique market ID (e.g., `CROSSCHAIN_MARKET`) is assigned to group all Executors belonging to a specific cross-chain strategy. - **Balance Fuse Association:** The `CrosschainBalanceFuse` is registered against this specific cross-chain market ID. When `PlasmaVault.updateMarketsBalances()` is called, it triggers the balance fuse for this market, updating the aggregate tracked value of all granted Executors. - **Dependency Graph:** The cross-chain market depends on the `ERC20_VAULT_BALANCE` market. This dependency ensures that the vault's local token balances are updated before calculating the cross-chain NAV, preventing double-counting during the `claim` process when assets move from the Executor's `idle` ledger back into the vault's local token balance. ## 4. Execution Flows This section details the lifecycle of capital and operational commands as they move between the source `PlasmaVault` and destination remote vaults. The operations rely on the `ALPHA_ROLE` passing specific `FuseAction` payloads to `PlasmaVault.execute()`. ### 4.1. Deployment & Initialization Before any capital is moved, the lane must be initialized: 1. **Factory Setup**: Shared transport infrastructure is deployed and routes are configured by the platform team. 2. **Executor & Dispatcher Creation**: The factory deploys a vault-specific Executor on the source chain, followed by a deterministically paired Dispatcher on the destination chain. 3. **Source Configuration**: Vault governance (`ATOMIST_ROLE`) registers the transport fuses, balance fuse, claim fuse, and the NAV pre-hook. It also grants the necessary `EXECUTOR` and `REMOTE_VAULT` substrates. 4. **Bounded Canary**: A small, bounded test transaction is executed to verify end-to-end accounting and attestation before full exposure limits are approved. ### 4.2. Cross-Chain Supply (Source to Destination) To move fresh liquidity to a remote network, the portfolio manager initiates a supply action. - **Execution**: The `ALPHA_ROLE` calls `execute()` with a `FuseAction` targeting the transport-specific supply fuse. The fuse verifies live NAV and transfers assets from the `PlasmaVault` to the Executor. - **Bridging**: The Executor dispatches the underlying assets via the configured transport layer (e.g., CCIP or Stargate) to the remote Dispatcher. - **Accounting State**: The assets transition from the vault's local physical balance to the Executor's `inFlight` ledger. They are not considered `settled` until an authenticated destination response completes the accounting transition. ### 4.3. Remote Vault Deposit/Redeem Once assets arrive safely at the Dispatcher, they are classified as tracked `idle` remote assets. The `ALPHA_ROLE` can now allocate them to remote yield strategies. - **Deposit**: The manager sends a `DEPOSIT` command via the `CrosschainCommandFuse`. The source validates the `REMOTE_VAULT` substrate. Once received by the Dispatcher, it deposits the `idle` assets into the allowlisted remote ERC-4626 vault and begins tracking the issued shares. - **Redeem**: The manager sends a `REDEEM` command. The Dispatcher redeems the specified shares, converting them back into tracked `idle` assets. - **Asynchronous Withdrawals**: If the remote PlasmaVault utilizes an asynchronous withdrawal queue, the redemption requires two sequential commands: `REQUEST_SHARES` (entering the queue) followed by `REDEEM_FROM_REQUEST` (claiming the assets once the lockup expires). ### 4.4. Recall and Claim (Destination to Source) Returning capital to the source vault is a managed, two-step asynchronous process to protect against liquidity shocks. 1. **Recall**: The `ALPHA_ROLE` triggers a `RECALL` request through the source supply fuse. The Dispatcher on the destination chain packages the requested `idle` assets and sends them back across the bridge. 2. **Claim**: When the assets arrive at the source Executor, they are added to the source `idle` ledger. Finally, the `ALPHA_ROLE` executes the shared `CrosschainClaimFuse`, which pulls the assets from the Executor back into the source `PlasmaVault`'s active balance, restoring local liquidity. ### 4.5. NAV Attestation Flow Because physical token balances on remote chains cannot be synchronously read, the system relies on an off-chain attestation pipeline to recognize remote yield. 1. **Observation**: The remote Dispatcher exposes a canonical observation containing its `idle` balances and remote vault shares, tied to a monotonic state version. 2. **Proposal**: An off-chain observer prepares this state, and an independent `PROPOSER` submits it to the source Executor. 3. **Approval**: An independent `APPROVER` verifies the state's freshness and version sequencing. Once approved, the Executor's `settled` ledger is updated with the new valuation. 4. **Market Refresh**: Immediately following approval, the source vault's `updateMarketsBalances()` function must be called. This triggers the `CrosschainBalanceFuse` to read the new `settled` value and persist the updated cross-chain market NAV within the `PlasmaVault` cache. Share-price-sensitive operations are then permitted against the fresh valuation. ## 5. NAV and Accounting Internals Accurate Net Asset Value (NAV) reporting is essential for maintaining the integrity of the ERC4626 share price. Cross-chain operations introduce the challenge of asynchronous state. To address this safely, the IPOR Fusion Cross-Chain architecture decouples physical token balances from accounted value and implements strict fail-closed state machines. ### 5.1. Tracked Value vs. Physical Balances The architecture relies strictly on "tracked value" ledgers rather than raw `IERC20.balanceOf()` readings. This design prevents direct, unauthenticated token transfers (e.g., unexpected airdrops or "donations" to the Dispatcher) from instantly distorting the vault's NAV. Value is divided into strict accounting ledgers across the two chain environments: - **Source Executor Ledgers:** - `idle`: Assets successfully returned to the source chain via a recall, but not yet swept into the primary `PlasmaVault` by the Claim Fuse. - `inFlight`: Outbound assets that have left the source chain but are awaiting authenticated delivery confirmation on the destination chain. - `settled`: The last approved, canonical valuation of assets deployed remotely on the destination chain. - **Remote Dispatcher Ledgers:** - `idle`: Unallocated assets physically held by the Dispatcher. - `remote shares`: Tracked share positions in approved ERC4626 remote vaults. - `pending requests`: Tracked shares locked in remote asynchronous withdrawal queues. - `queued response`: Token value queued for an outbound recall response (specifically applicable for CCIP transports). When the `CrosschainBalanceFuse` calculates the market value, it sums the Executor's `idle`, `inFlight`, and `settled` ledgers. It then prices the aggregated underlying asset using the source vault's `PriceOracleMiddleware` to return the precise USD-WAD market value. Remote yield only affects the `settled` ledger after passing through the off-chain attestation pipeline. ### 5.2. The Fail-Closed Mechanism Because cross-chain bridging is asynchronous, transport layers can experience congestion, outages, or delivery failures. To protect liquidity providers from minting or redeeming shares against stale or inaccurate valuations during these periods, the system adopts a "fail-closed" security posture. The `CrosschainBalanceFuse` actively monitors the health of the cross-chain lane. A call to `getBalance()` will intentionally revert if any of the following safety tripwires are hit: 1. Stale NAV: The last approved remote observation exceeds the configured maximum staleness threshold. 2. Stale In-Flight Transfers: An outbound transfer has remained in the `inFlight` ledger longer than the configured maximum settlement time. 3. Blocked Lanes: A state-version gap is detected, indicating that a sequential bridging message was lost or skipped, rendering the current state unreliable. When the balance fuse reverts, it triggers the `CrosschainNavValidationPreHook`. This hook acts as a circuit breaker attached to the source `PlasmaVault`. By catching the fail-closed state, it blocks share-price-sensitive vault entry points: - `deposit` and `depositWithPermit` - `mint` - `withdraw` - `redeem` and `redeemFromRequest` - `updateMarketsBalances` Special Handling for `execute`: Unlike deposits and withdrawals which are strictly blocked, the pre-hook delegates the blocking decision for `execute` to fuse-level controls. This crucial distinction ensures that while new cross-chain exposure is blocked during a fail-closed state, approved recovery and rescue actions can still continue within a narrowly scoped recovery context. ### 5.3. State Versions & Epochs To guarantee that remote observations are processed sequentially and accurately, the accounting system utilizes monotonic identifiers. - **State Versions:** The remote Dispatcher maintains a monotonic `stateVersion`. Every time a value-mutating remote action occurs (e.g., executing a remote `DEPOSIT` or generating yield), this version increments. When proposing a new remote observation back to the source Executor, the off-chain observer must include this state version. The source strictly validates that version numbers are sequential. If a state-version gap is detected (e.g., bridging message #4 arrives but message #3 never settled), the Executor enters a blocked state. - **Epochs:** The `epoch` counter tracks major structural configuration changes (like redefining rescue roles or changing allowed vault parameters). If an observer attempts to attest to a remote balance using an outdated epoch, the `APPROVER` pipeline will reject it, ensuring that old state cannot overwrite new architectural rules. ## 6. Transports and Cross-Chain Messaging The IPOR Fusion cross-chain architecture abstracts the underlying messaging protocols while strictly isolating their execution contexts. To prevent complex failure dependencies, the system does not automatically route between different bridge providers. Instead, a dedicated `Executor` and `Dispatcher` pair is deployed for each specific transport and asset combination. The current implementation natively supports two isolated transport stacks: Chainlink CCIP and Stargate with LayerZero V2. ### 6.1 Transport Layer Comparison While both transports expose a unified interface to the PlasmaVault's supply and command fuses, their underlying mechanics, routing identities, and failure-handling models differ significantly. | **Capability** | **Chainlink CCIP** | **Stargate / LayerZero V2** | | ----------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------- | | Asset Movement | Programmable Token Transfers (via CCIP Router). | Stargate `taxi` transfers with a mandatory `compose` payload. | | Message Transport | CCIP Router messages. | LayerZero V2 OApp messages. | | Route Identity | Immutable CCIP chain selector and registered peer. | Append-only chain ID/EID mapping and same-address peer. | | Remote Responses | Ordered, bounded FIFO response queue with permissionless head flush. | LayerZero delivery/retry execution model. | | Recall Behavior | Token-bearing return response via CCIP. | Request via OApp, Stargate token/compose return. | | Fee Model | Native or configured fee token, bounded by per-call ceilings. | Native token budgets for LayerZero/Stargate sends and responses. | _Note on USDC Operations: Both transports utilize their respective protocol-configured USDC liquidity pools. The current implementation does not integrate native Circle CCTP directly._ ### 6.2 Chainlink CCIP Implementation The CCIP implementation (`CcipCrosschainFactory`, `CcipRouteLib`) uses Chainlink's arbitrary messaging combined with Programmable Token Transfers. FIFO Response Queue & Flushing: Because CCIP strictly orders messages, a failed remote execution on the source chain (e.g., due to an out-of-gas error or logic revert upon receiving an ACK/NACK) can block subsequent messages in the lane. To prevent channel blocking, the CCIP `Dispatcher` implements a bounded FIFO response queue. - If a response fails to process automatically upon delivery, it is queued. - Keepers or users can invoke a permissionless flush of the FIFO head once the underlying issue (e.g., insufficient fee budget) is resolved. - A guardian-vetoable delayed skip is available as an emergency break-glass mechanism if a queued response is permanently unprocessable. Delivery Policy and Fees: Route policies—including fee tokens, gas limits, and fee ceilings—are governed by the factory. Route policy updates utilize a 24-hour configuration delay to protect inflight messages, followed by permissionless synchronization across the lane. ### 6.3 LayerZero V2 & Stargate Implementation The Stargate implementation leverages LayerZero V2's Omnichain Application (OApp) standard for passing arbitrary logic, coupled with Stargate V2 `taxi` transfers for capital movement. Execution and Retries: Unlike the CCIP implementation, LayerZero V2 handles message execution and retries natively at the protocol layer via the `EndpointV2`. - Outbound messages are paid for using native gas budgets. - If a `compose` payload or OApp message fails on the destination, it is caught by the LayerZero endpoint's internal retry mechanism rather than a custom vault-level FIFO queue. - The route mapping relies on EID (Endpoint ID) mappings, which are append-only to prevent historical message corruption. ### 6.4 Message Lifecycle and Delivery Guarantees Both implementations guarantee strict ordering for business commands to prevent race conditions during remote strategy execution. The lifecycle enforces the following rules: 1. One Active Command Limit: The `Executor` allows only one active business or configuration command per destination chain. Subsequent commands revert until the active command receives a terminal response. 2. Sequenced ACK / NACK: - ACK (Acknowledgement): The destination `Dispatcher` successfully executed the command (e.g., deposited into the remote ERC-4626 vault). The sequence is finalized. - NACK (Negative Acknowledgement): The remote execution reverted (e.g., due to slippage bounds or paused remote vaults). The command is returned as failed. 3. Retry and Cancellation: A command that results in a NACK is not automatically discarded. The `ALPHA_ROLE` can explicitly trigger a retry of the failed command or formally cancel it to clear the lane. 4. Command Abandonment: If a command is sent but an ACK/NACK response is permanently lost (due to bridge failure or irrecoverable state), a delayed, vetoable rescue action must be utilized to abandon the command and reconcile accounting. ## 7. Security, Governance, and Access Control The cross-chain architecture extends IPOR Fusion's robust `IporFusionAccessManager` with domain-specific access controls. Because cross-chain operations introduce asynchronous state and bridge-dependency risks, the system strictly separates day-to-day portfolio management from NAV attestation and emergency recovery. ### 7.1 Immutable Vault Binding To prevent omnibus-style commingling of funds, the `Executor` is not a shared router. Upon deployment, the `Executor` is permanently bound to a single source `PlasmaVault` (assigned as the immutable `MANAGER`). - The supply and command fuses actively verify this relationship before forwarding any `FuseAction`. - The destination `Dispatcher` deterministically inherits this isolation, accepting authenticated messages only from its paired `Executor`. ### 7.2 Attestation Roles: Proposer and Approver To prevent a single compromised key from artificially inflating the source vault's Net Asset Value (NAV), remote yield recognition requires a two-role attestation process on the `Executor`: - `PROPOSER_ROLE`: Responsible for querying the remote `Dispatcher`, structuring the canonical observation, and proposing it to the `Executor`. - `APPROVER_ROLE`: Independently verifies the proposed state (including state versions, aggregate vault shares, and tracked idle assets) and approves it. _Note: The smart contract enforces that the `PROPOSER_ROLE` and `APPROVER_ROLE` must be distinct addresses. Approvals are subject to configurable freshness thresholds and large-change rate limits._ ### 7.3 Recovery Roles: Admin and Guardian The generic rescue mechanism provides a powerful break-glass authority to execute arbitrary calls from the `Executor` or `Dispatcher` during critical bridge failures. To secure this, control is bifurcated: - `RESCUE_ADMIN`: Schedules and executes delayed recovery actions. This role operates as a full delayed administrator and should be assigned to a multisig or timelocked governance contract. - `RESCUE_GUARDIAN`: Possesses the authority to veto and cancel one or all pending rescue actions. _Note: The `RESCUE_ADMIN` and `RESCUE_GUARDIAN` must be independent addresses. The system natively enforces a strict minimum 24-hour execution delay on all scheduled rescues._ ### 7.4 PlasmaVault Configuration and Substrates On the source `PlasmaVault`, cross-chain interactions are authorized by the `ATOMIST_ROLE` using the standard `PlasmaVaultGovernance` interface. Integration requires granting typed substrates via `CrosschainSubstrateLib`: 1. Executor Substrates: Granted as `EXECUTOR(executorAddress)` to authorize the vault to supply capital to the bridge. 2. Remote Vault Substrates: Granted as `REMOTE_VAULT(chainId, vaultAddress)` to explicitly allowlist destination ERC-4626 vaults. The `Dispatcher` will reject any `DEPOSIT` command targeting a vault not explicitly granted by the source. ## 8. Failure Handling and the Rescue Module Cross-chain messaging protocols are subject to latency, message loss, and destination-chain reversions. The IPOR Fusion cross-chain architecture is built to gracefully handle `NACK` (Negative Acknowledgement) responses, state desynchronization, and permanent channel blocks. ### 8.1 Structural Safety Bounds To prevent out-of-gas errors and limit theoretical exposure during incident response, the contracts enforce strict structural bounds: - Max Remote Vaults: Maximum 32 approved/tracked remote-vault positions per `Dispatcher`. - Max In-Flight Transfers: Maximum 16 concurrent in-flight transfer records per chain where the transfer-slot model applies. - Active Commands: Only 1 active business or configuration command, and 1 active recall permitted per destination chain at any time. - FIFO Bounds: Maximum 64 queued responses in the CCIP `Dispatcher`. - Stargate Chain Limits: Maximum 32 registered Dispatcher chains in the Stargate Executor. - Rescue Queue: Maximum 64 pending rescue records. ### 8.2 Command Failures and Retries If a remote execution fails (e.g., due to slippage bounds in the remote ERC-4626 vault or a paused protocol), the destination returns a `NACK`. The active command sequence remains open on the source `Executor`. - Retry: The `ALPHA_ROLE` can adjust parameters (if applicable) and trigger a retry of the failed command. - Cancellation: The `ALPHA_ROLE` can formally cancel the failed command, resetting the lane and moving the allocated capital back to the tracked remote idle ledger. ### 8.3 State-Version Gaps and Frontier Repair The `Dispatcher` increments a monotonic state version on material accounting transitions. If the source `Executor` receives an observation where `$v_{remote} > v_{source} + 1$`, it indicates a lost or permanently stalled message. - This immediately triggers the fail-closed NAV protection, halting share-price-sensitive operations. - Operators must utilize the Frontier Repair mechanism. This allows the system to advance the source state version (up to 32 versions per action) after a 6-hour cooldown, forcefully reconciling the accounting ledgers and allowing fresh attestations to resume. ### 8.4 The Rescue Module When standard retry or cancellation paths are insufficient (e.g., unexpected token surpluses, permanent bridge blacklisting, or lost outbound settlement evidence), the `RescueModule` is invoked. Tracked-Ledger Protection: While the `RescueModule` can execute broad calls, it is constrained by a strict accounting invariant. The `RescueModule` cannot transfer or rescue tokens that are actively accounted for in the `Executor` or `Dispatcher` tracked ledgers (Settled Value, In-Flight, or Tracked Idle). It can only be used to extract untracked fee dust or unexpected airdrops/donations. If tracked backing must be manually manipulated due to a catastrophic bridge failure, it requires a fully governed, delayed force-resolution path that explicitly declares the final accounting outcome, subject to the `RESCUE_GUARDIAN` veto.# Introduction to Fusion ## Professional DeFi Asset Management Simplified IPOR Fusion is a professional-grade asset management layer that provides **one-click access to sophisticated DeFi strategies**. It acts as an intelligent gateway to the broader DeFi ecosystem, offering a non-custodial and transparent way to grow your capital using institutional-grade infrastructure. By depositing into a Fusion vault, you delegate the complexity of yield optimization to strategists ("[Atomists](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-atomist.md)") who actively manage your assets across protocols in the ecosystem. ## How It Works Fusion is designed to make participating in advanced DeFi strategies as simple as using a standard "Earn" button: 1. **Select a Strategy:** Choose a vault based on your risk profile and desired underlying asset (e.g., USDC or ETH). 2. **Provide Liquidity:** Deposit assets to receive vault shares. These shares represent your ownership of the pool and the yield it generates. 3. **Passive Management:** Professional strategists ("[Alphas](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-alpha.md)") manage the portfolio behind the scenes, e.g. automatically rebalancing funds across different DeFi protocols to capture optimal returns. 4. **Withdraw When You Need To:** Monitor your growth in real time. Withdraw instantly when the vault holds enough liquid assets, or through a scheduled withdrawal (see [Withdrawing](https://ipor.io/docs/fusion-for-depositors/user-guide/withdrawing.md)). ## Core Features - **Expert Management:** Benefit from active management by professional strategists. - **Universal Access:** Tap into liquidity across all major DeFi protocols from a single entry point. - **Cost-Efficient Management:** Strategy execution and rebalancing are handled by the vault, meaning depositors don't pay direct gas costs for complex portfolio operations. - **Total Transparency:** Every strategy move and performance metric is verifiable on-chain 24/7. > **Want to understand the specific advantages?** Check out the [TL;DR: Why Fusion?](https://ipor.io/docs/fusion-for-depositors/why-fusion.md) page. > > **Ready to dive in?** Explore the [available vaults](https://app.ipor.io/fusion).# Risks While no protocol is entirely risk-free, the Fusion infrastructure has implemented [extensive measures](https://ipor.io/docs/fusion-for-depositors/why-fusion/security-features.md) to minimize potential issues. The protocol's code is publicly available, has undergone multiple smart contract [audits](https://ipor.io/docs/fusion-for-depositors/why-fusion/security-features.md), and is subject to an ongoing bug bounty campaign and specialized technical reviews. Key risk categories and their corresponding mitigation strategies are detailed below. For more in-depth information, refer to the security and audits sections. | Risk Category | Description | Mitigation Strategy | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Smart Contract Risk | Potential for bugs or vulnerabilities in the Fusion Protocol code or underlying reserve tokens. | Audits and Governance: Code is public, audited by multiple firms, and any changes require thorough vetting and community approval via onchain governance. An ongoing bug bounty program is also in place. | | Oracle Risk | Reliance on third-party data providers (oracles) for price feeds and other external data (e.g., liquid staking token redemption ratios). A compromised or failed oracle could lead to incorrect asset valuations. | Decentralized Oracles: Uses robust, tamper-resistant decentralized oracle solutions, such as Chainlink, for reliable data feeds. | | Strategy Risk | Inherent in DeFi vaults, involving potential losses from the failure of underlying investment strategies defined by Atomists. This includes poorly performing strategies, improper asset management, and external risks from interacting with other protocols (e.g., impermanent loss, liquidation, exploits). | Transparency and Oversight: All Atomist-defined investment strategies are publicly visible, allowing for continuous scrutiny by the community, security experts, and contributors. | | Network Risk | Issues related to the underlying blockchain networks on which Fusion operates, such as congestion, censorship, or security vulnerabilities. | Robust Onboarding Framework: Fusion Governance employs a rigorous network onboarding framework to vet new networks before integration, with all integration decisions overseen by community-based onchain governance. |# Executive Summary: The Prime Brokerage Protocol of DeFi IPOR Fusion is **institutional grade vault infrastructure**, a modular, onchain prime brokerage protocol designed for regulated asset managers and institutional allocators. It provides an entire suite of vault technology and tools for managing digital asset strategies with institutional discipline, enabling segregated mandates, programmable risk controls, and transparent exposure verification. Unlike monolithic vault systems, Fusion utilizes a **mandate-adaptive architecture** that separates the **immutable non-custodial layer** from the configurable strategy layer. This allows institutions to extend their investment capabilities by adding markets or adjusting risk parameters without the operational risk of vault redeployment or opaque core contract upgrades.See https://deepwiki.com/IPOR-Labs/ipor-fusionSee https://deepwiki.com/IPOR-Labs/ipor-fusion.py# What is IPOR Fusion? A Technical Overview > A deep dive into IPOR Fusion’s architecture, value propositions, use cases, security principles, and more. Source: https://ipor.io/blog/what-is-ipor-fusion-a-technical-overview-114ccd67dfcf Published: 2024-05-29 Technology · Wookash · 15 min read > IPOR Fusion is a yield optimization framework for automated execution of smart asset management on-chain, saving users time and effort. It is an unopinionated and customizable infrastructure fund managers can use to deploy assets on-chain while implementing custom algorithms off-chain. Smart contracts ensure transparent allocation of assets on-chain, while off-chain agents (generally called Network Keepers or _Alphas_ in the context of Fusion) allow strategists to design custom algorithms or implement logic for allocating and rebalancing assets. The architecture is modular, built with flexibility and composability in mind. It consists of three core elements: 🔶 **Fusion Vaults** (ERC4626 compliant) accept deposits and put capital to work by deploying it in a series of strategies that will be dynamically managed by Alpha Keepers (Alphas for short); 🔶 **Fuses** are small contracts that are attached to Fusion Vaults to perform atomic actions on integrated protocols; 🔶 **Alphas** are the off-chain agents or bots responsible for maintaining the strategy operations of a specific vault. They run off-chain automated software to optimize asset allocation by calling functions on Fuses. > The ability to add and remove fuses is what makes Fusion Vaults a key component of a yield seeker’s toolbox, allowing fund managers to adapt and thrive in a variety of market conditions. Fuses can be added and removed at will, while the actual yield algorithms live off-chain and can be adjusted at any time. Fuse-based design is not only flexible but also safe. Fuses hold no balances and all assets or liabilities are owned directly by the vault. > Each individual vault can maintain its own risk profile without affecting others. This is instrumental when vaults are involved in credit markets and must maintain individual LTV ratios or anywhere else where one might be subject to liquidation. ![Illustration from “What is IPOR Fusion? A Technical Overview”](https://ipor.io/_astro/image-01.DQ5kxvnA_1yLatJ.webp) ## 1️⃣ Value Proposition > Fusion addresses the need for automation of both simple and complex DeFi strategies in a transparent permissionless manner. Thanks to the unique architecture, vaults can use leveraged positions without sharing the risk with other vaults. Some of the example strategies possible to be created and automated are: ⚡ **Asset allocation** to primary credit markets (AAVE/Compound/Euler/Morpho/Fluid etc) as well as secondary markets (Yearn, MetaMorpho, etc.); ⚡ **Leveraged looping** — with the use of flash loan or by using dedicated leveraged borrow functionalities provided by some of the credit markets; ⚡ **Spot/Perp trading to capture basis** (similar to Ethena). Automation for the vaults includes not only liquidation protection but also active portfolio management and yield optimization that considers efficient capital allocation, gas price, slippage, and even trends and market correlations. > Because the logic for automation lives off-chain, it can leverage complex data structures, heavy computation, and even AI. At the same time, fuses limit what the Alphas can do, preventing arbitrary transactions from being executed. Economies of scale: - Multiple LPs can pool their assets into the vault and save gas costs when interacting with DeFi protocols; - A single fuse can be used by multiple vaults. The more fuses the more options each vault has to seek yield; - Alphas can automatically support the integrated fuses for all the vaults. ## 2️⃣ Architecture Overview > The goal when designing IPOR Fusion architecture was to maximize flexibility while ensuring transparency and security. Those requirements led to a structure, where ERC4626 vaults can interact with external protocols via atomic fuses. > Only the actions defined in the Fuse can be executed by an Alpha. This ensures that no arbitrary functions can ever be run to manage the assets deposited in the vault. From the security perspective, it is crucial that automation is constrained by predefined rules. Otherwise, malicious transactions could be executed and only discovered once they are completed. > Various strategies can be created depending on which Fuses are connected and how they are configured at the vault level. Because all Fuses share a common interface, existing vaults can be integrated with an ever-expanding portfolio of Fuses. The infrastructure is flexible enough to allow for the development of both: - Low-risk stablecoin funds; - High-risk leveraged farms. The illustration below demonstrates how various Fusion elements work together: ![Illustration from “What is IPOR Fusion? A Technical Overview”](https://ipor.io/_astro/image-02.Db13LhVk_Z1l0PuT.webp) 1. Users can deposit assets in multiple vaults. 2. Each vault is connected to various markets via Fuses. 3. An Alpha monitors the markets with connected Fuses so that, when a vault is configured with compatible Fuses it can instantly be managed. The Alpha decides on the allocation of the assets across connected markets according to an appointed strategy (read below) and prepares transactions that can be executed on the vault. ## 3️⃣ Challenges Automation of strategies in DeFi is not exactly a new thing. Thus far, however, there has been a need to make some compromises when automating. - If you want to be efficient, you need to sacrifice the transparency. - If you need permisionlessness, then you need to sacrifice either transparency or flexibility. - If you want transparency, you need to compromise on performance and the amount of computation you can afford. - Greater flexibility calls for compromise on security. - Speed of development means you must compromise on transparency and possibly even security. IPOR Fusion’s approach is to limit what actions can be taken by the Automation. Instead of allowing any kind of interaction between the vault and an external market, Fusion opts for the Fuses as a means to enforce security. This supposed limitation of flexibility is overcome by creating a Fuse that can provide any desired functionality safely. This approach allows different vaults to interact with the same protocols in different ways; limiting the risk and exposure to levels acceptable by the liquidity providers (LPs). For example, one vault may be interested in depositing blue chip tokens to AAVE but not borrowing, another one may want to borrow and lever up using exotic assets as collateral. The first would use AAVE deposit Fuse, the second one would utilize AAVE borrow and DEX Fuses. ![Illustration from “What is IPOR Fusion? A Technical Overview”](https://ipor.io/_astro/image-01.DQ5kxvnA_1yLatJ.webp) ## 4️⃣ The Nature of the Fuse > Fuses are one of three pillars that Fusion stands on. Fuses are the remedy providing permissionlessness, transparency, security, and speed of development. All Fuses must meet the following requirements: - **Stateless** — They have no memory, no configuration, and they store no data. Thanks to that they can be used by multiple vaults at the same time without the risk of them interfering with one another. - **Non-upgradable** — Fuses, by nature, are simple contracts that can not be altered. That assures Vault’s actions can be trusted and Fuses can be shared without additional risk. Should the destination market’s ABI change, a new Fuse needs to be deployed. - **Standard ABI** — One of the tasks of the Fuse is to bring the interface of the market down to a standard ABI. Fuses implement two core functions: Enter and Exit that, depending on context deploy or withdraw assets, take a loan or repay it, etc. On top of that some Fuses can implement instantWithdraw — a function with predefined params to pull the assets from the market without the need for Alpha’s interaction. This is relevant if the Vault allows for instant redemptions of shares. - **Configurability** — because Fuses are stateless, any configuration is delegated to the vault. The Fuse is built to accept the calls with the configuration. > Fuses can be built and used by anyone. The IPOR DAO will provide a testing framework (see below) along with a DAO governance process that will help expedite the process of verifying the security of newly created Fuses so that the owners of the existing vaults can focus on expanding and evolving the strategies as the DeFi landscape evolves and new possibilities appear. ![Illustration from “What is IPOR Fusion? A Technical Overview”](https://ipor.io/_astro/image-03.BD6Z2V8F_ZN2KG5.webp) ## 5️⃣ How do Fusion Vaults Work? ### Bootstrapping — Basic Configuation Atomists can use the IPOR Fusion Factory to create a vault by configuring and deploying the necessary smart contracts. ### 🔸 Define Permissions Fusion vault can either be open or restricted. You, as an Atomist can decide, who can deposit tokens to your vault. There are 2 options: - Open vault — anyone can deposit. - Restricted vault — only user(s) on the predefined whitelist can deposit ### 🔸 Select Denomination Asset The asset that can be deposited or withdrawn from the vault. All accounting and APRs are calculated using this asset as a reference. ### 🔸 Set the Timelock Any configuration change after the vault creation would be subject to a waiting period before the changes would be enforced. ### 🔸 Choose a redemption strategy - **Instant** — useful if you need to be able to withdraw the funds without notice. For example, when your Vault is holding idle assets from another protocol. - **Asynchronous** — shares can be redeemed in two steps. First: request redemption allowing Alpha to prepare the assets, and second: actual redemption. ### 🔸Select Fuses and Define the Strategy Depending on which actions you want to allow Alpha to perform, you will have to select a matching set of Fuses. For example: if you would like the vault’s assets to be deposited to AAVE V3 and Compound V3; you would need AAVE V3 and Compound V3 deposit Fuses. If you would like Alpha to also be able to leverage loop on those credit markets you would additionally need a Flash loan Fuse (special kind of Fuse not discussed in this article), Swap Fuse (special kind of Fuse not discussed in this article), and Fuses to take a loan on both AAVE and Compound. With this array of Fuses, Alpha will be able to lever up, deleverage, and rebalance between 2 markets. If the looping is not profitable it can revert to depositing collateral without taking loans. The second step in defining a strategy is setting the configuration of the Fuses through the Fusion Vault. Settings related to Fuses are fairly limited and should be treated as hard limits used for validations. For example: The Atomist can define the maximum level of deposit to a particular market. If the level of deposits is met, then Alpha can no longer make allocations to that market, and it can only decrease the size of the position. ### 🔸Appoint and Configure an Alpha IPOR Fusion is an automation-first type of solution, intended to be managed via a dedicated engine. IPOR Labs-operated Alphas can be used along with any other Alphas operated either by yourself or a 3rd party that you trust. You can also appoint an externally owned account (EOA) or multisig as Alpha however that means the vault would be managed manually. ### 🔸Appoint a Paymaster In case Alpha is run by a party not directly associated with the Atomist and operates in a high-gas-cost environment such as Ethereum mainnet, the Alpha is limited to preparing a transaction, the execution and gas payment are outsourced to the so-called paymaster. ### 🔸Set Fees Fusion can facilitate charging two kinds of fees: - Management — charged as an annualized percentage of assets under management (AUM). - Performance — charged as a percentage of the positive yield generated by the vault Both kinds of fees can be split between the Atomist and IPOR DAO. Management of fees is done by a dedicated contract deployed when creating a vault — FeesManager. ## 6️⃣ Example Use Cases ### 🔸 TradFi Fund - Creates restricted vault by whitelisting only one account that is allowed to deposit. - Keeps the vault private by opting out from showing the Fusion Vault on the IPOR Fusion web interface. - Selects a list of Fuses to connect to blue-chip primary markets. - Selects IPOR Labs Alpha and sets the configuration to run a conservative yield optimization strategy. ### 🔸 DAO Treasury - Creates a restricted Fusion Vault by whitelisting only one account that is allowed to deposit. - Selects the “instant withdraw” option. - Opts in for making the vault visible so that protocol users can verify how the assets are allocated in real time. - Selects a list of Fuses to connect to blue-chip primary markets. - Selects IPOR Labs Alpha and sets the configuration to run a conservative yield optimization strategy. - Sets low or no fee. - Implements timelocks and governance layer. ### 🔸 DeFi-native manager - Creates an open Fusion Vault. - Opts in for making vault visible so that LPs can verify how the assets are allocated in real-time. - Selects a list of Fuses to connect to primary credit markets, DEXes and Perp markets to run looping or hedging strategy. - Sets competitive management and performance fees. - May consider running their own Alpha. - Implements timelocks without governance layer. - Advertises the vault and its performance to grow the AUM. - Frequently changes the Fuses and adapts the strategy to stay on the forefront of DeFi evolution. ![Illustration from “What is IPOR Fusion? A Technical Overview”](https://ipor.io/_astro/image-04.B5z6LZ15_ZRtXDm.webp) ## 7️⃣ Updating the Configuration The general rule is that all the settings can be changed by the Atomist but there are a few exceptions: - The denomination asset, symbol, and name can’t be changed. - The redemption strategy is not changeable. - If the vault has been made open to users, it can not be restricted after. > All the changes in the opened vaults can be subject to the timelock before coming into effect. It’s worth mentioning that each edition of the configuration emits an event that is picked up by the Alpha appointed by the vault allowing it to reconfigure itself to the new params. Any alpha-specific settings can be rolled into this event so that they can live on-chain and be fully transparent. ## 8️⃣ Executing Transactions Transaction preparation and execution sets the IPOR Fusion automation apart from other solutions. Each Alpha appointed to manage the vault needs to implement several components: - Transaction SDK — to be able to integrate with the Vault and Fuses easily - Decision logic - Blockchain monitoring The most complex is the decision logic. Initially, IPOR DAO will provide the set of Alphas that will integrate all the Fuses created by the DAO. Consequently, to get the IPOR Alpha to start managing the vault all it will take will be to bootstrap the vault with a collection of Fuses and authorize the Alpha to oversee the vault. In the future, there will be an open marketplace connecting Atomists and Alphas. The fee share at the management level may vary depending on the complexity of the logic. ![Illustration from “What is IPOR Fusion? A Technical Overview”](https://ipor.io/_astro/image-05.Brx0ixrl_Z1didnj.webp) ## 9️⃣ IPOR Labs Alpha The first type of Alpha available to all IPOR Fusion users will be the yield-optimizing Alpha. This algorithm takes into consideration the following data: - Cost of gas (negligible on L2s) and gas cost trends to ensure it doesn’t operate during gas price peaks. Gas price consideration is critical mainly for smaller vaults where efficient rebalancing may be so expensive, that potential earnings would be erased by the cost of transacting. In many cases, the Alpha would choose not to rebalance when it would consider potential benefits not worth the gas expenditure. - Effect of its actions when interacting with the external markets (aka slippage). This is especially important for larger vaults because of the sheer size of assets they can move. In some cases, vaults can be large enough to completely change the landscape by efficiently allocating assets. - Historical credit market rate trends (with regards to Fuses connecting to credit markets). > Depending on settings that are individually set per vault, the Alpha will behave differently. Its goal, however, is always to seek the optimal risk-adjusted yield for the deposited assets. It will run simulations of the most effective asset allocation and prepare rebalancing transactions. If, for example, an Alpha is managing a vault connected to 3 credit markets, it may deploy all assets to one market, or split it between two or all three, depending on which configuration will yield the best risk-adjusted result. Every time the market moves, the Alpha will reassess its decision. > Transactions are always limited to the Fuses that are connected to a particular vault, so no arbitrary code can ever be executed. ## 1️⃣0️⃣ Claiming and Managing Rewards and Airdrops It may happen that the vault will accrue some rewards by interacting with protocols. In this case, there must be a way to claim the rewards and utilize them according to the Atomist strategy. In IPOR Fusion, the strategy to deal with the tokens is delegated to the rewards manager — a smart contract responsible for managing rewards claimed by the vault. ![Illustration from “What is IPOR Fusion? A Technical Overview”](https://ipor.io/_astro/image-06.CD9dgKrA_Z2muJM7.webp) ## 1️⃣1️⃣ Security ### 🔸 Access management > IPOR Fusion uses dedicated access management for every function on the vault. When the function is executed, additional validation is made with the help of an access management contract. This contract stores the method signature and list of addresses authorized to use it. This allows for a very flexible, robust, and extensible access control. ### 🔸 Timelocks Some vaults, especially those that allow open participation, may want to enforce a timelock when changing settings that could affect the funds deposited by LPs. When bootstrapping a vault, the Atomist has the option to define the timelock period. This way, users have time to move their funds before changes come into effect. > To further improve the timelock governance the IPOR DAO is considering collaboration with Aragon to bring a solution where open vault participants would have a say in the process of changing parameters. An example of such implementation is the Steakhouse MetaMorpho vaults governance: [Steakhouse MetaMorpho Vaults are upgrading their guardian setups to fully trustless](https://forum.morpho.org/t/steakhouse-metamorpho-vaults-are-upgrading-their-guardian-setups-to-fully-trustless/527) ### 🔸 Testing and Auditing IPOR Fusion is subject to rigorous audits. This article will be updated as the reports become available. 💡 Perhaps more interesting is the approach that IPOR Fusion takes with regard to validating Fuses. ⬇️ As more Fuses are developed by a more diversified pool of developers, reviewing Fuses will become more burdensome and prohibitively expensive. Auditing all the Fuses, and by proxy the connections that vaults can establish with external protocols, would be difficult and costly to scale. It is then imperative that an alternative process for reviewing and validating Fuses is established early on. This approach is vital to the longevity and security of Fusion. Part of the infrastructure of IPOR Fusion is the framework of automatic Fuse testing that includes fuzz testing and more traditional unit testing. Each Fuse, because of its limited scope and standard interface, can be automatically verified to speed up the review process before it can be integrated by the keepers and receive the stamp of approval by the IPOR DAO. ### 🔸 Preventing Sandwich Attacks A sandwich attack is an attempt to profit from the order of executing transactions on the blockchain. Specialized players can watch transactions pending to be executed on the blockchain and try to wrap them in their own transactions to extract value. This is possible because it is ultimately up to the miner of the block to arrange transactions. An example of such type of attack would be to watch for transactions that add liquidity to the ERC4626 vault without minting additional tokens. This would happen when vault-owned liquidity rewards would be sold and proceeds distributed to the shareholders of the vault. In this case, the attacker could slide his deposit transaction just before the transfer of the tokens to the vault and execute a withdrawal just after additional assets were transferred. In this way, they would participate in the distribution of the rewards without the need to park their assets even for a second. This attack can be easily carried out with a flash loan, effectively eating all the proceeds of the vault and leaving the shareholders empty-handed. The easiest way to mitigate this type of attack is to make it unprofitable by implementing deposit or withdrawal fees or outright preventing permissionless deposits. Both solutions are not ideal as they limit access to the vault. IPOR Fusion provides an additional, less blunt approach to curb the sandwich attack. - First: the atomist can prevent vault deposits and withdrawals from taking place in one block to easily prevent flash loan usage - Second: proceeds from selling the rewards, or any other asset injections can be piped through a dedicated contract that will linearly “fade in” the amount over a defined period. This way the attack is rendered completely ineffective. ## 🔥 Conclusion IPOR Fusion is a composable, flexible, and secure way to automate even complex DeFi strategies. It brings innovative solutions and economies of scale. It allows DAOs to earn yield on idle assets, asset managers to build their bespoke trading strategies for DeFi users, and even TradFi funds to manage their clients’ assets in private vaults. > Initially, IPOR Fusion will be used to manage IPOR Protocol assets, and then it will be progressively opened up to allow permissionless creation and management of vaults. To make sure you don’t miss any news about IPOR Fusion make sure you follow the official [X Account](https://twitter.com/ipor_io). If you want to reach out to the team and join the community you can use [Discord](https://discord.com/invite/bSKzq6UMJ3).# News from the Fusion ecosystem > Partnerships, integrations and milestones from the teams building institutional-grade onchain yield on Fusion. Source: https://ipor.io/news Partnerships, integrations and milestones from the institutions, curators and custodians building institutional-grade onchain yield on Fusion. - [Fusion x Blockaid: Bringing Onchain Threat Monitoring to Fusion Vaults](https://ipor.io/news/fusion-blockaid-onchain-threat-monitoring.md) (2026-08-20, Ecosystem): Fusion has begun integrating Blockaid across its vault stack. Monitoring and post-transaction analysis are live for a selection of ecosystem vaults, with proactive detection in development. - [Fusion Vaults Now Account for ~45% of cbETH on Base](https://ipor.io/news/fusion-vaults-45-cbeth-base.md) (2026-07-15, Ecosystem): As of 15 July 2026, vaults built on Fusion hold around 30,500 cbETH on Base, roughly 45% of the network's cbETH and 19.3% of Coinbase's reported circulating supply. - [Base Rewards Now Live for Select Fusion Strategies via Anchorage Digital](https://ipor.io/news/base-rewards-fusion-strategies-anchorage-digital.md) (2026-06-29, Incentives): From 29 June, wallets that reach select Fusion strategies on Base through the Porto by Anchorage Digital portal may earn Base-supported rewards in cbETH via Merkl, on top of each strategy's onchain results. - [Turning cbETH's 2.75% Native Yield to 15% on Aave with Fusion Automation](https://ipor.io/news/fusion-automates-aave-yield-cbeth-looping.md) (2026-06-24, Strategies): There is far more yield inside Aave's markets than most participants ever reach. Fusion's automation turns that depth into performance, like TAU Labs' cbETH Dynamic Looping vault, which stacks cbETH's native yield into roughly 15% on Base. - [Covered Vaults Come to Fusion: Embedded Onchain Protection by OpenCover](https://ipor.io/news/covered-vaults-fusion-opencover-onchain-protection.md) (2026-06-18, Ecosystem): Onchain cover is now available for select Fusion vaults, provided by OpenCover and underwritten by Nexus Mutual, starting with a Covered Vault for the Liquity ETH Carry strategy. - [BitGo Opens Institutional Access to Tesseract's Dedicated Client Vaults Powered by Fusion](https://ipor.io/news/bitgo-institutional-access-tesseract-dedicated-client-vaults.md) (2026-06-09, Ecosystem): Eligible institutions can now allocate to Tesseract's Dedicated Client Vaults, powered by Fusion, directly from BitGo qualified custody, with assets staying inside the custodial perimeter. - [How Vault Curators Can Navigate MiCA in 2026](https://ipor.io/news/how-vault-curators-navigate-mica-2026.md) (2026-04-30, Regulation): At Vault Summit during EthCC Cannes 2026, Stéphane Daniel of d&a partners laid out the MiCA legal considerations for vault curators, from CASP reclassification risk to three structuring strategies. A guest article, with a note on how Fusion's architecture relates. - [Tesseract Selects IPOR Fusion as Onchain Vault Infrastructure for Institutional Clients](https://ipor.io/news/tesseract-selects-fusion-institutional-vault-infrastructure.md) (2026-03-31, Ecosystem): Tesseract Investment Oy, a Helsinki-based MiCA-authorised CASP, has selected Fusion as its onchain vault infrastructure for institutional onchain yield, with 21Shares among the launch partners. - [Post-mortem IPOR USDC optimizer Arbitrum vault exploit](https://ipor.io/news/post-mortem-ipor-usdc-optimizer-arbitrum-vault-exploit-aff11fd01b62.md) (2026-01-07, Ecosystem): How a legacy IPOR Fusion vault on Arbitrum was exploited through an EIP-7702 delegate and a missing fuse validation, and how it was resolved within a day: the vault got back 100%, no depositor lost funds. - [Introducing IPOR Fusion Points](https://ipor.io/news/introducing-ipor-fusion-points-6666d85abe8a.md) (2025-10-24, Incentives): Rewarding Fusion and IPOR AMM ecosystem participants. - [IPOR Fusion Alpha Drops | #3](https://ipor.io/news/ipor-fusion-alpha-drops-3-3a2111866c37.md) (2024-07-29, Incentives): Decentralized Ethena — beyond passive funding rate farming on CEXs. - [IPOR Fusion Alpha Drops | #2](https://ipor.io/news/ipor-fusion-alpha-drops-2-09b604d0ad31.md) (2024-07-23, Incentives): Liquidity fragmentation as an arbitrage opportunity. - [IPOR Fusion Alpha Drops | #1](https://ipor.io/news/ipor-fusion-alpha-drops-b23f237e8504.md) (2024-06-18, Incentives): Optimized supply of stablecoins in credit markets. - [112,500 $ARB: Now Available to IPOR LPs and Traders](https://ipor.io/news/112-500-arb-now-available-to-ipor-lps-and-traders-7c79f2015246.md) (2024-06-05, Incentives): IPOR DApp users can now tap into multi-incentivized pools on Arbitrum. - [IPOR Fusion: Transforming Liquidity into Pure Energy](https://ipor.io/news/ipor-fusion-transforming-liquidity-into-pure-energy-55bd41184277.md) (2024-04-11, Ecosystem): Fusion converts fragmented liquidity into intelligent DeFi yield optimization. - [IPOR Labs Talent Spotlight: Mau Hernandes, Chief Scientist](https://ipor.io/news/ipor-labs-talent-spotlight-mau-hernandes-chief-scientist-fb8174bdd373.md) (2024-03-28, Ecosystem): A short Q&A session with one of IPOR Labs’ math PhDs — Mauricio Hernandes. - [Arbitrum wstETH Stake Rate Swaps Extravaganza: Rules and Rewards](https://ipor.io/news/arbitrum-wsteth-stake-rate-swaps-extravaganza-rules-and-rewards-72c5af367cde.md) (2024-02-21, Incentives): Back for a second straight year to celebrate the Arbitrum launch with up to 50,000 $IPOR prize pool to trade juicy Ethereum staking rates. Here is all you need to know. - [Bringing Interest Rate Swaps to Arbitrum’s Derivatives Ecosystem](https://ipor.io/news/bringing-interest-rate-swaps-to-arbitrums-derivatives-ecosystem-114132ff332e.md) (2024-02-08, Ecosystem): After underwriting more than $4.3b in interest rate swaps on Ethereum, IPOR is bringing the critical missing piece to Arbitrum’s derivatives ecosystem. - [It’s ON! Join IPOR’s stETH Stake Rate Swap Trading Competition](https://ipor.io/news/its-on-join-ipor-s-steth-stake-rate-swap-trading-competition-831a28195b68.md) (2024-01-18, Incentives): Experience DeFi-native stETH interest rate swaps with up to 500x leverage and earn a piece of the $IPOR prize pool. - [IPOR Protocol 2023 Recap and Looking Forward](https://ipor.io/news/ipor-protocol-2023-recap-and-looking-forward-cb8dec0941b6.md) (2023-12-31, Ecosystem): The IPOR Labs team spent the year strategizing, upgrading, simplifying, launching products and features, and learning. What are the key milestones of 2023, and what’s coming in 2024? - [Live on Ethereum: IPOR’s stETH Stake Rate Swap](https://ipor.io/news/live-on-ethereum-ipors-steth-stake-rate-swap-ac8553771a71.md) (2023-12-18, Ecosystem): The IPOR Protocol’s first Stake Rate Swap product enables hedging, speculation, and arbitrage on Lido’s risk-free rate with up to 500x leverage. TradFi capital efficiency. - [Stake Rate Swaps: Where 🦇🔊, Risk-free Rates and Max Leverage Do Battle](https://ipor.io/news/stake-rate-swaps-where-risk-free-rates-and-max-leverage-do-battle-24694d50ab62.md) (2023-11-22, Ecosystem): IPOR’s upcoming Stake Rate Swap (SRS) is aimed at a wide range of participants in the $19b liquid staking Ethereum industry. What are SRS, and why do they matter? - [IPOR v2: Enabling Tighter Spreads](https://ipor.io/news/ipor-v2-enabling-tighter-spreads-d75421d6e70d.md) (2023-10-27, Ecosystem): A quick overview of the most significant updates to the IPOR spread model introduced by the v2 release. - [IPOR v2: Multiple Protocol Upgrades, New ETH Rate Markets, and Yield Opportunities](https://ipor.io/news/ipor-v2-multiple-protocol-upgrades-new-eth-rate-markets-and-yield-opportunities-d4afa7b2967a.md) (2023-10-12, Ecosystem): The rolling IPOR Protocol v2 launch brings massive DApp, architecture, and product upgrades, setting the stage for LSD-based derivatives and structured products. - [IPOR v2: Architecture For Growth](https://ipor.io/news/ipor-v2-architecture-for-growth-b1d36b5e05b7.md) (2023-10-09, Ecosystem): A description of the most notable IPOR v2 architectural changes and the reasoning behind their implementation. - [Welcome to DeFiance City](https://ipor.io/news/welcome-to-defiance-city-1ec4c9e50f57.md) (2023-09-19, Ecosystem): IPOR-powered since Ethereum block 16436883. - [Join the First Snapshot Vote!](https://ipor.io/news/join-the-first-snapshot-vote-d77f6ef30d6d.md) (2023-09-12, Ecosystem): With the creation of the IPOR DAO Snapshot voting space, it’s time to perform IPOR’s first pwIPOR-based vote. - [Where’s my Money, Stanley?](https://ipor.io/news/wheres-the-money-stanley-b17e9f61266f.md) (2023-09-07, Ecosystem): The latest IPOR DApp upgrade brings transparency to the IPOR Asset Management contract that earns top interest for LPs from any unutilized liquidity. - [IPOR v2: From the Heartbeat of DeFi to its Credit Hub](https://ipor.io/news/ipor-v2-from-the-heartbeat-of-defi-to-its-credit-hub-5545dd01c2d.md) (2023-09-04, Ecosystem): With the launch date of #IPORv2 rapidly approaching, it’s time to recap all the major innovations it will bring to the DeFi landscape. - [DIVA Protocol Collaborates with IPOR Protocol to Enable Trustless Interest Rate Derivatives](https://ipor.io/news/diva-protocol-collaborates-with-ipor-protocol-to-enable-trustless-interest-rate-derivatives-ed5c97f7e270.md) (2023-08-29, Ecosystem): DIVA Protocol enters into a strategic collaboration with IPOR Protocol to integrate their on-chain interest rate index to catalyze the creation of fully trustless interest rate derivative products. - [IPOR Protocol’s DAI Real-Yield, Now Boosted with EDSR](https://ipor.io/news/ipor-protocols-dai-real-yield-now-boosted-with-edsr-a7a65372a224.md) (2023-08-08, Strategies): The IPOR protocol is one of the first protocols in DeFi to incorporate MakerDAO’s EDSR, further enhancing the yield of DAI LPs. - [Even cozier IPOR LP yields? Incorporating the 8% EDSR to IPOR DAI asset management.](https://ipor.io/news/even-cozier-ipor-lp-yields-incorporating-the-8-edsr-to-ipor-dai-asset-management-70f4b053069f.md) (2023-07-28, Strategies): The IPOR Protocol is in a unique position to continue delivering the best stablecoin yields for liquidity providers on Ethereum. - [IPOR and Flux Finance Integrate to Enable a TradFi-DeFi Yield Portal](https://ipor.io/news/ipor-and-flux-finance-integrate-to-enable-a-tradfi-defi-yield-portal-5a0b8118cadf.md) (2023-07-18, Ecosystem): The IPOR Protocol is integrating with Flux Finance to empower DeFi participants with access to low-risk on-chain yield. - [Unwinding Swaps](https://ipor.io/news/unwinding-swaps-540abbe974a.md) (2023-06-19, Ecosystem): IPOR v2 incorporates an established TradFi approach to closing interest rate swaps enabling more efficient risk management and pricing. - [Engage! Grow! Earn!](https://ipor.io/news/engage-grow-earn-3064afc5857.md) (2023-06-07, Incentives): A summary of all the opportunities IPOR community members can make use of to support IPOR’s mission, grow, and earn rewards. - [The Credit Hub of DeFi — IPOR v2](https://ipor.io/news/the-credit-hub-of-defi-ipor-v2-1b35fcf8800c.md) (2023-05-08, Ecosystem): Introducing an expanded vision of the IPOR Protocol. - [Liquidity Mining Changes Approved: $IPOR Emission Reduction and Boosting Curve Update](https://ipor.io/news/liquidity-mining-changes-approved-ipor-emission-reduction-and-boosting-curve-update-e593673a68fb.md) (2023-04-25, Incentives): After three months of running, IPOR’s liquidity mining program was reviewed and revised by the IPOR Economics Workgroup. - [IPOR Protocol Q1 Recap](https://ipor.io/news/ipor-protocol-q1-recap-289af73a097b.md) (2023-04-19, Ecosystem): The first quarter of 2023 was an eventful one for the IPOR Protocol with TVL increasing 1540% and IPOR topping the rankings for DeFi derivatives markets. Here’s a full recap. - [Announcing IPOR’s Bug Bounty Program on Immunefi](https://ipor.io/news/announcing-ipors-bug-bounty-program-on-immunefi-8f001d3635c2.md) (2023-04-11, Ecosystem): The IPOR Protocol has established its first bounty program for the identification and reporting of smart contract bugs. - [$IPOR to be Listed on Bitget a Top 10 Global Exchange on March 22, 1pm UTC](https://ipor.io/news/ipor-to-be-listed-on-bitget-a-top-10-global-exchange-on-march-22-1pm-utc-f1469ab0b4e4.md) (2023-03-21, Ecosystem): The IPOR Protocol will take its next step as the native $IPOR token will be listed this Wednesday on Bitget, a Top 10 global crypto exchange. - [Power Up Power Tokens!](https://ipor.io/news/power-up-power-tokens-37d053a5d434.md) (2023-02-14, Incentives): The success of IPOR Protocol liquidity mining calls for a demonstration of the flexibility of the Power Token design. - [IPOR Labs Talent Spotlight: Konstantinos Tsoulos, Head of Business Development](https://ipor.io/news/ipor-labs-talent-spotlight-konstantinos-tsoulos-head-of-business-development-c31a2496b431.md) (2023-02-02, Ecosystem): A quick Q&A session with IPOR Labs’ newest member — Konstantinos Tsoulos — about macro, TradFi, DeFi, IPOR, and life as a DJ. - [IPOR USDT Index: Sustained Oracle Attack and Risk Mitigation](https://ipor.io/news/ipor-usdt-index-sustained-oracle-attack-and-risk-mitigation-4f3618876a2c.md) (2023-02-02, Ecosystem): With the opening of the IPOR Protocol trading competition, the Protocol experienced its first prolonged attack. - [IPOR Trading Competition: Rules and Rewards](https://ipor.io/news/ipor-trading-competition-launch-rules-and-rewards-c1b94e25d18b.md) (2023-02-01, Incentives): To celebrate the month of love (and IPOR as the Heartbeat of DeFi) 40,000 IPOR tokens will be distributed in the first Interest Rate Swap trading competition. Here is all you need to know. - [Launch of Liquidity Mining](https://ipor.io/news/launch-of-liquidity-mining-2cfe546f1678.md) (2023-01-25, Incentives): IPOR Protocol liquidity providers can now earn pwIPOR tokens in addition to the real yield generated by the Protocol. - [IPOR DAO Phase One: IPOR Rewards](https://ipor.io/news/ipor-dao-phase-one-ipor-rewards-9154fdfa4700.md) (2023-01-11, Incentives): After five months of mainnet activity, the IPOR Protocol is taking the next step towards decentralization on its way to governance by the IPOR DAO. - [Power Tokens: Incentives, Done Better](https://ipor.io/news/power-tokens-incentives-done-better-e9e88e38208b.md) (2022-12-15, Incentives): After over 2 years with barely any meaningful innovation in liquidity mining, the IPOR Protocol introduces a new DeFi primitive. - [Introducing the IPOR Community Activation Program](https://ipor.io/news/the-ipor-community-activation-program-b6e05971fa1e.md) (2022-10-19, Incentives): Help the IPOR Protocol grow and get recognition within the IPOR community. - [IPOR Protocol Launches Interest Rate Derivatives & Benchmark Rate on Ethereum](https://ipor.io/news/ipor-protocol-launches-interest-rate-derivatives-benchmark-rate-on-ethereum-to-bridge-450-d882412dcdeb.md) (2022-08-16, Ecosystem): The IPOR Index is a decentralized benchmark interest rate for DeFi credit markets and sits at the core of the IPOR protocol, which is a set of smart contracts on the Ethereum blockchain, and will feature a suite of… - [IPOR Protocol: Interest Rate Swaps, Liquidity Provisioning, Risk Management, and Sources of Yield](https://ipor.io/news/ipor-protocol-interest-rate-swaps-liquidity-provisioning-risk-management-and-sources-of-yield-45232c1cac02.md) (2022-08-09, Ecosystem): The original vision of the IPOR Manifesto is coming to fruition as the protocol’s mainnet launch approaches. Upon launch, the IPOR will be made available as a public good on the Ethereum mainnet. - [May 2022 update — 1 month after launching testnet.](https://ipor.io/news/may-2022-update-1-month-after-launching-testnet-f0db19eea6e3.md) (2022-06-01, Ecosystem): If you’re curious to come this far, let me share the above in a little more detail with you. - [IPOR Labs Sponsors the Graph Hack 2022 in San Francisco](https://ipor.io/news/ipor-labs-sponsors-the-graph-hack-2022-in-san-francisco-94ceeb40e660.md) (2022-05-30, Ecosystem): At IPOR Labs we’re proud to support the upcoming #GraphHack2022 and invite everyone to explore with us the DeFi credit markets of tomorrow! - [The IPOR Public Testnet Launch: Interest Rate Derivatives for the DeFi Credit Markets](https://ipor.io/news/the-ipor-public-testnet-launch-interest-rate-derivatives-for-the-defi-credit-markets-680d97adaf0f.md) (2022-05-04, Ecosystem): Welcome to the IPOR protocol - together we will build open, fair, and decentralized interest rate indices and derivatives to bridge TradFi with DeFi. - [Announcing $5.55m in funding for the IPOR Protocol](https://ipor.io/news/announcing-5-55m-in-funding-for-the-ipor-protocol-109470d9e5a2.md) (2022-04-20, Ecosystem): We are proud to announce funding from some of the top investors in the space to build the IPOR Protocol.# IPOR Fusion Protocol Documentation > The Prime Brokerage Protocol of DeFi. Technical documentation for depositors, curators and builders, institutions, and IPOR DAO governance. The main IPOR index is https://ipor.io/llms.txt. This file lists the documentation pages only. Every page is also available as Markdown: append `.md` to its URL. The full text of all pages is in https://ipor.io/docs/llms-full.txt. ## Home | IPOR Fusion Protocol - [IPOR Fusion Docs](https://ipor.io/docs.md): Documentation for IPOR Fusion, onchain vault infrastructure: guides for depositors, builders and institutions, governance and resources. ## Fusion for Depositors - [Introduction to Fusion](https://ipor.io/docs/fusion-for-depositors.md) - [What is a Fusion Vault?](https://ipor.io/docs/fusion-for-depositors/what-is-a-fusion-vault.md) - [TL;DR: Why Fusion?](https://ipor.io/docs/fusion-for-depositors/why-fusion.md) - [Security Features](https://ipor.io/docs/fusion-for-depositors/why-fusion/security-features.md): IPOR Fusion infrastructure is developed to deliver peace of mind for Atomists and liquidity providers. - [Depositing](https://ipor.io/docs/fusion-for-depositors/user-guide/depositing.md) - [How to Choose a Vault](https://ipor.io/docs/fusion-for-depositors/user-guide/depositing/how-to-choose-a-vault.md) - [Withdrawing](https://ipor.io/docs/fusion-for-depositors/user-guide/withdrawing.md) - [Tracking Your Position](https://ipor.io/docs/fusion-for-depositors/user-guide/tracking-your-position.md) - [Tracking Your Fusion Points](https://ipor.io/docs/fusion-for-depositors/user-guide/tracking-your-position/tracking-your-fusion-points.md) - [Vault Fees](https://ipor.io/docs/fusion-for-depositors/user-guide/vault-fees.md) - [Management Fee](https://ipor.io/docs/fusion-for-depositors/user-guide/vault-fees/management-fee.md) - [Performance Fee](https://ipor.io/docs/fusion-for-depositors/user-guide/vault-fees/performance-fee.md) - [Onboarding and Offboarding Contributions](https://ipor.io/docs/fusion-for-depositors/user-guide/vault-fees/onboarding-and-offboarding-contributions.md) - [Risks](https://ipor.io/docs/fusion-for-depositors/user-guide/risks.md) - [FAQ: Vault Pauses](https://ipor.io/docs/fusion-for-depositors/user-guide/faq-vault-pauses.md) ## Build on Fusion - [Fusion Introduction](https://ipor.io/docs/build-on-fusion.md): What IPOR Fusion is, who this documentation is for, and how onchain and offchain responsibilities split between vaults and Alphas. - [Fusion for agents](https://ipor.io/docs/build-on-fusion/fusion-for-agents.md): Router page for AI agents: which Fusion interface (MCP, SDK, API) to use for which task, plus the minimal vault-creation example. - [General](https://ipor.io/docs/build-on-fusion/architecture-overview/general.md): High-level map of the Fusion architecture: how vaults, fuses, Atomists and Alphas fit together. - [What is a Fusion vault?](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fusion-vault.md): The Plasma Vault's implemented standards (ERC-4626, ERC-20, OZ Votes, Access Manager), redemption delay, rewards, fees and configuration surface. - [What is a Fuse?](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-a-fuse.md): What a Fuse is, how it adapts vault calls to an external protocol, and the fuse whitelist registry. - [What is an Atomist?](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-atomist.md): The Atomist role: who configures a vault's risk and market boundaries. - [What is an Alpha?](https://ipor.io/docs/build-on-fusion/architecture-overview/what-is-an-alpha.md): The Alpha role: who executes strategy transactions within an Atomist's configured boundaries. - [Benefits for Atomists](https://ipor.io/docs/build-on-fusion/atomists/benefits-for-atomists.md): Why an asset manager would run their strategy as a Fusion vault instead of building custom infrastructure. - [Vault configuration step-by-step](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step.md): The full vault setup guide: fuses, substrates, price oracle, pre-hooks, scheduled withdrawals, instant withdrawal order and fees. - [Access Management](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/access-management.md): How OpenZeppelin Access Manager governs vault roles, and how to grant, revoke and time-lock them. - [Timelocks and Execution Delays](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/timelocks-and-execution-delays.md): How execution delays protect depositors from unilateral configuration changes, and how to size them. - [Timelocks and Execution Delays: Developer Reference](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/timelocks-and-execution-delays/timelocks-and-execution-delays-developer-reference.md): Contract-level reference for configuring and reading timelock parameters. - [Vault Governance & Depositor-Led Security Best Practices](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/vault-governance-and-depositor-led-security-best-practices.md): Role hierarchy, execution delays and an optional Aragon Depositor DAO for checks and balances against a compromised or malicious Owner. - [Substrates](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/substrates.md): What substrates are and how they scope which assets and markets a fuse is allowed to touch. - [Vault Ownership Changes](https://ipor.io/docs/build-on-fusion/atomists/vault-configuration-step-by-step/vault-ownership-changes.md): How to safely transfer or rotate the Owner and other administrative roles on a live vault. - [Curating a Fusion vault](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault.md): Ongoing vault-curation topics: share price, contributions, performance, oracle choice, caps and market impact. - [Share Price Dynamics](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/share-price-dynamics.md): A Guide (not only) for Atomists - [On- and Offboarding Contributions](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/on-and-offboarding-contributions.md): How to handle depositors' contributions moving into and out of the vault's accounting. - [Understanding Performance of a Vault](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/understanding-performance-of-a-vault.md): How to read and interpret a Fusion vault's realized performance. - [Choosing the right price oracle](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/choosing-the-right-price-oracle.md): This document is intended to serve as a decision-making aid for Atomists when selecting the appropriate price oracle. - [Asynchronous Reward Compounding](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/asynchronous-reward-compounding.md): How reward claiming and compounding work when they can't happen atomically with a strategy action. - [Dependency Graphs](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/dependency-graphs.md): How Fusion's cached balance dependency graph works and when to configure it for a market. - [Integration Maintenance and Deprecation](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/integration-maintenance-and-deprecation.md): How to safely wind down or replace a protocol integration a vault depends on. - [Managing Redemption Delays](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/managing-redemption-delays.md): How the mandatory deposit-to-redemption delay works and how to configure it against sandwich attacks. - [Deposit Caps](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/deposit-caps.md): Why and how to cap total vault deposits. - [Economics of Deposit Caps](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/deposit-caps/economics-of-deposit-caps.md): The economic reasoning for choosing a specific deposit cap. - [Yield Scalability and Market Impact](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/yield-scalability-and-market-impact.md): How a strategy's yield degrades as vault size grows, and how to plan around market impact. - [Integrating Pendle PT Tokens](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/integrating-pendle-pt-tokens.md): Considerations specific to holding and pricing Pendle PT tokens in a Fusion vault. - [Benefits for Alphas](https://ipor.io/docs/build-on-fusion/alpha/benefits-for-alphas.md): Why running strategy execution as an Alpha (automation, risk management, IP protection) beats manual operation. - [Running an Alpha](https://ipor.io/docs/build-on-fusion/alpha/running-an-alpha.md): Manual vs. automated Alpha operation, the SDK-driven transaction path, and handling accrued points. - [Best Practices for Alphas](https://ipor.io/docs/build-on-fusion/alpha/running-an-alpha/best-practices-for-alphas.md): Gas efficiency, leveraged-looping risk, accounting, withdrawal orchestration and security practices for an Alpha bot. - [SDK](https://ipor.io/docs/build-on-fusion/alpha/sdk.md): Install, connect and execute with the Python SDK; the CLI, MCP server, agent skills, examples and module reference. - [Quick Start Guide](https://ipor.io/docs/build-on-fusion/developer-guide/quick-start-guide.md): Video walkthrough of creating and configuring a vault via the webapp, plus the SDK/agent path for programmatic deployment. - [MCP](https://ipor.io/docs/build-on-fusion/developer-guide/mcp.md): Connect to the hosted Fusion MCP server: tools, resources, prompts and a worked example for inspecting vaults. - [Deploy your first Fusion vault](https://ipor.io/docs/build-on-fusion/developer-guide/deploy-your-first-fusion-vault.md): The executed eleven-transaction SDK walk to clone, configure and fund a Fusion vault from scratch. - [Addresses](https://ipor.io/docs/build-on-fusion/developer-guide/addresses.md): Canonical IporFusionFactoryProxy addresses per chain, verified against the ipor-abi registry. - [Common Errors](https://ipor.io/docs/build-on-fusion/developer-guide/common-errors.md): Revert selectors and the role table for the errors most likely to block a Fusion vault deployment. - [Smart Contracts](https://ipor.io/docs/build-on-fusion/developer-guide/smart-contracts.md): DeepWiki-hosted architecture reference for the ipor-fusion smart contracts. - [Integrating a New Market](https://ipor.io/docs/build-on-fusion/integrating-a-new-market.md): Add a protocol to IPOR Fusion as a new market, whether it is your own protocol or one you integrate for another team: write and deploy the fuses, open a market id pull request, and IPOR reviews the fuses for the Fuse Whitelist Registry. - [What a Market Integration Contains](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/market-components.md): The components of a new market integration: action fuses, a balance fuse, claim fuses, a substrate library, price feeds and a market id, with the reference families to copy from. - [Developing a Fuse](https://ipor.io/docs/build-on-fusion/developer-guide/developing-a-fuse.md): Anatomy of an action fuse and its balance fuse, shown on the ERC4626 supply fuse: data structs, enter, exit, transient variants, instant withdraw, events and errors. - [Coding Standard](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/coding-standard.md): What IPOR requires from the code of a third-party fuse family: structure, statelessness, safety checks, errors, events, NatSpec, compiler settings and formatting, plus recommended tests. - [Security Requirements](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/security-requirements.md): Security requirements for the fuses of a new market: vault control of positions, limits on every action, allowances, valuation without silent failures, price sources, venue risks and what the IPOR team does not accept. - [Whitelist Review](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/whitelist-review.md): How to request a Fuse Whitelist Registry entry for a new market: the market id pull request, a copyable pull request description, the review request on Discord, the review, the possible outcomes and what happens after the entry. - [Balance Fuses](https://ipor.io/docs/build-on-fusion/developer-guide/balance-fuses.md): What a Balance Fuse tracks and how it pairs with a Functional Fuse for a market. - [General share price calculation rules](https://ipor.io/docs/build-on-fusion/developer-guide/general-share-price-calculation-rules.md): How a Plasma Vault's share price is computed and what can move it. - [Accounting and PPS Protection](https://ipor.io/docs/build-on-fusion/developer-guide/accounting-and-pps-protection.md): How the vault protects its price-per-share against manipulation and accounting edge cases. - [Price Oracle Middleware](https://ipor.io/docs/build-on-fusion/developer-guide/price-oracle-middleware.md): How the price oracle middleware resolves asset prices and how to add or override a feed. - [Pause Functionality](https://ipor.io/docs/build-on-fusion/developer-guide/pause-functionality.md): What pausing a vault does, who can trigger it, and its effect on in-flight operations. - [Configuring Pre-hooks](https://ipor.io/docs/build-on-fusion/developer-guide/configuring-pre-hooks.md): How to attach pre-execution validation hooks (e.g. rate limits, price checks) to vault functions. - [Security and Audits](https://ipor.io/docs/build-on-fusion/developer-guide/security-and-audits.md): IPOR's internal QA process, AI-assisted audit tooling, and the list of completed third-party audits with scope and reports. - [Open-source Repository](https://ipor.io/docs/build-on-fusion/developer-guide/open-source-repository.md): Where the Fusion smart-contract source lives and how it's licensed. - [API](https://ipor.io/docs/build-on-fusion/developer-guide/api.md): Base URL, endpoints, address-casing and error-format details for calling the public Fusion API directly. ## Fusion for Institutions - [Executive Summary: The Prime Brokerage Protocol of DeFi](https://ipor.io/docs/fusion-for-institutions.md) - [Institutional-Grade Infrastructure](https://ipor.io/docs/fusion-for-institutions/institutional-grade-infrastructure.md) - [Governance & Operational Control](https://ipor.io/docs/fusion-for-institutions/governance-and-operational-control.md) - [Programmable Risk & Circuit Breakers](https://ipor.io/docs/fusion-for-institutions/programmable-risk-and-circuit-breakers.md) - [Lifecycle & Liquidity Management](https://ipor.io/docs/fusion-for-institutions/lifecycle-and-liquidity-management.md) - [Institutional Fee Structures & Performance Tracking](https://ipor.io/docs/fusion-for-institutions/institutional-fee-structures-and-performance-tracking.md) - [Enterprise Features & Compliance Controls](https://ipor.io/docs/fusion-for-institutions/enterprise-features-and-compliance-controls.md) - [Regulatory Alignment (MiCA & Global Standards)](https://ipor.io/docs/fusion-for-institutions/regulatory-alignment-mica-and-global-standards.md) ## Governance and Tokenomics - [Overview](https://ipor.io/docs/dao-governance.md) - [$FUSN Snapshot FAQ](https://ipor.io/docs/dao-governance/tokenomics/usdfusn-snapshot-faq.md) - [$IPOR Token (Legacy)](https://ipor.io/docs/dao-governance/tokenomics/usdipor-token-legacy.md) - [$IPOR Token](https://ipor.io/docs/dao-governance/tokenomics/usdipor-token-legacy/usdipor-token.md) - [Token distribution model](https://ipor.io/docs/dao-governance/tokenomics/usdipor-token-legacy/token-distribution-model.md) - [Fusion DAO Governance](https://ipor.io/docs/dao-governance/dao-governance/fusion-dao-governance.md) - [Discord - All Channels](https://ipor.io/docs/dao-governance/dao-governance/discord-all-channels.md) - [Discord - Open Governance Forum](https://ipor.io/docs/dao-governance/dao-governance/discord-open-governance-forum.md) - [Discord - Formal Governance](https://ipor.io/docs/dao-governance/dao-governance/discord-formal-governance.md) - [Snapshot - Formal Governance](https://ipor.io/docs/dao-governance/dao-governance/snapshot-formal-governance.md) - [Governing Multisig wallets](https://ipor.io/docs/dao-governance/dao-governance/governing-multisig-wallets.md) ## Resources - [Glossary of Terms](https://ipor.io/docs/resources.md) - [Research and Whitepapers](https://ipor.io/docs/resources/resources/research-and-whitepapers.md) - [IPOR Conceptual Whitepaper](https://ipor.io/docs/resources/resources/research-and-whitepapers/ipor-conceptual-whitepaper.md) - [Brand Assets](https://ipor.io/docs/resources/resources/brand-assets.md) ## Archived > IPOR Derivatives is closed: new swaps have been paused since 2 April 2026. These pages are kept for reference only; for current products see the sections above. ### IPOR Derivatives - [The IPOR Protocol Introduction](https://ipor.io/docs/ipor-derivatives.md) - [About IPOR](https://ipor.io/docs/ipor-derivatives/who-uses-ipor-and-for-what.md) - [About the IPOR Protocol](https://ipor.io/docs/ipor-derivatives/ipor-faq/about-the-ipor-protocol.md) - [Using the IPOR Protocol](https://ipor.io/docs/ipor-derivatives/ipor-faq/using-the-ipor-protocol.md) - [Swaps VS Perps](https://ipor.io/docs/ipor-derivatives/ipor-faq/swaps-vs-perps.md) - [What is the IPOR Index](https://ipor.io/docs/ipor-derivatives/ipor-index/what-is-the-ipor-index.md) - [Working with the IPOR Index](https://ipor.io/docs/ipor-derivatives/ipor-index/working-with-the-ipor-index.md) - [IPOR stETH Index](https://ipor.io/docs/ipor-derivatives/ipor-index/ipor-steth-index.md) - [Interest Rate Derivative](https://ipor.io/docs/ipor-derivatives/interest-rate-derivatives/interest-rate-derivative.md) - [Index Calculation](https://ipor.io/docs/ipor-derivatives/interest-rate-derivatives/index-calculation.md) - [IPOR Publication](https://ipor.io/docs/ipor-derivatives/interest-rate-derivatives/ipor-publication.md) - [IBT](https://ipor.io/docs/ipor-derivatives/interest-rate-derivatives/ibt.md): Interest Bearing Token - [Indicative Term Sheet](https://ipor.io/docs/ipor-derivatives/interest-rate-derivatives/indicative-term-sheet.md): Here is an example of how an indicative term sheet might look for an IPOR IRS if it were to be issued in TradFi: - [The Automated Market Maker](https://ipor.io/docs/ipor-derivatives/automated-market-maker/the-automated-market-maker.md): This page describes the automated market maker - [Liquidity Provisioning](https://ipor.io/docs/ipor-derivatives/automated-market-maker/liquidity-provisioning.md) - [IPOR Swaps](https://ipor.io/docs/ipor-derivatives/automated-market-maker/ipor-swaps.md) - [Hedging example with Morpho protocol](https://ipor.io/docs/ipor-derivatives/automated-market-maker/ipor-swaps/hedging-example-with-morpho-protocol.md): Below is an illustrative example of how to create a simple fixed rate borrow using the Morpho and IPOR protocols - [Spread](https://ipor.io/docs/ipor-derivatives/automated-market-maker/spread.md) - [Math behind the demand spread](https://ipor.io/docs/ipor-derivatives/automated-market-maker/spread/math-behind-the-demand-spread.md) - [Risk oracle](https://ipor.io/docs/ipor-derivatives/automated-market-maker/risk-oracle.md) - [SOAP](https://ipor.io/docs/ipor-derivatives/automated-market-maker/soap.md) - [Liquidations](https://ipor.io/docs/ipor-derivatives/automated-market-maker/liquidations.md) - [Asset Management](https://ipor.io/docs/ipor-derivatives/automated-market-maker/asset-management.md) - [Deployed Contracts](https://ipor.io/docs/ipor-derivatives/developers-docs/deployed-contracts.md) - [Ethereum](https://ipor.io/docs/ipor-derivatives/developers-docs/deployed-contracts/contracts-overview.md) - [Arbitrum](https://ipor.io/docs/ipor-derivatives/developers-docs/deployed-contracts/arbitrum.md) - [Base](https://ipor.io/docs/ipor-derivatives/developers-docs/deployed-contracts/arbitrum-1.md) - [Audits](https://ipor.io/docs/ipor-derivatives/developers-docs/audits.md) - [Working with IPOR Router](https://ipor.io/docs/ipor-derivatives/developers-docs/working-with-ipor-router.md) - [ABI](https://ipor.io/docs/ipor-derivatives/developers-docs/abi.md) - [V2 changes](https://ipor.io/docs/ipor-derivatives/developers-docs/v2-changes.md) - [IPOR Oracle](https://ipor.io/docs/ipor-derivatives/developers-docs/ipor-oracle.md) - [Decentralizing IPOR](https://ipor.io/docs/ipor-derivatives/developers-docs/decentralizing-ipor.md) - [Configuration Parameters](https://ipor.io/docs/ipor-derivatives/developers-docs/decentralizing-ipor/configuration-parameters.md)