ipor-fusion 4.0.0.dev286__tar.gz → 4.0.0.dev293__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 (350) hide show
  1. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/AGENTS.md +11 -1
  2. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/MIGRATION_4.md +22 -0
  3. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/PKG-INFO +8 -4
  4. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/README.md +7 -3
  5. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/pyproject.toml +1 -1
  6. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/__init__.py +10 -0
  7. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/about.py +2 -1
  8. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/external_state_executor.py +64 -10
  9. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/__init__.py +8 -0
  10. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/external_state.py +11 -0
  11. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/external_state_event_decoding.py +48 -0
  12. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/mcp/models.py +3 -2
  13. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/types.py +5 -0
  14. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_external_state_events.py +23 -3
  15. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_external_state_executor.py +21 -4
  16. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_fuse_encoding.py +7 -0
  17. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_public_api.py +10 -0
  18. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_solidity_mirrors.py +8 -6
  19. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.claude-plugin/marketplace.json +0 -0
  20. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.claude-plugin/plugin.json +0 -0
  21. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.env.example +0 -0
  22. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.gemini/settings.json +0 -0
  23. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.git-blame-ignore-revs +0 -0
  24. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/cd.yml +0 -0
  25. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/ci.yml +0 -0
  26. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/commit-lint.yml +0 -0
  27. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/notify-slack.yml +0 -0
  28. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/python-build.yml +0 -0
  29. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/python-publish.yml +0 -0
  30. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/release.yml +0 -0
  31. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.github/workflows/report-slack.yml +0 -0
  32. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.gitignore +0 -0
  33. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.mcp.json +0 -0
  34. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.pre-commit-config.yaml +0 -0
  35. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/.python-version +0 -0
  36. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/CHANGELOG.md +0 -0
  37. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/CLAUDE.md +0 -0
  38. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/CODEOWNERS +0 -0
  39. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/LICENSE +0 -0
  40. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/build.sh +0 -0
  41. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/CONTRIBUTING.md +0 -0
  42. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/README.md +0 -0
  43. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  44. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  45. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  46. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  47. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/external_state_margin_leg_base.py +0 -0
  48. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/examples/simple_aave_v3_supply_base.py +0 -0
  49. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/pytest.ini +0 -0
  50. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/scripts/commit-check.sh +0 -0
  51. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/scripts/crosschain_readiness.py +0 -0
  52. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/skills/ipor-deploy-vault/SKILL.md +0 -0
  53. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cache.py +0 -0
  54. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/chains.py +0 -0
  55. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/__init__.py +0 -0
  56. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/address_cmd.py +0 -0
  57. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  58. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/config_cmd.py +0 -0
  59. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/config_store.py +0 -0
  60. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/main.py +0 -0
  61. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/market_cmd.py +0 -0
  62. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/options.py +0 -0
  63. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  64. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  65. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/vault_health.py +0 -0
  66. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  67. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  68. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  69. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/config/__init__.py +0 -0
  70. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/config/roles.py +0 -0
  71. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/__init__.py +0 -0
  72. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/access.py +0 -0
  73. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/address.py +0 -0
  74. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/context.py +0 -0
  75. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/context_manager.py +0 -0
  76. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/contract.py +0 -0
  77. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/erc20.py +0 -0
  78. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/fee_manager.py +0 -0
  79. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/fuse.py +0 -0
  80. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/fuse_action.py +0 -0
  81. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/fusion_factory.py +0 -0
  82. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/logs.py +0 -0
  83. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/multicall.py +0 -0
  84. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/oracle.py +0 -0
  85. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/plasma_vault.py +0 -0
  86. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/rewards_manager.py +0 -0
  87. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/simulation.py +0 -0
  88. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/simulation_result.py +0 -0
  89. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  90. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/__init__.py +0 -0
  91. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  92. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  93. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  94. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  95. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  96. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  97. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  98. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  99. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  100. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/contracts.py +0 -0
  101. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/discovery.py +0 -0
  102. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/errors.py +0 -0
  103. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  104. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  105. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  106. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  107. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/lane.py +0 -0
  108. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/logs.py +0 -0
  109. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/messages.py +0 -0
  110. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/simulation.py +0 -0
  111. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  112. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  113. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  114. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  115. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  116. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/crosschain/transport.py +0 -0
  117. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/dependency_graph.py +0 -0
  118. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/entry_cli.py +0 -0
  119. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/entry_mcp.py +0 -0
  120. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/errors.py +0 -0
  121. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/explorer.py +0 -0
  122. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/field_docs.py +0 -0
  123. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  124. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/async_action.py +0 -0
  125. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/base.py +0 -0
  126. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  127. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/erc4626.py +0 -0
  128. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  129. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/events.py +0 -0
  130. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  131. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  132. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/hypercore.py +0 -0
  133. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  134. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/merkl.py +0 -0
  135. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/morpho.py +0 -0
  136. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  137. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  138. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/fuses/universal.py +0 -0
  139. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/guide/__init__.py +0 -0
  140. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/guide/architecture.md +0 -0
  141. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/guide/glossary.md +0 -0
  142. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/guide/invariants.md +0 -0
  143. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/guide/quickstart.md +0 -0
  144. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/hypercore/__init__.py +0 -0
  145. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/market_ids.py +0 -0
  146. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/market_info.py +0 -0
  147. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/mcp/__init__.py +0 -0
  148. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/mcp/guide.py +0 -0
  149. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/mcp/server.py +0 -0
  150. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/morpho_api.py +0 -0
  151. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/py.typed +0 -0
  152. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/__init__.py +0 -0
  153. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/aave_v3.py +0 -0
  154. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/aave_v4.py +0 -0
  155. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/compound_v3.py +0 -0
  156. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/hypercore.py +0 -0
  157. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  158. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/lending_health.py +0 -0
  159. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/morpho.py +0 -0
  160. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  161. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  162. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  163. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/position_manager.py +0 -0
  164. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  165. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  166. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/readers/unpriceable.py +0 -0
  167. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/registry.py +0 -0
  168. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/substrates.py +0 -0
  169. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/validation.py +0 -0
  170. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/vault_data.py +0 -0
  171. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/vault_erc20.py +0 -0
  172. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/vault_info.py +0 -0
  173. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/vault_managers.py +0 -0
  174. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/vault_positions.py +0 -0
  175. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/src/ipor_fusion/vault_reads.py +0 -0
  176. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/UniswapV3UniversalRouter.py +0 -0
  177. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_crosschain.py +0 -0
  178. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_crosschain_lifecycle.py +0 -0
  179. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_crosschain_recovery.py +0 -0
  180. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_euler_v2.py +0 -0
  181. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_event_args.py +0 -0
  182. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_fake_rpc.py +0 -0
  183. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_foundry.py +0 -0
  184. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_golden.py +0 -0
  185. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_guide.py +0 -0
  186. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_hypercore_model.py +0 -0
  187. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_hypercore_shadow.py +0 -0
  188. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_live.py +0 -0
  189. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_multicall.py +0 -0
  190. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_oracle_mapping_fake.py +0 -0
  191. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_simulate.py +0 -0
  192. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/_validation.py +0 -0
  193. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/addresses.py +0 -0
  194. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/conftest.py +0 -0
  195. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/constants.py +0 -0
  196. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  197. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/hypercore_market55.json +0 -0
  198. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/hypercore_official.json +0 -0
  199. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  200. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  201. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  202. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  203. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  204. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  205. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  206. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  207. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  208. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  209. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  210. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  211. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  212. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  213. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  214. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  215. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/simulate_v1_request.json +0 -0
  216. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/clean.json +0 -0
  217. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  218. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  219. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  220. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  221. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  222. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  223. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  224. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  225. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/full.json +0 -0
  226. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  227. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/full.txt +0 -0
  228. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/rich.json +0 -0
  229. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  230. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  231. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_about.py +0 -0
  232. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_access_manager.py +0 -0
  233. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cache.py +0 -0
  234. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_chains.py +0 -0
  235. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_address.py +0 -0
  236. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_changelog.py +0 -0
  237. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_commands.py +0 -0
  238. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_config_store.py +0 -0
  239. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_helpers.py +0 -0
  240. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_hypercore.py +0 -0
  241. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_lending_health.py +0 -0
  242. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_market_golden.py +0 -0
  243. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_market_morpho_blue.py +0 -0
  244. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_mcp_parity.py +0 -0
  245. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_oracle_mapping.py +0 -0
  246. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_oracle_mapping_golden.py +0 -0
  247. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_pending_requests_scan.py +0 -0
  248. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_role_accounts.py +0 -0
  249. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_vault_info_golden.py +0 -0
  250. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_vault_predates_getters.py +0 -0
  251. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_cli_vault_validate.py +0 -0
  252. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_contract_call.py +0 -0
  253. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_contract_encoder.py +0 -0
  254. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_ccip_events.py +0 -0
  255. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_ccip_indexer.py +0 -0
  256. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_ccip_quote.py +0 -0
  257. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_chainlink.py +0 -0
  258. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_contracts.py +0 -0
  259. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_encoding.py +0 -0
  260. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_errors.py +0 -0
  261. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_lane.py +0 -0
  262. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_layering.py +0 -0
  263. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_readiness.py +0 -0
  264. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_readiness_report.py +0 -0
  265. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_crosschain_transport.py +0 -0
  266. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_docs_typing.py +0 -0
  267. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_erc20.py +0 -0
  268. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_error_hierarchy.py +0 -0
  269. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_examples_composed_hypercore.py +0 -0
  270. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_examples_crosschain.py +0 -0
  271. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  272. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_examples_vaults.py +0 -0
  273. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_explorer.py +0 -0
  274. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_fee_manager.py +0 -0
  275. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_fusion_instance.py +0 -0
  276. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_governance_wrappers.py +0 -0
  277. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_guide.py +0 -0
  278. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_hypercore_deployment.py +0 -0
  279. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_hypercore_events.py +0 -0
  280. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_hypercore_flow.py +0 -0
  281. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_hypercore_live.py +0 -0
  282. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_hypercore_outcome.py +0 -0
  283. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_hypercore_readers.py +0 -0
  284. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_hypercore_simulation.py +0 -0
  285. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_logs.py +0 -0
  286. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_mcp_address.py +0 -0
  287. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_mcp_hypercore.py +0 -0
  288. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_mcp_models.py +0 -0
  289. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_mcp_server.py +0 -0
  290. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_mcp_vault_validate.py +0 -0
  291. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_multicall.py +0 -0
  292. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_oracle.py +0 -0
  293. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_oracle_mapping.py +0 -0
  294. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_plasma_vault.py +0 -0
  295. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_public_type_reachability.py +0 -0
  296. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_reader_encoding.py +0 -0
  297. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_readiness_selection.py +0 -0
  298. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_registry.py +0 -0
  299. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_revert_decoding.py +0 -0
  300. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_rewards_manager.py +0 -0
  301. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_roles.py +0 -0
  302. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_sdk_layering.py +0 -0
  303. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  304. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  305. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  306. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_crosschain_canary.py +0 -0
  307. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_crosschain_hypercore.py +0 -0
  308. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  309. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  310. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  311. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  312. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  313. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  314. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  315. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_examples_arbitrum.py +0 -0
  316. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_examples_base.py +0 -0
  317. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_examples_crosschain.py +0 -0
  318. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_external_state_executor_base.py +0 -0
  319. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  320. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  321. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  322. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_morpho_blue.py +0 -0
  323. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_pilot_v3.py +0 -0
  324. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  325. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  326. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  327. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  328. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_readers_integration.py +0 -0
  329. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_release_funds.py +0 -0
  330. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_swap_on_base.py +0 -0
  331. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  332. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulation_errors.py +0 -0
  333. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_simulation_observation.py +0 -0
  334. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_solidity_compiler.py +0 -0
  335. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_substrates.py +0 -0
  336. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_types_and_events.py +0 -0
  337. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_unpriceable_tokens.py +0 -0
  338. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_validation.py +0 -0
  339. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_vault_data_block.py +0 -0
  340. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_vault_dep_graph.py +0 -0
  341. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_vault_info_fetched_golden.py +0 -0
  342. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_vault_info_onchain_base.py +0 -0
  343. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  344. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  345. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_vault_simulator_blocks.py +0 -0
  346. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_web3_context.py +0 -0
  347. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/test_withdraw_manager.py +0 -0
  348. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/typed_client/consumer.py +0 -0
  349. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/tests/typed_client/rejected.py +0 -0
  350. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev293}/uv.lock +0 -0
@@ -154,6 +154,15 @@ the lifecycle matrix follows that.
154
154
  `commit-lint.yml` fails a PR whose commit subjects or title break it. Never
155
155
  hand-bump `project.version`; the release workflow stamps it and re-locks
156
156
  `uv.lock`.
157
+ - Preview features (`ipor_fusion.crosschain` with its subpackages, and
158
+ `ipor_fusion.hypercore`) keep no backward compatibility: they follow the
159
+ current official contracts only, and support for retired deployments (the
160
+ pilot executors and factories, canaries) is removed rather than kept beside
161
+ them. From 4.1 on, a breaking change confined to them ships as `feat`/`fix`
162
+ without `!` or a `BREAKING CHANGE:` footer, so it never forces a major
163
+ release; anything it changes outside those packages (the top level, the
164
+ `vault info` document, the CLI and MCP) is still breaking by the usual rules.
165
+ 4.0 itself keeps its MIGRATION_4.md entries for them, for 3.x users.
157
166
  - This repository is public, and commit messages also ship in `CHANGELOG.md`
158
167
  inside the wheel. No issue-tracker keys (`IL-` plus a number), internal hosts,
159
168
  dashboard links or private repository names in commits, branch names, PR
@@ -207,7 +216,8 @@ the lifecycle matrix follows that.
207
216
  path. Adding or removing a
208
217
  name updates the snapshots in `tests/test_public_api.py` (every `__all__`, and
209
218
  the names each public module keeps by path); moving a name out of a public
210
- module leaves a re-export there. A removal or rename is a breaking change.
219
+ module leaves a re-export there. A removal or rename is a breaking change,
220
+ except inside a preview package (see Conventions).
211
221
  A project type used in a public signature or field is exported too
212
222
  (`tests/test_public_type_reachability.py`). The package is `py.typed`, so public APIs stay fully
213
223
  annotated.
@@ -427,6 +427,28 @@ the modules are internal.
427
427
  `MAX_MARK_ORACLE_DEVIATION_BPS`, `NAV_JUMP_FLOOR_USD6` (7..11); `decode_substrate`
428
428
  decodes them instead of flagging them as errors.
429
429
 
430
+ #### ExternalState: two executor builds, one custodian marker
431
+
432
+ The 2026-10-21 contracts release (on Arbitrum in `ipor-abi`) changes what the
433
+ balance fuse and the pre-hook compare to detect an unprocessed custodian
434
+ update: the executor's confirm count instead of the last confirmed update's
435
+ timestamp. HyperEVM, Base and Ethereum still publish the earlier build, so both
436
+ exist; the comparison is equality either way.
437
+
438
+ - `ExternalStateVaultState.last_checked_custodian_timestamp` is
439
+ `last_checked_custodian_marker` and `BalanceFuseSnapshot.last_custodian_update_timestamp`
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()`
443
+ exists only on the released executor (it reverts on an earlier one), and
444
+ `last_custodian_update_timestamp()` reads the timestamp on both.
445
+ - New on the released build: `ExternalStateExecutor.unforwarded_credit(asset)`,
446
+ `proposal_base_balance(account)`, `ExternalStateOperationFuse.sweep_surplus(asset)`,
447
+ and the events `ExternalStateBalanceFuseLastCustodianConfirmCountUpdated` and
448
+ `ExternalStateOperationFuseSweepSurplus` (the released balance fuse no longer
449
+ emits `...LastCustodianTimestampUpdated`; it stays registered for the earlier
450
+ build).
451
+
430
452
  **Removed.**
431
453
 
432
454
  - `IporFusionMarkets.RWA`: market 50 is `IporFusionMarkets.EXTERNAL_STATE`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev286
3
+ Version: 4.0.0.dev293
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
@@ -418,7 +418,8 @@ release; adding one does not.
418
418
  - **Preview features** (`PREVIEW_FEATURES` in `ipor_fusion.about`) export only
419
419
  from their own subpackages, `ipor_fusion.crosschain` (with `crosschain.fuses`,
420
420
  `crosschain.ccip` and `crosschain.stargate`) and `ipor_fusion.hypercore`, never
421
- from the top level, and may change in a minor release. Their exception classes
421
+ from the top level, and keep no backward compatibility: a minor release may
422
+ change or remove them (see [Preview features](#preview-features-ipor_fusioncrosschain-ipor_fusionhypercore)). Their exception classes
422
423
  stay in `ipor_fusion.errors`, and the `vault info` document's HyperCore blocks
423
424
  (`HyperCoreJson`, ...) stay with the document.
424
425
  - **Third-party types** are not re-exported: import `ChecksumAddress` and
@@ -463,7 +464,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
463
464
  | `PriceOracleMiddleware` | Asset price feeds |
464
465
  | `PriceOracleMiddlewareManager` | Per-vault price-source overrides |
465
466
  | `ExternalStateExecutor` | NAV propose/confirm for off-vault capital (market 50); the executor's thresholds (`staleness_max`, `big_change_bps`, `dust_threshold`, `min_update_interval`), the live aggregate (`get_balance_fuse_snapshot`) and the pre-hook's staleness clock (`get_oldest_update_timestamp`) |
466
- | `read_external_state`, `ExternalStateVaultState` | The market's vault-scoped state from the vault's ERC-7201 storage at one block: executor, the balance fuse's cached total, the custodian timestamp it last checked and the pause flag the pre-hook enforces (no getter exposes them) |
467
+ | `read_external_state`, `ExternalStateVaultState` | The market's vault-scoped state from the vault's ERC-7201 storage at one block: executor, the balance fuse's cached total, the custodian marker it last checked (a timestamp on the earlier executor build, the confirm count on the 2026-10-21 one) and the pause flag the pre-hook enforces (no getter exposes them) |
467
468
  | `decode_external_state_event`, `EXTERNAL_STATE_TOPICS` | The ExternalState events the vault emits (big change, cache updates, unpause, rescue, executor creation and deployment, operation enter/exit), decoded by topic with their raw log identity; a malformed listed log raises |
468
469
  | `erc20_balance_slot`, `VaultSimulator.with_erc20_balance` | Fund any address with any ERC-20 in a simulation by a storage override (slot found by probing), no holder to impersonate |
469
470
  | `ERC20` | Token reads and approvals |
@@ -488,7 +489,10 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
488
489
  ### Preview features (`ipor_fusion.crosschain`, `ipor_fusion.hypercore`)
489
490
 
490
491
  Not production (see the note at the top) and outside the stable top level:
491
- import these from their subpackage, and expect changes in a minor release.
492
+ import these from their subpackage. They track the current official contracts
493
+ only, with no backward compatibility: from 4.1 on, a minor release may change or
494
+ remove any of these names and drops support for retired deployments (such as
495
+ the pilot executors and factories). Pin an exact version if you depend on them.
492
496
 
493
497
  | Name | Purpose |
494
498
  |------|---------|
@@ -369,7 +369,8 @@ release; adding one does not.
369
369
  - **Preview features** (`PREVIEW_FEATURES` in `ipor_fusion.about`) export only
370
370
  from their own subpackages, `ipor_fusion.crosschain` (with `crosschain.fuses`,
371
371
  `crosschain.ccip` and `crosschain.stargate`) and `ipor_fusion.hypercore`, never
372
- from the top level, and may change in a minor release. Their exception classes
372
+ from the top level, and keep no backward compatibility: a minor release may
373
+ change or remove them (see [Preview features](#preview-features-ipor_fusioncrosschain-ipor_fusionhypercore)). Their exception classes
373
374
  stay in `ipor_fusion.errors`, and the `vault info` document's HyperCore blocks
374
375
  (`HyperCoreJson`, ...) stay with the document.
375
376
  - **Third-party types** are not re-exported: import `ChecksumAddress` and
@@ -414,7 +415,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
414
415
  | `PriceOracleMiddleware` | Asset price feeds |
415
416
  | `PriceOracleMiddlewareManager` | Per-vault price-source overrides |
416
417
  | `ExternalStateExecutor` | NAV propose/confirm for off-vault capital (market 50); the executor's thresholds (`staleness_max`, `big_change_bps`, `dust_threshold`, `min_update_interval`), the live aggregate (`get_balance_fuse_snapshot`) and the pre-hook's staleness clock (`get_oldest_update_timestamp`) |
417
- | `read_external_state`, `ExternalStateVaultState` | The market's vault-scoped state from the vault's ERC-7201 storage at one block: executor, the balance fuse's cached total, the custodian timestamp it last checked and the pause flag the pre-hook enforces (no getter exposes them) |
418
+ | `read_external_state`, `ExternalStateVaultState` | The market's vault-scoped state from the vault's ERC-7201 storage at one block: executor, the balance fuse's cached total, the custodian marker it last checked (a timestamp on the earlier executor build, the confirm count on the 2026-10-21 one) and the pause flag the pre-hook enforces (no getter exposes them) |
418
419
  | `decode_external_state_event`, `EXTERNAL_STATE_TOPICS` | The ExternalState events the vault emits (big change, cache updates, unpause, rescue, executor creation and deployment, operation enter/exit), decoded by topic with their raw log identity; a malformed listed log raises |
419
420
  | `erc20_balance_slot`, `VaultSimulator.with_erc20_balance` | Fund any address with any ERC-20 in a simulation by a storage override (slot found by probing), no holder to impersonate |
420
421
  | `ERC20` | Token reads and approvals |
@@ -439,7 +440,10 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
439
440
  ### Preview features (`ipor_fusion.crosschain`, `ipor_fusion.hypercore`)
440
441
 
441
442
  Not production (see the note at the top) and outside the stable top level:
442
- import these from their subpackage, and expect changes in a minor release.
443
+ import these from their subpackage. They track the current official contracts
444
+ only, with no backward compatibility: from 4.1 on, a minor release may change or
445
+ remove any of these names and drops support for retired deployments (such as
446
+ the pilot executors and factories). Pin an exact version if you depend on them.
443
447
 
444
448
  | Name | Purpose |
445
449
  |------|---------|
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev286"
3
+ version = "4.0.0.dev293"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -196,10 +196,12 @@ from ipor_fusion.fuses.external_state_event_decoding import (
196
196
  EXTERNAL_STATE_TOPICS,
197
197
  ExternalStateAssetRescuedArgs,
198
198
  ExternalStateAssetRescuedEvent,
199
+ ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent,
199
200
  ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent,
200
201
  ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent,
201
202
  ExternalStateBigChangeDetectedArgs,
202
203
  ExternalStateBigChangeDetectedEvent,
204
+ ExternalStateCustodianConfirmCountUpdatedArgs,
203
205
  ExternalStateCustodianTimestampUpdatedArgs,
204
206
  ExternalStateEvent,
205
207
  ExternalStateEventArgs,
@@ -210,6 +212,8 @@ from ipor_fusion.fuses.external_state_event_decoding import (
210
212
  ExternalStateOperationArgs,
211
213
  ExternalStateOperationFuseEnterEvent,
212
214
  ExternalStateOperationFuseExitEvent,
215
+ ExternalStateOperationFuseSweepSurplusArgs,
216
+ ExternalStateOperationFuseSweepSurplusEvent,
213
217
  ExternalStateTotalBalanceUpdatedArgs,
214
218
  ExternalStateUnpausedArgs,
215
219
  ExternalStateUnpausedEvent,
@@ -333,6 +337,7 @@ from ipor_fusion.types import (
333
337
  AssetSource,
334
338
  ChainId,
335
339
  Check,
340
+ CustodianMarker,
336
341
  Decimals,
337
342
  Fee,
338
343
  FuseAddress,
@@ -437,6 +442,8 @@ __all__ = [
437
442
  "EXTERNAL_STATE_EVENT_CLASSES",
438
443
  "ExternalStateBigChangeDetectedEvent",
439
444
  "ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent",
445
+ "ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent",
446
+ "ExternalStateOperationFuseSweepSurplusEvent",
440
447
  "ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent",
441
448
  "ExternalStateUnpausedEvent",
442
449
  "ExternalStateAssetRescuedEvent",
@@ -447,6 +454,8 @@ __all__ = [
447
454
  "ExternalStateAssetRescuedArgs",
448
455
  "ExternalStateBigChangeDetectedArgs",
449
456
  "ExternalStateCustodianTimestampUpdatedArgs",
457
+ "ExternalStateCustodianConfirmCountUpdatedArgs",
458
+ "ExternalStateOperationFuseSweepSurplusArgs",
450
459
  "ExternalStateEventArgs",
451
460
  "ExternalStateExecutorArgs",
452
461
  "ExternalStateOperationArgs",
@@ -664,6 +673,7 @@ __all__ = [
664
673
  "format_amount",
665
674
  "format_shares",
666
675
  "Price",
676
+ "CustodianMarker",
667
677
  "Period",
668
678
  "MAX_UINT256",
669
679
  "ZERO_ADDRESS",
@@ -41,7 +41,8 @@ PREVIEW_FEATURES: tuple[PreviewFeature, ...] = (
41
41
  PREVIEW_STATEMENT = (
42
42
  "Preview, not production: the contracts are under active development, the "
43
43
  "mainnet deployments are a proof of concept and IPOR Labs canaries, and "
44
- "interfaces may change between minor versions."
44
+ "interfaces may change or be removed between minor versions, with no "
45
+ "backward compatibility."
45
46
  )
46
47
 
47
48
  _DISTRIBUTION = "ipor-fusion"
@@ -35,6 +35,7 @@ from ipor_fusion.types import (
35
35
  ZERO_ADDRESS,
36
36
  Amount,
37
37
  ChainId,
38
+ CustodianMarker,
38
39
  Period,
39
40
  PlasmaVaultAddress,
40
41
  ProposalHash,
@@ -127,10 +128,15 @@ class ExternalStateVaultState:
127
128
  `last_total_balance` is the balance fuse's cache of the executor's total in
128
129
  raw underlying units, as of the fuse's last refresh -- not the live total,
129
130
  which `ExternalStateExecutor.get_balance_fuse_snapshot` reads;
130
- `last_checked_custodian_timestamp` is the executor custodian timestamp the
131
- balance fuse last processed -- not necessarily the latest custodian update
131
+ `last_checked_custodian_marker` is the executor's custodian marker the
132
+ balance fuse last processed -- the last confirmed custodian update's block
133
+ timestamp on executors of the contracts before the 2026-10-21 release, the
134
+ executor's confirm count (`ExternalStateExecutor.confirm_count`) on the
135
+ executors of that release; either way not necessarily the latest update
132
136
  and not the pre-hook's staleness clock, which is
133
- `ExternalStateExecutor.get_oldest_update_timestamp`; `paused` is the flag
137
+ `ExternalStateExecutor.get_oldest_update_timestamp`. The fuse and the
138
+ pre-hook only compare it for equality with
139
+ `BalanceFuseSnapshot.custodian_marker`; `paused` is the flag
134
140
  the balance fuse sets on a big change and `ExternalStateUnpauseFuse` clears
135
141
  with an atomist signature. `ExternalStatePausePreHook` blocks the vault's
136
142
  gated user operations while the flag is set, and also, flag or not, while
@@ -141,7 +147,7 @@ class ExternalStateVaultState:
141
147
 
142
148
  executor: ChecksumAddress | None
143
149
  last_total_balance: Amount
144
- last_checked_custodian_timestamp: Timestamp
150
+ last_checked_custodian_marker: CustodianMarker
145
151
  paused: bool
146
152
  block: int
147
153
 
@@ -150,11 +156,14 @@ class ExternalStateVaultState:
150
156
  class BalanceFuseSnapshot:
151
157
  """`ExternalStateExecutor.getBalanceFuseSnapshot()`: the live sum of the
152
158
  tracked balances in raw underlying units, the cached big-change threshold
153
- in basis points and the timestamp of the last confirmed custodian update."""
159
+ in basis points and the custodian marker: the last confirmed custodian
160
+ update's timestamp on executors before the 2026-10-21 contracts release,
161
+ the confirm count on the executors of that release (two confirms in one
162
+ block then still differ)."""
154
163
 
155
164
  total_balance: Amount
156
165
  big_change_bps: int
157
- last_custodian_update_timestamp: Timestamp
166
+ custodian_marker: CustodianMarker
158
167
 
159
168
 
160
169
  def _storage_word(raw: bytes) -> int:
@@ -226,7 +235,7 @@ def read_external_state(
226
235
  return ExternalStateVaultState(
227
236
  executor=Web3.to_checksum_address(f"0x{words[0]:040x}") if words[0] else None,
228
237
  last_total_balance=Amount(words[1]),
229
- last_checked_custodian_timestamp=Timestamp(words[2]),
238
+ last_checked_custodian_marker=CustodianMarker(words[2]),
230
239
  paused=words[3] == 1,
231
240
  block=block,
232
241
  )
@@ -344,18 +353,63 @@ class ExternalStateExecutor(ContractWrapper):
344
353
 
345
354
  def get_balance_fuse_snapshot(self) -> Call[BalanceFuseSnapshot]:
346
355
  """The live aggregate the balance fuse reads: the sum of the tracked
347
- balances, the big-change threshold and the last confirmed custodian
348
- update timestamp, in one call. Pin the context's block to the one
356
+ balances, the big-change threshold and the custodian marker, in one
357
+ call. Pin the context's block to the one
349
358
  `read_external_state` returned to compare live against cached."""
350
359
  return self._view(
351
360
  "getBalanceFuseSnapshot()",
352
361
  output_types=["uint256", "uint256", "uint256"],
353
362
  ).map(
354
363
  lambda values: BalanceFuseSnapshot(
355
- Amount(values[0]), int(values[1]), Timestamp(values[2])
364
+ Amount(values[0]), int(values[1]), CustodianMarker(values[2])
356
365
  )
357
366
  )
358
367
 
368
+ def confirm_count(self) -> Call[CustodianMarker]:
369
+ """Executors of the 2026-10-21 contracts release only: the count of
370
+ confirmed custodian updates across every balance account, the marker
371
+ `get_balance_fuse_snapshot` reports. An earlier executor has no such
372
+ function and the call reverts."""
373
+ return self._view(
374
+ "confirmCount()", output_types=["uint256"], decoder=CustodianMarker
375
+ )
376
+
377
+ def last_custodian_update_timestamp(self) -> Call[Timestamp]:
378
+ """Block timestamp of the last confirmed custodian update on any
379
+ balance account."""
380
+ return self._view(
381
+ "lastCustodianUpdateTimestamp()",
382
+ output_types=["uint256"],
383
+ decoder=Timestamp,
384
+ )
385
+
386
+ def unforwarded_credit(self, asset: ChecksumAddress) -> Call[Amount]:
387
+ """Executors of the 2026-10-21 contracts release only: tokens of
388
+ `asset` credited through the vault path that have not left the
389
+ executor. The executor's `asset` balance above it is surplus (a
390
+ donation or a payout sent straight to the executor):
391
+ `ExternalStateOperationFuse.sweep_surplus` moves it to the vault, and
392
+ custodians should not propose while it exists unless they know whether
393
+ their report counts it."""
394
+ return self._view(
395
+ "unforwardedCredit(address)",
396
+ asset,
397
+ output_types=["uint256"],
398
+ decoder=Amount,
399
+ )
400
+
401
+ def proposal_base_balance(self, balance_account: ChecksumAddress) -> Call[Amount]:
402
+ """Executors of the 2026-10-21 contracts release only: the account
403
+ balance when the pending proposal was made. `confirmBalance` reverts
404
+ `ExternalStateExecutorBalanceChangedSinceProposal` once the balance
405
+ differs from it (an alpha flow ran in between): propose again."""
406
+ return self._view(
407
+ "proposalBaseBalance(address)",
408
+ balance_account,
409
+ output_types=["uint256"],
410
+ decoder=Amount,
411
+ )
412
+
359
413
  def get_oldest_update_timestamp(self) -> Call[Timestamp]:
360
414
  """The pre-hook's staleness clock: the oldest non-zero `lastUpdated`
361
415
  across the balance accounts, 0 when no account was ever updated. The
@@ -30,10 +30,12 @@ from ipor_fusion.fuses.external_state_event_decoding import (
30
30
  EXTERNAL_STATE_TOPICS,
31
31
  ExternalStateAssetRescuedArgs,
32
32
  ExternalStateAssetRescuedEvent,
33
+ ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent,
33
34
  ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent,
34
35
  ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent,
35
36
  ExternalStateBigChangeDetectedArgs,
36
37
  ExternalStateBigChangeDetectedEvent,
38
+ ExternalStateCustodianConfirmCountUpdatedArgs,
37
39
  ExternalStateCustodianTimestampUpdatedArgs,
38
40
  ExternalStateEvent,
39
41
  ExternalStateEventArgs,
@@ -44,6 +46,8 @@ from ipor_fusion.fuses.external_state_event_decoding import (
44
46
  ExternalStateOperationArgs,
45
47
  ExternalStateOperationFuseEnterEvent,
46
48
  ExternalStateOperationFuseExitEvent,
49
+ ExternalStateOperationFuseSweepSurplusArgs,
50
+ ExternalStateOperationFuseSweepSurplusEvent,
47
51
  ExternalStateTotalBalanceUpdatedArgs,
48
52
  ExternalStateUnpausedArgs,
49
53
  ExternalStateUnpausedEvent,
@@ -146,10 +150,14 @@ __all__ = [
146
150
  "ExternalStateAssetRescuedArgs",
147
151
  "ExternalStateAssetRescuedEvent",
148
152
  "ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent",
153
+ "ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent",
154
+ "ExternalStateOperationFuseSweepSurplusEvent",
149
155
  "ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent",
150
156
  "ExternalStateBigChangeDetectedArgs",
151
157
  "ExternalStateBigChangeDetectedEvent",
152
158
  "ExternalStateCustodianTimestampUpdatedArgs",
159
+ "ExternalStateCustodianConfirmCountUpdatedArgs",
160
+ "ExternalStateOperationFuseSweepSurplusArgs",
153
161
  "ExternalStateEvent",
154
162
  "ExternalStateEventArgs",
155
163
  "ExternalStateEventSpec",
@@ -92,6 +92,17 @@ class ExternalStateOperationFuse(Fuse):
92
92
  """
93
93
  return self._action_raw("createExecutor()", [])
94
94
 
95
+ def sweep_surplus(self, asset: ChecksumAddress) -> FuseAction:
96
+ """Operation fuses of the 2026-10-21 contracts release only: move to
97
+ the vault the executor's ``asset`` balance above its unforwarded
98
+ credit (tokens that did not arrive through the vault path, such as a
99
+ donation), emitting ``ExternalStateOperationFuseSweepSurplus``. The
100
+ swept tokens enter NAV at once only when ``asset`` is the underlying.
101
+ Use it for tokens the confirmed custodian report no longer counts; a
102
+ payout it still counts is booked with ``exit`` instead."""
103
+ self._validate_address(asset, "asset")
104
+ return self._action_raw("sweepSurplus(address)", [asset])
105
+
95
106
  def _build(
96
107
  self,
97
108
  method: str,
@@ -65,6 +65,10 @@ EXTERNAL_STATE_EVENT_SPECS: tuple[ExternalStateEventSpec, ...] = (
65
65
  "ExternalStateBalanceFuseLastCustodianTimestampUpdated",
66
66
  (("uint256", "oldTimestamp"), ("uint256", "newTimestamp")),
67
67
  ),
68
+ ExternalStateEventSpec(
69
+ "ExternalStateBalanceFuseLastCustodianConfirmCountUpdated",
70
+ (("uint256", "oldConfirmCount"), ("uint256", "newConfirmCount")),
71
+ ),
68
72
  ExternalStateEventSpec(
69
73
  "ExternalStateBalanceFuseLastTotalBalanceUpdated",
70
74
  (("uint256", "oldTotalBalance"), ("uint256", "newTotalBalance")),
@@ -106,6 +110,10 @@ EXTERNAL_STATE_EVENT_SPECS: tuple[ExternalStateEventSpec, ...] = (
106
110
  ("uint256", "actionsCount"),
107
111
  ),
108
112
  ),
113
+ ExternalStateEventSpec(
114
+ "ExternalStateOperationFuseSweepSurplus",
115
+ (("address", "version"), ("address", "asset"), ("uint256", "amount")),
116
+ ),
109
117
  # ExternalStateExecutorStorageLib
110
118
  ExternalStateEventSpec(
111
119
  "ExternalStateExecutorDeployed",
@@ -127,6 +135,24 @@ class ExternalStateCustodianTimestampUpdatedArgs(TypedDict):
127
135
  newTimestamp: Timestamp
128
136
 
129
137
 
138
+ class ExternalStateCustodianConfirmCountUpdatedArgs(TypedDict):
139
+ """``ExternalStateBalanceFuseLastCustodianConfirmCountUpdated`` (balance
140
+ fuses of the 2026-10-21 contracts release; earlier ones emit the
141
+ timestamp event)."""
142
+
143
+ oldConfirmCount: int
144
+ newConfirmCount: int
145
+
146
+
147
+ class ExternalStateOperationFuseSweepSurplusArgs(TypedDict):
148
+ """``ExternalStateOperationFuseSweepSurplus``: ``amount`` of ``asset``
149
+ swept from the executor to the vault."""
150
+
151
+ version: FuseAddress
152
+ asset: ChecksumAddress
153
+ amount: Amount
154
+
155
+
130
156
  class ExternalStateTotalBalanceUpdatedArgs(TypedDict):
131
157
  """``ExternalStateBalanceFuseLastTotalBalanceUpdated``."""
132
158
 
@@ -213,6 +239,24 @@ class ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent(
213
239
  args: ExternalStateCustodianTimestampUpdatedArgs
214
240
 
215
241
 
242
+ @dataclass(frozen=True, slots=True, kw_only=True)
243
+ class ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent(
244
+ _ExternalStateEventBase
245
+ ):
246
+ name: Literal["ExternalStateBalanceFuseLastCustodianConfirmCountUpdated"] = (
247
+ "ExternalStateBalanceFuseLastCustodianConfirmCountUpdated"
248
+ )
249
+ args: ExternalStateCustodianConfirmCountUpdatedArgs
250
+
251
+
252
+ @dataclass(frozen=True, slots=True, kw_only=True)
253
+ class ExternalStateOperationFuseSweepSurplusEvent(_ExternalStateEventBase):
254
+ name: Literal["ExternalStateOperationFuseSweepSurplus"] = (
255
+ "ExternalStateOperationFuseSweepSurplus"
256
+ )
257
+ args: ExternalStateOperationFuseSweepSurplusArgs
258
+
259
+
216
260
  @dataclass(frozen=True, slots=True, kw_only=True)
217
261
  class ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent(_ExternalStateEventBase):
218
262
  name: Literal["ExternalStateBalanceFuseLastTotalBalanceUpdated"] = (
@@ -262,6 +306,8 @@ class ExternalStateExecutorDeployedEvent(_ExternalStateEventBase):
262
306
  ExternalStateEvent: TypeAlias = (
263
307
  ExternalStateBigChangeDetectedEvent
264
308
  | ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent
309
+ | ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent
310
+ | ExternalStateOperationFuseSweepSurplusEvent
265
311
  | ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent
266
312
  | ExternalStateUnpausedEvent
267
313
  | ExternalStateAssetRescuedEvent
@@ -275,6 +321,8 @@ ExternalStateEvent: TypeAlias = (
275
321
  EXTERNAL_STATE_EVENT_CLASSES: dict[str, type[ExternalStateEvent]] = {
276
322
  "ExternalStateBigChangeDetected": ExternalStateBigChangeDetectedEvent,
277
323
  "ExternalStateBalanceFuseLastCustodianTimestampUpdated": ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent,
324
+ "ExternalStateBalanceFuseLastCustodianConfirmCountUpdated": ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent,
325
+ "ExternalStateOperationFuseSweepSurplus": ExternalStateOperationFuseSweepSurplusEvent,
278
326
  "ExternalStateBalanceFuseLastTotalBalanceUpdated": ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent,
279
327
  "ExternalStateUnpaused": ExternalStateUnpausedEvent,
280
328
  "ExternalStateAssetRescued": ExternalStateAssetRescuedEvent,
@@ -1176,8 +1176,9 @@ class PreviewFeatureModel(_Base):
1176
1176
  default="preview",
1177
1177
  description="Always 'preview': not production. The contracts are under "
1178
1178
  "active development, the mainnet deployments are a proof of concept "
1179
- "and IPOR Labs canaries, and interfaces may change between minor "
1180
- "versions. Route no production capital through these features.",
1179
+ "and IPOR Labs canaries, and interfaces may change or be removed "
1180
+ "between minor versions, with no backward compatibility. Route no "
1181
+ "production capital through these features.",
1181
1182
  )
1182
1183
 
1183
1184
 
@@ -17,6 +17,11 @@ RoleId = NewType("RoleId", int)
17
17
  ChainId = NewType("ChainId", int)
18
18
  # An 18-decimal fixed-point fraction: 10**18 is 1, i.e. 100% (a market limit).
19
19
  Wad = NewType("Wad", int)
20
+ # The ExternalState executor's custodian marker the balance fuse and pre-hook
21
+ # compare for equality only: the last confirmed update's block timestamp on an
22
+ # executor before the 2026-10-21 contracts release, the confirm count on one of
23
+ # that release. Opaque: never compare it with a clock or do arithmetic on it.
24
+ CustodianMarker = NewType("CustodianMarker", int)
20
25
  # A Morpho Blue market id (bytes32) in its canonical spelling: "0x" and 64
21
26
  # lowercase hex digits, as the Morpho API reports it. Build one with
22
27
  # parse_morpho_market_id.
@@ -7,7 +7,7 @@ import re
7
7
  import pytest
8
8
  from _event_args import assert_event_class_mirrors_spec
9
9
  from eth_abi import encode
10
- from test_solidity_mirrors import _solidity_source
10
+ from test_solidity_mirrors import IPOR_FUSION_REF, _solidity_source
11
11
  from web3 import Web3
12
12
 
13
13
  from ipor_fusion import (
@@ -28,6 +28,17 @@ _SOURCES = (
28
28
  "contracts/fuses/external_state/ExternalStateOperationFuse.sol",
29
29
  "contracts/fuses/external_state/lib/ExternalStateExecutorStorageLib.sol",
30
30
  )
31
+ # Both published builds: the release pinned in test_solidity_mirrors (Arbitrum)
32
+ # and the earlier one HyperEVM, Base and Ethereum still publish. The released
33
+ # balance fuse declares its events in a library of its own.
34
+ _EARLIER_REF = "a1f79d6c3d0d426ace4df55ed0fb8ea340580739"
35
+ _BUILDS = (
36
+ (
37
+ IPOR_FUSION_REF,
38
+ (*_SOURCES, "contracts/fuses/external_state/lib/ExternalStateBigChangeLib.sol"),
39
+ ),
40
+ (_EARLIER_REF, _SOURCES),
41
+ )
31
42
  _EVENT_RE = re.compile(r"\bevent\s+(\w+)\s*\(([^)]*)\)\s*;", re.S)
32
43
 
33
44
  VAULT = "0xdBEe6621Febce4C51Fa9DD1dB12dCd9d4CC3e7b9"
@@ -35,9 +46,18 @@ SIGNER = "0x00000000000000000000000000000000000000a5"
35
46
 
36
47
 
37
48
  def _declared():
49
+ """Every event either build declares; a name both declare must agree."""
50
+ out = {}
51
+ for ref, paths in _BUILDS:
52
+ for name, params in _declared_at(ref, paths).items():
53
+ assert out.setdefault(name, params) == params, name
54
+ return out
55
+
56
+
57
+ def _declared_at(ref, paths):
38
58
  out = {}
39
- for path in _SOURCES:
40
- for name, body in _EVENT_RE.findall(_solidity_source(path)):
59
+ for path in paths:
60
+ for name, body in _EVENT_RE.findall(_solidity_source(path, ref)):
41
61
  params = []
42
62
  for raw in [p.strip() for p in body.split(",") if p.strip()]:
43
63
  words = raw.split()
@@ -1203,7 +1203,7 @@ class TestReadExternalState:
1203
1203
 
1204
1204
  assert state.executor == EXECUTOR_ADDR
1205
1205
  assert state.last_total_balance == 32_928_135
1206
- assert state.last_checked_custodian_timestamp == 1_791_458_983
1206
+ assert state.last_checked_custodian_marker == 1_791_458_983
1207
1207
  assert state.paused is True
1208
1208
  assert state.block == 4_805_000 # "latest" resolved once, before the reads
1209
1209
  assert [c.args for c in ctx.get_storage_at.call_args_list] == [
@@ -1256,7 +1256,7 @@ class TestReadExternalState:
1256
1256
  self._ctx([0, (1 << 256) - 1, (1 << 256) - 1, 0]), VAULT_ADDR
1257
1257
  )
1258
1258
  assert top.last_total_balance == (1 << 256) - 1
1259
- assert top.last_checked_custodian_timestamp == (1 << 256) - 1
1259
+ assert top.last_checked_custodian_marker == (1 << 256) - 1
1260
1260
 
1261
1261
  def test_a_pinned_context_block_is_used_as_is(self):
1262
1262
  from ipor_fusion import read_external_state
@@ -1267,7 +1267,7 @@ class TestReadExternalState:
1267
1267
 
1268
1268
  assert state.block == 4_700_000
1269
1269
  assert state.executor is None and state.paused is False
1270
- assert (state.last_total_balance, state.last_checked_custodian_timestamp) == (
1270
+ assert (state.last_total_balance, state.last_checked_custodian_marker) == (
1271
1271
  0,
1272
1272
  0,
1273
1273
  )
@@ -1292,7 +1292,7 @@ class TestReadExternalState:
1292
1292
  5,
1293
1293
  True,
1294
1294
  )
1295
- assert state.last_checked_custodian_timestamp == 0
1295
+ assert state.last_checked_custodian_marker == 0
1296
1296
 
1297
1297
  def test_malformed_words_raise_instead_of_passing_as_values(self):
1298
1298
  from ipor_fusion import read_external_state
@@ -1320,6 +1320,8 @@ class TestExecutorViews:
1320
1320
  ("dust_threshold", "dustThreshold()"),
1321
1321
  ("min_update_interval", "minUpdateInterval()"),
1322
1322
  ("get_oldest_update_timestamp", "getOldestUpdateTimestamp()"),
1323
+ ("confirm_count", "confirmCount()"),
1324
+ ("last_custodian_update_timestamp", "lastCustodianUpdateTimestamp()"),
1323
1325
  ],
1324
1326
  )
1325
1327
  def test_uint256_views(self, method, signature):
@@ -1338,3 +1340,18 @@ class TestExecutorViews:
1338
1340
  assert call.decoder is not None
1339
1341
  snap = call.decoder((32_928_135, 50_000, 1_791_458_983))
1340
1342
  assert snap == BalanceFuseSnapshot(32_928_135, 50_000, 1_791_458_983)
1343
+ # a 2026-10-21 executor reports its confirm count in the same slot
1344
+ assert call.decoder((32_928_135, 50_000, 7)).custodian_marker == 7
1345
+
1346
+ @pytest.mark.parametrize(
1347
+ ("method", "signature"),
1348
+ [
1349
+ ("unforwarded_credit", "unforwardedCredit(address)"),
1350
+ ("proposal_base_balance", "proposalBaseBalance(address)"),
1351
+ ],
1352
+ )
1353
+ def test_released_executor_per_address_views(self, method, signature):
1354
+ executor = ExternalStateExecutor(EXECUTOR_ADDR)
1355
+ call = getattr(executor, method)(EXECUTOR_ADDR)
1356
+ assert bytes(call.data)[:4] == Web3.keccak(text=signature)[:4]
1357
+ assert call.output_types == ("uint256",)
@@ -1777,6 +1777,13 @@ class TestExternalStateOperationFuse:
1777
1777
  actions=[(ZERO_ADDRESS, b"\x00")],
1778
1778
  )
1779
1779
 
1780
+ def test_sweep_surplus_encodes_the_asset(self):
1781
+ action = ExternalStateOperationFuse(FUSE_ADDR).sweep_surplus(TOKEN_A)
1782
+ assert action.data[:4] == _selector("sweepSurplus(address)")
1783
+ assert decode(["address"], action.data[4:])[0].lower() == TOKEN_A.lower()
1784
+ with pytest.raises(ValueError, match="asset"):
1785
+ ExternalStateOperationFuse(FUSE_ADDR).sweep_surplus(ZERO_ADDRESS)
1786
+
1780
1787
  def test_create_executor_is_bare_selector(self):
1781
1788
  action = ExternalStateOperationFuse(FUSE_ADDR).create_executor()
1782
1789
  assert action.fuse == FUSE_ADDR