ipor-fusion 4.0.0.dev122__tar.gz → 4.0.0.dev129__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 (298) hide show
  1. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/AGENTS.md +12 -6
  2. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/MIGRATION_4.md +35 -18
  3. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/PKG-INFO +14 -7
  4. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/README.md +13 -6
  5. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/CONTRIBUTING.md +1 -5
  6. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/external_state_margin_leg_base.py +0 -7
  7. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/pyproject.toml +1 -1
  8. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/scripts/crosschain_readiness.py +1 -2
  9. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/__init__.py +26 -3
  10. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/__init__.py +4 -1
  11. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/context.py +48 -20
  12. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/external_state_executor.py +20 -160
  13. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/__init__.py +2 -0
  14. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/errors.py +2 -3
  15. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/__init__.py +12 -0
  16. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_external_state_executor.py +6 -362
  17. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_public_api.py +275 -37
  18. ipor_fusion-4.0.0.dev129/tests/test_public_type_reachability.py +228 -0
  19. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_web3_context.py +35 -0
  20. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.claude-plugin/marketplace.json +0 -0
  21. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.claude-plugin/plugin.json +0 -0
  22. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.env.example +0 -0
  23. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.gemini/settings.json +0 -0
  24. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.git-blame-ignore-revs +0 -0
  25. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/cd.yml +0 -0
  26. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/ci.yml +0 -0
  27. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/commit-lint.yml +0 -0
  28. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/notify-slack.yml +0 -0
  29. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/python-build.yml +0 -0
  30. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/python-publish.yml +0 -0
  31. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/release.yml +0 -0
  32. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.github/workflows/report-slack.yml +0 -0
  33. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.gitignore +0 -0
  34. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.mcp.json +0 -0
  35. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.pre-commit-config.yaml +0 -0
  36. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/.python-version +0 -0
  37. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/CHANGELOG.md +0 -0
  38. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/CLAUDE.md +0 -0
  39. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/CODEOWNERS +0 -0
  40. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/LICENSE +0 -0
  41. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/build.sh +0 -0
  42. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/README.md +0 -0
  43. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  44. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  45. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  46. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  47. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/examples/simple_aave_v3_supply_base.py +0 -0
  48. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/pytest.ini +0 -0
  49. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/scripts/commit-check.sh +0 -0
  50. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/skills/ipor-deploy-vault/SKILL.md +0 -0
  51. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/about.py +0 -0
  52. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cache.py +0 -0
  53. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/chains.py +0 -0
  54. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/__init__.py +0 -0
  55. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  56. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/config_cmd.py +0 -0
  57. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/config_store.py +0 -0
  58. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/main.py +0 -0
  59. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/market_cmd.py +0 -0
  60. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/options.py +0 -0
  61. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  62. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  63. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/vault_health.py +0 -0
  64. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  65. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  66. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  67. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/config/__init__.py +0 -0
  68. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/config/roles.py +0 -0
  69. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/access.py +0 -0
  70. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/address.py +0 -0
  71. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/contract.py +0 -0
  72. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/erc20.py +0 -0
  73. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/fee_manager.py +0 -0
  74. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/fuse.py +0 -0
  75. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/fuse_action.py +0 -0
  76. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/fusion_factory.py +0 -0
  77. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/logs.py +0 -0
  78. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/multicall.py +0 -0
  79. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/oracle.py +0 -0
  80. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/plasma_vault.py +0 -0
  81. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/rewards_manager.py +0 -0
  82. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/simulation.py +0 -0
  83. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  84. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  85. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  86. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  87. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  88. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  89. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  90. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  91. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  92. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  93. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/contracts.py +0 -0
  94. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/discovery.py +0 -0
  95. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/errors.py +0 -0
  96. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  97. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  98. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  99. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  100. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/lane.py +0 -0
  101. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/logs.py +0 -0
  102. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/messages.py +0 -0
  103. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/simulation.py +0 -0
  104. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  105. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  106. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  107. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  108. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  109. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/crosschain/transport.py +0 -0
  110. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/dependency_graph.py +0 -0
  111. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/entry_cli.py +0 -0
  112. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/entry_mcp.py +0 -0
  113. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/explorer.py +0 -0
  114. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/field_docs.py +0 -0
  115. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/__init__.py +0 -0
  116. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  117. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/async_action.py +0 -0
  118. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/base.py +0 -0
  119. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  120. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/erc4626.py +0 -0
  121. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  122. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/events.py +0 -0
  123. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/external_state.py +0 -0
  124. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  125. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  126. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  127. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/hypercore.py +0 -0
  128. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  129. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/merkl.py +0 -0
  130. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/morpho.py +0 -0
  131. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  132. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  133. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/fuses/universal.py +0 -0
  134. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/guide/__init__.py +0 -0
  135. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/guide/architecture.md +0 -0
  136. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/guide/glossary.md +0 -0
  137. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/guide/invariants.md +0 -0
  138. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/guide/quickstart.md +0 -0
  139. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/market_ids.py +0 -0
  140. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/market_info.py +0 -0
  141. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/mcp/__init__.py +0 -0
  142. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/mcp/guide.py +0 -0
  143. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/mcp/models.py +0 -0
  144. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/mcp/server.py +0 -0
  145. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/morpho_api.py +0 -0
  146. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/py.typed +0 -0
  147. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/aave_v3.py +0 -0
  148. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/aave_v4.py +0 -0
  149. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/compound_v3.py +0 -0
  150. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/hypercore.py +0 -0
  151. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  152. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/lending_health.py +0 -0
  153. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/morpho.py +0 -0
  154. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  155. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  156. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  157. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/position_manager.py +0 -0
  158. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  159. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  160. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/readers/unpriceable.py +0 -0
  161. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/substrates.py +0 -0
  162. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/types.py +0 -0
  163. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/validation.py +0 -0
  164. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/vault_data.py +0 -0
  165. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/vault_erc20.py +0 -0
  166. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/vault_info.py +0 -0
  167. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/vault_managers.py +0 -0
  168. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/vault_positions.py +0 -0
  169. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/src/ipor_fusion/vault_reads.py +0 -0
  170. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/UniswapV3UniversalRouter.py +0 -0
  171. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_crosschain.py +0 -0
  172. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_crosschain_lifecycle.py +0 -0
  173. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_crosschain_recovery.py +0 -0
  174. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_euler_v2.py +0 -0
  175. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_foundry.py +0 -0
  176. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_guide.py +0 -0
  177. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_hypercore_model.py +0 -0
  178. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_hypercore_shadow.py +0 -0
  179. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_multicall.py +0 -0
  180. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_simulate.py +0 -0
  181. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/_validation.py +0 -0
  182. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/addresses.py +0 -0
  183. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/conftest.py +0 -0
  184. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/constants.py +0 -0
  185. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  186. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/fixtures/hypercore_market55.json +0 -0
  187. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/fixtures/vault_info_golden/clean.json +0 -0
  188. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  189. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/fixtures/vault_info_golden/rich.json +0 -0
  190. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  191. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_about.py +0 -0
  192. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_access_manager.py +0 -0
  193. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cache.py +0 -0
  194. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_chains.py +0 -0
  195. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_changelog.py +0 -0
  196. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_commands.py +0 -0
  197. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_config_store.py +0 -0
  198. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_helpers.py +0 -0
  199. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_hypercore.py +0 -0
  200. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_lending_health.py +0 -0
  201. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_market_morpho_blue.py +0 -0
  202. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_oracle_mapping.py +0 -0
  203. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_pending_requests_scan.py +0 -0
  204. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_role_accounts.py +0 -0
  205. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_vault_info_golden.py +0 -0
  206. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_vault_predates_getters.py +0 -0
  207. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_cli_vault_validate.py +0 -0
  208. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_contract_call.py +0 -0
  209. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_contract_encoder.py +0 -0
  210. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_ccip_events.py +0 -0
  211. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_ccip_indexer.py +0 -0
  212. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_ccip_quote.py +0 -0
  213. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_chainlink.py +0 -0
  214. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_contracts.py +0 -0
  215. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_encoding.py +0 -0
  216. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_errors.py +0 -0
  217. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_lane.py +0 -0
  218. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_layering.py +0 -0
  219. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_readiness.py +0 -0
  220. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_readiness_report.py +0 -0
  221. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_crosschain_transport.py +0 -0
  222. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_erc20.py +0 -0
  223. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_error_hierarchy.py +0 -0
  224. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_examples_composed_hypercore.py +0 -0
  225. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_examples_crosschain.py +0 -0
  226. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  227. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_examples_vaults.py +0 -0
  228. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_explorer.py +0 -0
  229. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_external_state_events.py +0 -0
  230. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_fee_manager.py +0 -0
  231. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_fuse_encoding.py +0 -0
  232. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_fusion_instance.py +0 -0
  233. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_guide.py +0 -0
  234. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_hypercore_deployment.py +0 -0
  235. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_hypercore_events.py +0 -0
  236. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_hypercore_flow.py +0 -0
  237. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_hypercore_live.py +0 -0
  238. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_hypercore_outcome.py +0 -0
  239. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_hypercore_readers.py +0 -0
  240. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_hypercore_simulation.py +0 -0
  241. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_logs.py +0 -0
  242. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_mcp_hypercore.py +0 -0
  243. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_mcp_models.py +0 -0
  244. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_mcp_server.py +0 -0
  245. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_mcp_vault_validate.py +0 -0
  246. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_multicall.py +0 -0
  247. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_oracle.py +0 -0
  248. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_oracle_mapping.py +0 -0
  249. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_plasma_vault.py +0 -0
  250. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_reader_encoding.py +0 -0
  251. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_readiness_selection.py +0 -0
  252. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_revert_decoding.py +0 -0
  253. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_rewards_manager.py +0 -0
  254. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_roles.py +0 -0
  255. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_sdk_layering.py +0 -0
  256. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  257. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  258. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  259. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_crosschain_canary.py +0 -0
  260. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_crosschain_hypercore.py +0 -0
  261. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  262. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  263. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_examples_arbitrum.py +0 -0
  269. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_examples_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_examples_crosschain.py +0 -0
  271. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_external_state_executor_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  275. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_morpho_blue.py +0 -0
  276. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_pilot_v3.py +0 -0
  277. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  278. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  279. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  280. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  281. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_readers_integration.py +0 -0
  282. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_release_funds.py +0 -0
  283. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_swap_on_base.py +0 -0
  284. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  285. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_solidity_compiler.py +0 -0
  286. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_solidity_mirrors.py +0 -0
  287. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_substrates.py +0 -0
  288. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_types_and_events.py +0 -0
  289. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_unpriceable_tokens.py +0 -0
  290. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_validation.py +0 -0
  291. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_vault_dep_graph.py +0 -0
  292. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_vault_info_onchain_base.py +0 -0
  293. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  294. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_vault_simulator_blocks.py +0 -0
  295. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/test_withdraw_manager.py +0 -0
  296. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/typed_client/consumer.py +0 -0
  297. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/tests/typed_client/rejected.py +0 -0
  298. {ipor_fusion-4.0.0.dev122 → ipor_fusion-4.0.0.dev129}/uv.lock +0 -0
@@ -161,11 +161,16 @@ the lifecycle matrix follows that.
161
161
  public modules (`about`, `cache`, `chains`, `dependency_graph`, `errors`, `explorer`,
162
162
  `field_docs`, `market_ids`, `market_info`, `morpho_api`, `substrates`, `types`,
163
163
  `validation`, `vault_data`, `vault_info`) stay importable for the names they
164
- define; `cli`, `mcp` and every other module are internal. Examples, the guide
165
- and the skill import from `ipor_fusion` or a public module, never an internal
164
+ define and the moved names README lists; `cli`, `mcp`, every other module and
165
+ every submodule of a public subpackage (`core.contract`, `fuses.base`, ...) are
166
+ internal. Examples, the guide, the skill and `scripts/` import from
167
+ `ipor_fusion`, a public subpackage or a public module, never an internal
166
168
  path. Adding or removing a
167
- name updates the snapshot in `tests/test_public_api.py`; a removal or rename
168
- is a breaking change. The package is `py.typed`, so public APIs stay fully
169
+ name updates the snapshots in `tests/test_public_api.py` (every `__all__`, and
170
+ the names each public module keeps by path); moving a name out of a public
171
+ module leaves a re-export there. A removal or rename is a breaking change.
172
+ A project type used in a public signature or field is exported too
173
+ (`tests/test_public_type_reachability.py`). The package is `py.typed`, so public APIs stay fully
169
174
  annotated.
170
175
 
171
176
  ## Invariants that must change together
@@ -395,8 +400,9 @@ role-accounts, oracle-mapping). `vault info` runs independent fetch pipelines
395
400
  (vault/asset, fees, WithdrawManager, markets, roles) concurrently on a
396
401
  `ThreadPoolExecutor`, each a short chain of `Multicall3` batches; keep new
397
402
  reads inside a batch rather than adding per-call round trips. A `from_url` context
398
- bounds requests in flight (`max_concurrent_requests`, default 8) whatever the pool
399
- nesting, so a new fan-out does not need its own limit. Contract names,
403
+ bounds requests in flight (`max_concurrent_requests`, default 8; an
404
+ `InFlightLimit` shares one bound across contexts) whatever the pool nesting, so a
405
+ new fan-out does not need its own limit. Contract names,
400
406
  token symbols and decimals and deployment blocks are cached per chain in
401
407
  `$XDG_CACHE_HOME/ipor-fusion/contract_cache.json` (`<chain>:<address>:<field>`
402
408
  keys); tests redirect it with `monkeypatch.setenv("XDG_CACHE_HOME", ...)` or
@@ -56,7 +56,11 @@ a `bytes32` return (recall) request is `ReturnRequestId`.
56
56
  names earlier reachable only by module path (`FusionFactory`, `FusionInstance`,
57
57
  `fetch_market`, `resolve_token_symbol`, the crosschain codecs, ...). Import from
58
58
  `ipor_fusion`: a module path outside the README's list (*Public API and stability*)
59
- still works but is internal.
59
+ still works but is internal. Every project type a public signature or field uses is
60
+ exported too, among them `AaveV3PositionBreakdown`, `AaveV3ReserveTokens`,
61
+ `AccountRequest`, `StateOverrideProvider`, `TransferDirection` and the validation
62
+ vocabulary `Severity`, `Verdict` and `Check`. Also new: `InFlightLimit` (below) and
63
+ `log_bytes`, the hex-or-bytes log field helper the crosschain decoders use.
60
64
 
61
65
  **Vault reads and validation.** The vault read is one pinned snapshot, and the
62
66
  chain is always the context's:
@@ -110,8 +114,13 @@ now shown in share decimals labelled `shares` (``fusion vault info``) and its JS
110
114
  `Web3Context.from_url(..., max_concurrent_requests=8)` bounds the RPC requests in
111
115
  flight across every thread using the context (``None`` lifts the bound); the vault
112
116
  reads fan out on nested thread pools, and the bound is what a rate-limited provider
113
- sees. A context built from your own `Web3` object is not bounded unless you add the
114
- same middleware.
117
+ sees. An `int` bounds one context. To bound several contexts on one provider (a
118
+ service that builds a context per request or per scan), create one
119
+ `InFlightLimit(n)` per provider and pass it as `max_concurrent_requests=`: every
120
+ context built with it shares the same `n` slots. A context built from your own
121
+ `Web3` object is not bounded unless you add the limit to it:
122
+ `limit.install(web3)` (innermost, as `from_url` does) or
123
+ `web3.middleware_onion.inject(limit.middleware, layer=0)`.
115
124
 
116
125
  `ensure_supported_chain` takes `ChainId`. `fetch_unpriceable_priced_tokens` takes the oracle and
117
126
  underlying as `ChecksumAddress`. `resolve_token_symbol`, `resolve_token_decimals`,
@@ -197,7 +206,13 @@ keyword arguments only. `ZERO_ADDRESS` is a `ChecksumAddress` (same value).
197
206
  `HYPERCORE_PENDING_READERS` are keyed by `ChainId` (`HYPEREVM_CHAIN_ID` is one) and
198
207
  `explorer.SUPPORTED_CHAINS` is a `frozenset[ChainId]`, so
199
208
  `ensure_supported_chain(CHAIN_NAME_TO_ID["ethereum"])` type-checks;
200
- `PUBLIC_ALLOCATOR_ADDRESSES` values are `ChecksumAddress`. `IporFusionMarkets.RWA`
209
+ `PUBLIC_ALLOCATOR_ADDRESSES` values are `ChecksumAddress`. Import `CHAIN_NAMES`,
210
+ `CHAIN_NAME_TO_ID`, `SUPPORTED_CHAIN_IDS`, `GET_LOGS_RANGE_HINTS`,
211
+ `PUBLIC_ALLOCATOR_ADDRESSES`, `HYPERCORE_PENDING_READERS`, `HYPEREVM_CHAIN_ID` and
212
+ `HYPERCORE_MARKET` from `ipor_fusion` (the HyperCore ones also from
213
+ `ipor_fusion.readers`). `BLOCK_EXPLORER_URLS`, `MORPHO_BLUE_ADDRESSES`,
214
+ `AAVE_V3_POOL` and `explorer.SUPPORTED_CHAINS` are internal: use
215
+ `morpho_blue_address(chain_id)` for Morpho Blue. `IporFusionMarkets.RWA`
201
216
  is gone: market 50 is `IporFusionMarkets.EXTERNAL_STATE`. `MORPHO_BLUE_ADDRESS` is
202
217
  gone: use `morpho_blue_address(chain_id)`.
203
218
 
@@ -240,8 +255,11 @@ before passing it on.
240
255
  `HyperCorePendingState` and `HyperCoreTransferSnapshot` carry them.
241
256
 
242
257
  **External state.** `ExternalStateExecutor.proposal_hash` returns and `confirm_balance`
243
- takes a `ProposalHash`; `BalanceProposal.proposal_hash` and `NavMark.proposal_hash`
244
- are `ProposalHash`; `ExternalStateEvent.transaction_hash` is a `TxHash`.
258
+ takes a `ProposalHash`; `BalanceProposal.proposal_hash` is a `ProposalHash`;
259
+ `ExternalStateEvent.transaction_hash` is a `TxHash`.
260
+ `ExternalStateExecutor.mark_nav` and `NavMark` are gone: send `propose_balance` and
261
+ `confirm_balance` from separate custodians, then
262
+ `PlasmaVault.update_markets_balances([IporFusionMarkets.EXTERNAL_STATE])`.
245
263
 
246
264
  **Errors and explorer.** `TransactionError(tx_hash=)` takes a `TxHash`,
247
265
  `LogScanError(address=)` a `ChecksumAddress`; `get_revert_reason` takes a `TxHash`;
@@ -253,8 +271,10 @@ except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
253
271
  `MarketId`. Calling an MCP tool function directly from Python takes `ChainId`.
254
272
 
255
273
  **Fuse base.** `Fuse`, `StakeFuse` and the shared validators and substrate packers
256
- live in `ipor_fusion.core.fuse`; `ipor_fusion.fuses.base`, `ipor_fusion.fuses` and
257
- `ipor_fusion` re-export them, so existing imports keep working. Fuse equality and
274
+ moved to `ipor_fusion.core.fuse`. Import `Fuse` and `StakeFuse` from `ipor_fusion`
275
+ or `ipor_fusion.fuses`. `ipor_fusion.fuses.base` still re-exports them in 4.0, but
276
+ it is a submodule of a public subpackage and therefore internal (see the README,
277
+ *Public API and stability*); so are the underscore validators and packers. Fuse equality and
258
278
  hash now cover every attribute a fuse stores, not only its class and address (see
259
279
  [Behavior changes](#behavior-changes)).
260
280
 
@@ -267,9 +287,7 @@ unchanged.
267
287
  | 3.x import | 4.0 import |
268
288
  |---|---|
269
289
  | `ipor_fusion.fuses.crosschain` | `ipor_fusion.crosschain.fuses` (or `ipor_fusion.crosschain`) |
270
- | `ipor_fusion.fuses.crosschain.base` | `ipor_fusion.crosschain.fuses.base` |
271
- | `ipor_fusion.fuses.crosschain.ccip` | `ipor_fusion.crosschain.fuses.ccip` |
272
- | `ipor_fusion.fuses.crosschain.stargate` | `ipor_fusion.crosschain.fuses.stargate` |
290
+ | `ipor_fusion.fuses.crosschain.base`, `.ccip`, `.stargate` | `ipor_fusion.crosschain.fuses` (its submodules `base`, `ccip` and `stargate` exist but are internal) |
273
291
  | `from ipor_fusion.fuses import CcipCrosschainSupplyFuse` (and the other crosschain encoders, `SendParams`, `CrosschainSubstrate*`, `crosschain_market_id`) | `from ipor_fusion.crosschain import ...` or `from ipor_fusion import ...` |
274
292
 
275
293
  `ipor_fusion.crosschain` now exports the whole crosschain surface: besides the
@@ -280,8 +298,9 @@ the transports and chains, `discover_deployment`, `open_lane(s)`, `LANES` and
280
298
  and wrappers.
281
299
 
282
300
  **Moved, still importable.** `FuseAction` lives in `ipor_fusion.core.fuse_action` and
283
- `ZERO_ADDRESS` in `ipor_fusion.types`; both are re-exported from
284
- `ipor_fusion.fuses.base` and `FuseAction` from `ipor_fusion`. The CLI's internal
301
+ `ZERO_ADDRESS` in `ipor_fusion.types`; import both from `ipor_fusion` (`FuseAction`
302
+ also from `ipor_fusion.fuses`, `ZERO_ADDRESS` from the public module
303
+ `ipor_fusion.types`). The internal `ipor_fusion.fuses.base` still re-exports them. The CLI's internal
285
304
  `ipor_fusion.cli.vault_cmd` was split into `options`, `vault_info_view`,
286
305
  `vault_markets_view` and `oracle_mapping_view` (private names, not API).
287
306
 
@@ -333,7 +352,7 @@ placeholder wrapper.
333
352
  and `VaultSimulator.observe` stamp the same hash for the same bytes, whatever
334
353
  context executes them. Pass `ctx.chain_id` for the chain you read from.
335
354
  `pending_proposals` now refuses an executor at the zero address when the Call is
336
- built, not when it is decoded. `mark_nav` takes the chain from its contexts.
355
+ built, not when it is decoded.
337
356
 
338
357
  | 3.x | 4.0 |
339
358
  |---|---|
@@ -544,10 +563,8 @@ is the wrong one.
544
563
  `parse_balance_proposed` / `find_balance_proposed` for one of the executor's own
545
564
  `BalanceProposed` logs whose data is missing or malformed (including data that is
546
565
  not hex at all, which raised `TypeError`) or whose hash is not the hash of its own
547
- fields; and `mark_nav` when the propose receipt it fetched has no such log or
548
- records another value or proposer. A log that is not the event or not from the
549
- executor, and `find_balance_proposed` over logs that hold no match, are still
550
- `ValueError`.
566
+ fields. A log that is not the event or not from the executor, and
567
+ `find_balance_proposed` over logs that hold no match, are still `ValueError`.
551
568
  - `ccip_token_lane` raises the new `UnsupportedContractVersionError` (still a
552
569
  `ValueError`) for an OnRamp version whose layout is not verified.
553
570
  - `quote_ccip_native_fee` raises `SimulationError` (label `"quote"`, with
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev122
3
+ Version: 4.0.0.dev129
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
@@ -383,16 +383,23 @@ release; adding one does not.
383
383
  - **Public modules** stay importable for the top-level names they define:
384
384
  `about`, `cache`, `chains`, `dependency_graph`, `errors`, `explorer`, `field_docs`,
385
385
  `market_ids`, `market_info`, `morpho_api`, `substrates`, `types`, `validation`,
386
- `vault_data` and `vault_info` (all under `ipor_fusion.`).
386
+ `vault_data` and `vault_info` (all under `ipor_fusion.`). A name that moved out of
387
+ one of them keeps its old path there (`vault_data.FeeData`,
388
+ `vault_data.WithdrawManagerData`, `validation.Erc20Holding`,
389
+ `validation.Erc20Holdings`, `morpho_api.MorphoApiError`).
387
390
  - **Internal:** `ipor_fusion.cli`, `ipor_fusion.mcp`, every other module (e.g.
388
- `vault_reads`, the submodules of the public subpackages), underscore
389
- names, and names in no `__all__`. They change without notice; the CLI and the
390
- MCP server are stable as commands and tools, not as Python modules.
391
+ `vault_reads`) and every submodule of a public subpackage (e.g.
392
+ `ipor_fusion.core.contract`, `ipor_fusion.fuses.base`; import their names from
393
+ the subpackage or `ipor_fusion`), underscore names, and names in no `__all__`.
394
+ They change without notice; the CLI and the MCP server are stable as commands
395
+ and tools, not as Python modules.
391
396
  - **Preview features** (`PREVIEW_FEATURES` in `ipor_fusion.about`: crosschain,
392
397
  HyperCore) are exported like the rest but may change in a minor release.
393
398
 
394
- `tests/test_public_api.py` snapshots every `__all__`, so a change to the surface
395
- is a deliberate, reviewed edit.
399
+ `tests/test_public_api.py` snapshots every `__all__` and the names each public
400
+ module keeps by path, so a change to the surface is a deliberate, reviewed edit.
401
+ `tests/test_public_type_reachability.py` fails when a public signature or field
402
+ uses a project type that no `__all__` exports.
396
403
 
397
404
  ## Architecture
398
405
 
@@ -335,16 +335,23 @@ release; adding one does not.
335
335
  - **Public modules** stay importable for the top-level names they define:
336
336
  `about`, `cache`, `chains`, `dependency_graph`, `errors`, `explorer`, `field_docs`,
337
337
  `market_ids`, `market_info`, `morpho_api`, `substrates`, `types`, `validation`,
338
- `vault_data` and `vault_info` (all under `ipor_fusion.`).
338
+ `vault_data` and `vault_info` (all under `ipor_fusion.`). A name that moved out of
339
+ one of them keeps its old path there (`vault_data.FeeData`,
340
+ `vault_data.WithdrawManagerData`, `validation.Erc20Holding`,
341
+ `validation.Erc20Holdings`, `morpho_api.MorphoApiError`).
339
342
  - **Internal:** `ipor_fusion.cli`, `ipor_fusion.mcp`, every other module (e.g.
340
- `vault_reads`, the submodules of the public subpackages), underscore
341
- names, and names in no `__all__`. They change without notice; the CLI and the
342
- MCP server are stable as commands and tools, not as Python modules.
343
+ `vault_reads`) and every submodule of a public subpackage (e.g.
344
+ `ipor_fusion.core.contract`, `ipor_fusion.fuses.base`; import their names from
345
+ the subpackage or `ipor_fusion`), underscore names, and names in no `__all__`.
346
+ They change without notice; the CLI and the MCP server are stable as commands
347
+ and tools, not as Python modules.
343
348
  - **Preview features** (`PREVIEW_FEATURES` in `ipor_fusion.about`: crosschain,
344
349
  HyperCore) are exported like the rest but may change in a minor release.
345
350
 
346
- `tests/test_public_api.py` snapshots every `__all__`, so a change to the surface
347
- is a deliberate, reviewed edit.
351
+ `tests/test_public_api.py` snapshots every `__all__` and the names each public
352
+ module keeps by path, so a change to the surface is a deliberate, reviewed edit.
353
+ `tests/test_public_type_reachability.py` fails when a public signature or field
354
+ uses a project type that no `__all__` exports.
348
355
 
349
356
  ## Architecture
350
357
 
@@ -63,11 +63,7 @@ role-separation note is about. Label the collapse itself, not the calls: sending
63
63
  as two separate calls *is* the production shape, and only "both keys in one process" is
64
64
  simulation-only.
65
65
 
66
- Cross-reference the SDK method a production caller would use instead, where one exists -- but only
67
- where the SDK method is the one to reach for. `ExternalStateExecutor.mark_nav` is the counter-example:
68
- it composes both custodian calls, its own docstring warns that this "does not preserve the
69
- dual-custodian separation", and it is expected to be deprecated in favour of the two primitives. Do
70
- not point readers at it.
66
+ Cross-reference the SDK method a production caller would use instead, where one exists.
71
67
 
72
68
  ## 3. One self-contained file
73
69
 
@@ -894,13 +894,6 @@ def run_simulation(web3: Web3) -> SimulationResult:
894
894
  # driven from this one process. In a real deployment they live in separate
895
895
  # systems -- if one process can send both, a single compromise moves the
896
896
  # vault's NAV and the two-party design has bought nothing.
897
- #
898
- # ExternalStateExecutor.mark_nav runs the propose, the confirm and -- when
899
- # handed a vault -- the refresh in one call. It cannot be used here for a
900
- # mechanical reason: it .send(ctx)s each leg, and nothing is broadcast under
901
- # eth_simulateV1. It is also not what a production deployment wants, for the
902
- # reason above: one process holding both custodian keys is one key wearing
903
- # two hats.
904
897
  sim.next_block().with_block_time_shift(PROPOSE_OFFSET)
905
898
  sim.add_call(
906
899
  call=executor.propose_balance(VENUE_BALANCE_ACCOUNT, MARGIN_AMOUNT),
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev122"
3
+ version = "4.0.0.dev129"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -41,8 +41,7 @@ from eth_utils import function_signature_to_4byte_selector, keccak
41
41
  from web3 import Web3
42
42
  from web3.types import RPCEndpoint
43
43
 
44
- from ipor_fusion import ERC20, Web3Context, ccip_token_lane
45
- from ipor_fusion.core.contract import Call
44
+ from ipor_fusion import ERC20, Call, Web3Context, ccip_token_lane
46
45
  from ipor_fusion.crosschain import CcipCrosschainFactory, CcipOffRamp, CcipRouter
47
46
  from ipor_fusion.types import ChainId
48
47
 
@@ -31,7 +31,7 @@ from ipor_fusion.core.access import (
31
31
  resolve_access_manager,
32
32
  role_account_sort_key,
33
33
  )
34
- from ipor_fusion.core.context import Web3Context
34
+ from ipor_fusion.core.context import InFlightLimit, Web3Context
35
35
  from ipor_fusion.core.contract import Call
36
36
  from ipor_fusion.core.erc20 import ERC20
37
37
  from ipor_fusion.core.external_state_executor import (
@@ -39,7 +39,6 @@ from ipor_fusion.core.external_state_executor import (
39
39
  BalanceProposal,
40
40
  ExternalStateExecutor,
41
41
  ExternalStateVaultState,
42
- NavMark,
43
42
  RawLog,
44
43
  read_external_state,
45
44
  )
@@ -69,11 +68,13 @@ from ipor_fusion.core.rewards_manager import RewardsManager, VestingData
69
68
  from ipor_fusion.core.simulation import (
70
69
  SimulatedCallResult,
71
70
  SimulationResult,
71
+ StateOverrideProvider,
72
72
  VaultSimulator,
73
73
  erc20_balance_slot,
74
74
  is_simulate_v1_supported,
75
75
  )
76
76
  from ipor_fusion.core.withdraw_manager import (
77
+ AccountRequest,
77
78
  PendingRequestsInfo,
78
79
  WithdrawManager,
79
80
  WithdrawRequestInfo,
@@ -183,6 +184,7 @@ from ipor_fusion.crosschain import (
183
184
  encode_oft_compose_msg,
184
185
  fetch_verifier_result,
185
186
  find_ccip_events,
187
+ log_bytes,
186
188
  lz_compose_calldata,
187
189
  lz_receive_calldata,
188
190
  manual_execution,
@@ -332,9 +334,14 @@ from ipor_fusion.morpho_api import (
332
334
  fetch_vault,
333
335
  )
334
336
  from ipor_fusion.readers import (
337
+ HYPERCORE_MARKET,
338
+ HYPERCORE_PENDING_READERS,
339
+ HYPEREVM_CHAIN_ID,
335
340
  AaveV3FuseReader,
336
341
  AaveV3PoolAddressesProvider,
342
+ AaveV3PositionBreakdown,
337
343
  AaveV3Reader,
344
+ AaveV3ReserveTokens,
338
345
  AaveV3UserAccountData,
339
346
  AaveV4OracleReader,
340
347
  AaveV4SpokeReader,
@@ -375,6 +382,7 @@ from ipor_fusion.readers import (
375
382
  PositionData,
376
383
  RamsesV2Position,
377
384
  RamsesV2Reader,
385
+ TransferDirection,
378
386
  UniswapV3Position,
379
387
  UniswapV3Reader,
380
388
  VaultLendingHealth,
@@ -404,6 +412,7 @@ from ipor_fusion.types import (
404
412
  AssetId,
405
413
  AssetSource,
406
414
  ChainId,
415
+ Check,
407
416
  CommandId,
408
417
  Decimals,
409
418
  DeploymentRequestId,
@@ -432,12 +441,14 @@ from ipor_fusion.types import (
432
441
  ProposalId,
433
442
  ReturnRequestId,
434
443
  RoleId,
444
+ Severity,
435
445
  Shares,
436
446
  StargateAssetId,
437
447
  Tick,
438
448
  TokenId,
439
449
  TransferId,
440
450
  TxHash,
451
+ Verdict,
441
452
  parse_access_manager_address,
442
453
  parse_fuse_address,
443
454
  parse_hex_address,
@@ -497,6 +508,7 @@ __all__ = [
497
508
  "read_changelog",
498
509
  "repository_url",
499
510
  "Web3Context",
511
+ "InFlightLimit",
500
512
  "Call",
501
513
  "get_logs_adaptive",
502
514
  "find_creation_block",
@@ -504,6 +516,7 @@ __all__ = [
504
516
  "Multicall3",
505
517
  "MULTICALL3_ADDRESS",
506
518
  "VaultSimulator",
519
+ "StateOverrideProvider",
507
520
  "SimulationResult",
508
521
  "SimulatedCallResult",
509
522
  "is_simulate_v1_supported",
@@ -517,6 +530,7 @@ __all__ = [
517
530
  "VestingData",
518
531
  "ERC20",
519
532
  "WithdrawManager",
533
+ "AccountRequest",
520
534
  "FeeAccount",
521
535
  "FeeManager",
522
536
  "RecipientFee",
@@ -538,7 +552,6 @@ __all__ = [
538
552
  "BalanceFuseSnapshot",
539
553
  "ExternalStateVaultState",
540
554
  "read_external_state",
541
- "NavMark",
542
555
  "CrosschainExecutor",
543
556
  "StargateCrosschainExecutor",
544
557
  "StargateCrosschainDispatcher",
@@ -563,6 +576,7 @@ __all__ = [
563
576
  "CcipChain",
564
577
  "CrosschainSimulator",
565
578
  "CrosschainLane",
579
+ "log_bytes",
566
580
  "StargateLane",
567
581
  "CcipLane",
568
582
  "LaneFuses",
@@ -820,12 +834,17 @@ __all__ = [
820
834
  "morpho_blue_address",
821
835
  "AaveV3FuseReader",
822
836
  "AaveV3PoolAddressesProvider",
837
+ "AaveV3PositionBreakdown",
823
838
  "AaveV3Reader",
839
+ "AaveV3ReserveTokens",
824
840
  "AaveV3UserAccountData",
825
841
  "AaveV4OracleReader",
826
842
  "AaveV4SpokeReader",
827
843
  "AaveV4UserAccountData",
828
844
  "CompoundV3Reader",
845
+ "HYPERCORE_MARKET",
846
+ "HYPERCORE_PENDING_READERS",
847
+ "HYPEREVM_CHAIN_ID",
829
848
  "HyperCoreAccountMarginSummary",
830
849
  "HyperCoreActionClass",
831
850
  "HyperCoreEvmExitCeiling",
@@ -843,6 +862,7 @@ __all__ = [
843
862
  "HyperCoreTransferOutcome",
844
863
  "HyperCoreTransferSnapshot",
845
864
  "HyperCoreTransferStatus",
865
+ "TransferDirection",
846
866
  "compare_hypercore_transfer",
847
867
  "read_hypercore_evm_exit_ceiling",
848
868
  "read_hypercore_transfer_outcome",
@@ -871,6 +891,9 @@ __all__ = [
871
891
  "Erc20Holding",
872
892
  "Erc20Holdings",
873
893
  "Finding",
894
+ "Severity",
895
+ "Verdict",
896
+ "Check",
874
897
  "Reconciliation",
875
898
  "UnpriceableToken",
876
899
  "VaultState",
@@ -1,5 +1,5 @@
1
1
  from ipor_fusion.core.access import AccessManager, RoleAccount, RoleStatus
2
- from ipor_fusion.core.context import Web3Context
2
+ from ipor_fusion.core.context import InFlightLimit, Web3Context
3
3
  from ipor_fusion.core.erc20 import ERC20
4
4
  from ipor_fusion.core.fee_manager import (
5
5
  FeeAccount,
@@ -23,6 +23,7 @@ from ipor_fusion.core.plasma_vault import (
23
23
  )
24
24
  from ipor_fusion.core.rewards_manager import RewardsManager, VestingData
25
25
  from ipor_fusion.core.withdraw_manager import (
26
+ AccountRequest,
26
27
  PendingRequestsInfo,
27
28
  WithdrawManager,
28
29
  WithdrawRequestInfo,
@@ -30,6 +31,7 @@ from ipor_fusion.core.withdraw_manager import (
30
31
 
31
32
  __all__ = [
32
33
  "Web3Context",
34
+ "InFlightLimit",
33
35
  "Multicall3",
34
36
  "MULTICALL3_ADDRESS",
35
37
  "PlasmaVault",
@@ -40,6 +42,7 @@ __all__ = [
40
42
  "VestingData",
41
43
  "ERC20",
42
44
  "WithdrawManager",
45
+ "AccountRequest",
43
46
  "WithdrawRequestInfo",
44
47
  "PendingRequestsInfo",
45
48
  "BalanceFuse",
@@ -170,11 +170,15 @@ class Web3Context:
170
170
  log_scan_max_requests: int | None = DEFAULT_LOG_SCAN_MAX_REQUESTS,
171
171
  log_source: LogSource | None = None,
172
172
  cache: ContractCache | None = None,
173
- max_concurrent_requests: int | None = DEFAULT_MAX_CONCURRENT_REQUESTS,
173
+ max_concurrent_requests: int
174
+ | InFlightLimit
175
+ | None = DEFAULT_MAX_CONCURRENT_REQUESTS,
174
176
  ) -> Web3Context:
175
177
  """A context on an HTTP provider at ``url``. ``max_concurrent_requests``
176
- bounds the RPC requests in flight across every thread using it
177
- (``None``: unbounded)."""
178
+ bounds the RPC requests in flight: an ``int`` across every thread using
179
+ this context, an `InFlightLimit` across every context and `Web3` it is
180
+ installed on (share one per provider in a service that builds a context
181
+ per request); ``None`` leaves them unbounded."""
178
182
  web3 = Web3(
179
183
  Web3.HTTPProvider(
180
184
  url,
@@ -192,15 +196,11 @@ class Web3Context:
192
196
  # with a ValueError. The middleware moves it to proofOfAuthorityData
193
197
  # and leaves every other block untouched.
194
198
  web3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
199
+ if isinstance(max_concurrent_requests, int):
200
+ max_concurrent_requests = InFlightLimit(max_concurrent_requests)
195
201
  if max_concurrent_requests is not None:
196
- # Innermost (layer 0, injected last): a slot covers only the provider
197
- # call, so a middleware that issues its own request (the gas price
198
- # strategy reads a block) never waits on the slot it already holds.
199
- web3.middleware_onion.inject(
200
- _in_flight_limit(max_concurrent_requests),
201
- name="in_flight_limit",
202
- layer=0,
203
- )
202
+ # Injected last, so innermost (see `InFlightLimit.install`).
203
+ max_concurrent_requests.install(web3)
204
204
  chain_id = ChainId(web3.eth.chain_id)
205
205
 
206
206
  return cls(
@@ -417,15 +417,43 @@ class Web3Context:
417
417
  return value * percentage // 100
418
418
 
419
419
 
420
- def _in_flight_limit(limit: int) -> type[Web3Middleware]:
421
- """A middleware class (web3 instantiates it per Web3) holding a semaphore
422
- slot for the duration of each provider call; a JSON-RPC batch is one HTTP
423
- request, so it takes one slot whatever its item count."""
424
- if limit < 1:
425
- raise ValueError(f"max_concurrent_requests must be at least 1, got {limit}")
426
- slots = threading.BoundedSemaphore(limit)
420
+ class InFlightLimit:
421
+ """A bound on the RPC requests in flight, shareable across `Web3` objects.
422
+
423
+ Every `Web3` it is installed on (``install``, or
424
+ ``Web3Context.from_url(max_concurrent_requests=limit)``) takes a slot from
425
+ the same semaphore for each provider call, so several contexts on one
426
+ provider, or a service that builds a context per request, stay within
427
+ ``limit`` in total. A JSON-RPC batch is one HTTP request and takes one slot
428
+ whatever its item count; a failed request releases its slot.
429
+ """
430
+
431
+ def __init__(self, limit: int):
432
+ if limit < 1:
433
+ raise ValueError(f"an in-flight limit must be at least 1, got {limit}")
434
+ self._limit = limit
435
+ self._middleware = _slot_middleware(threading.BoundedSemaphore(limit))
436
+
437
+ @property
438
+ def limit(self) -> int:
439
+ return self._limit
440
+
441
+ @property
442
+ def middleware(self) -> type[Web3Middleware]:
443
+ """The middleware class to add to a `Web3` by hand; every instance
444
+ web3 builds from it shares this limit's slots."""
445
+ return self._middleware
446
+
447
+ def install(self, web3: Web3) -> None:
448
+ """Add the limit to ``web3`` as its innermost middleware
449
+ (``in_flight_limit``). A slot then covers only the provider call, so a
450
+ middleware that issues its own request (the gas price strategy reads a
451
+ block) never waits on the slot it already holds."""
452
+ web3.middleware_onion.inject(self._middleware, name="in_flight_limit", layer=0)
453
+
427
454
 
428
- class InFlightLimit(Web3Middleware):
455
+ def _slot_middleware(slots: threading.BoundedSemaphore) -> type[Web3Middleware]:
456
+ class InFlightLimitMiddleware(Web3Middleware):
429
457
  def wrap_make_request(self, make_request: MakeRequestFn) -> MakeRequestFn:
430
458
  def limited(method: RPCEndpoint, params: Any) -> RPCResponse:
431
459
  with slots:
@@ -444,4 +472,4 @@ def _in_flight_limit(limit: int) -> type[Web3Middleware]:
444
472
 
445
473
  return limited
446
474
 
447
- return InFlightLimit
475
+ return InFlightLimitMiddleware