ipor-fusion 4.0.0.dev293__tar.gz → 4.0.0.dev303__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 (358) hide show
  1. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/AGENTS.md +13 -5
  2. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/MIGRATION_4.md +6 -2
  3. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/PKG-INFO +22 -1
  4. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/README.md +21 -0
  5. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/pyproject.toml +1 -1
  6. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/__init__.py +42 -0
  7. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/main.py +2 -0
  8. ipor_fusion-4.0.0.dev303/src/ipor_fusion/cli/preflight_cmd.py +128 -0
  9. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/__init__.py +12 -0
  10. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/access.py +22 -1
  11. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/external_state_executor.py +46 -3
  12. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fuse.py +22 -0
  13. ipor_fusion-4.0.0.dev303/src/ipor_fusion/core/fuse_decoding.py +109 -0
  14. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/base.py +2 -0
  15. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/ccip.py +2 -0
  16. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/stargate.py +2 -0
  17. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/errors.py +5 -0
  18. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/aave_v3.py +10 -0
  19. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/async_action.py +1 -0
  20. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/compound_v3.py +5 -0
  21. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/erc4626.py +5 -0
  22. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/euler_v2.py +36 -0
  23. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/external_state.py +6 -0
  24. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/fluid_instadapp.py +5 -0
  25. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/gearbox_v3.py +5 -0
  26. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/hypercore.py +18 -0
  27. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/merkl.py +2 -0
  28. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/morpho.py +19 -0
  29. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/ramses_v2.py +14 -0
  30. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/uniswap_v3.py +14 -0
  31. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/universal.py +6 -0
  32. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/models.py +63 -0
  33. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/server.py +49 -0
  34. ipor_fusion-4.0.0.dev303/src/ipor_fusion/preflight.py +464 -0
  35. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/types.py +2 -0
  36. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/conftest.py +18 -0
  37. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_access_manager.py +16 -0
  38. ipor_fusion-4.0.0.dev303/tests/test_cli_preflight.py +108 -0
  39. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_external_state_executor.py +30 -1
  40. ipor_fusion-4.0.0.dev303/tests/test_fuse_decoding.py +103 -0
  41. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_models.py +7 -0
  42. ipor_fusion-4.0.0.dev303/tests/test_mcp_preflight.py +86 -0
  43. ipor_fusion-4.0.0.dev303/tests/test_preflight.py +282 -0
  44. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_public_api.py +44 -2
  45. ipor_fusion-4.0.0.dev303/tests/test_simulate_preflight_base.py +46 -0
  46. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.claude-plugin/marketplace.json +0 -0
  47. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.claude-plugin/plugin.json +0 -0
  48. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.env.example +0 -0
  49. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.gemini/settings.json +0 -0
  50. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.git-blame-ignore-revs +0 -0
  51. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/cd.yml +0 -0
  52. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/ci.yml +0 -0
  53. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/commit-lint.yml +0 -0
  54. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/notify-slack.yml +0 -0
  55. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/python-build.yml +0 -0
  56. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/python-publish.yml +0 -0
  57. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/release.yml +0 -0
  58. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.github/workflows/report-slack.yml +0 -0
  59. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.gitignore +0 -0
  60. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.mcp.json +0 -0
  61. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.pre-commit-config.yaml +0 -0
  62. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/.python-version +0 -0
  63. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/CHANGELOG.md +0 -0
  64. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/CLAUDE.md +0 -0
  65. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/CODEOWNERS +0 -0
  66. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/LICENSE +0 -0
  67. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/build.sh +0 -0
  68. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/CONTRIBUTING.md +0 -0
  69. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/README.md +0 -0
  70. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  71. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  72. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  73. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  74. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/external_state_margin_leg_base.py +0 -0
  75. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/examples/simple_aave_v3_supply_base.py +0 -0
  76. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/pytest.ini +0 -0
  77. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/scripts/commit-check.sh +0 -0
  78. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/scripts/crosschain_readiness.py +0 -0
  79. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/skills/ipor-deploy-vault/SKILL.md +0 -0
  80. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/about.py +0 -0
  81. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cache.py +0 -0
  82. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/chains.py +0 -0
  83. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/__init__.py +0 -0
  84. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/address_cmd.py +0 -0
  85. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  86. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/config_cmd.py +0 -0
  87. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/config_store.py +0 -0
  88. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/market_cmd.py +0 -0
  89. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/options.py +0 -0
  90. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  91. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  92. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_health.py +0 -0
  93. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  94. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  95. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  96. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/config/__init__.py +0 -0
  97. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/config/roles.py +0 -0
  98. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/address.py +0 -0
  99. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/context.py +0 -0
  100. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/context_manager.py +0 -0
  101. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/contract.py +0 -0
  102. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/erc20.py +0 -0
  103. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fee_manager.py +0 -0
  104. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fuse_action.py +0 -0
  105. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/fusion_factory.py +0 -0
  106. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/logs.py +0 -0
  107. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/multicall.py +0 -0
  108. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/oracle.py +0 -0
  109. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/plasma_vault.py +0 -0
  110. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/rewards_manager.py +0 -0
  111. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/simulation.py +0 -0
  112. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/simulation_result.py +0 -0
  113. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  114. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/__init__.py +0 -0
  115. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  116. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  117. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  118. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  119. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  120. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  121. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  122. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  123. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  124. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/contracts.py +0 -0
  125. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/discovery.py +0 -0
  126. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/errors.py +0 -0
  127. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  128. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/lane.py +0 -0
  129. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/logs.py +0 -0
  130. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/messages.py +0 -0
  131. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/simulation.py +0 -0
  132. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  133. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  134. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  135. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  136. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  137. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/crosschain/transport.py +0 -0
  138. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/dependency_graph.py +0 -0
  139. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/entry_cli.py +0 -0
  140. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/entry_mcp.py +0 -0
  141. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/explorer.py +0 -0
  142. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/field_docs.py +0 -0
  143. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/__init__.py +0 -0
  144. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/base.py +0 -0
  145. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/events.py +0 -0
  146. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  147. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  148. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/__init__.py +0 -0
  149. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/architecture.md +0 -0
  150. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/glossary.md +0 -0
  151. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/invariants.md +0 -0
  152. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/guide/quickstart.md +0 -0
  153. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/hypercore/__init__.py +0 -0
  154. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/market_ids.py +0 -0
  155. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/market_info.py +0 -0
  156. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/__init__.py +0 -0
  157. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/mcp/guide.py +0 -0
  158. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/morpho_api.py +0 -0
  159. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/py.typed +0 -0
  160. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/__init__.py +0 -0
  161. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/aave_v3.py +0 -0
  162. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/aave_v4.py +0 -0
  163. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/compound_v3.py +0 -0
  164. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/hypercore.py +0 -0
  165. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  166. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/lending_health.py +0 -0
  167. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/morpho.py +0 -0
  168. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  169. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  170. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  171. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/position_manager.py +0 -0
  172. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  173. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  174. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/readers/unpriceable.py +0 -0
  175. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/registry.py +0 -0
  176. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/substrates.py +0 -0
  177. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/validation.py +0 -0
  178. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_data.py +0 -0
  179. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_erc20.py +0 -0
  180. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_info.py +0 -0
  181. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_managers.py +0 -0
  182. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_positions.py +0 -0
  183. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/src/ipor_fusion/vault_reads.py +0 -0
  184. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/UniswapV3UniversalRouter.py +0 -0
  185. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_crosschain.py +0 -0
  186. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_crosschain_lifecycle.py +0 -0
  187. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_crosschain_recovery.py +0 -0
  188. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_euler_v2.py +0 -0
  189. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_event_args.py +0 -0
  190. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_fake_rpc.py +0 -0
  191. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_foundry.py +0 -0
  192. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_golden.py +0 -0
  193. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_guide.py +0 -0
  194. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_hypercore_model.py +0 -0
  195. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_hypercore_shadow.py +0 -0
  196. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_live.py +0 -0
  197. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_multicall.py +0 -0
  198. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_oracle_mapping_fake.py +0 -0
  199. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_simulate.py +0 -0
  200. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/_validation.py +0 -0
  201. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/addresses.py +0 -0
  202. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/constants.py +0 -0
  203. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  204. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/hypercore_market55.json +0 -0
  205. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/hypercore_official.json +0 -0
  206. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  207. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  208. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  209. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  210. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  211. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  212. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  213. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  214. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  215. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  216. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  217. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  218. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  219. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  220. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  221. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  222. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/simulate_v1_request.json +0 -0
  223. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/clean.json +0 -0
  224. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  225. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  226. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  227. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  228. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  229. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  230. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  231. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  232. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/full.json +0 -0
  233. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  234. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/full.txt +0 -0
  235. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/rich.json +0 -0
  236. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  237. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  238. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_about.py +0 -0
  239. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cache.py +0 -0
  240. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_chains.py +0 -0
  241. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_address.py +0 -0
  242. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_changelog.py +0 -0
  243. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_commands.py +0 -0
  244. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_config_store.py +0 -0
  245. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_helpers.py +0 -0
  246. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_hypercore.py +0 -0
  247. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_lending_health.py +0 -0
  248. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_market_golden.py +0 -0
  249. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_market_morpho_blue.py +0 -0
  250. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_mcp_parity.py +0 -0
  251. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_oracle_mapping.py +0 -0
  252. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_oracle_mapping_golden.py +0 -0
  253. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_pending_requests_scan.py +0 -0
  254. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_role_accounts.py +0 -0
  255. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_vault_info_golden.py +0 -0
  256. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_vault_predates_getters.py +0 -0
  257. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_cli_vault_validate.py +0 -0
  258. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_contract_call.py +0 -0
  259. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_contract_encoder.py +0 -0
  260. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_ccip_events.py +0 -0
  261. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_ccip_indexer.py +0 -0
  262. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_ccip_quote.py +0 -0
  263. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_chainlink.py +0 -0
  264. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_contracts.py +0 -0
  265. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_encoding.py +0 -0
  266. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_errors.py +0 -0
  267. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_lane.py +0 -0
  268. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_layering.py +0 -0
  269. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_readiness.py +0 -0
  270. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_readiness_report.py +0 -0
  271. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_crosschain_transport.py +0 -0
  272. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_docs_typing.py +0 -0
  273. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_erc20.py +0 -0
  274. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_error_hierarchy.py +0 -0
  275. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_composed_hypercore.py +0 -0
  276. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_crosschain.py +0 -0
  277. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  278. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_examples_vaults.py +0 -0
  279. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_explorer.py +0 -0
  280. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_external_state_events.py +0 -0
  281. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_fee_manager.py +0 -0
  282. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_fuse_encoding.py +0 -0
  283. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_fusion_instance.py +0 -0
  284. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_governance_wrappers.py +0 -0
  285. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_guide.py +0 -0
  286. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_deployment.py +0 -0
  287. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_events.py +0 -0
  288. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_flow.py +0 -0
  289. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_live.py +0 -0
  290. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_outcome.py +0 -0
  291. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_readers.py +0 -0
  292. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_hypercore_simulation.py +0 -0
  293. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_logs.py +0 -0
  294. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_address.py +0 -0
  295. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_hypercore.py +0 -0
  296. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_server.py +0 -0
  297. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_mcp_vault_validate.py +0 -0
  298. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_multicall.py +0 -0
  299. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_oracle.py +0 -0
  300. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_oracle_mapping.py +0 -0
  301. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_plasma_vault.py +0 -0
  302. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_public_type_reachability.py +0 -0
  303. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_reader_encoding.py +0 -0
  304. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_readiness_selection.py +0 -0
  305. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_registry.py +0 -0
  306. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_revert_decoding.py +0 -0
  307. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_rewards_manager.py +0 -0
  308. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_roles.py +0 -0
  309. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_sdk_layering.py +0 -0
  310. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  311. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  312. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  313. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_canary.py +0 -0
  314. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_hypercore.py +0 -0
  315. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  316. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  317. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  318. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  319. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  320. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  321. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  322. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_examples_arbitrum.py +0 -0
  323. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_examples_base.py +0 -0
  324. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_examples_crosschain.py +0 -0
  325. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_external_state_executor_base.py +0 -0
  326. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  327. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  328. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  329. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_morpho_blue.py +0 -0
  330. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v3.py +0 -0
  331. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  332. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  333. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  334. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  335. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_readers_integration.py +0 -0
  336. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_release_funds.py +0 -0
  337. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_swap_on_base.py +0 -0
  338. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  339. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulation_errors.py +0 -0
  340. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_simulation_observation.py +0 -0
  341. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_solidity_compiler.py +0 -0
  342. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_solidity_mirrors.py +0 -0
  343. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_substrates.py +0 -0
  344. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_types_and_events.py +0 -0
  345. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_unpriceable_tokens.py +0 -0
  346. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_validation.py +0 -0
  347. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_data_block.py +0 -0
  348. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_dep_graph.py +0 -0
  349. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_info_fetched_golden.py +0 -0
  350. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_info_onchain_base.py +0 -0
  351. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  352. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  353. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_vault_simulator_blocks.py +0 -0
  354. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_web3_context.py +0 -0
  355. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/test_withdraw_manager.py +0 -0
  356. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/typed_client/consumer.py +0 -0
  357. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/tests/typed_client/rejected.py +0 -0
  358. {ipor_fusion-4.0.0.dev293 → ipor_fusion-4.0.0.dev303}/uv.lock +0 -0
@@ -207,7 +207,7 @@ the lifecycle matrix follows that.
207
207
  subpackages do not list them). Their exceptions stay in `errors` and the top
208
208
  level. `ipor_fusion.guide` keeps its own namespace. The
209
209
  public modules (`about`, `cache`, `chains`, `dependency_graph`, `errors`, `explorer`,
210
- `field_docs`, `market_ids`, `market_info`, `morpho_api`, `registry`, `substrates`, `types`,
210
+ `field_docs`, `market_ids`, `market_info`, `morpho_api`, `preflight`, `registry`, `substrates`, `types`,
211
211
  `validation`, `vault_data`, `vault_info`) stay importable for the names they
212
212
  define and the moved names README lists; `cli`, `mcp`, every other module and
213
213
  every submodule of a public subpackage (`core.contract`, `fuses.base`, ...) are
@@ -268,6 +268,9 @@ the lifecycle matrix follows that.
268
268
  `ctx.cache` (`Web3Context(cache=...)`, default `default_contract_cache()` under
269
269
  `$XDG_CACHE_HOME/ipor-fusion/`) or `get_contract_name(cache=...)`. Never cache an
270
270
  absence (no code, no answer): it can change at a later block.
271
+ `preflight.py` — `preflight_execute`: an execute batch checked as its sender would
272
+ send it (canCall, registered fuses, MARKET_ID, balance fuses, one eth_simulateV1
273
+ run), `ExecutePreflight` and the `fusion vault preflight` document;
271
274
  `registry.py` the ipor-abi address registry (`fetch_address_registry` →
272
275
  `AddressRegistry`, one raw-GitHub file per chain in `REGISTRY_CHAIN_IDS`, cached ten
273
276
  minutes in the process, for good at a commit ref) and the `fusion address` documents;
@@ -295,7 +298,9 @@ the lifecycle matrix follows that.
295
298
  of the node's response), `oracle`, `fusion_factory`,
296
299
  `external_state_executor` (NAV marks for market 50), `erc20`, `fuse_action`
297
300
  (`FuseAction`), `fuse` (the `Fuse` base, `StakeFuse`, the shared validators and
298
- substrate packers), `address` (`_validate_not_zero_address`). `core` imports neither
301
+ substrate packers), `fuse_decoding` (`decode_execute_calldata`, `decode_fuse_action`,
302
+ `fuse_classes_for`: an action back to the function its class encoded, from each
303
+ class's `SIGNATURES`), `address` (`_validate_not_zero_address`). `core` imports neither
299
304
  `fuses` nor `crosschain` (`tests/test_sdk_layering.py`)
300
305
  - `fuses/` — per-protocol fuse encoders (aave_v3, async_action, compound_v3,
301
306
  erc4626, euler_v2, external_state, fluid_instadapp, gearbox_v3, merkl, morpho, ramses_v2,
@@ -459,7 +464,10 @@ or examples.
459
464
  `ipor-abi` `addresses.json`; a fuse must be registered on the vault
460
465
  (`PlasmaVault.get_fuses()`) and its market granted substrates before an
461
466
  action can succeed.
462
- - Adding a protocol: new module in `fuses/`, re-export from `fuses/__init__.py`
467
+ - Adding a protocol: new module in `fuses/` whose classes list every Solidity
468
+ function they encode in `SIGNATURES` (`tests/conftest.py` fails any call an
469
+ encoder builds under test that its class does not declare; it cannot catch an
470
+ extra or never-exercised entry), re-export from `fuses/__init__.py`
463
471
  and the top-level `__all__` (and both snapshots in `test_public_api.py`),
464
472
  encoding test in `test_fuse_encoding.py`, and a row in the README protocol
465
473
  table.
@@ -497,7 +505,7 @@ byte, e.g. Ebisu ZAPPER/REGISTRY).
497
505
  `fusion address` (lookup, names, list; the ipor-abi registry through `registry.py`),
498
506
  `fusion config` (set-provider, set-etherscan-key, show), `fusion market`
499
507
  (morpho-blue, meta-morpho), `fusion vault` (add, remove, list, info, validate,
500
- role-accounts, oracle-mapping). `vault info` runs independent fetch pipelines
508
+ role-accounts, oracle-mapping, preflight). `vault info` runs independent fetch pipelines
501
509
  (vault/asset, fees, WithdrawManager, markets, roles) concurrently on a
502
510
  `ThreadPoolExecutor`, each a short chain of `Multicall3` batches; keep new
503
511
  reads inside a batch rather than adding per-call round trips. A `from_url` context
@@ -510,7 +518,7 @@ keys); tests redirect it with `monkeypatch.setenv("XDG_CACHE_HOME", ...)` or
510
518
  inject a cache.
511
519
 
512
520
  Every CLI command has an MCP tool (`fusion changelog` maps to `server_info`;
513
- the rest share names). Read-only: `vault_info`, `vault_validate`,
521
+ the rest share names). Read-only: `vault_info`, `vault_validate`, `vault_preflight`,
514
522
  `vault_list`, `vault_role_accounts`, `vault_oracle_mapping`, `market_morpho_blue`,
515
523
  `market_meta_morpho`, `address_lookup`, `address_names`, `address_list`,
516
524
  `config_show`, `server_info`. Mutating: `vault_add`,
@@ -438,8 +438,12 @@ exist; the comparison is equality either way.
438
438
  - `ExternalStateVaultState.last_checked_custodian_timestamp` is
439
439
  `last_checked_custodian_marker` and `BalanceFuseSnapshot.last_custodian_update_timestamp`
440
440
  is `custodian_marker`, both a `CustodianMarker` (an opaque value the
441
- contracts only compare for equality): a timestamp on an earlier executor, the
442
- confirm count on a released one. `ExternalStateExecutor.confirm_count()`
441
+ contracts only compare for equality): a unix timestamp on an earlier
442
+ executor, a confirmation count on a released one.
443
+ `ExternalStateVaultState.custodian_marker_kind` (`CustodianMarkerKind`, read
444
+ from the executor at the snapshot block; `None` without an executor) and
445
+ `ExternalStateExecutor.read_custodian_marker_kind(ctx)` say which; never
446
+ compare a count with a clock. `ExternalStateExecutor.confirm_count()`
443
447
  exists only on the released executor (it reverts on an earlier one), and
444
448
  `last_custodian_update_timestamp()` reads the timestamp on both.
445
449
  - New on the released build: `ExternalStateExecutor.unforwarded_credit(asset)`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev293
3
+ Version: 4.0.0.dev303
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
@@ -201,6 +201,23 @@ if result.all_success:
201
201
  print(after.value - before.value)
202
202
  ```
203
203
 
204
+ Read a batch back from calldata, e.g. to review a multisig transaction:
205
+
206
+ ```python
207
+ from ipor_fusion import AaveV3SupplyFuse, decode_execute_calldata, decode_fuse_action, fuse_classes_for
208
+
209
+ # The class behind each fuse address you know (from the registry or your config);
210
+ # fuse_classes_for(action) lists the candidates when you do not.
211
+ known = {fuse.address: AaveV3SupplyFuse}
212
+ for action in decode_execute_calldata(payload): # execute / claimRewards calldata
213
+ cls = known.get(action.fuse)
214
+ if cls is None:
215
+ print(action.fuse, "one of", [c.__name__ for c in fuse_classes_for(action)])
216
+ continue
217
+ decoded = decode_fuse_action(action, cls)
218
+ print(decoded.label, decoded.args) # AaveV3SupplyFuse.enter ((asset, amount, e_mode),)
219
+ ```
220
+
204
221
  ## CLI Quickstart
205
222
 
206
223
  The SDK ships with a `fusion` CLI for inspecting and managing Plasma Vaults from the terminal.
@@ -223,6 +240,9 @@ fusion vault validate 0xB8a451107A9f87FDe481D4D686247D6e43Ed715e --chain-id ethe
223
240
  # List saved vaults
224
241
  fusion vault list
225
242
 
243
+ # Check an execute batch before signing it (role, fuses, balance fuses, simulation)
244
+ fusion vault preflight 0xVAULT_ADDRESS --calldata 0xEXECUTE_CALLDATA --sender 0xALPHA
245
+
226
246
  # Inspect a Morpho Blue market or MetaMorpho vault
227
247
  fusion market morpho-blue 0xMARKET_ID --chain ethereum
228
248
  fusion market meta-morpho 0xVAULT_ADDRESS --chain ethereum
@@ -289,6 +309,7 @@ Available tools:
289
309
  | `config_set_etherscan_key` | Set Etherscan API key (enables contract name resolution) |
290
310
  | `vault_info` | Full on-chain vault state — assets, fuses, balances, fees, lending health, reconciliation |
291
311
  | `vault_validate` | Health verdict (ok / warning / critical) with the reconciliation, lending-health and ERC20 numbers behind it |
312
+ | `vault_preflight` | Check execute calldata as its sender would send it: permission, fuses, balance fuses, simulation |
292
313
  | `vault_role_accounts` | Accounts holding each AccessManager role on a vault |
293
314
  | `vault_oracle_mapping` | Price-oracle sources per asset for a vault |
294
315
  | `vault_list` | List all saved vaults |
@@ -152,6 +152,23 @@ if result.all_success:
152
152
  print(after.value - before.value)
153
153
  ```
154
154
 
155
+ Read a batch back from calldata, e.g. to review a multisig transaction:
156
+
157
+ ```python
158
+ from ipor_fusion import AaveV3SupplyFuse, decode_execute_calldata, decode_fuse_action, fuse_classes_for
159
+
160
+ # The class behind each fuse address you know (from the registry or your config);
161
+ # fuse_classes_for(action) lists the candidates when you do not.
162
+ known = {fuse.address: AaveV3SupplyFuse}
163
+ for action in decode_execute_calldata(payload): # execute / claimRewards calldata
164
+ cls = known.get(action.fuse)
165
+ if cls is None:
166
+ print(action.fuse, "one of", [c.__name__ for c in fuse_classes_for(action)])
167
+ continue
168
+ decoded = decode_fuse_action(action, cls)
169
+ print(decoded.label, decoded.args) # AaveV3SupplyFuse.enter ((asset, amount, e_mode),)
170
+ ```
171
+
155
172
  ## CLI Quickstart
156
173
 
157
174
  The SDK ships with a `fusion` CLI for inspecting and managing Plasma Vaults from the terminal.
@@ -174,6 +191,9 @@ fusion vault validate 0xB8a451107A9f87FDe481D4D686247D6e43Ed715e --chain-id ethe
174
191
  # List saved vaults
175
192
  fusion vault list
176
193
 
194
+ # Check an execute batch before signing it (role, fuses, balance fuses, simulation)
195
+ fusion vault preflight 0xVAULT_ADDRESS --calldata 0xEXECUTE_CALLDATA --sender 0xALPHA
196
+
177
197
  # Inspect a Morpho Blue market or MetaMorpho vault
178
198
  fusion market morpho-blue 0xMARKET_ID --chain ethereum
179
199
  fusion market meta-morpho 0xVAULT_ADDRESS --chain ethereum
@@ -240,6 +260,7 @@ Available tools:
240
260
  | `config_set_etherscan_key` | Set Etherscan API key (enables contract name resolution) |
241
261
  | `vault_info` | Full on-chain vault state — assets, fuses, balances, fees, lending health, reconciliation |
242
262
  | `vault_validate` | Health verdict (ok / warning / critical) with the reconciliation, lending-health and ERC20 numbers behind it |
263
+ | `vault_preflight` | Check execute calldata as its sender would send it: permission, fuses, balance fuses, simulation |
243
264
  | `vault_role_accounts` | Accounts holding each AccessManager role on a vault |
244
265
  | `vault_oracle_mapping` | Price-oracle sources per asset for a vault |
245
266
  | `vault_list` | List all saved vaults |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev293"
3
+ version = "4.0.0.dev303"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -42,6 +42,7 @@ from ipor_fusion.core.erc20 import ERC20
42
42
  from ipor_fusion.core.external_state_executor import (
43
43
  BalanceFuseSnapshot,
44
44
  BalanceProposal,
45
+ CustodianMarkerKind,
45
46
  ExternalStateExecutor,
46
47
  ExternalStateVaultState,
47
48
  read_external_state,
@@ -52,6 +53,12 @@ from ipor_fusion.core.fee_manager import (
52
53
  HighWaterMarkPerformanceFee,
53
54
  RecipientFee,
54
55
  )
56
+ from ipor_fusion.core.fuse_decoding import (
57
+ DecodedFuseAction,
58
+ decode_execute_calldata,
59
+ decode_fuse_action,
60
+ fuse_classes_for,
61
+ )
55
62
  from ipor_fusion.core.fusion_factory import CloneArgs, FusionFactory, FusionInstance
56
63
  from ipor_fusion.core.logs import (
57
64
  LogSource,
@@ -113,6 +120,7 @@ from ipor_fusion.errors import (
113
120
  ContractNotFoundError,
114
121
  CrosschainDiscoveryError,
115
122
  EmptyCallResultError,
123
+ ExecutePreflightError,
116
124
  ExternalStateError,
117
125
  ExternalStateEventDecodeError,
118
126
  HyperCoreEventDecodeError,
@@ -255,6 +263,20 @@ from ipor_fusion.morpho_api import (
255
263
  fetch_market,
256
264
  fetch_vault,
257
265
  )
266
+ from ipor_fusion.preflight import (
267
+ ExecutePreflight,
268
+ ExecutePreflightJson,
269
+ PreflightAction,
270
+ PreflightActionJson,
271
+ PreflightCheck,
272
+ PreflightFinding,
273
+ PreflightFindingJson,
274
+ PreflightSimulation,
275
+ PreflightSimulationJson,
276
+ build_execute_preflight_json,
277
+ parse_execute_actions,
278
+ preflight_execute,
279
+ )
258
280
  from ipor_fusion.readers import (
259
281
  HYPEREVM_CHAIN_ID,
260
282
  AaveV3FuseReader,
@@ -351,6 +373,7 @@ from ipor_fusion.types import (
351
373
  Price,
352
374
  ProposalHash,
353
375
  RoleId,
376
+ ScheduleId,
354
377
  Severity,
355
378
  Shares,
356
379
  Tick,
@@ -477,6 +500,23 @@ __all__ = [
477
500
  "repository_url",
478
501
  "Web3Context",
479
502
  "InFlightLimit",
503
+ "ExecutePreflightError",
504
+ "ExecutePreflight",
505
+ "ExecutePreflightJson",
506
+ "PreflightAction",
507
+ "PreflightActionJson",
508
+ "PreflightCheck",
509
+ "PreflightFinding",
510
+ "PreflightFindingJson",
511
+ "PreflightSimulation",
512
+ "PreflightSimulationJson",
513
+ "build_execute_preflight_json",
514
+ "parse_execute_actions",
515
+ "preflight_execute",
516
+ "DecodedFuseAction",
517
+ "decode_execute_calldata",
518
+ "decode_fuse_action",
519
+ "fuse_classes_for",
480
520
  "AddressNotInRegistryError",
481
521
  "AddressRegistryError",
482
522
  "REGISTRY_CHAIN_IDS",
@@ -545,6 +585,7 @@ __all__ = [
545
585
  "CloneArgs",
546
586
  "ExternalStateExecutor",
547
587
  "BalanceFuseSnapshot",
588
+ "CustodianMarkerKind",
548
589
  "ExternalStateVaultState",
549
590
  "read_external_state",
550
591
  "erc20_balance_slot",
@@ -655,6 +696,7 @@ __all__ = [
655
696
  "Liquidity",
656
697
  "RoleId",
657
698
  "ChainId",
699
+ "ScheduleId",
658
700
  "Wad",
659
701
  "MorphoBlueMarketId",
660
702
  "ProposalHash",
@@ -9,6 +9,7 @@ from ipor_fusion.cli.address_cmd import address
9
9
  from ipor_fusion.cli.changelog_cmd import changelog
10
10
  from ipor_fusion.cli.config_cmd import config
11
11
  from ipor_fusion.cli.market_cmd import market
12
+ from ipor_fusion.cli.preflight_cmd import preflight
12
13
  from ipor_fusion.cli.vault_cmd import vault
13
14
 
14
15
 
@@ -35,6 +36,7 @@ cli.add_command(changelog)
35
36
  cli.add_command(config)
36
37
  cli.add_command(market)
37
38
  cli.add_command(vault)
39
+ vault.add_command(preflight)
38
40
 
39
41
 
40
42
  def main() -> None:
@@ -0,0 +1,128 @@
1
+ """`fusion vault preflight`: check an execute batch before sending it."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import click
10
+ from eth_typing import ChecksumAddress
11
+
12
+ from ipor_fusion.cli.config_store import load_config
13
+ from ipor_fusion.cli.options import (
14
+ ADDRESS,
15
+ BLOCK_NUMBER,
16
+ CHAIN,
17
+ VAULT_ADDRESS,
18
+ _build_ctx,
19
+ )
20
+ from ipor_fusion.cli.vault_rendering import _print_table
21
+ from ipor_fusion.preflight import (
22
+ ExecutePreflight,
23
+ build_execute_preflight_json,
24
+ parse_execute_actions,
25
+ preflight_execute,
26
+ )
27
+ from ipor_fusion.types import ChainId, PlasmaVaultAddress
28
+
29
+
30
+ def _read_calldata(calldata: str) -> str:
31
+ if calldata.startswith("@"):
32
+ try:
33
+ return Path(calldata[1:]).read_text()
34
+ except OSError as exc:
35
+ raise click.BadParameter(str(exc), param_hint="--calldata") from exc
36
+ return calldata
37
+
38
+
39
+ @click.command("preflight")
40
+ @click.argument("vault_address", type=VAULT_ADDRESS)
41
+ @click.option(
42
+ "--calldata",
43
+ required=True,
44
+ help="execute((address,bytes)[]) calldata as hex, or @file holding it.",
45
+ )
46
+ @click.option(
47
+ "--sender",
48
+ type=ADDRESS,
49
+ required=True,
50
+ help="The account that will send execute (an ALPHA).",
51
+ )
52
+ @click.option(
53
+ "--chain-id", type=CHAIN, default=None, help="Chain ID or name (e.g. 1, ethereum)."
54
+ )
55
+ @click.option(
56
+ "--block-number", type=BLOCK_NUMBER, default=None, help="Block (default: latest)."
57
+ )
58
+ @click.option(
59
+ "--simulate/--no-simulate",
60
+ default=True,
61
+ help="Run the batch through eth_simulateV1 (default) or only the static checks.",
62
+ )
63
+ @click.option(
64
+ "--json", "json_output", is_flag=True, default=False, help="Output as JSON."
65
+ )
66
+ def preflight(
67
+ vault_address: PlasmaVaultAddress,
68
+ calldata: str,
69
+ sender: ChecksumAddress,
70
+ chain_id: ChainId | None,
71
+ block_number: int | None,
72
+ simulate: bool,
73
+ json_output: bool,
74
+ ) -> None:
75
+ """Check an execute batch as SENDER would send it: role, fuse registration,
76
+ balance fuses, and a simulation of the exact calldata. Read-only. Exits with
77
+ status 1 when a check is critical."""
78
+ try:
79
+ actions = parse_execute_actions(_read_calldata(calldata))
80
+ except ValueError as exc:
81
+ raise click.BadParameter(str(exc), param_hint="--calldata") from exc
82
+ if not actions:
83
+ raise click.BadParameter("the batch has no actions", param_hint="--calldata")
84
+ _, ctx = _build_ctx(load_config(), vault_address, chain_id, block_number)
85
+ result = preflight_execute(
86
+ ctx, vault_address, actions, sender=sender, simulate=simulate
87
+ )
88
+ if json_output:
89
+ click.echo(json.dumps(build_execute_preflight_json(result), indent=2))
90
+ else:
91
+ _print_preflight(result)
92
+ if result.verdict == "critical":
93
+ sys.exit(1)
94
+
95
+
96
+ def _print_preflight(result: ExecutePreflight) -> None:
97
+ click.echo(f"Vault: {result.vault}")
98
+ click.echo(f"Block: {result.block_number}")
99
+ click.echo(f"Sender: {result.sender}")
100
+ click.echo(f"Verdict: {result.verdict}")
101
+ click.echo("\nActions")
102
+ _print_table(
103
+ ("#", "Fuse", "Registered", "Market", "Encoder"),
104
+ [
105
+ (
106
+ str(a.index),
107
+ a.fuse,
108
+ "yes" if a.registered else "NO",
109
+ "?" if a.market_id is None else str(a.market_id),
110
+ ", ".join(a.candidates) or "unknown",
111
+ )
112
+ for a in result.actions
113
+ ],
114
+ )
115
+ click.echo("\nFindings")
116
+ _print_table(
117
+ ("Severity", "Check", "Message"),
118
+ [(f.severity, f.check, f.message) for f in result.findings],
119
+ )
120
+ if result.simulation is not None:
121
+ sim = result.simulation
122
+ click.echo("\nSimulation")
123
+ click.echo(f" success: {sim.success} gas: {sim.gas_used}")
124
+ click.echo(
125
+ f" totalAssets: {sim.total_assets_before} -> {sim.total_assets_after}"
126
+ )
127
+ if sim.revert_reason:
128
+ click.echo(f" revert: {sim.revert_reason}")
@@ -18,6 +18,7 @@ from ipor_fusion.core.erc20 import ERC20
18
18
  from ipor_fusion.core.external_state_executor import (
19
19
  BalanceFuseSnapshot,
20
20
  BalanceProposal,
21
+ CustodianMarkerKind,
21
22
  ExternalStateExecutor,
22
23
  ExternalStateVaultState,
23
24
  read_external_state,
@@ -35,6 +36,12 @@ from ipor_fusion.core.fuse import (
35
36
  from ipor_fusion.core.fuse_action import (
36
37
  FuseAction,
37
38
  )
39
+ from ipor_fusion.core.fuse_decoding import (
40
+ DecodedFuseAction,
41
+ decode_execute_calldata,
42
+ decode_fuse_action,
43
+ fuse_classes_for,
44
+ )
38
45
  from ipor_fusion.core.fusion_factory import CloneArgs, FusionFactory, FusionInstance
39
46
  from ipor_fusion.core.logs import (
40
47
  LogSource,
@@ -86,6 +93,10 @@ from ipor_fusion.core.withdraw_manager import (
86
93
  __all__ = [
87
94
  "Web3Context",
88
95
  "InFlightLimit",
96
+ "DecodedFuseAction",
97
+ "decode_execute_calldata",
98
+ "decode_fuse_action",
99
+ "fuse_classes_for",
89
100
  "ContextManager",
90
101
  "Multicall3",
91
102
  "Multicall3Batch",
@@ -122,6 +133,7 @@ __all__ = [
122
133
  "BalanceFuseSnapshot",
123
134
  "BalanceProposal",
124
135
  "ExternalStateExecutor",
136
+ "CustodianMarkerKind",
125
137
  "ExternalStateVaultState",
126
138
  "read_external_state",
127
139
  "Fuse",
@@ -2,7 +2,7 @@ from collections.abc import Callable, Sequence
2
2
  from dataclasses import dataclass
3
3
  from typing import TypedDict
4
4
 
5
- from eth_abi import decode
5
+ from eth_abi import decode, encode
6
6
  from eth_abi.exceptions import InsufficientDataBytes
7
7
  from eth_typing import ChecksumAddress
8
8
  from hexbytes import HexBytes
@@ -27,6 +27,7 @@ from ipor_fusion.types import (
27
27
  Period,
28
28
  PlasmaVaultAddress,
29
29
  RoleId,
30
+ ScheduleId,
30
31
  )
31
32
 
32
33
 
@@ -183,6 +184,26 @@ class AccessManager(ContractWrapper):
183
184
  decoder=_role_status_decoder,
184
185
  )
185
186
 
187
+ def get_schedule(self, operation: ScheduleId) -> Call[Timestamp]:
188
+ """When the scheduled ``operation`` becomes executable; 0 when it is not
189
+ scheduled, was executed or cancelled, or expired (a week after that time)."""
190
+ return self._view(
191
+ "getSchedule(bytes32)",
192
+ operation,
193
+ output_types=["uint48"],
194
+ decoder=Timestamp,
195
+ )
196
+
197
+ @staticmethod
198
+ def hash_operation(
199
+ caller: ChecksumAddress, target: ChecksumAddress, data: bytes
200
+ ) -> ScheduleId:
201
+ """The id `schedule` and `get_schedule` use for ``caller`` calling
202
+ ``target`` with ``data``, computed offline as the manager does."""
203
+ return ScheduleId(
204
+ Web3.keccak(encode(["address", "address", "bytes"], [caller, target, data]))
205
+ )
206
+
186
207
  def can_call(
187
208
  self, caller: ChecksumAddress, target: ChecksumAddress, selector: bytes
188
209
  ) -> Call[CallPermission]:
@@ -18,13 +18,15 @@ from __future__ import annotations
18
18
 
19
19
  from collections.abc import Iterable
20
20
  from dataclasses import dataclass, replace
21
+ from enum import Enum
21
22
 
22
23
  from eth_abi import decode, encode
23
24
  from eth_abi.exceptions import DecodingError
24
25
  from eth_typing import ChecksumAddress
25
- from eth_utils import keccak
26
+ from eth_utils import function_signature_to_4byte_selector, keccak
26
27
  from hexbytes import HexBytes
27
28
  from web3 import Web3
29
+ from web3.exceptions import ContractLogicError
28
30
  from web3.types import BlockIdentifier, Timestamp
29
31
 
30
32
  from ipor_fusion.core.context import Web3Context
@@ -118,6 +120,35 @@ def _require_matching_hash(
118
120
  )
119
121
 
120
122
 
123
+ class CustodianMarkerKind(Enum):
124
+ """What an executor's custodian marker counts: the last confirmed update's
125
+ unix timestamp (executors before the 2026-10-21 contracts release) or the
126
+ number of confirmed updates (executors of that release)."""
127
+
128
+ TIMESTAMP = "timestamp"
129
+ CONFIRM_COUNT = "confirm_count"
130
+
131
+
132
+ _CONFIRM_COUNT_SELECTOR = function_signature_to_4byte_selector("confirmCount()")
133
+
134
+
135
+ def _read_marker_kind(
136
+ ctx: Web3Context, executor: ChecksumAddress, block: BlockIdentifier
137
+ ) -> CustodianMarkerKind:
138
+ """Decided from the executor, not guessed from a value: a released
139
+ executor answers ``confirmCount()``, an earlier one has no such function
140
+ and the call reverts. Any other failure raises."""
141
+ try:
142
+ raw = ctx.call(executor, _CONFIRM_COUNT_SELECTOR, block)
143
+ except ContractLogicError:
144
+ return CustodianMarkerKind.TIMESTAMP
145
+ if len(raw) != 32:
146
+ raise ExternalStateError(
147
+ f"confirmCount() of {executor} returned {len(raw)} bytes, not a uint256"
148
+ )
149
+ return CustodianMarkerKind.CONFIRM_COUNT
150
+
151
+
121
152
  @dataclass(frozen=True, slots=True)
122
153
  class ExternalStateVaultState:
123
154
  """The external-state market's vault-scoped state, read from the vault's
@@ -136,7 +167,9 @@ class ExternalStateVaultState:
136
167
  and not the pre-hook's staleness clock, which is
137
168
  `ExternalStateExecutor.get_oldest_update_timestamp`. The fuse and the
138
169
  pre-hook only compare it for equality with
139
- `BalanceFuseSnapshot.custodian_marker`; `paused` is the flag
170
+ `BalanceFuseSnapshot.custodian_marker`. `custodian_marker_kind` says
171
+ which, read from the executor at the same block (``None`` without an
172
+ executor); `paused` is the flag
140
173
  the balance fuse sets on a big change and `ExternalStateUnpauseFuse` clears
141
174
  with an atomist signature. `ExternalStatePausePreHook` blocks the vault's
142
175
  gated user operations while the flag is set, and also, flag or not, while
@@ -148,6 +181,7 @@ class ExternalStateVaultState:
148
181
  executor: ChecksumAddress | None
149
182
  last_total_balance: Amount
150
183
  last_checked_custodian_marker: CustodianMarker
184
+ custodian_marker_kind: CustodianMarkerKind | None
151
185
  paused: bool
152
186
  block: int
153
187
 
@@ -232,10 +266,14 @@ def read_external_state(
232
266
  raise ExternalStateError(
233
267
  f"malformed pause flag in the external-state storage of {checksum}"
234
268
  )
269
+ executor = Web3.to_checksum_address(f"0x{words[0]:040x}") if words[0] else None
235
270
  return ExternalStateVaultState(
236
- executor=Web3.to_checksum_address(f"0x{words[0]:040x}") if words[0] else None,
271
+ executor=executor,
237
272
  last_total_balance=Amount(words[1]),
238
273
  last_checked_custodian_marker=CustodianMarker(words[2]),
274
+ custodian_marker_kind=(
275
+ _read_marker_kind(ctx, executor, read_at) if executor else None
276
+ ),
239
277
  paused=words[3] == 1,
240
278
  block=block,
241
279
  )
@@ -365,6 +403,11 @@ class ExternalStateExecutor(ContractWrapper):
365
403
  )
366
404
  )
367
405
 
406
+ def read_custodian_marker_kind(self, ctx: Web3Context) -> CustodianMarkerKind:
407
+ """Whether this executor's custodian marker is a timestamp or a
408
+ confirm count, at ``ctx``'s default block (see `CustodianMarkerKind`)."""
409
+ return _read_marker_kind(ctx, self._require_address(), ctx.default_block)
410
+
368
411
  def confirm_count(self) -> Call[CustodianMarker]:
369
412
  """Executors of the 2026-10-21 contracts release only: the count of
370
413
  confirmed custodian updates across every balance account, the marker
@@ -9,6 +9,7 @@ without importing that package, whose ``__init__`` loads every encoder.
9
9
  from abc import ABC
10
10
  from collections.abc import Sequence
11
11
  from types import MemberDescriptorType
12
+ from typing import ClassVar
12
13
 
13
14
  from eth_abi import encode
14
15
  from eth_typing import ChecksumAddress
@@ -27,8 +28,15 @@ class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
27
28
  same class and carry the same constructor state (the fuse address, plus e.g.
28
29
  a `StakeFuse`'s staking contract), so a subclass that stores more state is
29
30
  compared on it without overriding anything. That state must be hashable.
31
+
32
+ ``SIGNATURES`` lists the Solidity functions a class's own methods encode;
33
+ `signatures` adds the inherited ones. The decoder reads them to recognize an
34
+ action; the test suite fails any call an encoder builds under test that its
35
+ class does not declare.
30
36
  """
31
37
 
38
+ SIGNATURES: ClassVar[tuple[str, ...]] = ()
39
+
32
40
  def __init__(self, address: FuseAddress):
33
41
  _validate_not_zero_address(address, "fuse address")
34
42
  self._address = address
@@ -37,6 +45,15 @@ class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
37
45
  def address(self) -> FuseAddress:
38
46
  return self._address
39
47
 
48
+ @classmethod
49
+ def signatures(cls) -> tuple[str, ...]:
50
+ """Every Solidity function this class encodes, inherited ones included."""
51
+ seen: dict[str, None] = {}
52
+ for klass in reversed(cls.__mro__):
53
+ for signature in vars(klass).get("SIGNATURES", ()):
54
+ seen[signature] = None
55
+ return tuple(seen)
56
+
40
57
  @staticmethod
41
58
  def _validate_amount(value: Amount, name: str) -> None:
42
59
  if value <= 0:
@@ -94,6 +111,11 @@ class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
94
111
  class StakeFuse(Fuse):
95
112
  """Base fuse for stake/unstake operations on a staking contract."""
96
113
 
114
+ SIGNATURES = (
115
+ "enter((uint256,address))",
116
+ "exit((uint256,address))",
117
+ )
118
+
97
119
  def __init__(self, fuse_address: FuseAddress, staking_address: ChecksumAddress):
98
120
  super().__init__(fuse_address)
99
121
  self._staking_address = staking_address