# What a Market Integration Contains

> 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.

A market integration is a family of contracts that you write in your own repository and deploy yourself. This page lists the components, the interface each one implements, and what must be checkable on-chain. For the process around them, start with the [overview](https://ipor.io/docs/build-on-fusion/integrating-a-new-market.md). For the coding rules, see the [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). For how a single fuse is built, see [Developing a Fuse](https://ipor.io/docs/build-on-fusion/developer-guide/developing-a-fuse.md).

Two families in the Fusion repository show the pattern, as read at commit `632350d`: [`contracts/fuses/erc4626`](https://github.com/IPOR-Labs/ipor-fusion/tree/main/contracts/fuses/erc4626) is the simple one (a supply fuse and a balance fuse), and [`contracts/fuses/aave_v4`](https://github.com/IPOR-Labs/ipor-fusion/tree/main/contracts/fuses/aave_v4) is a full lending market (supply, borrow and collateral fuses, a balance fuse, a substrate library, a README and tests).

## Components at a glance

| Component                  | Interface                             | Required                                                     | Reference                                 |
| -------------------------- | ------------------------------------- | ------------------------------------------------------------ | ----------------------------------------- |
| Action fuses (enter, exit) | `IFuseCommon`                         | Yes                                                          | `Erc4626SupplyFuse`, `AaveV4SupplyFuse`   |
| Instant withdraw           | `IFuseInstantWithdraw`                | Optional                                                     | `Erc4626SupplyFuse`, `AaveV4SupplyFuse`   |
| Transient variants         | `enterTransient()`, `exitTransient()` | Optional                                                     | `Erc4626SupplyFuse`                       |
| Balance fuse               | `IMarketBalanceFuse`                  | Yes, or `ZeroBalanceFuse`                                    | `Erc4626BalanceFuse`, `AaveV4BalanceFuse` |
| Claim fuse                 | none specific                         | Optional                                                     | `MorphoClaimFuse`                         |
| Substrate library          | none specific                         | When a substrate carries more than an address                | `AaveV4SubstrateLib`                      |
| Price feed                 | `IPriceFeed`                          | When the middleware has no price source for an asset         | none specific                             |
| Market id                  | constant in `IporFusionMarkets`       | Yes                                                          | none specific                             |
| README                     | none                                  | Yes, covering the market, the substrates and the venue risks | `aave_v4/README.md`                       |

## Action fuses

An action fuse moves the vault into or out of a position. An action fuse is typically named `<Venue><Action>Fuse`, such as `AaveV4SupplyFuse`, with one contract per file and the file named after the contract.

The fuse implements [`IFuseCommon`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/IFuseCommon.sol#L5-L9), which has one function, `MARKET_ID()`. The vault engine itself requires only that function: it reads `MARKET_ID()` from the fuse and delegatecalls the encoded call, so the action functions are reached by 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) through the call data of a `FuseAction`. A vault runs only fuses that were added to it; any other address reverts with `UnsupportedFuse`.

The reference families do not implement `IFuse`, which declares `enter(bytes)` and `exit(bytes)`. They expose typed functions instead, for example `enter(Erc4626SupplyFuseEnterData)` and `exit(AaveV4SupplyFuseExitData)`.

> **Note:** The ERC4626 example is the shortest one to read, but it is not a model for every requirement. It does not reject market id `0` in the constructor (the guard is shown on `AaveV4SupplyFuse`), and it does not set the allowance back to zero after approving (shown on `AguaSupplyFuse`). Its `enter`, `exit` and transient functions have no NatSpec; the Aave V4 fuses show the expected level.

### Instant withdraw

Implementing [`IFuseInstantWithdraw`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/IFuseInstantWithdraw.sol#L4-L13) is optional. An action fuse that implements it exposes `instantWithdraw(bytes32[] params)`, where `params[0]` is the amount in the underlying asset and the following params are specific to the fuse. The layouts differ per family: ERC4626 uses `params[1]` as the vault address; Aave V4 uses `params[1]` to `params[4]` for asset, spoke, reserve id and a reserved slot.

During a user withdrawal the vault sets `params[0]` to the amount still needed and delegatecalls `instantWithdraw` on each configured fuse in order, until the amount still needed is covered. The call is not wrapped in `try/catch`, so a revert inside it reverts the user withdrawal. This is why the reference fuses catch only the venue withdraw call on this path and report a failure with an event. In the ERC4626 example the catch covers only `vault.withdraw`: the substrate check and the `maxWithdraw` read are not caught, and `instantWithdraw` passes `maxSharesBurned = 0`, which switches the burned-shares limit off on that path. A zero requested or clamped amount returns `0` without an event.

### Transient variants

`enterTransient()` and `exitTransient()` read their inputs from `TransientStorageLib`, keyed by the fuse `VERSION`, call the same logic, and write the outputs back to transient storage. Both reference families provide them.

## Balance fuse

Every market needs a balance fuse, because the vault engine asks it for the market balance. It implements [`IMarketBalanceFuse`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/IMarketBalanceFuse.sol#L4-L10): `balanceOf()` returns the balance of the vault in the market in USD with 18 decimals (WAD). Use this exact signature. The interface does not declare `balanceOf()` as `view`; the reference implementations declare it `view`. You may declare it `view`, as the reference implementations do.

The vault calls it by delegatecall in the context of the vault and does not catch a revert, so a reverting balance fuse blocks the balance update. The fuse reads its substrates with `PlasmaVaultConfigLib.getMarketSubstrates(MARKET_ID)`, prices each asset through the Price Oracle Middleware and converts the result to WAD. An atomist registers it with `addBalanceFuse(marketId, fuse)`, which reverts with `BalanceFuseMarketIdMismatch` if the fuse `MARKET_ID()` differs from the market id.

### ZeroBalanceFuse

[`ZeroBalanceFuse`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/ZeroBalanceFuse.sol#L6-L26) always returns `0` and takes the market id in its constructor. Use it only for a market whose fuse holds no assets and creates no separate position of its own, with the position valued on another market through a dependency graph. The LitePSM market works this way: it holds nothing itself, the sUSDS position is valued on a separate market, and a dependency graph links the two.

Never use it for a position you cannot value, because it hides the position from the vault balance. You deploy it from the unchanged Fusion source with your own market id. It has `MARKET_ID` but no `VERSION` and does not reject market id `0`; those requirements apply to the fuses you write.

## Prices

The balance fuse prices assets through the [Price Oracle Middleware](https://ipor.io/docs/build-on-fusion/developer-guide/price-oracle-middleware.md), which returns USD prices with 18 decimals. For an asset with no registered source the middleware falls back to a Chainlink feed registry if one is configured, and otherwise reverts; it also reverts for a price that is zero or negative. A vault created by the Fusion factory reads prices through its own Price Oracle Middleware Manager: the vault's price-manager role sets a source per asset there with `setAssetsPriceSources(assets, sources)`, and an asset without a vault-specific source falls back to the shared middleware, whose owner registers sources with `setAssetsPricesSources(assets, sources)`. A balance fuse that prices an asset neither of them can price reverts.

If an asset needs a custom source, implement [`IPriceFeed`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/price_oracle/price_feed/IPriceFeed.sol#L4-L23) (`decimals()` and `latestRoundData()`). The middleware reads the feed's decimals and price, so report decimals correctly. A third-party feed must not be manipulable within one transaction.

## Substrates

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; fuses check substrates before moving funds. Plain address substrates are checked with `isSubstrateAsAssetGranted`, other encodings with `isMarketSubstrateGranted`.

When a substrate carries more than an address, put the encoding and decoding in a substrate library, as [`AaveV4SubstrateLib`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/aave_v4/AaveV4SubstrateLib.sol#L44-L64) does: a type flag in the top byte, then the fields, with reserved bits that must be zero. It looks permissions up in constant time and rejects non-canonical words, so a hand-encoded word that does not match the canonical encoding never grants anything. Aave V4 uses one market id for all Spokes; the substrates select the Spoke and the reserve.

## Claim fuses

If the venue pays out rewards, add a claim fuse. In the Fusion repository, claim fuses live in `contracts/rewards_fuses/<venue>/`; in your repository the location is your choice. A claim fuse is registered in the vault's `RewardsClaimManager` with `addRewardFuses` (fuse manager role), not with the vault's `addFuses`. The claim role calls `RewardsClaimManager.claimRewards`, which reverts with `FuseUnsupported` for a fuse that is not registered there and then calls `PlasmaVault.claimRewards`, which delegatecalls the fuse; the fuse forwards the claimed rewards to the `RewardsClaimManager`. As [`MorphoClaimFuse`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/rewards_fuses/morpho/MorphoClaimFuse.sol#L66-L105) does, it checks that the distributor is granted as a substrate before claiming and exposes `VERSION` and `MARKET_ID`.

## Market id

Third-party markets use ids from 400_001 to 400_999. Use the next free number in that range; the IPOR team confirms it in the pull request. The id is added as one constant in [`IporFusionMarkets`](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/libraries/IporFusionMarkets.sol), the library where Fusion markets are listed and described. Every fuse must expose `MARKET_ID()`, because when a fuse is added the Fuse Whitelist Registry reads `MARKET_ID()` inside `try/catch`, and a fuse without it is silently skipped for the per-market list. `MARKET_ID` is immutable, so a fuse deployed with an id that changes later must be redeployed.

## Dependency balance graphs

When one action changes the balance of two markets, for example the vault's own token holdings (the `ERC20_VAULT_BALANCE` market) and your market, the markets need a [dependency balance graph](https://ipor.io/docs/build-on-fusion/atomists/curating-a-fusion-vault/dependency-graphs.md). The atomist sets it with `updateDependencyBalanceGraphs(marketIds, dependencies)`. A call overwrites the dependencies of each listed market, and dependencies are directional. When a market balance is refreshed, the markets it depends on are refreshed in the same update. Describe the graph your market needs in the pull request.

## What must be verifiable on-chain

The IPOR team reviews the code that is deployed, so the following must be checkable from the chain:

- The source of every fuse is verified in the block explorer of its chain, and the verified source carries the exact compiler settings of the deployment (see the [coding standard](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/coding-standard.md)).
- `VERSION()` returns the fuse's own address, `MARKET_ID()` equals the constant from the pull request, and the constructor arguments are the ones listed in the pull request.
- The fuse has no storage variables other than ERC-7201 namespaced slots, and no roles, owner, admin keys, pause or upgrade mechanism; it is not a proxy.
- On every chain you use the same source code and the same compiler settings. The runtime bytecode can still differ, because immutable values (`VERSION`, `MARKET_ID`, venue addresses) are written into it. List these expected differences, and any other difference such as a different compiler or EVM version, in the pull request.

Deployed fuses cannot be changed: a fix or a new version means a new deployment with new addresses. The market id stays the same, so no new pull request is needed; send the new addresses and their verified sources to the IPOR team in a ticket in the [IPOR Discord](https://discord.com/invite/bSKzq6UMJ3) for review. Vaults already using the old address must migrate.

## README in your repository

Keep a README next to the fuses. It describes the market, the substrates and the venue risks (see [Security Requirements](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/security-requirements.md)). A good one follows the sections of the [Aave V4 README](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/aave_v4/README.md): overview, market structure, key components, substrate configuration with the validation matrix, operations, balance calculation, security considerations, price oracle setup and tests. Link it from the pull request; see [Whitelist Review](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/whitelist-review.md).
