ipor-fusion 4.0.0.dev86__tar.gz → 4.0.0.dev88__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 (296) hide show
  1. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/AGENTS.md +8 -4
  2. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/MIGRATION_4.md +36 -1
  3. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/advanced_euler_v2_credit_market_base.py +2 -2
  5. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/advanced_euler_v2_looping_arbitrum.py +2 -2
  6. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/external_state_margin_leg_base.py +4 -4
  7. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/simple_aave_v3_supply_base.py +2 -2
  8. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/pyproject.toml +1 -1
  9. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/access.py +3 -8
  10. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/contract.py +14 -30
  11. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/external_state_executor.py +10 -11
  12. ipor_fusion-4.0.0.dev86/src/ipor_fusion/fuses/base.py → ipor_fusion-4.0.0.dev88/src/ipor_fusion/core/fuse.py +17 -8
  13. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/multicall.py +6 -4
  14. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/oracle.py +2 -2
  15. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/plasma_vault.py +5 -9
  16. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/withdraw_manager.py +4 -4
  17. ipor_fusion-4.0.0.dev88/src/ipor_fusion/fuses/base.py +17 -0
  18. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/crosschain/base.py +3 -6
  19. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/crosschain/ccip.py +3 -2
  20. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/crosschain/stargate.py +2 -1
  21. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/universal.py +0 -10
  22. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/aave_v3.py +5 -3
  23. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/morpho.py +3 -3
  24. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_crosschain_recovery.py +1 -1
  25. ipor_fusion-4.0.0.dev88/tests/test_contract_encoder.py +118 -0
  26. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_layering.py +13 -0
  27. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_external_state_executor.py +27 -27
  28. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_fee_manager.py +2 -2
  29. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_fuse_encoding.py +17 -0
  30. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_hypercore_flow.py +3 -3
  31. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_plasma_vault.py +1 -1
  32. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_fusion_factory_clone_base.py +7 -23
  33. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_plasma_vault_setters_base.py +1 -4
  34. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_solidity_mirrors.py +1 -1
  35. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/typed_client/consumer.py +8 -0
  36. ipor_fusion-4.0.0.dev86/tests/test_contract_encoder.py +0 -118
  37. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.claude-plugin/marketplace.json +0 -0
  38. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.claude-plugin/plugin.json +0 -0
  39. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.env.example +0 -0
  40. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.gemini/settings.json +0 -0
  41. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.git-blame-ignore-revs +0 -0
  42. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/cd.yml +0 -0
  43. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/ci.yml +0 -0
  44. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/commit-lint.yml +0 -0
  45. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/notify-slack.yml +0 -0
  46. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/python-build.yml +0 -0
  47. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/python-publish.yml +0 -0
  48. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/release.yml +0 -0
  49. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.github/workflows/report-slack.yml +0 -0
  50. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.gitignore +0 -0
  51. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.mcp.json +0 -0
  52. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.pre-commit-config.yaml +0 -0
  53. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/.python-version +0 -0
  54. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/CHANGELOG.md +0 -0
  55. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/CLAUDE.md +0 -0
  56. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/CODEOWNERS +0 -0
  57. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/LICENSE +0 -0
  58. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/README.md +0 -0
  59. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/build.sh +0 -0
  60. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/CONTRIBUTING.md +0 -0
  61. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/README.md +0 -0
  62. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  63. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  64. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/pytest.ini +0 -0
  65. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/scripts/commit-check.sh +0 -0
  66. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/scripts/crosschain_readiness.py +0 -0
  67. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/skills/ipor-deploy-vault/SKILL.md +0 -0
  68. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/__init__.py +0 -0
  69. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/about.py +0 -0
  70. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cache.py +0 -0
  71. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/chains.py +0 -0
  72. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/__init__.py +0 -0
  73. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  74. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/config_cmd.py +0 -0
  75. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/config_store.py +0 -0
  76. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/main.py +0 -0
  77. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/market_cmd.py +0 -0
  78. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/options.py +0 -0
  79. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  80. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  81. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/vault_health.py +0 -0
  82. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  83. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  84. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  85. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/config/__init__.py +0 -0
  86. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/config/roles.py +0 -0
  87. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/__init__.py +0 -0
  88. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/address.py +0 -0
  89. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/context.py +0 -0
  90. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/erc20.py +0 -0
  91. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/fee_manager.py +0 -0
  92. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/fuse_action.py +0 -0
  93. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/fusion_factory.py +0 -0
  94. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/logs.py +0 -0
  95. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/rewards_manager.py +0 -0
  96. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/core/simulation.py +0 -0
  97. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/__init__.py +0 -0
  98. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  99. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  100. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  101. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  102. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  103. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  104. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  105. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  106. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  107. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/contracts.py +0 -0
  108. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/discovery.py +0 -0
  109. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/errors.py +0 -0
  110. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/lane.py +0 -0
  111. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/logs.py +0 -0
  112. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/messages.py +0 -0
  113. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/simulation.py +0 -0
  114. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  115. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  116. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  117. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  118. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  119. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/crosschain/transport.py +0 -0
  120. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/dependency_graph.py +0 -0
  121. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/entry_cli.py +0 -0
  122. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/entry_mcp.py +0 -0
  123. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/errors.py +0 -0
  124. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/explorer.py +0 -0
  125. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/field_docs.py +0 -0
  126. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/__init__.py +0 -0
  127. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  128. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/async_action.py +0 -0
  129. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  130. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  131. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/erc4626.py +0 -0
  132. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  133. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/events.py +0 -0
  134. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/external_state.py +0 -0
  135. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  136. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  137. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  138. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/hypercore.py +0 -0
  139. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  140. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/merkl.py +0 -0
  141. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/morpho.py +0 -0
  142. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  143. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  144. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/guide/__init__.py +0 -0
  145. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/guide/architecture.md +0 -0
  146. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/guide/glossary.md +0 -0
  147. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/guide/invariants.md +0 -0
  148. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/guide/quickstart.md +0 -0
  149. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/market_ids.py +0 -0
  150. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/market_info.py +0 -0
  151. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/mcp/__init__.py +0 -0
  152. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/mcp/guide.py +0 -0
  153. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/mcp/models.py +0 -0
  154. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/mcp/server.py +0 -0
  155. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/morpho_api.py +0 -0
  156. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/py.typed +0 -0
  157. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/__init__.py +0 -0
  158. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/aave_v4.py +0 -0
  159. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/compound_v3.py +0 -0
  160. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/hypercore.py +0 -0
  161. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  162. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/lending_health.py +0 -0
  163. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  164. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  165. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  166. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/position_manager.py +0 -0
  167. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  168. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  169. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/readers/unpriceable.py +0 -0
  170. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/substrates.py +0 -0
  171. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/types.py +0 -0
  172. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/validation.py +0 -0
  173. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/vault_data.py +0 -0
  174. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/vault_info.py +0 -0
  175. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/vault_managers.py +0 -0
  176. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/vault_positions.py +0 -0
  177. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/src/ipor_fusion/vault_reads.py +0 -0
  178. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/UniswapV3UniversalRouter.py +0 -0
  179. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_crosschain.py +0 -0
  180. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_crosschain_lifecycle.py +0 -0
  181. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_euler_v2.py +0 -0
  182. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_foundry.py +0 -0
  183. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_guide.py +0 -0
  184. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_hypercore_model.py +0 -0
  185. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_hypercore_shadow.py +0 -0
  186. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_multicall.py +0 -0
  187. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_simulate.py +0 -0
  188. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/_validation.py +0 -0
  189. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/addresses.py +0 -0
  190. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/conftest.py +0 -0
  191. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/constants.py +0 -0
  192. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  193. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/fixtures/hypercore_market55.json +0 -0
  194. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/fixtures/vault_info_golden/clean.json +0 -0
  195. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  196. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/fixtures/vault_info_golden/rich.json +0 -0
  197. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  198. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_about.py +0 -0
  199. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_access_manager.py +0 -0
  200. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cache.py +0 -0
  201. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_chains.py +0 -0
  202. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_changelog.py +0 -0
  203. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_commands.py +0 -0
  204. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_config_store.py +0 -0
  205. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_helpers.py +0 -0
  206. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_hypercore.py +0 -0
  207. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_lending_health.py +0 -0
  208. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_market_morpho_blue.py +0 -0
  209. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_oracle_mapping.py +0 -0
  210. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_pending_requests_scan.py +0 -0
  211. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_role_accounts.py +0 -0
  212. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_vault_info_golden.py +0 -0
  213. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_vault_predates_getters.py +0 -0
  214. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_cli_vault_validate.py +0 -0
  215. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_contract_call.py +0 -0
  216. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_ccip_events.py +0 -0
  217. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_ccip_indexer.py +0 -0
  218. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_ccip_quote.py +0 -0
  219. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_chainlink.py +0 -0
  220. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_contracts.py +0 -0
  221. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_encoding.py +0 -0
  222. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_errors.py +0 -0
  223. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_lane.py +0 -0
  224. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_readiness.py +0 -0
  225. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_readiness_report.py +0 -0
  226. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_crosschain_transport.py +0 -0
  227. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_erc20.py +0 -0
  228. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_error_hierarchy.py +0 -0
  229. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_examples_composed_hypercore.py +0 -0
  230. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_examples_crosschain.py +0 -0
  231. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  232. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_examples_vaults.py +0 -0
  233. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_explorer.py +0 -0
  234. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_external_state_events.py +0 -0
  235. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_fusion_instance.py +0 -0
  236. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_guide.py +0 -0
  237. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_hypercore_deployment.py +0 -0
  238. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_hypercore_events.py +0 -0
  239. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_hypercore_live.py +0 -0
  240. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_hypercore_outcome.py +0 -0
  241. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_hypercore_readers.py +0 -0
  242. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_hypercore_simulation.py +0 -0
  243. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_logs.py +0 -0
  244. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_mcp_hypercore.py +0 -0
  245. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_mcp_models.py +0 -0
  246. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_mcp_server.py +0 -0
  247. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_mcp_vault_validate.py +0 -0
  248. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_multicall.py +0 -0
  249. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_oracle.py +0 -0
  250. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_oracle_mapping.py +0 -0
  251. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_public_api.py +0 -0
  252. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_reader_encoding.py +0 -0
  253. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_readiness_selection.py +0 -0
  254. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_revert_decoding.py +0 -0
  255. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_rewards_manager.py +0 -0
  256. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_roles.py +0 -0
  257. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_sdk_layering.py +0 -0
  258. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  259. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  260. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  261. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_crosschain_canary.py +0 -0
  262. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_crosschain_hypercore.py +0 -0
  263. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  264. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  265. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_examples_arbitrum.py +0 -0
  271. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_examples_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_examples_crosschain.py +0 -0
  273. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_external_state_executor_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  275. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_morpho_blue.py +0 -0
  277. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_pilot_v3.py +0 -0
  278. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  279. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  280. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  281. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_readers_integration.py +0 -0
  282. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_release_funds.py +0 -0
  283. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_swap_on_base.py +0 -0
  284. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  285. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_solidity_compiler.py +0 -0
  286. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_substrates.py +0 -0
  287. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_types_and_events.py +0 -0
  288. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_unpriceable_tokens.py +0 -0
  289. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_validation.py +0 -0
  290. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_vault_dep_graph.py +0 -0
  291. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_vault_info_onchain_base.py +0 -0
  292. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  293. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_vault_simulator_blocks.py +0 -0
  294. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_web3_context.py +0 -0
  295. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/tests/test_withdraw_manager.py +0 -0
  296. {ipor_fusion-4.0.0.dev86 → ipor_fusion-4.0.0.dev88}/uv.lock +0 -0
@@ -218,12 +218,14 @@ the lifecycle matrix follows that.
218
218
  `simulation` (`VaultSimulator`, eth_simulateV1, `erc20_balance_slot` and ERC-20 balance
219
219
  overrides), `oracle`, `fusion_factory`,
220
220
  `external_state_executor` (NAV marks for market 50), `erc20`, `fuse_action`
221
- (`FuseAction`), `address` (`_validate_not_zero_address`). `core` imports neither
221
+ (`FuseAction`), `fuse` (the `Fuse` base, `StakeFuse`, the shared validators and
222
+ substrate packers), `address` (`_validate_not_zero_address`). `core` imports neither
222
223
  `fuses` nor `crosschain` (`tests/test_sdk_layering.py`)
223
224
  - `fuses/` — per-protocol fuse encoders (aave_v3, async_action, compound_v3, crosschain/,
224
225
  erc4626, euler_v2, external_state, fluid_instadapp, gearbox_v3, merkl, morpho, ramses_v2,
225
- uniswap_v3, universal, `events.py`); `base.py` holds `Fuse` and the
226
- shared validators and re-exports `FuseAction` and `ZERO_ADDRESS` (from `types`)
226
+ uniswap_v3, universal, `events.py`); `base.py` re-exports the `core/fuse.py` base and validators,
227
+ `FuseAction` and `ZERO_ADDRESS` (from `types`). A fuse is a value: equality and
228
+ hash cover its class and every attribute it stores
227
229
  - `crosschain/` — crosschain Plasma Vaults (executor on the hub chain, dispatcher at
228
230
  the same address on each spoke chain), split like the contracts: shared `messages`
229
231
  (`Command`, `MsgType`, envelopes), `contracts` (wrapper bases), `transport`
@@ -276,7 +278,9 @@ or examples.
276
278
  - `Call[T]` (`core/contract.py`): every `ContractWrapper` method (one per
277
279
  Solidity function) returns a `Call` instead of executing. `.call()` runs
278
280
  `eth_call` and decodes to `T`; `.send()` signs and submits, returning a
279
- `TxReceipt`; `.calldata` hands the bytes to an external signer;
281
+ `TxReceipt`; `.calldata` hands the bytes to an external signer (a wrapper
282
+ built with `ctx=None`, e.g. `PlasmaVault(None, address)`, builds calldata
283
+ offline; there is no separate encoder factory);
280
284
  `.build_transaction()` / `Web3Context.estimate_gas` preview without signing.
281
285
  A `Call` without `output_types` (a write) raises on `.call()`. The same
282
286
  `Call` feeds `VaultSimulator.observe`, so reads, sends and simulations share
@@ -93,7 +93,7 @@ The Uniswap V3 / Ramses V2 position events' `version`, `sender` and `recipient`
93
93
  `quote_ccip_native_fee(block=)` and `OracleMappingReader.asset_source_events` take a
94
94
  `BlockIdentifier`.
95
95
 
96
- **Address roles.** `PlasmaVault(ctx, address)`, `PlasmaVault.encoder(address)`,
96
+ **Address roles.** `PlasmaVault(ctx, address)` (`ctx` may be `None`, see below),
97
97
  `validate_vault`, `fetch_deployment_info`, the HyperCore, lending-health and
98
98
  oracle-mapping readers, `read_external_state`, `VaultSimulator(vault=)`,
99
99
  `CrosschainSimulator`, crosschain discovery, the hub-manager inputs
@@ -194,6 +194,35 @@ are `ProposalHash`; `ExternalStateEvent.transaction_hash` is a `TxHash`.
194
194
  except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
195
195
  `MarketId`. Calling an MCP tool function directly from Python takes `ChainId`.
196
196
 
197
+ **Offline wrappers.** `ContractWrapper.encoder()` (and the `PlasmaVault` /
198
+ `AccessManager` overrides) is gone. Every wrapper constructor takes
199
+ `ctx: Web3Context | None`, so build an offline wrapper with the normal
200
+ constructor and an address: `PlasmaVault(None, vault)`, `ERC20(None, token)`,
201
+ `FusionFactory(None, factory)`, `LegacyPriceOracleMiddleware(None, oracle,
202
+ Decimals(8))`. The address is required (there is no zero-address placeholder) and
203
+ subclass parameters stay required, so the wrapper is fully initialized:
204
+ `.encoder()` skipped the constructor, and a legacy oracle built that way failed to
205
+ decode `get_asset_price`. `.calldata` still needs no context; `.call()`, `.send()`,
206
+ `.build_transaction()` and `.estimate_gas()` raise `ValueError` until one is passed
207
+ to them, and a method that reads the chain itself (log scans such as
208
+ `get_balance_fuses` or `get_pending_requests`, `withdraw_manager_address`,
209
+ `Multicall3.aggregate` / `try_aggregate`, the Aave V3 and Morpho position reads)
210
+ raises `ValueError("Web3Context required: <wrapper> at <address> ...")` instead of
211
+ an `AttributeError` on `None`. `ExternalStateExecutor` still refuses the zero
212
+ address for its hash and event readers.
213
+
214
+ | 3.x | 4.0 |
215
+ |---|---|
216
+ | `PlasmaVault.encoder(vault)` | `PlasmaVault(None, vault)` |
217
+ | `ERC20.encoder()` (no address) | `ERC20(None, token)`: pass the real address |
218
+ | `Cls.__new__(Cls)` + `_ctx` / `_address` | `Cls(None, address)` |
219
+
220
+ **Fuse base.** `Fuse`, `StakeFuse` and the shared validators and substrate packers
221
+ live in `ipor_fusion.core.fuse`; `ipor_fusion.fuses.base`, `ipor_fusion.fuses` and
222
+ `ipor_fusion` re-export them, so existing imports keep working. Fuse equality and
223
+ hash now cover every attribute a fuse stores, not only its class and address (see
224
+ [Behavior changes](#behavior-changes)).
225
+
197
226
  **Moved, still importable.** `FuseAction` lives in `ipor_fusion.core.fuse_action` and
198
227
  `ZERO_ADDRESS` in `ipor_fusion.types`; both are re-exported from
199
228
  `ipor_fusion.fuses.base` and `FuseAction` from `ipor_fusion`. The CLI's internal
@@ -202,6 +231,12 @@ except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
202
231
 
203
232
  ## Behavior changes
204
233
 
234
+ - Two fuses are equal (and hash alike) only when they have the same class and the
235
+ same stored state. `StakeFuse` subclasses (`GearboxStakeFuse`,
236
+ `FluidInstadappStakingFuse`) with the same fuse address but different
237
+ `staking_address` were equal and collapsed in a set or dict key; they are now
238
+ distinct. A custom `Fuse` subclass is compared on its instance attributes, which
239
+ must be hashable; drop an `__eq__` / `__hash__` override that only added them.
205
240
  - The MCP `vault_info` document reports the vault address checksummed (the CLI
206
241
  already did).
207
242
  - `SubstrateInfo.address` is `None` instead of `""` for a substrate without an
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev86
3
+ Version: 4.0.0.dev88
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
@@ -261,12 +261,12 @@ def clone_args() -> dict:
261
261
  def unsigned_clone_calldata() -> bytes:
262
262
  """Unsigned creation calldata (selector + ABI-encoded args) for the deploy.
263
263
 
264
- Built with a ctx-less encoder, so it needs no provider. Hand these bytes to
264
+ Built on a wrapper with ctx=None, so it needs no provider. Hand these bytes to
265
265
  an external signer / multisig instead of calling ``.send()``. NOTE: the owner
266
266
  encoded here is the simulation ``OWNER`` (a throwaway EOA) -- set your real
267
267
  owner in ``clone_args()`` before deploying, or the vault is owned by it.
268
268
  """
269
- return FusionFactory.encoder(BASE_FUSION_FACTORY).clone(**clone_args()).calldata
269
+ return FusionFactory(None, BASE_FUSION_FACTORY).clone(**clone_args()).calldata
270
270
 
271
271
 
272
272
  def market_substrates() -> list[bytes]:
@@ -208,7 +208,7 @@ def clone_args() -> dict:
208
208
 
209
209
 
210
210
  def unsigned_clone_calldata() -> bytes:
211
- return FusionFactory.encoder(ARBITRUM_FUSION_FACTORY).clone(**clone_args()).calldata
211
+ return FusionFactory(None, ARBITRUM_FUSION_FACTORY).clone(**clone_args()).calldata
212
212
 
213
213
 
214
214
  def euler_market_substrates() -> list[bytes]:
@@ -260,7 +260,7 @@ def enable_euler_actions() -> list[FuseAction]:
260
260
  def build_swap_action() -> FuseAction:
261
261
  """Swap the borrowed USDC through Uniswap V3 and require flash repayment."""
262
262
  transfer_data = (
263
- ERC20.encoder(ARBITRUM_USDC)
263
+ ERC20(None, ARBITRUM_USDC)
264
264
  .transfer(UNISWAP_UNIVERSAL_ROUTER, BORROW_USDC)
265
265
  .calldata
266
266
  )
@@ -438,12 +438,12 @@ def clone_args() -> dict:
438
438
  def unsigned_clone_calldata() -> bytes:
439
439
  """Unsigned creation calldata (selector + ABI-encoded args) for the deploy.
440
440
 
441
- Built with a ctx-less encoder, so it needs no provider. Hand these bytes to
441
+ Built on a wrapper with ctx=None, so it needs no provider. Hand these bytes to
442
442
  an external signer / multisig instead of calling ``.send()``. NOTE: the owner
443
443
  encoded here is the simulation ``OWNER`` (a throwaway EOA) -- set your real
444
444
  owner in ``clone_args()`` before deploying, or the vault is owned by it.
445
445
  """
446
- return FusionFactory.encoder(BASE_FUSION_FACTORY).clone(**clone_args()).calldata
446
+ return FusionFactory(None, BASE_FUSION_FACTORY).clone(**clone_args()).calldata
447
447
 
448
448
 
449
449
  def market_substrates() -> list[bytes]:
@@ -534,9 +534,9 @@ def market_substrates() -> list[bytes]:
534
534
  def venue_transfer_calldata(amount: Amount) -> bytes:
535
535
  """``USDC.transfer(VENUE_DEPOSIT_ADDRESS, amount)`` calldata, run from the executor.
536
536
 
537
- Encoded with a ctx-less ERC20 wrapper -- pure bytes, no chain access.
537
+ Encoded with an ERC20 wrapper built with ctx=None -- pure bytes, no chain access.
538
538
  """
539
- return ERC20.encoder(BASE_USDC).transfer(VENUE_DEPOSIT_ADDRESS, amount).calldata
539
+ return ERC20(None, BASE_USDC).transfer(VENUE_DEPOSIT_ADDRESS, amount).calldata
540
540
 
541
541
 
542
542
  def build_enter_action(amount: Amount) -> FuseAction:
@@ -201,12 +201,12 @@ def clone_args() -> dict:
201
201
  def unsigned_clone_calldata() -> bytes:
202
202
  """Unsigned creation calldata (selector + ABI-encoded args) for the deploy.
203
203
 
204
- Built with a ctx-less encoder, so it needs no provider. Hand these bytes to
204
+ Built on a wrapper with ctx=None, so it needs no provider. Hand these bytes to
205
205
  an external signer / multisig instead of calling ``.send()``. NOTE: the owner
206
206
  encoded here is the simulation ``OWNER`` (a throwaway EOA) -- set your real
207
207
  owner in ``clone_args()`` before deploying, or the vault is owned by it.
208
208
  """
209
- return FusionFactory.encoder(BASE_FUSION_FACTORY).clone(**clone_args()).calldata
209
+ return FusionFactory(None, BASE_FUSION_FACTORY).clone(**clone_args()).calldata
210
210
 
211
211
 
212
212
  def build_supply_action(amount: Amount) -> FuseAction:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev86"
3
+ version = "4.0.0.dev88"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -1,6 +1,5 @@
1
1
  from collections.abc import Callable
2
2
  from dataclasses import dataclass
3
- from typing import Self
4
3
 
5
4
  from eth_abi import decode
6
5
  from eth_abi.exceptions import InsufficientDataBytes
@@ -68,17 +67,13 @@ def _role_status_decoder(values: tuple) -> RoleStatus:
68
67
  class AccessManager(ContractWrapper):
69
68
  """Manages role-based access control for PlasmaVault operations."""
70
69
 
71
- def __init__(self, ctx: Web3Context, address: AccessManagerAddress):
70
+ def __init__(self, ctx: Web3Context | None, address: AccessManagerAddress):
72
71
  super().__init__(ctx, address)
73
72
 
74
73
  @property
75
74
  def address(self) -> AccessManagerAddress:
76
75
  return AccessManagerAddress(self._address)
77
76
 
78
- @classmethod
79
- def encoder(cls, address: AccessManagerAddress | None = None) -> Self: # type: ignore[override]
80
- return super().encoder(address)
81
-
82
77
  def grant_role(
83
78
  self, role_id: RoleId | Roles, account: ChecksumAddress, execution_delay: Period
84
79
  ) -> Call[None]:
@@ -161,7 +156,7 @@ class AccessManager(ContractWrapper):
161
156
  if predicate(role_id, account):
162
157
  candidates[(role_id, account)] = None
163
158
 
164
- statuses = Multicall3(self._ctx).aggregate(
159
+ statuses = Multicall3(self._require_ctx()).aggregate(
165
160
  [self.has_role(role_id, account) for role_id, account in candidates]
166
161
  )
167
162
  return [
@@ -180,7 +175,7 @@ class AccessManager(ContractWrapper):
180
175
  Web3.keccak(text="RoleGranted(uint64,address,uint32,uint48,bool)")
181
176
  ).to_0x_hex()
182
177
  return list(
183
- self._ctx.get_logs(
178
+ self._require_ctx().get_logs(
184
179
  contract_address=self._address, topics=[event_signature_hash]
185
180
  )
186
181
  )
@@ -129,18 +129,19 @@ class Call(Generic[T]):
129
129
  return actual
130
130
 
131
131
 
132
- _ENCODER_PLACEHOLDER_ADDRESS: ChecksumAddress = cast(
133
- ChecksumAddress, "0x0000000000000000000000000000000000000000"
134
- )
135
-
136
-
137
132
  class ContractWrapper:
138
133
  """Base wrapper. Subclasses expose one method per Solidity function that
139
134
  returns a `Call[T]` — never executing eagerly. Callers chain `.call()` for
140
135
  reads, `.send()` for writes, or hand the `Call` to `VaultSimulator`.
136
+
137
+ `ctx` may be ``None`` for offline use, e.g. external-signer flows (HTTP
138
+ signing service, multisig) that only need `Call.calldata`: every builder
139
+ works, and `.call()` / `.send()` on its Calls raise until a context is
140
+ passed to them. Methods that read the chain themselves (log scans, storage
141
+ reads, batched reads) raise `ValueError` on such a wrapper.
141
142
  """
142
143
 
143
- def __init__(self, ctx: Web3Context, address: ChecksumAddress):
144
+ def __init__(self, ctx: Web3Context | None, address: ChecksumAddress):
144
145
  self._ctx = ctx
145
146
  self._address = Web3.to_checksum_address(address)
146
147
 
@@ -148,30 +149,13 @@ class ContractWrapper:
148
149
  def address(self) -> ChecksumAddress:
149
150
  return self._address
150
151
 
151
- @classmethod
152
- def encoder(cls, address: ChecksumAddress | None = None):
153
- """Build a ctx-less wrapper: every builder method, no Web3Context.
154
-
155
- Named for its first use, external-signer flows (HTTP signing service,
156
- multisig), which only need `Call.calldata` — the actual `to` and `ctx`
157
- are filled in by the signer. It serves any builder that needs no chain
158
- access, decoders included. Calling `.call()` or `.send()` on a Call
159
- produced this way raises (no ctx).
160
-
161
- Pass `address` unless `.calldata` really is the only consumer. The
162
- encoding ignores it, but a builder that reads `self._address`
163
- semantically — matching it against a log's emitter, say, or folding it
164
- into a hash — runs against the placeholder zero address without one,
165
- and so rejects input that is genuinely the caller's.
166
- """
167
- instance = cls.__new__(cls)
168
- instance._ctx = None
169
- instance._address = (
170
- Web3.to_checksum_address(address)
171
- if address is not None
172
- else _ENCODER_PLACEHOLDER_ADDRESS
173
- )
174
- return instance
152
+ def _require_ctx(self) -> Web3Context:
153
+ if self._ctx is None:
154
+ raise ValueError(
155
+ f"Web3Context required: {type(self).__name__} at {self._address} "
156
+ "was built without one (ctx=None), which only builds calldata."
157
+ )
158
+ return self._ctx
175
159
 
176
160
  def _view(
177
161
  self,
@@ -463,13 +463,13 @@ class ExternalStateExecutor(ContractWrapper):
463
463
  The hash is bound to a chain. `chain_id` defaults to this wrapper's
464
464
  context; pass it explicitly when the context that executes the call is
465
465
  not the one the wrapper was built with (`Call.call(other_ctx)`), or to
466
- read through a ctx-less `encoder()` instance.
466
+ read through a wrapper built with `ctx=None`.
467
467
  """
468
468
  account = Web3.to_checksum_address(balance_account)
469
469
 
470
470
  def to_proposal(values: tuple[int, str, int, int]) -> BalanceProposal | None:
471
471
  # Both hash inputs first, before the empty-slot return. Resolved
472
- # inside the decoder so a calldata-only `encoder()` wrapper keeps
472
+ # inside the decoder so a calldata-only (`ctx=None`) wrapper keeps
473
473
  # working like every sibling, but ahead of the sentinel so a
474
474
  # misconfigured poller fails on its first read rather than on the
475
475
  # first read that happens to find a proposal.
@@ -504,29 +504,28 @@ class ExternalStateExecutor(ContractWrapper):
504
504
  )
505
505
 
506
506
  def _require_address(self) -> ChecksumAddress:
507
- """This executor's address, or a clear error on an address-less
508
- `encoder()`.
507
+ """This executor's address, or a clear error for the zero address.
509
508
 
510
- The other half of the hash's binding, and the placeholder zero address
511
- is not a usable stand-in for it: it stamps a hash bound to no contract,
509
+ The other half of the hash's binding, and the zero address is not a
510
+ usable stand-in for it: it stamps a hash bound to no contract,
512
511
  and it matches only a log claiming to come from none."""
513
512
  if self._address == ZERO_ADDRESS:
514
513
  raise ValueError(
515
514
  "executor address required: the proposal hash binds it and the "
516
515
  "event readers match it against a log's emitter, so build this "
517
- "wrapper with an address rather than a bare encoder()."
516
+ "wrapper with the executor's address, not the zero address."
518
517
  )
519
518
  return self._address
520
519
 
521
520
  def _require_chain_id(self, override: ChainId | None = None) -> ChainId:
522
521
  """`override` if the caller named one, else the wrapper's chain, else a
523
- clear error on a ctx-less `encoder()`."""
522
+ clear error on a wrapper built with `ctx=None`."""
524
523
  if override is not None:
525
524
  return override
526
525
  if self._ctx is None:
527
526
  raise ValueError(
528
527
  "Web3Context required: the proposal hash is bound to a chain id, "
529
- "so build this wrapper with a ctx rather than via encoder(), "
528
+ "so build this wrapper with a ctx rather than ctx=None, "
530
529
  "or pass chain_id= to this method."
531
530
  )
532
531
  return self._ctx.chain_id
@@ -673,7 +672,7 @@ class ExternalStateExecutor(ContractWrapper):
673
672
 
674
673
  The hash is bound to a chain. `chain_id` defaults to this wrapper's
675
674
  context; a caller with no context -- decoding indexer or `get_logs`
676
- rows -- reaches this through `encoder(address)` and passes `chain_id`
675
+ rows -- builds it with `ExternalStateExecutor(None, address)` and passes `chain_id`
677
676
  explicitly. Either way the whole path is pure, with no RPC.
678
677
 
679
678
  `find_balance_proposed` applies the same emitter check while scanning.
@@ -755,7 +754,7 @@ class ExternalStateExecutor(ContractWrapper):
755
754
 
756
755
  The hash is bound to a chain. `chain_id` defaults to this wrapper's
757
756
  context; pass it explicitly to verify against a different chain, or to
758
- scan through a ctx-less `encoder()` instance.
757
+ scan through a wrapper built with `ctx=None`.
759
758
 
760
759
  Over a `get_logs` range this still answers a different question than
761
760
  `pending_proposals`: a proposal may have been confirmed since, which
@@ -1,3 +1,11 @@
1
+ """The fuse encoder base: `Fuse`, `StakeFuse` and the shared validators and
2
+ substrate packers.
3
+
4
+ It lives in ``core`` so encoders outside ``ipor_fusion.fuses`` can build on it
5
+ without importing that package, whose ``__init__`` loads every encoder.
6
+ ``ipor_fusion.fuses.base`` re-exports all of it.
7
+ """
8
+
1
9
  from abc import ABC
2
10
 
3
11
  from eth_abi import encode
@@ -8,11 +16,16 @@ from ipor_fusion.core.address import _validate_not_zero_address
8
16
  from ipor_fusion.core.contract import _parse_param_types
9
17
  from ipor_fusion.core.fuse_action import FuseAction
10
18
  from ipor_fusion.types import MAX_UINT256, Amount, FuseAddress
11
- from ipor_fusion.types import ZERO_ADDRESS as ZERO_ADDRESS
12
19
 
13
20
 
14
21
  class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
15
- """Abstract base class for all protocol fuse adapters."""
22
+ """Abstract base class for all protocol fuse adapters.
23
+
24
+ A fuse is a value: two fuses are equal (and hash alike) when they are of the
25
+ same class and carry the same constructor state (the fuse address, plus e.g.
26
+ a `StakeFuse`'s staking contract), so a subclass that stores more state is
27
+ compared on it without overriding anything. That state must be hashable.
28
+ """
16
29
 
17
30
  def __init__(self, address: FuseAddress):
18
31
  _validate_not_zero_address(address, "fuse address")
@@ -42,14 +55,10 @@ class Fuse(ABC): # noqa: B024 # ABC marks intent; no shared abstract method
42
55
  raise ValueError(f"{name} must not be negative, got {value}")
43
56
 
44
57
  def __eq__(self, other: object) -> bool:
45
- return (
46
- isinstance(other, Fuse)
47
- and type(self) is type(other)
48
- and self._address == other._address
49
- )
58
+ return type(self) is type(other) and vars(self) == vars(other)
50
59
 
51
60
  def __hash__(self) -> int:
52
- return hash((type(self), self._address))
61
+ return hash((type(self), *sorted(vars(self).items())))
53
62
 
54
63
  def _action_raw(self, signature: str, values: list) -> FuseAction:
55
64
  selector = function_signature_to_4byte_selector(signature)
@@ -43,7 +43,7 @@ class Multicall3(ContractWrapper):
43
43
 
44
44
  def __init__(
45
45
  self,
46
- ctx: Web3Context,
46
+ ctx: Web3Context | None,
47
47
  address: ChecksumAddress = MULTICALL3_ADDRESS,
48
48
  batch_size: int = DEFAULT_BATCH_SIZE,
49
49
  ):
@@ -66,20 +66,22 @@ class Multicall3(ContractWrapper):
66
66
 
67
67
  def aggregate(self, calls: Sequence[Call[T]]) -> list[T]:
68
68
  """Execute all `calls`; raise as `Call.call()` would on any failure."""
69
+ ctx = self._require_ctx()
69
70
  raw = self._fetch(calls)
70
71
  if raw is None:
71
- return [call.call(self._ctx) for call in calls]
72
+ return [call.call(ctx) for call in calls]
72
73
  return [
73
- call.decode(data) if ok and data else call.call(self._ctx)
74
+ call.decode(data) if ok and data else call.call(ctx)
74
75
  for call, (ok, data) in zip(calls, raw, strict=True)
75
76
  ]
76
77
 
77
78
  def try_aggregate(self, calls: Sequence[Call[T]]) -> list[T | None]:
78
79
  """Execute all `calls`; ``None`` for each that reverts, returns no data
79
80
  or returns data its `Call` cannot decode."""
81
+ ctx = self._require_ctx()
80
82
  raw = self._fetch(calls)
81
83
  if raw is None:
82
- return [_try_call(call, self._ctx) for call in calls]
84
+ return [_try_call(call, ctx) for call in calls]
83
85
  return [
84
86
  _try_decode(call, ok, data)
85
87
  for call, (ok, data) in zip(calls, raw, strict=True)
@@ -78,7 +78,7 @@ class PriceOracleMiddleware(ContractWrapper):
78
78
  Web3.keccak(text="AssetPriceSourceUpdated(address,address)")
79
79
  ).to_0x_hex()
80
80
  return list(
81
- self._ctx.get_logs(
81
+ self._require_ctx().get_logs(
82
82
  contract_address=self._address, topics=[event_signature_hash]
83
83
  )
84
84
  )
@@ -91,7 +91,7 @@ class LegacyPriceOracleMiddleware(PriceOracleMiddleware):
91
91
 
92
92
  def __init__(
93
93
  self,
94
- ctx: Web3Context,
94
+ ctx: Web3Context | None,
95
95
  address: ChecksumAddress,
96
96
  base_currency_decimals: Decimals,
97
97
  ):
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from collections.abc import Callable, Sequence
4
4
  from concurrent.futures import ThreadPoolExecutor
5
5
  from dataclasses import dataclass
6
- from typing import Any, Self, TypeVar
6
+ from typing import Any, TypeVar
7
7
 
8
8
  from eth_abi import decode, encode
9
9
  from eth_typing import ChecksumAddress
@@ -148,17 +148,13 @@ class PlasmaVault(ContractWrapper):
148
148
  service, multisig), grab the bytes directly: `vault.add_fuses(...).calldata`.
149
149
  """
150
150
 
151
- def __init__(self, ctx: Web3Context, address: PlasmaVaultAddress):
151
+ def __init__(self, ctx: Web3Context | None, address: PlasmaVaultAddress):
152
152
  super().__init__(ctx, address)
153
153
 
154
154
  @property
155
155
  def address(self) -> PlasmaVaultAddress:
156
156
  return PlasmaVaultAddress(self._address)
157
157
 
158
- @classmethod
159
- def encoder(cls, address: PlasmaVaultAddress | None = None) -> Self: # type: ignore[override]
160
- return super().encoder(address)
161
-
162
158
  def execute(self, actions: list[FuseAction]) -> Call[None]:
163
159
  data = FuseAction.encode_execute_payload(actions, "execute((address,bytes)[])")
164
160
  return Call(to=self._address, data=data, ctx=self._ctx)
@@ -573,7 +569,7 @@ class PlasmaVault(ContractWrapper):
573
569
  ]
574
570
 
575
571
  def _address_at(self, slot: int) -> ChecksumAddress:
576
- return _address_in_word(self._ctx.get_storage_at(self._address, slot))
572
+ return _address_in_word(self._require_ctx().get_storage_at(self._address, slot))
577
573
 
578
574
  def _is_own_withdraw_manager(self, candidate: ChecksumAddress) -> bool:
579
575
  call = Call(
@@ -650,7 +646,7 @@ class PlasmaVault(ContractWrapper):
650
646
  Web3.keccak(text="WithdrawManagerChanged(address)")
651
647
  ).to_0x_hex()
652
648
  return list(
653
- self._ctx.get_logs(
649
+ self._require_ctx().get_logs(
654
650
  contract_address=self._address, topics=[event_signature_hash]
655
651
  )
656
652
  )
@@ -658,7 +654,7 @@ class PlasmaVault(ContractWrapper):
658
654
  def _get_balance_fuse_events(self) -> list[LogReceipt]:
659
655
  """BalanceFuseAdded and BalanceFuseRemoved logs, from one eth_getLogs."""
660
656
  return list(
661
- self._ctx.get_logs(
657
+ self._require_ctx().get_logs(
662
658
  contract_address=self._address,
663
659
  topics=[
664
660
  [
@@ -120,7 +120,7 @@ class WithdrawManager(ContractWrapper):
120
120
 
121
121
  Replays `WithdrawRequestUpdated` from ``from_block``, or from the
122
122
  WithdrawManager's creation when None."""
123
- current_timestamp = self._ctx.get_block()["timestamp"]
123
+ current_timestamp = self._require_ctx().get_block()["timestamp"]
124
124
  events = self._get_withdraw_request_updated_events(from_block=from_block)
125
125
 
126
126
  accounts: list[str] = []
@@ -137,7 +137,7 @@ class WithdrawManager(ContractWrapper):
137
137
 
138
138
  checksummed = [Web3.to_checksum_address(account) for account in accounts]
139
139
  calls = [self.request_info(account) for account in checksummed]
140
- infos = Multicall3(self._ctx).try_aggregate(calls)
140
+ infos = Multicall3(self._require_ctx()).try_aggregate(calls)
141
141
  results: list[AccountRequest] = []
142
142
  for account, call, req in zip(checksummed, calls, infos, strict=True):
143
143
  if req is None:
@@ -164,7 +164,7 @@ class WithdrawManager(ContractWrapper):
164
164
  self,
165
165
  from_block: BlockNumber | None = None,
166
166
  ) -> PendingRequestsInfo:
167
- current_timestamp = self._ctx.get_block()["timestamp"]
167
+ current_timestamp = self._require_ctx().get_block()["timestamp"]
168
168
  requests = self.get_pending_requests(from_block=from_block)
169
169
  total = sum(r.shares for r in requests)
170
170
  return PendingRequestsInfo(
@@ -180,7 +180,7 @@ class WithdrawManager(ContractWrapper):
180
180
  Web3.keccak(text="WithdrawRequestUpdated(address,uint256,uint32)")
181
181
  ).to_0x_hex()
182
182
  return list(
183
- self._ctx.get_logs(
183
+ self._require_ctx().get_logs(
184
184
  contract_address=self._address,
185
185
  topics=[event_signature_hash],
186
186
  from_block=from_block,
@@ -0,0 +1,17 @@
1
+ """Public path of the fuse encoder base, defined in `ipor_fusion.core.fuse`."""
2
+
3
+ from ipor_fusion.core.address import (
4
+ _validate_not_zero_address as _validate_not_zero_address,
5
+ )
6
+ from ipor_fusion.core.fuse import Fuse as Fuse
7
+ from ipor_fusion.core.fuse import StakeFuse as StakeFuse
8
+ from ipor_fusion.core.fuse import (
9
+ _encode_address_substrate as _encode_address_substrate,
10
+ )
11
+ from ipor_fusion.core.fuse import (
12
+ _encode_uint248_substrate as _encode_uint248_substrate,
13
+ )
14
+ from ipor_fusion.core.fuse import _substrate_address_bytes as _substrate_address_bytes
15
+ from ipor_fusion.core.fuse import _validate_selector as _validate_selector
16
+ from ipor_fusion.core.fuse_action import FuseAction as FuseAction
17
+ from ipor_fusion.types import ZERO_ADDRESS as ZERO_ADDRESS
@@ -31,13 +31,10 @@ from eth_typing import ChecksumAddress
31
31
  from eth_utils import keccak
32
32
  from web3 import Web3
33
33
 
34
+ from ipor_fusion.core.address import _validate_not_zero_address
35
+ from ipor_fusion.core.fuse import Fuse, _substrate_address_bytes
36
+ from ipor_fusion.core.fuse_action import FuseAction
34
37
  from ipor_fusion.crosschain.messages import Command
35
- from ipor_fusion.fuses.base import (
36
- Fuse,
37
- FuseAction,
38
- _substrate_address_bytes,
39
- _validate_not_zero_address,
40
- )
41
38
  from ipor_fusion.types import Amount, ChainId, MarketId
42
39
 
43
40
  _NO_CHAIN = ChainId(0)
@@ -22,15 +22,16 @@ from enum import IntEnum
22
22
 
23
23
  from eth_typing import ChecksumAddress
24
24
 
25
+ from ipor_fusion.core.fuse import Fuse
26
+ from ipor_fusion.core.fuse_action import FuseAction
25
27
  from ipor_fusion.crosschain.ccip.contracts import CcipRouteConfig
26
28
  from ipor_fusion.crosschain.messages import COMMAND_TUPLE_TYPE, EMPTY_COMMAND, Command
27
- from ipor_fusion.fuses.base import ZERO_ADDRESS, Fuse, FuseAction
28
29
  from ipor_fusion.fuses.crosschain.base import (
29
30
  CrosschainCommandFuse,
30
31
  CrosschainSupplyFuse,
31
32
  SendParams,
32
33
  )
33
- from ipor_fusion.types import Amount, ChainId
34
+ from ipor_fusion.types import ZERO_ADDRESS, Amount, ChainId
34
35
 
35
36
 
36
37
  @dataclass(frozen=True, slots=True)
@@ -20,6 +20,8 @@ from enum import IntEnum
20
20
 
21
21
  from eth_typing import ChecksumAddress
22
22
 
23
+ from ipor_fusion.core.fuse import Fuse
24
+ from ipor_fusion.core.fuse_action import FuseAction
23
25
  from ipor_fusion.crosschain.messages import (
24
26
  COMMAND_TUPLE_TYPE,
25
27
  EMPTY_COMMAND,
@@ -27,7 +29,6 @@ from ipor_fusion.crosschain.messages import (
27
29
  Command,
28
30
  )
29
31
  from ipor_fusion.crosschain.stargate.layerzero import OptionsBuilder
30
- from ipor_fusion.fuses.base import Fuse, FuseAction
31
32
  from ipor_fusion.fuses.crosschain.base import (
32
33
  CrosschainCommandFuse,
33
34
  CrosschainSupplyFuse,
@@ -64,16 +64,6 @@ class UniversalTokenSwapperFuse(Fuse):
64
64
  super().__init__(address)
65
65
  self._abi = abi
66
66
 
67
- def __eq__(self, other: object) -> bool:
68
- return (
69
- isinstance(other, UniversalTokenSwapperFuse)
70
- and super().__eq__(other)
71
- and self._abi is other._abi
72
- )
73
-
74
- def __hash__(self) -> int:
75
- return hash((type(self), self._address, self._abi))
76
-
77
67
  def swap(
78
68
  self,
79
69
  *,