IPOR Fusion Python SDK
ipor_fusion is the official Python SDK for IPOR Fusion Plasma Vaults — typed abstractions for DeFi protocol interactions on EVM chains through a fuse adapter pattern.
Maintained by IPOR Labs AG.
Documentation · SDK docs · llms.txt for AI agents · Hosted MCP server · Example bot · Contracts
| Workflow |
|
| Social |
|
| Code |
|
Quickstart
Install
pip install ipor-fusion
Connect and execute
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 (mainnet/mainnet-<chain>-fusion/addresses.json).
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. The same Call powers all three modes:
from ipor_fusion import VaultSimulator
total = vault.total_assets().call() # eth_call -> Amount
receipt = vault.execute([action]).send() # signed tx -> TxReceipt
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"))
CLI Quickstart
The SDK ships with a fusion CLI for inspecting and managing Plasma Vaults from the terminal.
You need an RPC provider URL — get a free key at Alchemy or Infura.
# 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 Server
Two ways to reach IPOR Fusion from an MCP-compatible AI assistant (Claude Code, Cursor, Windsurf, etc.).
Hosted server (no install)
https://mcp.ipor.io/mcp is public, unauthenticated and read-only — inspect any Fusion vault without your own RPC key.
claude mcp add --transport http ipor-fusion https://mcp.ipor.io/mcp
{
"mcpServers": {
"ipor-fusion": {
"type": "http",
"url": "https://mcp.ipor.io/mcp"
}
}
}
Tools: vaults_list, vault_info, vault_oracle_mapping, fusion_addresses_list, fusion_address_names, fusion_address_lookup, market_morpho_blue, market_meta_morpho.
Local server (from the SDK)
The SDK ships a fusion-mcp server that exposes the CLI over MCP, against your own RPC providers and local config.
# Install with MCP extras (pipx keeps dependencies isolated)
pipx install 'ipor-fusion[mcp]'
Add to your MCP client configuration (e.g. .mcp.json):
{
"mcpServers": {
"ipor-fusion": {
"command": "fusion-mcp",
"type": "stdio"
}
}
}
Available tools:
| Tool | Description |
|---|---|
server_info |
Server name, running version and changelog entries |
config_show |
Show current configuration (providers, vaults, API key status) |
config_set_provider |
Set RPC provider URL for a chain (auto-detects chain ID) |
config_set_etherscan_key |
Set Etherscan API key (enables contract name resolution) |
vault_info |
Full on-chain vault state — assets, fuses, balances, fees, lending health, reconciliation |
vault_role_accounts |
Accounts holding each AccessManager role on a vault |
vault_oracle_mapping |
Price-oracle sources per asset for a vault |
vault_list |
List all saved vaults |
vault_add |
Save a vault to the local config (auto-fetches on-chain name) |
vault_remove |
Remove a vault from the local config |
market_morpho_blue |
Morpho Blue market parameters and state |
market_meta_morpho |
MetaMorpho V1 or Morpho Vault V2 allocations and caps (Morpho API) |
Configure providers and vaults via fusion config or the MCP config tools first.
Besides the tools, fusion-mcp serves the guide from ipor_fusion.guide as the
resources fusion://glossary, fusion://architecture, fusion://invariants and
fusion://quickstart (the executed deploy-and-operate walk), and the prompts
quickstart, deploy_vault, analyze_vault, trace_oracle_pricing and
explain_fuse (slash commands in clients that support MCP prompts).
Agent skills
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.
Its body is fusion://invariants and fusion://quickstart from ipor_fusion.guide
verbatim (a test keeps them identical), so the skill and the MCP resources are one
text with two delivery paths. It follows the
Agent Skills format and versions with the
SDK. One install per machine:
# 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
Common errors
Reverts on the deploy-and-configure path, keyed by selector so a failed transaction is greppable. This table and the full invariants ship in the wheel as ipor_fusion.guide, which fusion-mcp serves as fusion://invariants.
| 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=...) |
clone() grants the owner only Roles.OWNER_ROLE (1). OWNER grants Roles.ATOMIST_ROLE (100), which administers Roles.FUSE_MANAGER_ROLE (300, configuration), Roles.ALPHA_ROLE (200, execute) and Roles.WHITELIST_ROLE (800, deposit on a private vault).
Configuration order on a fresh vault: add_fuses → grant_market_substrates → add_balance_fuse → execute; all three configuration steps are mandatory.
A flash-loan fuse also needs update_callback_handler(handler, sender, selector) before its first execute: Morpho Blue calls back onMorphoFlashLoan(uint256,bytes), the Euler EVC onEulerFlashLoan(bytes), and an unregistered callback reverts HandlerNotFound().
The full clone → configure → deposit → execute sequence is exercised in tests/test_simulate_vault_from_scratch_base.py.
Custom errors of the crosschain contracts decode by name: importing
ipor_fusion.crosschain registers their signatures (CROSSCHAIN_ERROR_SIGNATURES),
so a simulated or sent call that reverts reads NavSettledStale(8453) in
SimulationResult.revert_reason and TransactionError instead of a bare selector.
register_custom_errors([...]) adds your own, and SimulationResult.raise_for_failure()
raises SimulationError naming the first failed call.
Vault construction examples
Runnable, canonical examples for building and configuring a vault from scratch live in
examples. They preview and simulate through eth_simulateV1 — nothing is
ever signed or broadcast.
- Simple Aave V3 supply vault — start here for vault creation, role bootstrap, and configuring a single supported market.
- Advanced Euler V2 credit-market vault — continue here for composing multiple functional fuses, typed Euler substrates and sub-accounts, and an ordered collateral → borrow → repay → unwind lifecycle.
- External-state margin leg — a vault whose capital sits off-chain: typed market-50 substrates, a dual-custodian propose/confirm marking the NAV, the scheduled-withdrawal prep an alpha performs, and a mismatched confirmation being rejected.
- Crosschain CCIP lifecycle — a vault
whose capital works on another chain: supply USDC from an Arbitrum hub to a HyperEVM spoke
over CCIP, attest the remote balance with two keys, deposit and redeem on the spoke, recall
and claim, driven through
CrosschainLaneand relayed byCrosschainSimulator.
export BASE_PROVIDER_URL="https://base-mainnet.g.alchemy.com/v2/YOUR_KEY"
uv run python examples/simple_aave_v3_supply_base.py
uv run python examples/advanced_euler_v2_credit_market_base.py
uv run python examples/external_state_margin_leg_base.py
uv run python examples/crosschain_ccip_usdc_arbitrum_hyperevm.py # needs ARBITRUM_PROVIDER_URL and HYPEREVM_PROVIDER_URL
No RPC key? BASE_PROVIDER_URL=https://mainnet.base.org (Base's public RPC) runs the examples out of
the box — it supports eth_simulateV1 and serves their pinned blocks. It is rate-limited, so use a
dedicated archive node for repeated or CI runs.
Architecture
The SDK uses a fuse adapter pattern:
- Fuses encode protocol-specific calls into
FuseActionobjects (pure calldata, no state) - PlasmaVault batches and executes
FuseActionsequences on-chain viaexecute(); a batch is atomic - Web3Context manages provider connections, signing, and transaction dispatch
- Call is the lazy result of every wrapper method:
.call(),.send(),.calldata, or feed it toVaultSimulator
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 |
FeeAccount |
Fee escrow account, resolves its FeeManager |
PriceOracleMiddleware |
Asset price feeds |
PriceOracleMiddlewareManager |
Per-vault price-source overrides |
ExternalStateExecutor |
NAV propose/confirm for off-vault capital (market 50) |
StargateCrosschainExecutor, CcipCrosschainExecutor |
Crosschain executor reads and attestation (propose_balance, approve_balance) |
StargateCrosschainDispatcher, CcipCrosschainDispatcher |
Remote dispatcher state (observation, tracked idle, command lane) |
StargateCrosschainFactory, CcipCrosschainFactory |
Executor creation and route/asset configuration reads |
CrosschainLane, open_lane |
One executor/dispatcher pair driven transport-agnostically: supply, recall, command, claim, buckets, attestation (attestation, needs_attestation, staleness_max); enforces_min_received identifies real supply floors (CCIP requires zero); open_lane detects the transport and finds the fuses |
discover_deployment, open_lanes |
Read a vault's crosschain market from the hub alone (executors with transport, factory, fuses, attestation keys and spokes served; remote vaults per spoke) and open every lane; LANES maps a transport kind to its lane class |
CrosschainSimulator |
Multi-chain eth_simulateV1 relay: replays LayerZero and CCIP messages between pinned chains by impersonating the endpoint or router on delivery, no bridge needed |
ccip_token_lane |
Whether a CCIP message lane exists from a chain and whether a token's pool serves it, read on Chainlink's Router, OnRamp, TokenAdminRegistry and pool |
erc20_balance_slot, VaultSimulator.with_erc20_balance |
Fund any address with any ERC-20 in a simulation by a storage override (slot found by probing), no holder to impersonate |
ERC20 |
Token reads and approvals |
Supported protocols (ipor_fusion.fuses)
| Protocol | Fuses |
|---|---|
| Aave V3 | AaveV3SupplyFuse, AaveV3BorrowFuse |
| Morpho | MorphoSupplyFuse, MorphoCollateralFuse, MorphoBorrowFuse, MorphoFlashLoanFuse, MorphoClaimFuse |
| Euler V2 | EulerV2SupplyFuse, EulerV2CollateralFuse, EulerV2ControllerFuse, EulerV2BorrowFuse, EulerV2BatchFuse, EulerV2SwapDeployFuse, EulerV2SwapReconfigureFuse, EulerV2SwapRegistryFuse |
| Uniswap V3 | UniswapV3SwapFuse, UniswapV3NewPositionFuse, UniswapV3ModifyPositionFuse, UniswapV3CollectFuse |
| Ramses V2 | RamsesV2NewPositionFuse, RamsesV2ModifyPositionFuse, RamsesV2CollectFuse, RamsesClaimFuse |
| Compound V3 | CompoundV3SupplyFuse |
| Gearbox V3 | GearboxSupplyFuse, GearboxStakeFuse |
| ERC-4626 | ERC4626SupplyFuse |
| Fluid Instadapp | FluidInstadappSupplyFuse, FluidInstadappStakingFuse |
| Merkl | MerklClaimWrapperFuse |
| Universal | UniversalTokenSwapperFuse |
| Off-vault capital | AsyncActionFuse (market 40), ExternalStateOperationFuse (market 50) |
| Crosschain | StargateCrosschainSupplyFuse, StargateCrosschainCommandFuse, CcipCrosschainSupplyFuse, CcipCrosschainCommandFuse, CrosschainClaimFuse (market 54); transport-agnostic bases CrosschainSupplyFuse / CrosschainCommandFuse with StargateSendParams / CcipSendParams, plus Command, CrosschainSubstrateLib, OptionsBuilder |
The mainnet crosschain POC vault uses a keccak-derived market ID, not 54. Use
crosschain_market_id("IPOR_FUSION_CROSSCHAIN_USDC_POC_V1") for that deployment;
open_lane discovers the vault's actual market grants rather than assuming 54.
| HyperCore (Hyperliquid, market 55) | HyperCoreDepositFuse, HyperCoreMarginFuse, HyperCoreSendFuse, HyperCoreOrderFuse, HyperCoreCancelFuse, HyperCoreBuilderFeeFuse, HyperCoreSubstrates |
Readers (ipor_fusion.readers)
Read-only aggregators over positions and health, no fuses involved:
| Reader | Purpose |
|---|---|
MorphoReader, AaveV3Reader, CompoundV3Reader |
Market params, rates and vault positions per lending protocol |
UniswapV3Reader, RamsesV2Reader |
LP position details |
fetch_vault_lending_health |
Health factor per lending market a vault is in |
build_oracle_mapping |
How the vault prices every configured asset |
Supported networks
- Ethereum mainnet
- Arbitrum One
- Base
Development
uv sync --all-extras # Install dependencies
uv run pytest tests/test_fuse_encoding.py -n auto -v # Unit tests (fast, offline)
uv run pytest -v -s # All tests (needs `.*_PROVIDER_URL` variables in `.env`)
uv run ruff format ./ # Format
uv run ruff check ./ # Lint
uv run pyright # Type check
Integration tests need provider URLs in .env (eth_simulateV1 at a pinned fork block — no local node):
cp .env.example .env
# Edit .env with ARBITRUM_PROVIDER_URL, ETHEREUM_PROVIDER_URL, BASE_PROVIDER_URL
Contributor and coding-agent instructions (commands, conventions, invariants, domain rules) live in AGENTS.md.
Examples
For full usage patterns, see the example repository: ipor-fusion-alpha-example
Metadata
Release files for ipor-fusion 3.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ipor_fusion-3.9.0.tar.gz | 843.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ipor_fusion-3.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.2 MB
Release files / ipor_fusion-3.9.0.tar.gz
| Download URL | ipor_fusion-3.9.0.tar.gz |
|---|---|
| Size | 843.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9c85ec9d33902010482b88fae4f95ac5c7a0e9b68b2a6e0b7ddc06e03258e500
|
|
BLAKE2b-256 checksum How to use checksums |
78070ccdef94e3fb50b108ba4610ab0dfe402a9016d42576f3e3da5753204958
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ipor_fusion-3.9.0-py3-none-any.whl
| Download URL | ipor_fusion-3.9.0-py3-none-any.whl |
|---|---|
| Size | 326.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0027b45741b1f1d35a7d4e387c28c4205dcab042268c7f8612efc6cb4ee115fb
|
|
BLAKE2b-256 checksum How to use checksums |
f1e19a027eeccfce55f1b2563dd9fab10682a16f1847f02a11ed0a203a477123
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|