ipor-fusion 4.0.0.dev286__tar.gz → 4.0.0.dev291__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.dev291}/MIGRATION_4.md +22 -0
  2. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/PKG-INFO +2 -2
  3. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/README.md +1 -1
  4. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/pyproject.toml +1 -1
  5. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/__init__.py +10 -0
  6. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/external_state_executor.py +64 -10
  7. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/__init__.py +8 -0
  8. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/external_state.py +11 -0
  9. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/external_state_event_decoding.py +48 -0
  10. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/types.py +5 -0
  11. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_external_state_events.py +23 -3
  12. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_external_state_executor.py +21 -4
  13. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_fuse_encoding.py +7 -0
  14. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_public_api.py +10 -0
  15. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_solidity_mirrors.py +8 -6
  16. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.claude-plugin/marketplace.json +0 -0
  17. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.claude-plugin/plugin.json +0 -0
  18. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.env.example +0 -0
  19. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.gemini/settings.json +0 -0
  20. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.git-blame-ignore-revs +0 -0
  21. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/cd.yml +0 -0
  22. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/ci.yml +0 -0
  23. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/commit-lint.yml +0 -0
  24. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/notify-slack.yml +0 -0
  25. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/python-build.yml +0 -0
  26. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/python-publish.yml +0 -0
  27. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/release.yml +0 -0
  28. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.github/workflows/report-slack.yml +0 -0
  29. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.gitignore +0 -0
  30. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.mcp.json +0 -0
  31. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.pre-commit-config.yaml +0 -0
  32. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/.python-version +0 -0
  33. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/AGENTS.md +0 -0
  34. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/CHANGELOG.md +0 -0
  35. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/CLAUDE.md +0 -0
  36. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/CODEOWNERS +0 -0
  37. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/LICENSE +0 -0
  38. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/build.sh +0 -0
  39. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/CONTRIBUTING.md +0 -0
  40. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/README.md +0 -0
  41. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  42. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  43. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  44. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  45. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/external_state_margin_leg_base.py +0 -0
  46. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/examples/simple_aave_v3_supply_base.py +0 -0
  47. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/pytest.ini +0 -0
  48. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/scripts/commit-check.sh +0 -0
  49. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/scripts/crosschain_readiness.py +0 -0
  50. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/skills/ipor-deploy-vault/SKILL.md +0 -0
  51. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/about.py +0 -0
  52. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cache.py +0 -0
  53. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/chains.py +0 -0
  54. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/__init__.py +0 -0
  55. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/address_cmd.py +0 -0
  56. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  57. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/config_cmd.py +0 -0
  58. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/config_store.py +0 -0
  59. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/main.py +0 -0
  60. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/market_cmd.py +0 -0
  61. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/options.py +0 -0
  62. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  63. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  64. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/vault_health.py +0 -0
  65. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  66. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  67. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  68. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/config/__init__.py +0 -0
  69. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/config/roles.py +0 -0
  70. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/__init__.py +0 -0
  71. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/access.py +0 -0
  72. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/address.py +0 -0
  73. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/context.py +0 -0
  74. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/context_manager.py +0 -0
  75. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/contract.py +0 -0
  76. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/erc20.py +0 -0
  77. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/fee_manager.py +0 -0
  78. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/fuse.py +0 -0
  79. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/fuse_action.py +0 -0
  80. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/fusion_factory.py +0 -0
  81. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/logs.py +0 -0
  82. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/multicall.py +0 -0
  83. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/oracle.py +0 -0
  84. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/plasma_vault.py +0 -0
  85. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/rewards_manager.py +0 -0
  86. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/simulation.py +0 -0
  87. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/simulation_result.py +0 -0
  88. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  89. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/__init__.py +0 -0
  90. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  91. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  92. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  93. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  94. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  95. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  96. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  97. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  98. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  99. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/contracts.py +0 -0
  100. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/discovery.py +0 -0
  101. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/errors.py +0 -0
  102. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  103. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  104. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  105. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  106. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/lane.py +0 -0
  107. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/logs.py +0 -0
  108. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/messages.py +0 -0
  109. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/simulation.py +0 -0
  110. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  111. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  112. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  113. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  114. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  115. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/crosschain/transport.py +0 -0
  116. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/dependency_graph.py +0 -0
  117. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/entry_cli.py +0 -0
  118. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/entry_mcp.py +0 -0
  119. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/errors.py +0 -0
  120. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/explorer.py +0 -0
  121. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/field_docs.py +0 -0
  122. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  123. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/async_action.py +0 -0
  124. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/base.py +0 -0
  125. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  126. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/erc4626.py +0 -0
  127. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  128. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/events.py +0 -0
  129. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  130. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  131. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/hypercore.py +0 -0
  132. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  133. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/merkl.py +0 -0
  134. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/morpho.py +0 -0
  135. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  136. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  137. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/fuses/universal.py +0 -0
  138. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/guide/__init__.py +0 -0
  139. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/guide/architecture.md +0 -0
  140. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/guide/glossary.md +0 -0
  141. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/guide/invariants.md +0 -0
  142. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/guide/quickstart.md +0 -0
  143. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/hypercore/__init__.py +0 -0
  144. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/market_ids.py +0 -0
  145. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/market_info.py +0 -0
  146. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/mcp/__init__.py +0 -0
  147. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/mcp/guide.py +0 -0
  148. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/mcp/models.py +0 -0
  149. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/mcp/server.py +0 -0
  150. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/morpho_api.py +0 -0
  151. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/py.typed +0 -0
  152. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/__init__.py +0 -0
  153. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/aave_v3.py +0 -0
  154. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/aave_v4.py +0 -0
  155. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/compound_v3.py +0 -0
  156. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/hypercore.py +0 -0
  157. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  158. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/lending_health.py +0 -0
  159. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/morpho.py +0 -0
  160. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  161. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  162. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  163. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/position_manager.py +0 -0
  164. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  165. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  166. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/readers/unpriceable.py +0 -0
  167. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/registry.py +0 -0
  168. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/substrates.py +0 -0
  169. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/validation.py +0 -0
  170. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/vault_data.py +0 -0
  171. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/vault_erc20.py +0 -0
  172. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/vault_info.py +0 -0
  173. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/vault_managers.py +0 -0
  174. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/vault_positions.py +0 -0
  175. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/src/ipor_fusion/vault_reads.py +0 -0
  176. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/UniswapV3UniversalRouter.py +0 -0
  177. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_crosschain.py +0 -0
  178. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_crosschain_lifecycle.py +0 -0
  179. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_crosschain_recovery.py +0 -0
  180. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_euler_v2.py +0 -0
  181. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_event_args.py +0 -0
  182. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_fake_rpc.py +0 -0
  183. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_foundry.py +0 -0
  184. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_golden.py +0 -0
  185. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_guide.py +0 -0
  186. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_hypercore_model.py +0 -0
  187. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_hypercore_shadow.py +0 -0
  188. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_live.py +0 -0
  189. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_multicall.py +0 -0
  190. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_oracle_mapping_fake.py +0 -0
  191. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_simulate.py +0 -0
  192. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/_validation.py +0 -0
  193. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/addresses.py +0 -0
  194. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/conftest.py +0 -0
  195. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/constants.py +0 -0
  196. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  197. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/hypercore_market55.json +0 -0
  198. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/hypercore_official.json +0 -0
  199. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  200. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  201. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  202. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  203. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  204. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  205. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  206. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/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.dev291}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  208. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  209. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  210. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  211. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  212. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  213. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  214. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  215. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/simulate_v1_request.json +0 -0
  216. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/clean.json +0 -0
  217. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  218. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  219. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  220. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  221. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  222. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  223. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  224. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  225. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/full.json +0 -0
  226. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  227. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/full.txt +0 -0
  228. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/rich.json +0 -0
  229. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  230. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  231. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_about.py +0 -0
  232. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_access_manager.py +0 -0
  233. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cache.py +0 -0
  234. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_chains.py +0 -0
  235. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_address.py +0 -0
  236. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_changelog.py +0 -0
  237. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_commands.py +0 -0
  238. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_config_store.py +0 -0
  239. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_helpers.py +0 -0
  240. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_hypercore.py +0 -0
  241. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_lending_health.py +0 -0
  242. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_market_golden.py +0 -0
  243. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_market_morpho_blue.py +0 -0
  244. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_mcp_parity.py +0 -0
  245. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_oracle_mapping.py +0 -0
  246. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_oracle_mapping_golden.py +0 -0
  247. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_pending_requests_scan.py +0 -0
  248. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_role_accounts.py +0 -0
  249. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_vault_info_golden.py +0 -0
  250. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_vault_predates_getters.py +0 -0
  251. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_cli_vault_validate.py +0 -0
  252. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_contract_call.py +0 -0
  253. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_contract_encoder.py +0 -0
  254. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_ccip_events.py +0 -0
  255. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_ccip_indexer.py +0 -0
  256. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_ccip_quote.py +0 -0
  257. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_chainlink.py +0 -0
  258. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_contracts.py +0 -0
  259. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_encoding.py +0 -0
  260. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_errors.py +0 -0
  261. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_lane.py +0 -0
  262. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_layering.py +0 -0
  263. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_readiness.py +0 -0
  264. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_readiness_report.py +0 -0
  265. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_crosschain_transport.py +0 -0
  266. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_docs_typing.py +0 -0
  267. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_erc20.py +0 -0
  268. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_error_hierarchy.py +0 -0
  269. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_examples_composed_hypercore.py +0 -0
  270. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_examples_crosschain.py +0 -0
  271. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  272. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_examples_vaults.py +0 -0
  273. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_explorer.py +0 -0
  274. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_fee_manager.py +0 -0
  275. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_fusion_instance.py +0 -0
  276. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_governance_wrappers.py +0 -0
  277. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_guide.py +0 -0
  278. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_hypercore_deployment.py +0 -0
  279. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_hypercore_events.py +0 -0
  280. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_hypercore_flow.py +0 -0
  281. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_hypercore_live.py +0 -0
  282. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_hypercore_outcome.py +0 -0
  283. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_hypercore_readers.py +0 -0
  284. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_hypercore_simulation.py +0 -0
  285. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_logs.py +0 -0
  286. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_mcp_address.py +0 -0
  287. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_mcp_hypercore.py +0 -0
  288. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_mcp_models.py +0 -0
  289. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_mcp_server.py +0 -0
  290. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_mcp_vault_validate.py +0 -0
  291. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_multicall.py +0 -0
  292. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_oracle.py +0 -0
  293. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_oracle_mapping.py +0 -0
  294. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_plasma_vault.py +0 -0
  295. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_public_type_reachability.py +0 -0
  296. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_reader_encoding.py +0 -0
  297. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_readiness_selection.py +0 -0
  298. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_registry.py +0 -0
  299. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_revert_decoding.py +0 -0
  300. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_rewards_manager.py +0 -0
  301. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_roles.py +0 -0
  302. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_sdk_layering.py +0 -0
  303. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  304. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  305. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  306. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_crosschain_canary.py +0 -0
  307. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_crosschain_hypercore.py +0 -0
  308. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  309. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  310. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  311. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  312. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  313. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  314. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  315. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_examples_arbitrum.py +0 -0
  316. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_examples_base.py +0 -0
  317. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_examples_crosschain.py +0 -0
  318. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_external_state_executor_base.py +0 -0
  319. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  320. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  321. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  322. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_morpho_blue.py +0 -0
  323. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_pilot_v3.py +0 -0
  324. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  325. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  326. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  327. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  328. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_readers_integration.py +0 -0
  329. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_release_funds.py +0 -0
  330. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_swap_on_base.py +0 -0
  331. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  332. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulation_errors.py +0 -0
  333. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_simulation_observation.py +0 -0
  334. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_solidity_compiler.py +0 -0
  335. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_substrates.py +0 -0
  336. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_types_and_events.py +0 -0
  337. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_unpriceable_tokens.py +0 -0
  338. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_validation.py +0 -0
  339. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_vault_data_block.py +0 -0
  340. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_vault_dep_graph.py +0 -0
  341. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_vault_info_fetched_golden.py +0 -0
  342. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_vault_info_onchain_base.py +0 -0
  343. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  344. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  345. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_vault_simulator_blocks.py +0 -0
  346. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_web3_context.py +0 -0
  347. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/test_withdraw_manager.py +0 -0
  348. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/typed_client/consumer.py +0 -0
  349. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/tests/typed_client/rejected.py +0 -0
  350. {ipor_fusion-4.0.0.dev286 → ipor_fusion-4.0.0.dev291}/uv.lock +0 -0
@@ -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.dev291
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
@@ -463,7 +463,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
463
463
  | `PriceOracleMiddleware` | Asset price feeds |
464
464
  | `PriceOracleMiddlewareManager` | Per-vault price-source overrides |
465
465
  | `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) |
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 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
467
  | `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
468
  | `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
469
  | `ERC20` | Token reads and approvals |
@@ -414,7 +414,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
414
414
  | `PriceOracleMiddleware` | Asset price feeds |
415
415
  | `PriceOracleMiddlewareManager` | Per-vault price-source overrides |
416
416
  | `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) |
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 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
418
  | `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
419
  | `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
420
  | `ERC20` | Token reads and approvals |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev286"
3
+ version = "4.0.0.dev291"
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",
@@ -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,
@@ -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
@@ -128,10 +128,14 @@ _ALL_SNAPSHOT: dict[str, frozenset[str]] = {
128
128
  "ExternalStateAssetRescuedArgs",
129
129
  "ExternalStateAssetRescuedEvent",
130
130
  "ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent",
131
+ "ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent",
132
+ "ExternalStateOperationFuseSweepSurplusEvent",
131
133
  "ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent",
132
134
  "ExternalStateBigChangeDetectedArgs",
133
135
  "ExternalStateBigChangeDetectedEvent",
134
136
  "ExternalStateCustodianTimestampUpdatedArgs",
137
+ "ExternalStateCustodianConfirmCountUpdatedArgs",
138
+ "ExternalStateOperationFuseSweepSurplusArgs",
135
139
  "ExternalStateError",
136
140
  "ExternalStateEvent",
137
141
  "ExternalStateEventArgs",
@@ -253,6 +257,7 @@ _ALL_SNAPSHOT: dict[str, frozenset[str]] = {
253
257
  "PendingRequestJson",
254
258
  "PendingRequestsInfo",
255
259
  "PerformanceFeeData",
260
+ "CustodianMarker",
256
261
  "Period",
257
262
  "PlasmaVault",
258
263
  "PlasmaVaultAddress",
@@ -816,10 +821,14 @@ _ALL_SNAPSHOT: dict[str, frozenset[str]] = {
816
821
  "ExternalStateAssetRescuedArgs",
817
822
  "ExternalStateAssetRescuedEvent",
818
823
  "ExternalStateBalanceFuseLastCustodianTimestampUpdatedEvent",
824
+ "ExternalStateBalanceFuseLastCustodianConfirmCountUpdatedEvent",
825
+ "ExternalStateOperationFuseSweepSurplusEvent",
819
826
  "ExternalStateBalanceFuseLastTotalBalanceUpdatedEvent",
820
827
  "ExternalStateBigChangeDetectedArgs",
821
828
  "ExternalStateBigChangeDetectedEvent",
822
829
  "ExternalStateCustodianTimestampUpdatedArgs",
830
+ "ExternalStateCustodianConfirmCountUpdatedArgs",
831
+ "ExternalStateOperationFuseSweepSurplusArgs",
823
832
  "ExternalStateEvent",
824
833
  "ExternalStateEventArgs",
825
834
  "ExternalStateEventSpec",
@@ -1121,6 +1130,7 @@ _MODULE_PATH_SNAPSHOT: dict[str, frozenset[str]] = {
1121
1130
  "NodeStatus",
1122
1131
  "OperationId",
1123
1132
  "PayloadHash",
1133
+ "CustodianMarker",
1124
1134
  "Period",
1125
1135
  "PlasmaVaultAddress",
1126
1136
  "PositionSetHash",
@@ -22,8 +22,9 @@ from ipor_fusion.core.external_state_executor import ExternalStateExecutor
22
22
  from ipor_fusion.fuses import ExternalStateOperationFuse
23
23
  from ipor_fusion.market_ids import IporFusionMarkets
24
24
 
25
- # IPOR-Labs/ipor-fusion main as of 2026-09-10
26
- IPOR_FUSION_REF = "a1f79d6c3d0d426ace4df55ed0fb8ea340580739"
25
+ # IPOR-Labs/ipor-fusion main as of 2026-10-21 (the contracts release
26
+ # ipor-abi publishes)
27
+ IPOR_FUSION_REF = "10f0f8e4031980b6f130d4f6460c6f8edfbcab32"
27
28
 
28
29
  _RAW_URL = "https://raw.githubusercontent.com/IPOR-Labs/ipor-fusion/{ref}/{path}"
29
30
  _LIBRARIES = "contracts/libraries"
@@ -50,12 +51,13 @@ def _parse_solidity_int(value: str) -> int:
50
51
 
51
52
 
52
53
  @cache
53
- def _solidity_source(path: str) -> str:
54
- """The contract at `path` (repo-relative), at the pinned upstream commit.
54
+ def _solidity_source(path: str, ref: str = IPOR_FUSION_REF) -> str:
55
+ """The contract at `path` (repo-relative), at the pinned upstream commit
56
+ (or ``ref``).
55
57
 
56
58
  Cached so gates reading the same file share one fetch; `cache` stores
57
59
  nothing on a raising path, so a transient failure still re-tries."""
58
- url = _RAW_URL.format(ref=IPOR_FUSION_REF, path=path)
60
+ url = _RAW_URL.format(ref=ref, path=path)
59
61
  try:
60
62
  resp = requests.get(url, timeout=15)
61
63
  except requests.RequestException as exc:
@@ -82,7 +84,7 @@ def _solidity_constants(file_name: str) -> dict[str, int]:
82
84
  # a live deployment or a contracts feature branch). Each must be ABSENT
83
85
  # upstream at the pinned ref; once it lands, drop it here and bump
84
86
  # IPOR_FUSION_REF in the same change.
85
- _AHEAD_OF_UPSTREAM = {"CROSSCHAIN": 54, "HYPERCORE": 55}
87
+ _AHEAD_OF_UPSTREAM: dict[str, int] = {}
86
88
 
87
89
 
88
90
  def _member_words(body: str) -> list[list[str]]: