ipor-fusion 4.0.0.dev74__tar.gz → 4.0.0.dev83__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/MIGRATION_4.md +54 -2
  2. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/__init__.py +4 -0
  5. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/chains.py +28 -28
  6. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/access.py +9 -4
  7. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/contract.py +2 -2
  8. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/multicall.py +1 -1
  9. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/simulation.py +7 -3
  10. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/indexer.py +4 -2
  11. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/discovery.py +31 -11
  12. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/simulation.py +6 -4
  13. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/errors.py +43 -12
  14. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/explorer.py +3 -1
  15. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/external_state_events.py +4 -2
  16. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/market_ids.py +0 -7
  17. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/morpho_api.py +116 -72
  18. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/hypercore.py +3 -2
  19. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/lending_health.py +5 -10
  20. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/morpho.py +10 -10
  21. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/substrates.py +2 -3
  22. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/vault_info.py +9 -9
  23. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/vault_managers.py +5 -3
  24. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_multicall.py +2 -1
  25. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_helpers.py +13 -5
  26. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_lending_health.py +3 -4
  27. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_market_morpho_blue.py +90 -2
  28. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_contract_call.py +23 -5
  29. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_lane.py +40 -7
  30. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_transport.py +5 -4
  31. ipor_fusion-4.0.0.dev83/tests/test_error_hierarchy.py +110 -0
  32. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_public_api.py +2 -0
  33. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_solidity_mirrors.py +0 -2
  34. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_substrates.py +1 -4
  35. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_vault_simulator_blocks.py +12 -2
  36. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.claude-plugin/marketplace.json +0 -0
  37. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.claude-plugin/plugin.json +0 -0
  38. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.env.example +0 -0
  39. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.gemini/settings.json +0 -0
  40. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.git-blame-ignore-revs +0 -0
  41. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/cd.yml +0 -0
  42. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/ci.yml +0 -0
  43. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/commit-lint.yml +0 -0
  44. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/notify-slack.yml +0 -0
  45. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/python-build.yml +0 -0
  46. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/python-publish.yml +0 -0
  47. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/release.yml +0 -0
  48. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.github/workflows/report-slack.yml +0 -0
  49. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.gitignore +0 -0
  50. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.mcp.json +0 -0
  51. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.pre-commit-config.yaml +0 -0
  52. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/.python-version +0 -0
  53. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/AGENTS.md +0 -0
  54. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/CHANGELOG.md +0 -0
  55. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/CLAUDE.md +0 -0
  56. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/CODEOWNERS +0 -0
  57. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/LICENSE +0 -0
  58. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/README.md +0 -0
  59. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/build.sh +0 -0
  60. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/CONTRIBUTING.md +0 -0
  61. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/README.md +0 -0
  62. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  63. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  64. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  65. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  66. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/external_state_margin_leg_base.py +0 -0
  67. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/examples/simple_aave_v3_supply_base.py +0 -0
  68. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/pytest.ini +0 -0
  69. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/scripts/commit-check.sh +0 -0
  70. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/scripts/crosschain_readiness.py +0 -0
  71. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/skills/ipor-deploy-vault/SKILL.md +0 -0
  72. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/about.py +0 -0
  73. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cache.py +0 -0
  74. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/__init__.py +0 -0
  75. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  76. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/config_cmd.py +0 -0
  77. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/config_store.py +0 -0
  78. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/main.py +0 -0
  79. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/market_cmd.py +0 -0
  80. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/options.py +0 -0
  81. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  82. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  83. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/vault_health.py +0 -0
  84. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  85. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  86. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  87. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/config/__init__.py +0 -0
  88. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/config/roles.py +0 -0
  89. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/__init__.py +0 -0
  90. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/address.py +0 -0
  91. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/context.py +0 -0
  92. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/erc20.py +0 -0
  93. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/external_state_executor.py +0 -0
  94. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/fee_manager.py +0 -0
  95. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/fuse_action.py +0 -0
  96. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/fusion_factory.py +0 -0
  97. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/logs.py +0 -0
  98. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/oracle.py +0 -0
  99. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/plasma_vault.py +0 -0
  100. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/rewards_manager.py +0 -0
  101. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  102. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/__init__.py +0 -0
  103. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  104. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  105. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  106. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  107. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  108. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  109. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  110. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  111. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/contracts.py +0 -0
  112. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/errors.py +0 -0
  113. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/lane.py +0 -0
  114. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/logs.py +0 -0
  115. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/messages.py +0 -0
  116. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  117. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  118. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  119. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  120. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  121. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/crosschain/transport.py +0 -0
  122. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/dependency_graph.py +0 -0
  123. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/entry_cli.py +0 -0
  124. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/entry_mcp.py +0 -0
  125. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/field_docs.py +0 -0
  126. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/__init__.py +0 -0
  127. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  128. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/async_action.py +0 -0
  129. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/base.py +0 -0
  130. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  131. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  132. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  133. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  134. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  135. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/erc4626.py +0 -0
  136. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  137. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/events.py +0 -0
  138. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/external_state.py +0 -0
  139. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  140. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  141. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/hypercore.py +0 -0
  142. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  143. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/merkl.py +0 -0
  144. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/morpho.py +0 -0
  145. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  146. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  147. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/fuses/universal.py +0 -0
  148. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/guide/__init__.py +0 -0
  149. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/guide/architecture.md +0 -0
  150. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/guide/glossary.md +0 -0
  151. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/guide/invariants.md +0 -0
  152. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/guide/quickstart.md +0 -0
  153. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/market_info.py +0 -0
  154. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/mcp/__init__.py +0 -0
  155. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/mcp/guide.py +0 -0
  156. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/mcp/models.py +0 -0
  157. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/mcp/server.py +0 -0
  158. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/py.typed +0 -0
  159. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/__init__.py +0 -0
  160. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/aave_v3.py +0 -0
  161. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/aave_v4.py +0 -0
  162. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/compound_v3.py +0 -0
  163. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  164. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  165. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  166. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  167. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/position_manager.py +0 -0
  168. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  169. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  170. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/readers/unpriceable.py +0 -0
  171. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/types.py +0 -0
  172. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/validation.py +0 -0
  173. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/vault_data.py +0 -0
  174. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/vault_positions.py +0 -0
  175. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/src/ipor_fusion/vault_reads.py +0 -0
  176. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/UniswapV3UniversalRouter.py +0 -0
  177. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_crosschain.py +0 -0
  178. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_crosschain_lifecycle.py +0 -0
  179. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_crosschain_recovery.py +0 -0
  180. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_euler_v2.py +0 -0
  181. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_foundry.py +0 -0
  182. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_guide.py +0 -0
  183. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_hypercore_model.py +0 -0
  184. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_hypercore_shadow.py +0 -0
  185. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_simulate.py +0 -0
  186. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/_validation.py +0 -0
  187. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/addresses.py +0 -0
  188. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/conftest.py +0 -0
  189. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/constants.py +0 -0
  190. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  191. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/fixtures/hypercore_market55.json +0 -0
  192. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/fixtures/vault_info_golden/clean.json +0 -0
  193. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  194. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/fixtures/vault_info_golden/rich.json +0 -0
  195. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  196. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_about.py +0 -0
  197. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_access_manager.py +0 -0
  198. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cache.py +0 -0
  199. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_chains.py +0 -0
  200. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_changelog.py +0 -0
  201. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_commands.py +0 -0
  202. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_config_store.py +0 -0
  203. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_hypercore.py +0 -0
  204. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_oracle_mapping.py +0 -0
  205. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_pending_requests_scan.py +0 -0
  206. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_role_accounts.py +0 -0
  207. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_vault_info_golden.py +0 -0
  208. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_vault_predates_getters.py +0 -0
  209. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_cli_vault_validate.py +0 -0
  210. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_contract_encoder.py +0 -0
  211. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_ccip_events.py +0 -0
  212. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_ccip_indexer.py +0 -0
  213. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_ccip_quote.py +0 -0
  214. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_chainlink.py +0 -0
  215. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_contracts.py +0 -0
  216. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_encoding.py +0 -0
  217. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_errors.py +0 -0
  218. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_layering.py +0 -0
  219. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_readiness.py +0 -0
  220. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_crosschain_readiness_report.py +0 -0
  221. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_erc20.py +0 -0
  222. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_examples_composed_hypercore.py +0 -0
  223. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_examples_crosschain.py +0 -0
  224. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  225. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_examples_vaults.py +0 -0
  226. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_explorer.py +0 -0
  227. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_external_state_events.py +0 -0
  228. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_external_state_executor.py +0 -0
  229. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_fee_manager.py +0 -0
  230. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_fuse_encoding.py +0 -0
  231. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_fusion_instance.py +0 -0
  232. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_guide.py +0 -0
  233. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_hypercore_deployment.py +0 -0
  234. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_hypercore_events.py +0 -0
  235. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_hypercore_flow.py +0 -0
  236. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_hypercore_live.py +0 -0
  237. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_hypercore_outcome.py +0 -0
  238. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_hypercore_readers.py +0 -0
  239. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_hypercore_simulation.py +0 -0
  240. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_logs.py +0 -0
  241. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_mcp_hypercore.py +0 -0
  242. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_mcp_models.py +0 -0
  243. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_mcp_server.py +0 -0
  244. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_mcp_vault_validate.py +0 -0
  245. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_multicall.py +0 -0
  246. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_oracle.py +0 -0
  247. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_oracle_mapping.py +0 -0
  248. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_plasma_vault.py +0 -0
  249. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_reader_encoding.py +0 -0
  250. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_readiness_selection.py +0 -0
  251. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_revert_decoding.py +0 -0
  252. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_rewards_manager.py +0 -0
  253. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_roles.py +0 -0
  254. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_sdk_layering.py +0 -0
  255. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  256. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  257. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  258. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_crosschain_canary.py +0 -0
  259. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_crosschain_hypercore.py +0 -0
  260. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  261. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  262. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_examples_arbitrum.py +0 -0
  268. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_examples_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_examples_crosschain.py +0 -0
  270. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_external_state_executor_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_morpho_blue.py +0 -0
  275. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_pilot_v3.py +0 -0
  276. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  277. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  278. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  279. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  280. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_readers_integration.py +0 -0
  281. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_release_funds.py +0 -0
  282. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_swap_on_base.py +0 -0
  283. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  284. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_solidity_compiler.py +0 -0
  285. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_types_and_events.py +0 -0
  286. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_unpriceable_tokens.py +0 -0
  287. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_validation.py +0 -0
  288. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_vault_dep_graph.py +0 -0
  289. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_vault_info_onchain_base.py +0 -0
  290. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  291. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_web3_context.py +0 -0
  292. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/test_withdraw_manager.py +0 -0
  293. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/tests/typed_client/consumer.py +0 -0
  294. {ipor_fusion-4.0.0.dev74 → ipor_fusion-4.0.0.dev83}/uv.lock +0 -0
@@ -133,6 +133,16 @@ and the Stargate fuse's `eid=` take it (`LayerZeroEid(30101)`).
133
133
  `encode_oft_compose_msg(*, nonce=, src_eid=, amount_ld=, compose_msg=)` takes
134
134
  keyword arguments only. `ZERO_ADDRESS` is a `ChecksumAddress` (same value).
135
135
 
136
+ **Chain registry and removed aliases.** `CHAIN_NAMES`, `CHAIN_NAME_TO_ID`,
137
+ `SUPPORTED_CHAIN_IDS`, `GET_LOGS_RANGE_HINTS`, `BLOCK_EXPLORER_URLS`,
138
+ `MORPHO_BLUE_ADDRESSES`, `AAVE_V3_POOL`, `PUBLIC_ALLOCATOR_ADDRESSES` and
139
+ `HYPERCORE_PENDING_READERS` are keyed by `ChainId` (`HYPEREVM_CHAIN_ID` is one) and
140
+ `explorer.SUPPORTED_CHAINS` is a `frozenset[ChainId]`, so
141
+ `ensure_supported_chain(CHAIN_NAME_TO_ID["ethereum"])` type-checks;
142
+ `PUBLIC_ALLOCATOR_ADDRESSES` values are `ChecksumAddress`. `IporFusionMarkets.RWA`
143
+ is gone: market 50 is `IporFusionMarkets.EXTERNAL_STATE`. `MORPHO_BLUE_ADDRESS` is
144
+ gone: use `morpho_blue_address(chain_id)`.
145
+
136
146
  **Markets and substrates.** Every `IporFusionMarkets` member is a `MarketId`.
137
147
  `decode_substrate`, `market_name`, `format_market_label` take `MarketId`.
138
148
 
@@ -210,8 +220,10 @@ except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
210
220
  - It raises `MorphoApiError` for a market id or address it parses (market and V1
211
221
  allocation ids, V1 flow-cap ids, vault/asset/owner/token/oracle/IRM/allocator
212
222
  addresses) when the value is invalid, or absent where the field is optional in the
213
- answer. A key the parser requires can instead raise `KeyError`, e.g. a `loanAsset`
214
- without `address`. When matching a market's own allocation and flow cap, an id that
223
+ answer. Any other answer it cannot read (a missing key such as a `loanAsset`
224
+ without `address`, a value of the wrong type, a body that is not a JSON object) and
225
+ any transport failure also raise `MorphoApiError`, never `KeyError`, `TypeError` or
226
+ `ValueError` (see [Errors](#errors)). When matching a market's own allocation and flow cap, an id that
215
227
  does not parse matches nothing, and a V1 flow cap without an id is skipped, as
216
228
  before. `--market` rejects a malformed id.
217
229
  - `LendingMarketHealth.substrate_id` for a Morpho market is `0x`-prefixed (the JSON
@@ -222,3 +234,43 @@ except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
222
234
  - The on-disk caches (`~/.cache/ipor-fusion/`) key addresses lowercased; older entries
223
235
  are rekeyed on load, so a contract name cached for one spelling now also resolves
224
236
  for the other.
237
+
238
+ ## Errors
239
+
240
+ Every exception class the SDK defines now derives from `IporFusionError` and is
241
+ importable from `ipor_fusion`; the builtin bases they had are kept. The rule the
242
+ changes below apply is: a caller's mistake (a bad argument, or an operation the
243
+ object was not built for) raises a plain `ValueError`; a chain, provider or remote
244
+ API answering something the SDK cannot use raises an `IporFusionError` subclass.
245
+ 4.0 applies it to the paths listed here, not yet everywhere: the HyperCore NAV
246
+ reader (`read_hypercore_nav`) still raises `ValueError` for a failed precompile read,
247
+ a missing oracle price or an unsupported dex configuration,
248
+ `fetch_verifier_result` can still raise `KeyError` or `ValueError` on a malformed
249
+ indexer answer, and `erc20_balance_slot` raises `ValueError` when no slot answers.
250
+
251
+ - `MorphoApiError` (still a `RuntimeError`), `ExternalStateEventDecodeError` (still a
252
+ `ValueError`) and `VerifierResultUnavailable` (still a `LookupError`) now derive
253
+ from `IporFusionError`, so an `except IporFusionError` placed before their own
254
+ handlers catches them first. `MorphoApiError` lives in `ipor_fusion.errors` and is
255
+ exported from `ipor_fusion`; `ipor_fusion.morpho_api.MorphoApiError` still works.
256
+ - `EmptyCallResultError` is no longer an eth_abi `InsufficientDataBytes` (nor a
257
+ `DecodingError`). Code that caught either around `.call()` to tolerate a missing
258
+ contract or getter must catch `EmptyCallResultError`; return data that is present
259
+ but too short still raises eth_abi's error.
260
+ - Crosschain discovery (`discover_deployment`, `discover_lane_fuses`,
261
+ `detect_transport`, `open_lane`, `CrosschainDeployment.executor`) raises the new
262
+ `CrosschainDiscoveryError` instead of `ValueError` for on-chain configuration it
263
+ cannot use: a malformed or unknown market substrate, an executor managed by another
264
+ vault or of no known transport, a missing or duplicate lane fuse, or not exactly one
265
+ executor of a transport. An executor reporting `transportKind()` `UNDEFINED` is now
266
+ rejected by `detect_transport` instead of being returned. A transport kind passed in
267
+ that has no lane is still a `ValueError`.
268
+ - `Call.call()` and `Call.decode()` on a write-only `Call` raise `ValueError`, not
269
+ `RuntimeError`. `CrosschainSimulator.relay` raises `ValueError`, not
270
+ `RuntimeError`, for a message to a chain that was not added.
271
+ - `VaultSimulator.run` (provider error from `eth_simulateV1`, or a result count that
272
+ does not match the queued calls) and `CrosschainSimulator.relay` (not settled within
273
+ `max_rounds`) raise the new `SimulationRunError`, still a `RuntimeError`.
274
+ - The Morpho API client raises `MorphoApiError` for every response it cannot read and
275
+ for any `OSError` or `http.client.HTTPException` while fetching (it caught only
276
+ `URLError` and `TimeoutError` before).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev74
3
+ Version: 4.0.0.dev83
4
4
  Summary: The IPOR Fusion SDK for Python
5
5
  Project-URL: homepage, https://ipor.io
6
6
  Project-URL: repository, https://github.com/IPOR-Labs/ipor-fusion.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev74"
3
+ version = "4.0.0.dev83"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -174,6 +174,7 @@ from ipor_fusion.dependency_graph import (
174
174
  from ipor_fusion.errors import (
175
175
  CUSTOM_ERRORS,
176
176
  ContractNotFoundError,
177
+ CrosschainDiscoveryError,
177
178
  EmptyCallResultError,
178
179
  IporFusionError,
179
180
  LogScanError,
@@ -181,6 +182,7 @@ from ipor_fusion.errors import (
181
182
  MorphoMarketNotFoundError,
182
183
  NotPlasmaVaultError,
183
184
  SimulationError,
185
+ SimulationRunError,
184
186
  TransactionError,
185
187
  UnsupportedChainError,
186
188
  UnsupportedVaultVersionError,
@@ -716,6 +718,7 @@ __all__ = [
716
718
  "market_name",
717
719
  "DOCS",
718
720
  "ContractNotFoundError",
721
+ "CrosschainDiscoveryError",
719
722
  "EmptyCallResultError",
720
723
  "IporFusionError",
721
724
  "LogScanError",
@@ -724,6 +727,7 @@ __all__ = [
724
727
  "NotPlasmaVaultError",
725
728
  "TransactionError",
726
729
  "SimulationError",
730
+ "SimulationRunError",
727
731
  "UnsupportedChainError",
728
732
  "UnsupportedVaultVersionError",
729
733
  "CUSTOM_ERRORS",
@@ -5,48 +5,48 @@ from __future__ import annotations
5
5
  from ipor_fusion.errors import UnsupportedChainError
6
6
  from ipor_fusion.types import ChainId
7
7
 
8
- CHAIN_NAMES: dict[int, str] = {
9
- 1: "ethereum",
10
- 42161: "arbitrum",
11
- 8453: "base",
12
- 10: "optimism",
13
- 130: "unichain",
14
- 137: "polygon",
15
- 56: "bsc",
16
- 239: "tac",
17
- 9745: "plasma",
18
- 43114: "avalanche",
19
- 250: "fantom",
20
- 143: "monad",
21
- 999: "hyperevm",
22
- 4663: "robinhood",
23
- 747474: "katana",
24
- 3637: "botanix",
25
- 57073: "ink",
26
- 14: "flare",
8
+ CHAIN_NAMES: dict[ChainId, str] = {
9
+ ChainId(1): "ethereum",
10
+ ChainId(42161): "arbitrum",
11
+ ChainId(8453): "base",
12
+ ChainId(10): "optimism",
13
+ ChainId(130): "unichain",
14
+ ChainId(137): "polygon",
15
+ ChainId(56): "bsc",
16
+ ChainId(239): "tac",
17
+ ChainId(9745): "plasma",
18
+ ChainId(43114): "avalanche",
19
+ ChainId(250): "fantom",
20
+ ChainId(143): "monad",
21
+ ChainId(999): "hyperevm",
22
+ ChainId(4663): "robinhood",
23
+ ChainId(747474): "katana",
24
+ ChainId(3637): "botanix",
25
+ ChainId(57073): "ink",
26
+ ChainId(14): "flare",
27
27
  }
28
28
 
29
- CHAIN_NAME_TO_ID: dict[str, int] = {name: cid for cid, name in CHAIN_NAMES.items()}
29
+ CHAIN_NAME_TO_ID: dict[str, ChainId] = {name: cid for cid, name in CHAIN_NAMES.items()}
30
30
 
31
31
  # Chains the on-chain vault tooling (vault info/health/substrate fetch) is
32
32
  # validated on. A provider for another chain may connect fine and still fail
33
33
  # deeper in the stack (a missing Multicall3, an oracle quirk, an eth_getLogs
34
34
  # cap the adaptive scan cannot page through). Extend only after the full
35
35
  # vault_info path passes on every live vault of that chain.
36
- SUPPORTED_CHAIN_IDS: frozenset[int] = frozenset(
37
- {1, 130, 143, 999, 8453, 9745, 42161, 43114, 747474}
36
+ SUPPORTED_CHAIN_IDS: frozenset[ChainId] = frozenset(
37
+ ChainId(cid) for cid in (1, 130, 143, 999, 8453, 9745, 42161, 43114, 747474)
38
38
  )
39
39
 
40
40
  # First eth_getLogs page size, in blocks, on chains whose providers cap the
41
41
  # range (10,000 blocks) or time out on wide queries. The scan still shrinks a
42
42
  # page the provider rejects; a hint only skips the whole-range attempt and the
43
43
  # probing round trips.
44
- GET_LOGS_RANGE_HINTS: dict[int, int] = {
45
- 130: 10_000,
46
- 9745: 10_000,
47
- 57073: 10_000,
48
- 43114: 1_000_000,
49
- 747474: 1_000_000,
44
+ GET_LOGS_RANGE_HINTS: dict[ChainId, int] = {
45
+ ChainId(130): 10_000,
46
+ ChainId(9745): 10_000,
47
+ ChainId(57073): 10_000,
48
+ ChainId(43114): 1_000_000,
49
+ ChainId(747474): 1_000_000,
50
50
  }
51
51
 
52
52
 
@@ -15,7 +15,11 @@ from ipor_fusion.core.context import Web3Context
15
15
  from ipor_fusion.core.contract import Call, ContractWrapper
16
16
  from ipor_fusion.core.multicall import Multicall3
17
17
  from ipor_fusion.core.plasma_vault import PlasmaVault
18
- from ipor_fusion.errors import ContractNotFoundError, NotPlasmaVaultError
18
+ from ipor_fusion.errors import (
19
+ ContractNotFoundError,
20
+ EmptyCallResultError,
21
+ NotPlasmaVaultError,
22
+ )
19
23
  from ipor_fusion.types import AccessManagerAddress, Period, PlasmaVaultAddress, RoleId
20
24
 
21
25
 
@@ -189,8 +193,9 @@ def resolve_access_manager(
189
193
 
190
194
  Raises ContractNotFoundError when no code is deployed at the address (as
191
195
  of ctx.default_block), and NotPlasmaVaultError when the contract does not
192
- expose getAccessManagerAddress() — either an empty eth_call return
193
- (InsufficientDataBytes on decode) or a revert (ContractLogicError).
196
+ expose getAccessManagerAddress(): an empty eth_call return
197
+ (EmptyCallResultError), return data too short to decode
198
+ (InsufficientDataBytes) or a revert (ContractLogicError).
194
199
  Provider/transport errors propagate unchanged.
195
200
  """
196
201
  checksum = Web3.to_checksum_address(vault_address)
@@ -210,7 +215,7 @@ def resolve_access_manager(
210
215
  .get_access_manager_address()
211
216
  .call()
212
217
  )
213
- except (InsufficientDataBytes, ContractLogicError) as exc:
218
+ except (EmptyCallResultError, InsufficientDataBytes, ContractLogicError) as exc:
214
219
  raise NotPlasmaVaultError(
215
220
  f"Address {checksum} on chain {ctx.chain_id} does not appear "
216
221
  f"to be a Plasma Vault."
@@ -73,7 +73,7 @@ class Call(Generic[T]):
73
73
  """Execute as `eth_call`; decode and convert per `output_types`/`decoder`."""
74
74
  actual = self._resolve_ctx(ctx)
75
75
  if not self.output_types:
76
- raise RuntimeError(
76
+ raise ValueError(
77
77
  "Call.call() on a write-only Call — use .send() instead "
78
78
  "(no output_types declared)."
79
79
  )
@@ -91,7 +91,7 @@ class Call(Generic[T]):
91
91
  """Decode raw return data per `output_types`/`decoder` — the second half
92
92
  of `.call()`, for return data fetched elsewhere (e.g. `Multicall3`)."""
93
93
  if not self.output_types:
94
- raise RuntimeError("Call.decode() on a write-only Call.")
94
+ raise ValueError("Call.decode() on a write-only Call.")
95
95
  values = tuple(decode(self.output_types, bytes(raw)))
96
96
  single: Any = values[0] if len(values) == 1 else values
97
97
  if self.decoder is not None:
@@ -23,7 +23,7 @@ MULTICALL3_ADDRESS: ChecksumAddress = Web3.to_checksum_address(
23
23
  DEFAULT_BATCH_SIZE = 100
24
24
 
25
25
  # A sub-call that reverts, returns nothing or returns undecodable data.
26
- _CALL_FAILURES = (ContractLogicError, DecodingError)
26
+ _CALL_FAILURES = (ContractLogicError, EmptyCallResultError, DecodingError)
27
27
 
28
28
 
29
29
  class Multicall3(ContractWrapper):
@@ -15,7 +15,11 @@ from web3.utils.address import get_create_address
15
15
 
16
16
  from ipor_fusion.core.contract import Call, _encode_calldata
17
17
  from ipor_fusion.core.fuse_action import FuseAction
18
- from ipor_fusion.errors import SimulationError, decode_custom_error
18
+ from ipor_fusion.errors import (
19
+ SimulationError,
20
+ SimulationRunError,
21
+ decode_custom_error,
22
+ )
19
23
  from ipor_fusion.types import PlasmaVaultAddress
20
24
 
21
25
  #: ``n`` (the sent block's index in the run) -> account overrides for it.
@@ -570,7 +574,7 @@ class VaultSimulator:
570
574
  )
571
575
  if "error" in response:
572
576
  err = response["error"]
573
- raise RuntimeError(f"eth_simulateV1 failed: {err}")
577
+ raise SimulationRunError(f"eth_simulateV1 failed: {err}")
574
578
 
575
579
  return self._parse_response(response["result"])
576
580
 
@@ -597,7 +601,7 @@ class VaultSimulator:
597
601
  for block_result in result:
598
602
  raw_calls.extend(block_result.get("calls", []))
599
603
  if len(raw_calls) != len(sources):
600
- raise RuntimeError(
604
+ raise SimulationRunError(
601
605
  "eth_simulateV1 returned "
602
606
  f"{len(raw_calls)} call results for {len(sources)} queued calls"
603
607
  )
@@ -21,6 +21,7 @@ from web3 import Web3
21
21
 
22
22
  from ipor_fusion.core.contract import Call
23
23
  from ipor_fusion.crosschain.ccip.chainlink import CcipOffRamp
24
+ from ipor_fusion.errors import IporFusionError
24
25
  from ipor_fusion.types import MessageId
25
26
 
26
27
  #: Chainlink Labs' public mainnet indexers, tried in order.
@@ -32,9 +33,10 @@ INDEXER_URLS: tuple[str, ...] = (
32
33
  _HEADERS = {"Accept": "application/json", "User-Agent": "ipor-fusion-sdk"}
33
34
 
34
35
 
35
- class VerifierResultUnavailable(LookupError):
36
+ class VerifierResultUnavailable(IporFusionError, LookupError):
36
37
  """No indexer could serve a verifier result for the message (not verified
37
- yet, or the indexers were unreachable); the message says which."""
38
+ yet, or the indexers were unreachable); the message says which. Also a
39
+ LookupError: the result looked up is not there (yet)."""
38
40
 
39
41
 
40
42
  @dataclass(frozen=True, slots=True)
@@ -9,6 +9,9 @@ whose ``hasDispatcher`` is true), every
9
9
  turns that into one ``CrosschainLane`` per executor and spoke; ``open_lane``
10
10
  does it for one executor address. ``LANES`` is the only place that knows the
11
11
  concrete lane classes: a new transport is a new subpackage plus one entry.
12
+
13
+ On-chain configuration that discovery cannot use raises ``CrosschainDiscoveryError``;
14
+ ``ValueError`` is kept for a bad argument (a transport kind with no lane).
12
15
  """
13
16
 
14
17
  from __future__ import annotations
@@ -31,7 +34,7 @@ from ipor_fusion.crosschain.lane import CrosschainLane, LaneFuses
31
34
  from ipor_fusion.crosschain.messages import CrosschainTransportKind
32
35
  from ipor_fusion.crosschain.stargate.contracts import StargateCrosschainExecutor
33
36
  from ipor_fusion.crosschain.stargate.lane import StargateLane
34
- from ipor_fusion.errors import EmptyCallResultError
37
+ from ipor_fusion.errors import CrosschainDiscoveryError, EmptyCallResultError
35
38
  from ipor_fusion.fuses.crosschain.base import (
36
39
  CrosschainSubstrateLib,
37
40
  CrosschainSubstrateType,
@@ -75,11 +78,18 @@ def detect_transport(
75
78
  non-zero ``STARGATE_POOL()`` and no ``transportKind()``."""
76
79
  transport_kind = _probe(CcipCrosschainExecutor(ctx, executor).transport_kind())
77
80
  if transport_kind is not None:
78
- return CrosschainTransportKind(transport_kind)
81
+ if transport_kind not in LANES:
82
+ raise CrosschainDiscoveryError(
83
+ f"{executor} reports transportKind {transport_kind.name}, "
84
+ "which has no lane"
85
+ )
86
+ return transport_kind
79
87
  pool = _probe(StargateCrosschainExecutor(ctx, executor).stargate_pool())
80
88
  if pool is not None and pool != ZERO_ADDRESS:
81
89
  return CrosschainTransportKind.STARGATE_LAYERZERO
82
- raise ValueError(f"{executor} is not a crosschain executor of a known transport")
90
+ raise CrosschainDiscoveryError(
91
+ f"{executor} is not a crosschain executor of a known transport"
92
+ )
83
93
 
84
94
 
85
95
  def discover_lane_fuses(
@@ -108,14 +118,16 @@ def discover_lane_fuses(
108
118
  for role, selector in selectors.items():
109
119
  if selector in code:
110
120
  if role in found:
111
- raise ValueError(
121
+ raise CrosschainDiscoveryError(
112
122
  f"two {role} fuses on market {market_id}: "
113
123
  f"{found[role]} and {fuse}"
114
124
  )
115
125
  found[role] = fuse
116
126
  missing = [role for role in selectors if role not in found]
117
127
  if missing:
118
- raise ValueError(f"no {', '.join(missing)} fuse on market {market_id}")
128
+ raise CrosschainDiscoveryError(
129
+ f"no {', '.join(missing)} fuse on market {market_id}"
130
+ )
119
131
  return LaneFuses(found["supply"], found["command"], found["claim"])
120
132
 
121
133
 
@@ -163,7 +175,7 @@ class CrosschainDeployment:
163
175
  or more than one."""
164
176
  found = [e for e in self.executors if e.transport_kind == transport_kind]
165
177
  if len(found) != 1:
166
- raise ValueError(
178
+ raise CrosschainDiscoveryError(
167
179
  f"{len(found)} {transport_kind.name} executors on market "
168
180
  f"{self.market_id} of {self.vault}"
169
181
  )
@@ -180,17 +192,23 @@ def discover_deployment(
180
192
  remote_vaults: dict[ChainId, list[ChecksumAddress]] = {}
181
193
  for raw in plasma_vault.get_market_substrates(market_id).call():
182
194
  raw_bytes = bytes(raw)
183
- substrate = CrosschainSubstrateLib.bytes32_to_substrate(raw_bytes)
195
+ try:
196
+ substrate = CrosschainSubstrateLib.bytes32_to_substrate(raw_bytes)
197
+ except ValueError as exc:
198
+ raise CrosschainDiscoveryError(
199
+ f"substrate {raw_bytes.hex()} on market {market_id} does not "
200
+ f"decode: {exc}"
201
+ ) from exc
184
202
  if substrate.substrate_type == CrosschainSubstrateType.EXECUTOR:
185
203
  if substrate.chain_id != 0:
186
- raise ValueError(
204
+ raise CrosschainDiscoveryError(
187
205
  f"executor substrate {raw_bytes.hex()} on market {market_id} "
188
206
  f"has nonzero chain id {substrate.chain_id}"
189
207
  )
190
208
  executors.append(substrate.substrate_address)
191
209
  elif substrate.substrate_type == CrosschainSubstrateType.REMOTE_VAULT:
192
210
  if substrate.chain_id == 0:
193
- raise ValueError(
211
+ raise CrosschainDiscoveryError(
194
212
  f"remote vault substrate {raw_bytes.hex()} on market "
195
213
  f"{market_id} has zero chain id"
196
214
  )
@@ -198,7 +216,7 @@ def discover_deployment(
198
216
  substrate.substrate_address
199
217
  )
200
218
  else:
201
- raise ValueError(
219
+ raise CrosschainDiscoveryError(
202
220
  f"substrate {raw_bytes.hex()} on market {market_id} has type "
203
221
  f"{substrate.substrate_type.name}"
204
222
  )
@@ -208,7 +226,9 @@ def discover_deployment(
208
226
  wrapper = CrosschainExecutor(hub_ctx, address)
209
227
  manager = wrapper.manager().call()
210
228
  if manager != vault:
211
- raise ValueError(f"executor {address} is managed by {manager}, not {vault}")
229
+ raise CrosschainDiscoveryError(
230
+ f"executor {address} is managed by {manager}, not {vault}"
231
+ )
212
232
  transport_kind = detect_transport(hub_ctx, address)
213
233
  infos.append(
214
234
  CrosschainExecutorInfo(
@@ -40,6 +40,7 @@ from ipor_fusion.crosschain.transport import (
40
40
  CrosschainTransport,
41
41
  OutboundMessage,
42
42
  )
43
+ from ipor_fusion.errors import SimulationRunError
43
44
  from ipor_fusion.types import ChainId, PlasmaVaultAddress
44
45
 
45
46
  #: What the synthetic token source holds: enough for any test amount.
@@ -117,8 +118,9 @@ class CrosschainSimulator:
117
118
  def relay(self, max_rounds: int = 8) -> dict[ChainId, SimulationResult]:
118
119
  """Run every chain and deliver messages until nothing is in flight.
119
120
 
120
- Runs at most ``max_rounds`` rounds and raises ``RuntimeError`` if
121
- messages are still being produced afterward. A delivery that reverts
121
+ Runs at most ``max_rounds`` rounds and raises ``SimulationRunError`` if
122
+ messages are still being produced afterward, and ``ValueError`` if a
123
+ message targets a chain that was not added. A delivery that reverts
122
124
  stays in the destination call list and surfaces through that chain's
123
125
  ``SimulationResult``.
124
126
  """
@@ -128,7 +130,7 @@ class CrosschainSimulator:
128
130
  break
129
131
  pending = self._run_round(pending)
130
132
  if pending:
131
- raise RuntimeError(f"relay did not settle within {max_rounds} rounds")
133
+ raise SimulationRunError(f"relay did not settle within {max_rounds} rounds")
132
134
  return dict(self._results)
133
135
 
134
136
  def _run_round(self, chain_ids: set[ChainId]) -> set[ChainId]:
@@ -147,7 +149,7 @@ class CrosschainSimulator:
147
149
  def _deliver(self, message: OutboundMessage) -> None:
148
150
  destination = self._chains.get(message.dst_chain_id)
149
151
  if destination is None:
150
- raise RuntimeError(
152
+ raise ValueError(
151
153
  f"message {message.message_id.hex()} targets chain "
152
154
  f"{message.dst_chain_id}, which was not added to the simulator"
153
155
  )
@@ -5,7 +5,6 @@ import re
5
5
  from collections.abc import Iterable
6
6
 
7
7
  from eth_abi import decode as abi_decode
8
- from eth_abi.exceptions import InsufficientDataBytes
9
8
  from eth_typing import ChecksumAddress
10
9
  from eth_utils import function_signature_to_4byte_selector
11
10
  from web3 import Web3
@@ -147,35 +146,48 @@ def get_revert_reason(web3: Web3, tx_hash: TxHash, receipt: TxReceipt) -> str |
147
146
 
148
147
 
149
148
  class IporFusionError(Exception):
150
- """Base exception for all IPOR Fusion SDK errors."""
149
+ """Base of every exception the SDK defines.
150
+
151
+ The rule the SDK follows (a few older readers do not yet; see
152
+ MIGRATION_4.md): a subclass when the chain, a provider or a remote API
153
+ answers something it cannot use. A caller's mistake (a bad argument, or an
154
+ operation the object was not built for, such as ``.call()`` on a write-only
155
+ ``Call``) raises a builtin ``ValueError`` instead, as the standard library
156
+ does for an operation on a closed file. A subclass that also describes bad
157
+ input (an address that is not a vault, a chain the tooling does not cover)
158
+ additionally derives from ``ValueError``; a few keep the builtin base they
159
+ had before 4.0 (``RuntimeError``, ``LookupError``) so existing handlers
160
+ still catch them.
161
+ """
151
162
 
152
163
 
153
164
  class ContractNotFoundError(IporFusionError, ValueError):
154
165
  """No contract deployed at the given address.
155
166
 
156
- Also a ValueError so MCP adapters can let it propagate unmapped.
167
+ Also a ValueError: the address the caller passed holds nothing to read.
157
168
  """
158
169
 
159
170
 
160
171
  class NotPlasmaVaultError(IporFusionError, ValueError):
161
172
  """Contract exists but does not implement the Plasma Vault interface.
162
173
 
163
- Also a ValueError so MCP adapters can let it propagate unmapped.
174
+ Also a ValueError: the address the caller passed is not a vault.
164
175
  """
165
176
 
166
177
 
167
178
  class UnsupportedChainError(IporFusionError, ValueError):
168
179
  """Chain is not (yet) supported by the on-chain vault tooling.
169
180
 
170
- Also a ValueError so MCP adapters can let it propagate unmapped.
181
+ Also a ValueError: the chain the caller passed is outside what the tooling
182
+ covers.
171
183
  """
172
184
 
173
185
 
174
186
  class UnsupportedVaultVersionError(IporFusionError, ValueError):
175
187
  """A Plasma Vault predates a getter the on-chain vault tooling requires.
176
188
 
177
- Also a ValueError, so callers that report ValueError as a user error
178
- surface it as one.
189
+ Also a ValueError: the vault the caller passed cannot be read by this
190
+ tooling.
179
191
  """
180
192
 
181
193
 
@@ -202,21 +214,40 @@ class LogSourceUnavailable(IporFusionError):
202
214
  falls back to its own `eth_getLogs` scan."""
203
215
 
204
216
 
205
- class EmptyCallResultError(IporFusionError, InsufficientDataBytes):
217
+ class EmptyCallResultError(IporFusionError):
206
218
  """`eth_call` returned no data for a call that declares return values.
207
219
 
208
220
  Almost always there is no contract at the target address on the connected
209
221
  chain (or at the requested block); a contract whose catch-all fallback
210
- returns nothing looks the same. Subclasses eth_abi's InsufficientDataBytes
211
- — the error the ABI decoder used to raise — so existing handlers still
212
- catch it.
222
+ returns nothing looks the same. Return data that is present but too short
223
+ still raises eth_abi's own decoding error.
213
224
  """
214
225
 
215
226
 
216
227
  class MorphoMarketNotFoundError(IporFusionError, ValueError):
217
228
  """Morpho Blue market ID was never created on the connected chain.
218
229
 
219
- Also a ValueError so MCP adapters can let it propagate unmapped.
230
+ Also a ValueError: the market id the caller passed names no market.
231
+ """
232
+
233
+
234
+ class MorphoApiError(IporFusionError, RuntimeError):
235
+ """The Morpho API is unreachable, returned an error, or answered with a
236
+ response the client cannot parse (missing keys, wrong types, invalid
237
+ addresses or market ids). Also a RuntimeError, its base before 4.0."""
238
+
239
+
240
+ class CrosschainDiscoveryError(IporFusionError):
241
+ """A crosschain vault's on-chain configuration is not one discovery can
242
+ use: a malformed or unexpected market grant, an executor managed by another
243
+ vault or of no known transport, or a missing or duplicate lane fuse."""
244
+
245
+
246
+ class SimulationRunError(IporFusionError, RuntimeError):
247
+ """A simulation could not run to completion: the provider rejected
248
+ ``eth_simulateV1`` or answered with a different number of call results than
249
+ were queued, or a crosschain relay was still producing messages after its
250
+ last round. Distinct from a simulated call that reverts (``SimulationError``).
220
251
  """
221
252
 
222
253
 
@@ -17,7 +17,9 @@ from ipor_fusion.types import ChainId, TxHash, parse_tx_hash
17
17
 
18
18
  ETHERSCAN_V2_URL = "https://api.etherscan.io/v2/api"
19
19
 
20
- SUPPORTED_CHAINS = {1, 42161, 8453, 10, 137, 56, 43114, 250}
20
+ SUPPORTED_CHAINS: frozenset[ChainId] = frozenset(
21
+ ChainId(cid) for cid in (1, 42161, 8453, 10, 137, 56, 43114, 250)
22
+ )
21
23
 
22
24
  _MAX_RETRIES = 3
23
25
  _RETRY_DELAY = 1.0
@@ -32,6 +32,7 @@ from eth_typing import ChecksumAddress
32
32
  from web3 import Web3
33
33
  from web3.types import TxReceipt
34
34
 
35
+ from ipor_fusion.errors import IporFusionError
35
36
  from ipor_fusion.types import TxHash, parse_tx_hash
36
37
 
37
38
 
@@ -123,9 +124,10 @@ EXTERNAL_STATE_TOPICS: tuple[str, ...] = tuple(
123
124
  )
124
125
 
125
126
 
126
- class ExternalStateEventDecodeError(ValueError):
127
+ class ExternalStateEventDecodeError(IporFusionError, ValueError):
127
128
  """A log carries a listed ExternalState topic but its data does not decode
128
- to the declared parameters: malformed, never a plausible event."""
129
+ to the declared parameters: malformed, never a plausible event. Also a
130
+ ValueError: the log the caller passed is not a decodable event."""
129
131
 
130
132
 
131
133
  @dataclass(frozen=True, slots=True)
@@ -87,13 +87,6 @@ class IporFusionMarkets:
87
87
  # 4 BALANCE_ACCOUNT, 5 STALENESS_MAX, 6 BIG_CHANGE_BPS, 7 DUST_THRESHOLD,
88
88
  # 8 MIN_UPDATE_INTERVAL).
89
89
  EXTERNAL_STATE = MarketId(50)
90
- # Deprecated alias of EXTERNAL_STATE, kept for backward compatibility.
91
- # Reverse id->name lookups skip aliases (see _DEPRECATED_ALIASES) so the
92
- # canonical name is the one displayed.
93
- RWA = MarketId(50)
94
- # Deprecated member names that alias a canonical member of the same value;
95
- # reverse id->name lookups skip these so the canonical name wins.
96
- _DEPRECATED_ALIASES = frozenset({"RWA"})
97
90
  # substrate type: AguaSubstrateLib (VAULT or ASSET)
98
91
  AGUA_GLOBAL_CARRY = MarketId(51)
99
92
  # dependence graph: balance of ERC20_VAULT_BALANCE (purchase tokens return