ipor-fusion 4.0.0.dev93__tar.gz → 4.0.0.dev106__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 (299) hide show
  1. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/AGENTS.md +49 -26
  2. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/MIGRATION_4.md +113 -28
  3. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/PKG-INFO +18 -15
  4. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/README.md +17 -14
  5. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/CONTRIBUTING.md +1 -1
  6. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/README.md +1 -1
  7. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/advanced_euler_v2_credit_market_base.py +17 -17
  8. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/advanced_euler_v2_looping_arbitrum.py +12 -15
  9. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +23 -25
  10. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +4 -4
  11. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/external_state_margin_leg_base.py +21 -21
  12. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/examples/simple_aave_v3_supply_base.py +13 -13
  13. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/pyproject.toml +4 -2
  14. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/scripts/crosschain_readiness.py +22 -21
  15. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/skills/ipor-deploy-vault/SKILL.md +10 -10
  16. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/__init__.py +37 -37
  17. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/market_cmd.py +3 -3
  18. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/vault_cmd.py +6 -6
  19. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/access.py +26 -24
  20. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/contract.py +37 -64
  21. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/erc20.py +1 -1
  22. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/external_state_executor.py +30 -53
  23. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/multicall.py +37 -22
  24. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/oracle.py +16 -13
  25. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/plasma_vault.py +35 -30
  26. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/rewards_manager.py +1 -1
  27. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/simulation.py +1 -1
  28. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/withdraw_manager.py +16 -11
  29. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/__init__.py +89 -10
  30. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/__init__.py +4 -3
  31. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/chainlink.py +10 -12
  32. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/contracts.py +3 -3
  33. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/lane.py +26 -18
  34. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/transport.py +4 -9
  35. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/contracts.py +1 -1
  36. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/discovery.py +21 -22
  37. {ipor_fusion-4.0.0.dev93/src/ipor_fusion/fuses/crosschain → ipor_fusion-4.0.0.dev106/src/ipor_fusion/crosschain/fuses}/__init__.py +5 -4
  38. {ipor_fusion-4.0.0.dev93/src/ipor_fusion/fuses/crosschain → ipor_fusion-4.0.0.dev106/src/ipor_fusion/crosschain/fuses}/ccip.py +2 -2
  39. {ipor_fusion-4.0.0.dev93/src/ipor_fusion/fuses/crosschain → ipor_fusion-4.0.0.dev106/src/ipor_fusion/crosschain/fuses}/stargate.py +5 -5
  40. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/lane.py +29 -14
  41. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/stargate/__init__.py +5 -4
  42. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/stargate/contracts.py +2 -2
  43. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/stargate/lane.py +13 -12
  44. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/stargate/transport.py +4 -9
  45. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/transport.py +2 -7
  46. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/errors.py +1 -1
  47. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/__init__.py +0 -34
  48. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/guide/architecture.md +4 -4
  49. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/guide/invariants.md +3 -3
  50. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/guide/quickstart.md +7 -7
  51. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/market_ids.py +1 -1
  52. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/mcp/server.py +6 -6
  53. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/aave_v3.py +10 -11
  54. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/hypercore.py +6 -10
  55. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/hypercore_outcome.py +5 -5
  56. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/lending_health.py +9 -7
  57. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/morpho.py +21 -18
  58. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/oracle_mapping.py +4 -4
  59. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/oracle_mapping_reader.py +24 -25
  60. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/unpriceable.py +4 -6
  61. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/vault_data.py +16 -17
  62. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/vault_erc20.py +8 -4
  63. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/vault_managers.py +6 -5
  64. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/vault_positions.py +8 -10
  65. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/vault_reads.py +5 -5
  66. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_crosschain.py +1 -1
  67. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_crosschain_lifecycle.py +10 -10
  68. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_crosschain_recovery.py +1 -1
  69. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_euler_v2.py +14 -18
  70. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_multicall.py +2 -2
  71. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_access_manager.py +24 -23
  72. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_commands.py +20 -20
  73. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_helpers.py +11 -12
  74. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_lending_health.py +7 -7
  75. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_pending_requests_scan.py +3 -3
  76. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_role_accounts.py +9 -9
  77. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_vault_info_golden.py +4 -4
  78. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_vault_predates_getters.py +2 -2
  79. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_contract_call.py +15 -21
  80. ipor_fusion-4.0.0.dev106/tests/test_contract_encoder.py +158 -0
  81. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_ccip_events.py +2 -2
  82. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_ccip_indexer.py +1 -1
  83. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_chainlink.py +15 -13
  84. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_contracts.py +28 -27
  85. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_encoding.py +5 -5
  86. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_lane.py +126 -21
  87. ipor_fusion-4.0.0.dev106/tests/test_crosschain_layering.py +212 -0
  88. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_readiness.py +11 -13
  89. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_transport.py +4 -8
  90. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_erc20.py +9 -9
  91. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_examples_vaults.py +2 -2
  92. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_external_state_executor.py +191 -187
  93. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_fee_manager.py +12 -12
  94. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_hypercore_deployment.py +5 -5
  95. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_hypercore_flow.py +3 -3
  96. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_hypercore_live.py +17 -15
  97. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_hypercore_readers.py +7 -9
  98. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_hypercore_simulation.py +2 -2
  99. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_mcp_server.py +12 -10
  100. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_multicall.py +48 -3
  101. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_oracle.py +65 -23
  102. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_oracle_mapping.py +3 -3
  103. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_plasma_vault.py +62 -60
  104. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_public_api.py +58 -36
  105. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_reader_encoding.py +22 -22
  106. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_rewards_manager.py +12 -12
  107. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_borrow_aave_v3_mainnet.py +11 -15
  108. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_claim_merkl_wrapper_base.py +8 -12
  109. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_claim_morpho_rewards.py +3 -5
  110. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_crosschain_hypercore.py +4 -6
  111. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_crosschain_hyperevm.py +63 -62
  112. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_euler_v2_batch_base.py +5 -5
  113. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_euler_v2_credit_market_base.py +4 -8
  114. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_euler_v2_swap_deploy_base.py +6 -6
  115. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +4 -4
  116. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_euler_v2_swap_registry_base.py +5 -5
  117. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_external_state_executor_base.py +17 -7
  118. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_fusion_factory_clone_base.py +12 -16
  119. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_looping_aave_v3_base.py +12 -14
  120. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_looping_morpho_blue_base.py +9 -11
  121. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_morpho_blue.py +5 -5
  122. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_pilot_v3.py +34 -42
  123. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_pilot_v4_uniswap.py +26 -38
  124. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_pilot_v5_ramses.py +18 -26
  125. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_pilot_v5_scheduled.py +30 -42
  126. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_plasma_vault_setters_base.py +4 -5
  127. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_readers_integration.py +26 -24
  128. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_release_funds.py +11 -15
  129. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_swap_on_base.py +9 -11
  130. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_vault_from_scratch_base.py +5 -5
  131. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_solidity_mirrors.py +1 -1
  132. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_unpriceable_tokens.py +1 -1
  133. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +3 -3
  134. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_vault_simulator_blocks.py +1 -1
  135. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_web3_context.py +11 -11
  136. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_withdraw_manager.py +25 -27
  137. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/typed_client/consumer.py +69 -28
  138. ipor_fusion-4.0.0.dev106/tests/typed_client/rejected.py +51 -0
  139. ipor_fusion-4.0.0.dev93/tests/test_contract_encoder.py +0 -118
  140. ipor_fusion-4.0.0.dev93/tests/test_crosschain_layering.py +0 -117
  141. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.claude-plugin/marketplace.json +0 -0
  142. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.claude-plugin/plugin.json +0 -0
  143. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.env.example +0 -0
  144. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.gemini/settings.json +0 -0
  145. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.git-blame-ignore-revs +0 -0
  146. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/cd.yml +0 -0
  147. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/ci.yml +0 -0
  148. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/commit-lint.yml +0 -0
  149. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/notify-slack.yml +0 -0
  150. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/python-build.yml +0 -0
  151. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/python-publish.yml +0 -0
  152. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/release.yml +0 -0
  153. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.github/workflows/report-slack.yml +0 -0
  154. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.gitignore +0 -0
  155. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.mcp.json +0 -0
  156. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.pre-commit-config.yaml +0 -0
  157. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/.python-version +0 -0
  158. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/CHANGELOG.md +0 -0
  159. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/CLAUDE.md +0 -0
  160. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/CODEOWNERS +0 -0
  161. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/LICENSE +0 -0
  162. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/build.sh +0 -0
  163. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/pytest.ini +0 -0
  164. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/scripts/commit-check.sh +0 -0
  165. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/about.py +0 -0
  166. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cache.py +0 -0
  167. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/chains.py +0 -0
  168. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/__init__.py +0 -0
  169. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  170. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/config_cmd.py +0 -0
  171. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/config_store.py +0 -0
  172. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/main.py +0 -0
  173. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/options.py +0 -0
  174. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  175. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/vault_health.py +0 -0
  176. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  177. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  178. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  179. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/config/__init__.py +0 -0
  180. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/config/roles.py +0 -0
  181. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/__init__.py +0 -0
  182. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/address.py +0 -0
  183. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/context.py +0 -0
  184. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/fee_manager.py +0 -0
  185. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/fuse.py +0 -0
  186. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/fuse_action.py +0 -0
  187. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/fusion_factory.py +0 -0
  188. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/core/logs.py +0 -0
  189. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  190. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  191. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  192. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  193. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/errors.py +0 -0
  194. {ipor_fusion-4.0.0.dev93/src/ipor_fusion/fuses/crosschain → ipor_fusion-4.0.0.dev106/src/ipor_fusion/crosschain/fuses}/base.py +0 -0
  195. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/logs.py +0 -0
  196. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/messages.py +0 -0
  197. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/simulation.py +0 -0
  198. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  199. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/dependency_graph.py +0 -0
  200. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/entry_cli.py +0 -0
  201. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/entry_mcp.py +0 -0
  202. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/explorer.py +0 -0
  203. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/field_docs.py +0 -0
  204. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  205. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/async_action.py +0 -0
  206. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/base.py +0 -0
  207. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  208. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/erc4626.py +0 -0
  209. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  210. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/events.py +0 -0
  211. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/external_state.py +0 -0
  212. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  213. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  214. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  215. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/hypercore.py +0 -0
  216. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  217. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/merkl.py +0 -0
  218. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/morpho.py +0 -0
  219. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  220. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  221. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/fuses/universal.py +0 -0
  222. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/guide/__init__.py +0 -0
  223. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/guide/glossary.md +0 -0
  224. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/market_info.py +0 -0
  225. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/mcp/__init__.py +0 -0
  226. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/mcp/guide.py +0 -0
  227. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/mcp/models.py +0 -0
  228. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/morpho_api.py +0 -0
  229. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/py.typed +0 -0
  230. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/__init__.py +0 -0
  231. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/aave_v4.py +0 -0
  232. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/compound_v3.py +0 -0
  233. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  234. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/position_manager.py +0 -0
  235. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  236. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  237. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/substrates.py +0 -0
  238. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/types.py +0 -0
  239. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/validation.py +0 -0
  240. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/src/ipor_fusion/vault_info.py +0 -0
  241. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/UniswapV3UniversalRouter.py +0 -0
  242. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_foundry.py +0 -0
  243. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_guide.py +0 -0
  244. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_hypercore_model.py +0 -0
  245. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_hypercore_shadow.py +0 -0
  246. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_simulate.py +0 -0
  247. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/_validation.py +0 -0
  248. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/addresses.py +0 -0
  249. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/conftest.py +0 -0
  250. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/constants.py +0 -0
  251. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  252. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/fixtures/hypercore_market55.json +0 -0
  253. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/fixtures/vault_info_golden/clean.json +0 -0
  254. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  255. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/fixtures/vault_info_golden/rich.json +0 -0
  256. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  257. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_about.py +0 -0
  258. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cache.py +0 -0
  259. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_chains.py +0 -0
  260. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_changelog.py +0 -0
  261. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_config_store.py +0 -0
  262. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_hypercore.py +0 -0
  263. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_market_morpho_blue.py +0 -0
  264. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_oracle_mapping.py +0 -0
  265. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_cli_vault_validate.py +0 -0
  266. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_ccip_quote.py +0 -0
  267. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_errors.py +0 -0
  268. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_crosschain_readiness_report.py +0 -0
  269. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_error_hierarchy.py +0 -0
  270. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_examples_composed_hypercore.py +0 -0
  271. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_examples_crosschain.py +0 -0
  272. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  273. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_explorer.py +0 -0
  274. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_external_state_events.py +0 -0
  275. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_fuse_encoding.py +0 -0
  276. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_fusion_instance.py +0 -0
  277. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_guide.py +0 -0
  278. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_hypercore_events.py +0 -0
  279. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_hypercore_outcome.py +0 -0
  280. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_logs.py +0 -0
  281. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_mcp_hypercore.py +0 -0
  282. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_mcp_models.py +0 -0
  283. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_mcp_vault_validate.py +0 -0
  284. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_readiness_selection.py +0 -0
  285. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_revert_decoding.py +0 -0
  286. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_roles.py +0 -0
  287. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_sdk_layering.py +0 -0
  288. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_crosschain_canary.py +0 -0
  289. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  290. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_examples_arbitrum.py +0 -0
  291. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_examples_base.py +0 -0
  292. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_simulate_examples_crosschain.py +0 -0
  293. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_solidity_compiler.py +0 -0
  294. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_substrates.py +0 -0
  295. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_types_and_events.py +0 -0
  296. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_validation.py +0 -0
  297. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_vault_dep_graph.py +0 -0
  298. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/tests/test_vault_info_onchain_base.py +0 -0
  299. {ipor_fusion-4.0.0.dev93 → ipor_fusion-4.0.0.dev106}/uv.lock +0 -0
@@ -155,8 +155,8 @@ the lifecycle matrix follows that.
155
155
  cases and non-obvious logic.
156
156
  - The public API is `ipor_fusion.__all__` (README, "Public API and stability").
157
157
  Every public symbol is imported and listed there; a symbol of a public
158
- subpackage (`core`, `config`, `fuses`, `fuses.crosschain`, `readers`,
159
- `crosschain`, `crosschain.ccip`, `crosschain.stargate`) also goes in that
158
+ subpackage (`core`, `config`, `fuses`, `readers`, `crosschain`,
159
+ `crosschain.fuses`, `crosschain.ccip`, `crosschain.stargate`) also goes in that
160
160
  subpackage's `__all__`. `ipor_fusion.guide` keeps its own namespace. The
161
161
  public modules (`about`, `chains`, `dependency_graph`, `errors`, `explorer`,
162
162
  `field_docs`, `market_ids`, `market_info`, `morpho_api`, `substrates`, `types`,
@@ -216,15 +216,15 @@ the lifecycle matrix follows that.
216
216
  time and request budget, and raises `LogScanError` rather than return partial logs;
217
217
  `LogSource`: an optional index `Web3Context.get_logs` asks first for a topic0-only
218
218
  filter, falling back to the scan on `LogSourceUnavailable`;
219
- prefer a storage or view read over a scan, as `get_balance_fuses` and
220
- `withdraw_manager_address` do), `plasma_vault`, `access`, `withdraw_manager`, `rewards_manager`, `fee_manager`,
219
+ prefer a storage or view read over a scan, as `read_balance_fuses` and
220
+ `read_withdraw_manager_address` do), `plasma_vault`, `access`, `withdraw_manager`, `rewards_manager`, `fee_manager`,
221
221
  `simulation` (`VaultSimulator`, eth_simulateV1, `erc20_balance_slot` and ERC-20 balance
222
222
  overrides), `oracle`, `fusion_factory`,
223
223
  `external_state_executor` (NAV marks for market 50), `erc20`, `fuse_action`
224
224
  (`FuseAction`), `fuse` (the `Fuse` base, `StakeFuse`, the shared validators and
225
225
  substrate packers), `address` (`_validate_not_zero_address`). `core` imports neither
226
226
  `fuses` nor `crosschain` (`tests/test_sdk_layering.py`)
227
- - `fuses/` — per-protocol fuse encoders (aave_v3, async_action, compound_v3, crosschain/,
227
+ - `fuses/` — per-protocol fuse encoders (aave_v3, async_action, compound_v3,
228
228
  erc4626, euler_v2, external_state, fluid_instadapp, gearbox_v3, merkl, morpho, ramses_v2,
229
229
  uniswap_v3, universal, `events.py`); `base.py` re-exports the `core/fuse.py` base and validators,
230
230
  `FuseAction` and `ZERO_ADDRESS` (from `types`). A fuse is a value: equality and
@@ -251,15 +251,20 @@ the lifecycle matrix follows that.
251
251
  (`CcipOffRamp.execute` is the permissionless delivery) and `ccip/indexer` fetches a
252
252
  message's verifier result from Chainlink's public indexers and builds that
253
253
  `manual_execution` call.
254
- `fuses/crosschain/` mirrors it (`base`, `stargate`, `ccip`). Names mirror
255
- the Solidity contracts and libraries. Adding a transport: a new subpackage plus one
256
- `LANES` entry. Layering: the fuse encoders import the wire codecs and wrappers; lanes,
257
- discovery, transports and the simulator import the encoders. A package `__init__`
258
- runs before its modules, so `ipor_fusion.crosschain`, `.stargate` and `.ccip`
259
- export only the fuse-free half; the rest is exported from `ipor_fusion` and
260
- imported by module path. `tests/test_crosschain_layering.py` pins this (no
261
- function-level or `TYPE_CHECKING` imports in either package); never work around
262
- a cycle with a lazy import, move the module instead.
254
+ The market fuse encoders live in `crosschain/fuses/` (`base`, `stargate`, `ccip`),
255
+ mirroring `contracts/fuses/crosschain/`; they build on `core/fuse.py`, so the
256
+ dependency direction is `core` <- `crosschain` and `fuses/` holds no crosschain code
257
+ (neither package imports the other). Names mirror the Solidity contracts and
258
+ libraries. Adding a transport: a new subpackage, its encoders in `crosschain/fuses/`
259
+ and one `LANES` entry. Layering: the encoders import the wire codecs and wrappers;
260
+ lanes, discovery, transports and the simulator import the encoders.
261
+ `ipor_fusion.crosschain` exports the whole surface (encoders, lanes, discovery,
262
+ transports, simulator). A package `__init__` runs before its modules and the
263
+ encoders import `ccip.contracts` / `stargate.layerzero`, so `.ccip` and `.stargate`
264
+ export only their codecs and wrappers. `tests/test_crosschain_layering.py` pins this:
265
+ the SDK's module-level import graph, with the implicit package-`__init__` edges, is
266
+ acyclic, and the crosschain package has no function-level or `TYPE_CHECKING`
267
+ imports; never work around a cycle with a lazy import, move the module instead.
263
268
  - `readers/` — read side: lending_health, oracle_mapping, position_manager, aave_v3,
264
269
  compound_v3, morpho, ramses_v2, uniswap_v3, unpriceable (middleware-priced tokens of
265
270
  granted substrates the oracle cannot price)
@@ -279,18 +284,36 @@ Two patterns explain most of the code; know both before writing SDK code
279
284
  or examples.
280
285
 
281
286
  - `Call[T]` (`core/contract.py`): every `ContractWrapper` method (one per
282
- Solidity function) returns a `Call` instead of executing. `.call()` runs
283
- `eth_call` and decodes to `T`; `.send()` signs and submits, returning a
284
- `TxReceipt`; `.calldata` hands the bytes to an external signer (a wrapper
285
- built with `ctx=None`, e.g. `PlasmaVault(None, address)`, builds calldata
286
- offline; there is no separate encoder factory);
287
- `.build_transaction()` / `Web3Context.estimate_gas` preview without signing.
288
- A `Call` without `output_types` (a write) raises on `.call()`. The same
289
- `Call` feeds `VaultSimulator.observe`, so reads, sends and simulations share
290
- one definition.
287
+ Solidity function) returns a `Call` instead of executing. Construction and
288
+ Call building are offline: a wrapper is its address (plus genuine contract
289
+ parameters such as a legacy oracle's decimals) and holds no `Web3Context`;
290
+ a `Call` holds `to`, `data`, output types and decoder, never a context.
291
+ Execution takes the context as a required argument: `.call(ctx)` runs
292
+ `eth_call` (at the context's, possibly pinned, block; `block=` overrides)
293
+ and decodes to `T`; `.send(ctx)` signs and submits, returning a `TxReceipt`;
294
+ `.build_transaction(ctx)` / `.estimate_gas(ctx)` preview without signing
295
+ (default `"latest"`). `.calldata`, `.decode(raw)` and `.map(f)` stay
296
+ offline. A `Call` without `output_types` (a write) raises on `.call(ctx)`.
297
+ The same `Call` feeds `VaultSimulator.observe`, so reads, sends and
298
+ simulations share one definition.
299
+ - A wrapper method that must execute to answer (storage, a log scan, a read
300
+ that depends on another read) is named `read_*` and takes `ctx` first:
301
+ `PlasmaVault.read_balance_fuses(ctx)`, `AccessManager.read_all_role_accounts(ctx)`,
302
+ `MorphoReader.read_rates(ctx, id)`. Decide by the I/O a method performs, not
303
+ by the class name. Resolvers that probe the chain to pick a wrapper
304
+ (`price_oracle_middleware(ctx, a)`, `resolve_access_manager`,
305
+ `ExternalStateExecutor.for_vault`) return context-free wrappers. Executors
306
+ and orchestrators own their contexts: `Multicall3(ctx)`, `VaultSimulator`,
307
+ `CrosschainSimulator`, `OracleMappingReader(ctx, oracle)` and
308
+ `CrosschainLane` (`hub_ctx` for the executor, `spoke_ctx` for the
309
+ dispatcher at the same address: run a lane's Calls on the matching one).
310
+ A chain-dependent decode takes the chain explicitly
311
+ (`ExternalStateExecutor.pending_proposals(account, chain_id=)`); a decoder
312
+ never consults the executing context.
291
313
  - Batch independent reads: `Multicall3(ctx).aggregate(calls)` (raises as
292
- `.call()` would) or `.try_aggregate(calls)` (``None`` per failed read) runs
293
- many `Call`s in one `eth_call` at one block. A loop of `.call()`s, or a
314
+ `.call(ctx)` would) or `.try_aggregate(calls)` (``None`` per failed read) runs
315
+ many `Call`s in one `eth_call` at one block; every batch, re-run and
316
+ no-Multicall fallback runs on its `ctx`. A loop of `.call(ctx)`s, or a
294
317
  thread pool of them, costs one round trip each; RPC latency, not decoding,
295
318
  is what makes the vault tooling slow.
296
319
  - Fuses (`fuses/`) are stateless encoders: a method returns an immutable
@@ -299,7 +322,7 @@ or examples.
299
322
  helpers raise before encoding. `PlasmaVault.execute([actions])` runs the
300
323
  batch atomically.
301
324
  - Pin reads, never mutate a shared context: `ctx.at_block(n)` is a copy whose
302
- reads (and `get_logs` upper bound) default to block ``n``; `Call.call(block=n)`
325
+ reads (and `get_logs` upper bound) default to block ``n``; `Call.call(ctx, block=n)`
303
326
  pins one read.
304
327
  - Simulate before sending: `VaultSimulator(web3, vault=, alpha=)` batches
305
328
  `execute` and `observe` calls into one `eth_simulateV1` round trip, across
@@ -80,7 +80,7 @@ chain is always the context's:
80
80
  market_substrates, aave_pools=None)` take the chain from `ctx`.
81
81
  - `Web3Context(web3, chain_id, *, ...)` and `Web3Context.from_url(url, *, ...)` take
82
82
  their options by keyword. New: `Web3Context.at_block(block)` (a pinned copy) and
83
- `Call.call(ctx=None, *, block=None)`. `Web3Context.get_logs(to_block=None)` ends at
83
+ `Call.call(ctx, *, block=None)` (see [Context-free wrappers](#context-free-wrappers)). `Web3Context.get_logs(to_block=None)` ends at
84
84
  the context's default block and `Web3Context.get_block(block=None)` returns it,
85
85
  both instead of ``"latest"``.
86
86
 
@@ -113,7 +113,7 @@ The Uniswap V3 / Ramses V2 position events' `version`, `sender` and `recipient`
113
113
  `quote_ccip_native_fee(block=)` and `OracleMappingReader.asset_source_events` take a
114
114
  `BlockIdentifier`.
115
115
 
116
- **Address roles.** `PlasmaVault(ctx, address)` (`ctx` may be `None`, see below),
116
+ **Address roles.** `PlasmaVault(address)`,
117
117
  `validate_vault`, `fetch_deployment_info`, the HyperCore, lending-health and
118
118
  oracle-mapping readers, `read_external_state`, `VaultSimulator(vault=)`,
119
119
  `CrosschainSimulator`, crosschain discovery, the hub-manager inputs
@@ -130,7 +130,7 @@ Every fuse constructor, `FuseAction.fuse`, `BalanceFuse.fuse`,
130
130
  `LaneFuses` and the HyperCore balance-fuse argument take a `FuseAddress`, and
131
131
  `get_fuses()`, `get_instant_withdrawal_fuses()`, `get_rewards_fuses()` and
132
132
  `VaultData.fuses` / `instant_fuses` / `fuse_markets` keys return it.
133
- `AccessManager(ctx, address)` takes an `AccessManagerAddress`;
133
+ `AccessManager(address)` takes an `AccessManagerAddress`;
134
134
  `get_access_manager_address()`, `VaultData.access_manager` and
135
135
  `FusionInstance.access_manager` return one. `resolve_access_manager` still takes any
136
136
  `ChecksumAddress`: it is what checks that the address is a vault.
@@ -167,7 +167,7 @@ gone: use `morpho_blue_address(chain_id)`.
167
167
  `decode_substrate`, `market_name`, `format_market_label` take `MarketId`.
168
168
 
169
169
  **Access.** `AccessManager.has_role`, `grant_role`, `revoke_role`,
170
- `get_accounts_with_role` take `RoleId | Roles`; `RoleAccount.role_id` is `RoleId`.
170
+ `read_accounts_with_role` take `RoleId | Roles`; `RoleAccount.role_id` is `RoleId`.
171
171
 
172
172
  **Morpho.** `MorphoBlueMarketId` now always carries `0x`; build it with
173
173
  `parse_morpho_market_id` instead of `MorphoBlueMarketId("<64 hex>")`. `fetch_market`,
@@ -214,41 +214,126 @@ are `ProposalHash`; `ExternalStateEvent.transaction_hash` is a `TxHash`.
214
214
  except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
215
215
  `MarketId`. Calling an MCP tool function directly from Python takes `ChainId`.
216
216
 
217
- **Offline wrappers.** `ContractWrapper.encoder()` (and the `PlasmaVault` /
218
- `AccessManager` overrides) is gone. Every wrapper constructor takes
219
- `ctx: Web3Context | None`, so build an offline wrapper with the normal
220
- constructor and an address: `PlasmaVault(None, vault)`, `ERC20(None, token)`,
221
- `FusionFactory(None, factory)`, `LegacyPriceOracleMiddleware(None, oracle,
222
- Decimals(8))`. The address is required (there is no zero-address placeholder) and
223
- subclass parameters stay required, so the wrapper is fully initialized:
224
- `.encoder()` skipped the constructor, and a legacy oracle built that way failed to
225
- decode `get_asset_price`. `.calldata` still needs no context; `.call()`, `.send()`,
226
- `.build_transaction()` and `.estimate_gas()` raise `ValueError` until one is passed
227
- to them, and a method that reads the chain itself (log scans such as
228
- `get_balance_fuses` or `get_pending_requests`, `withdraw_manager_address`,
229
- `Multicall3.aggregate` / `try_aggregate`, the Aave V3 and Morpho position reads)
230
- raises `ValueError("Web3Context required: <wrapper> at <address> ...")` instead of
231
- an `AttributeError` on `None`. `ExternalStateExecutor` still refuses the zero
232
- address for its hash and event readers.
233
-
234
- | 3.x | 4.0 |
235
- |---|---|
236
- | `PlasmaVault.encoder(vault)` | `PlasmaVault(None, vault)` |
237
- | `ERC20.encoder()` (no address) | `ERC20(None, token)`: pass the real address |
238
- | `Cls.__new__(Cls)` + `_ctx` / `_address` | `Cls(None, address)` |
239
-
240
217
  **Fuse base.** `Fuse`, `StakeFuse` and the shared validators and substrate packers
241
218
  live in `ipor_fusion.core.fuse`; `ipor_fusion.fuses.base`, `ipor_fusion.fuses` and
242
219
  `ipor_fusion` re-export them, so existing imports keep working. Fuse equality and
243
220
  hash now cover every attribute a fuse stores, not only its class and address (see
244
221
  [Behavior changes](#behavior-changes)).
245
222
 
223
+ **Crosschain fuse encoders moved.** The crosschain market encoders moved from
224
+ `ipor_fusion.fuses.crosschain` into the crosschain package, and
225
+ `ipor_fusion.fuses` no longer re-exports them. There is no shim: the old paths
226
+ raise `ImportError`. The top-level names (`from ipor_fusion import ...`) are
227
+ unchanged.
228
+
229
+ | 3.x import | 4.0 import |
230
+ |---|---|
231
+ | `ipor_fusion.fuses.crosschain` | `ipor_fusion.crosschain.fuses` (or `ipor_fusion.crosschain`) |
232
+ | `ipor_fusion.fuses.crosschain.base` | `ipor_fusion.crosschain.fuses.base` |
233
+ | `ipor_fusion.fuses.crosschain.ccip` | `ipor_fusion.crosschain.fuses.ccip` |
234
+ | `ipor_fusion.fuses.crosschain.stargate` | `ipor_fusion.crosschain.fuses.stargate` |
235
+ | `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 ...` |
236
+
237
+ `ipor_fusion.crosschain` now exports the whole crosschain surface: besides the
238
+ codecs and wrappers, the encoders, `CrosschainLane`, `CcipLane`, `StargateLane`,
239
+ the transports and chains, `discover_deployment`, `open_lane(s)`, `LANES` and
240
+ `CrosschainSimulator`, which 3.x exported only from `ipor_fusion`.
241
+ `ipor_fusion.crosschain.ccip` and `.stargate` keep exporting only their codecs
242
+ and wrappers.
243
+
246
244
  **Moved, still importable.** `FuseAction` lives in `ipor_fusion.core.fuse_action` and
247
245
  `ZERO_ADDRESS` in `ipor_fusion.types`; both are re-exported from
248
246
  `ipor_fusion.fuses.base` and `FuseAction` from `ipor_fusion`. The CLI's internal
249
247
  `ipor_fusion.cli.vault_cmd` was split into `options`, `vault_info_view`,
250
248
  `vault_markets_view` and `oracle_mapping_view` (private names, not API).
251
249
 
250
+ ## Context-free wrappers
251
+
252
+ A contract wrapper is its address (plus genuine contract parameters, such as a legacy
253
+ oracle's decimals) and nothing chain-bound. Construction and `Call` building are
254
+ offline; the `Web3Context` is a required argument where something executes. In 3.x
255
+ the context was stored on the wrapper and copied into every `Call`; that is gone,
256
+ together with `ContractWrapper.encoder()`, `Call.ctx` and the zero-address
257
+ placeholder wrapper.
258
+
259
+ - Build every wrapper from its address: `PlasmaVault(vault)`, `ERC20(token)`,
260
+ `FusionFactory(factory)`, `AccessManager(manager)`,
261
+ `LegacyPriceOracleMiddleware(oracle, Decimals(8))`, the crosschain executor,
262
+ dispatcher and factory wrappers, the Chainlink CCIP wrappers, the readers
263
+ (`MorphoReader(morpho)`, `AaveV3Reader(pool)`, ...). `HyperCoreReader()` takes no
264
+ argument. The nominal address types are unchanged.
265
+ - Execute a `Call` on a context: `.call(ctx, *, block=None)` (reads default to the
266
+ context's, possibly pinned, block), `.send(ctx)`, `.build_transaction(ctx, *,
267
+ block="latest")`, `.estimate_gas(ctx, *, block="latest")`. `ctx` may be passed by
268
+ keyword. Without it Python raises `TypeError` (and pyright reports the call),
269
+ instead of the 3.x `ValueError("Web3Context required ...")`. `.calldata`,
270
+ `.decode(raw)` and `.map(f)` need no context. `Call(...)` no longer takes `ctx=`.
271
+ - A wrapper method that executes to answer (storage, log scans, dependent reads) is
272
+ named `read_*` and takes the context first (table below).
273
+ - Executors and orchestrators keep their contexts: `Multicall3(ctx)` (every batch,
274
+ every re-run of a failed sub-call and the per-call fallback run on its `ctx`, so a
275
+ pinned context keeps them all at its block; it is no longer a `ContractWrapper`,
276
+ `aggregate3(calls)` still returns the raw `Call`), `VaultSimulator`,
277
+ `CrosschainSimulator` and `OracleMappingReader(ctx, oracle)`.
278
+ - Resolvers that probe the chain stay eager and return context-free wrappers:
279
+ `price_oracle_middleware(ctx, oracle)`, `resolve_access_manager(ctx, vault)`,
280
+ `ExternalStateExecutor.for_vault(ctx, vault)`. A resolved oracle carries the ABI
281
+ found on that chain and block; resolve again for another one.
282
+ - Crosschain lanes own both contexts: `CcipLane` / `StargateLane` take `hub_ctx=` and
283
+ `spoke_ctx=` instead of `spoke_chain_id=` (it is `spoke_ctx.chain_id`), and expose
284
+ them as `lane.hub_ctx` / `lane.spoke_ctx`. `needs_attestation`,
285
+ `command_in_flight` and `transfer_in_flight` read on `hub_ctx`. The Calls a lane
286
+ returns are unexecuted, and the executor and dispatcher share one address, so pick
287
+ the context yourself: `lane.get_balance().call(lane.hub_ctx)`,
288
+ `lane.observation().call(lane.spoke_ctx)`. `CrosschainTransport.executor(address)`
289
+ and `.dispatcher(address)` no longer take a context.
290
+ - Chain-bound decoding takes the chain explicitly:
291
+ `ExternalStateExecutor.pending_proposals(account, *, chain_id)`,
292
+ `parse_balance_proposed(log, *, chain_id)` and
293
+ `find_balance_proposed(logs, account=None, *, chain_id)` require it (3.x fell back
294
+ to the wrapper's context). The decoder captures it, so `.call(ctx)`, `Multicall3`
295
+ and `VaultSimulator.observe` stamp the same hash for the same bytes, whatever
296
+ context executes them. Pass `ctx.chain_id` for the chain you read from.
297
+ `pending_proposals` now refuses an executor at the zero address when the Call is
298
+ built, not when it is decoded. `mark_nav` takes the chain from its contexts.
299
+
300
+ | 3.x | 4.0 |
301
+ |---|---|
302
+ | `PlasmaVault(ctx, vault)`, `ERC20(ctx, token)`, any `Wrapper(ctx, address, ...)` | `PlasmaVault(vault)`, `ERC20(token)`, `Wrapper(address, ...)` |
303
+ | `PlasmaVault.encoder(vault)`, `ERC20.encoder()` | `PlasmaVault(vault)`, `ERC20(token)` (the real address) |
304
+ | `LegacyPriceOracleMiddleware(ctx, oracle, decimals)` | `LegacyPriceOracleMiddleware(oracle, decimals)` |
305
+ | `HyperCoreReader(ctx)` | `HyperCoreReader()` |
306
+ | `Call(to=, data=, ..., ctx=ctx)` | `Call(to=, data=, ...)` |
307
+ | `call.call()` / `call.call(block=n)` | `call.call(ctx)` / `call.call(ctx, block=n)` |
308
+ | `call.send()` | `call.send(ctx)` |
309
+ | `call.build_transaction(block=b)`, `call.estimate_gas(block=b)` | `call.build_transaction(ctx, block=b)`, `call.estimate_gas(ctx, block=b)` |
310
+ | `call.ctx` | gone: keep the context yourself |
311
+ | `PlasmaVault.get_balance_fuses()` | `PlasmaVault.read_balance_fuses(ctx)` |
312
+ | `PlasmaVault.withdraw_manager_address()` | `PlasmaVault.read_withdraw_manager_address(ctx)` |
313
+ | `PlasmaVault.price_oracle_address()` | `PlasmaVault.read_price_oracle_address(ctx)` |
314
+ | `AccessManager.owner()` / `owners()` / `atomists()` | `read_owner(ctx)` / `read_owners(ctx)` / `read_atomists(ctx)` |
315
+ | `AccessManager.get_accounts_with_role(role)` | `AccessManager.read_accounts_with_role(ctx, role)` |
316
+ | `AccessManager.get_all_role_accounts()` | `AccessManager.read_all_role_accounts(ctx)` |
317
+ | `PriceOracleMiddleware.get_assets_price_sources()` | `PriceOracleMiddleware.read_assets_price_sources(ctx)` |
318
+ | `WithdrawManager.get_pending_requests(from_block)` | `WithdrawManager.read_pending_requests(ctx, from_block)` |
319
+ | `WithdrawManager.get_pending_requests_info(from_block)` | `WithdrawManager.read_pending_requests_info(ctx, from_block)` |
320
+ | `MorphoReader.position_breakdown(id, user)` | `MorphoReader.read_position_breakdown(ctx, id, user)` |
321
+ | `MorphoReader.require_market(id)` | `MorphoReader.read_existing_market(ctx, id)` |
322
+ | `MorphoReader.rates(id)` | `MorphoReader.read_rates(ctx, id)` |
323
+ | `MorphoReader.rates_from(market, params)` | `MorphoReader.read_rates_from(ctx, market, params)` (it reads the IRM) |
324
+ | `AaveV3Reader.position_breakdown(asset, user)` | `AaveV3Reader.read_position_breakdown(ctx, asset, user)` |
325
+ | `AaveV3FuseReader.pool()` | `AaveV3FuseReader.read_pool(ctx)` |
326
+ | `executor.pending_proposals(account)` | `executor.pending_proposals(account, chain_id=ctx.chain_id)` |
327
+ | `executor.parse_balance_proposed(log)` / `find_balance_proposed(logs)` | `...(log, chain_id=...)` / `...(logs, chain_id=...)` |
328
+ | `CcipLane(executor=, dispatcher=, spoke_chain_id=, ...)` | `CcipLane(hub_ctx=, spoke_ctx=, executor=, dispatcher=, ...)` (same for `StargateLane`) |
329
+ | `transport.executor(ctx, address)` / `transport.dispatcher(ctx, address)` | `transport.executor(address)` / `transport.dispatcher(address)` |
330
+
331
+ A service that caches wrappers per chain can now share one wrapper across chains and
332
+ snapshots; what it must not share is a `Call` result decoded with a chain-bound
333
+ argument (`chain_id=`) for another chain. An external signer still needs the real
334
+ target address (`call.to`) and builds the chain-qualified transaction itself, or
335
+ gets it from `call.build_transaction(ctx)`.
336
+
252
337
  ## Behavior changes
253
338
 
254
339
  - Two fuses are equal (and hash alike) only when they have the same class and the
@@ -325,7 +410,7 @@ is the wrong one.
325
410
  executor of a transport. An executor reporting `transportKind()` `UNDEFINED` is now
326
411
  rejected by `detect_transport` instead of being returned. A transport kind passed in
327
412
  that has no lane is still a `ValueError`.
328
- - `Call.call()` and `Call.decode()` on a write-only `Call` raise `ValueError`, not
413
+ - `Call.call(ctx)` and `Call.decode()` on a write-only `Call` raise `ValueError`, not
329
414
  `RuntimeError`. `CrosschainSimulator.relay` raises `ValueError`, not
330
415
  `RuntimeError`, for a message to a chain that was not added.
331
416
  - `VaultSimulator.run` (provider error from `eth_simulateV1`, or a result count that
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev93
3
+ Version: 4.0.0.dev106
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
@@ -141,8 +141,8 @@ ctx = Web3Context.from_url(
141
141
  private_key="0x...",
142
142
  )
143
143
 
144
- # 2. Wrap the PlasmaVault contract
145
- vault = PlasmaVault(ctx, Web3.to_checksum_address("0xVAULT_ADDRESS"))
144
+ # 2. Wrap the PlasmaVault contract (an address only; contexts are passed at execution)
145
+ vault = PlasmaVault(Web3.to_checksum_address("0xVAULT_ADDRESS"))
146
146
 
147
147
  # 3. Build a fuse action (e.g. supply USDC to Aave V3)
148
148
  fuse = AaveV3SupplyFuse(Web3.to_checksum_address("0xFUSE_ADDRESS"))
@@ -151,25 +151,28 @@ action = fuse.supply(
151
151
  amount=1_000_000, # 1 USDC (6 decimals)
152
152
  )
153
153
 
154
- # 4. Execute on-chain (execute() returns a Call; .send() signs and submits it)
155
- receipt = vault.execute([action]).send()
154
+ # 4. Execute on-chain (execute() returns a Call; .send(ctx) signs and submits it)
155
+ receipt = vault.execute([action]).send(ctx)
156
156
  ```
157
157
 
158
158
  Fuse, factory and manager addresses per chain are published in
159
159
  [ipor-abi](https://github.com/IPOR-Labs/ipor-abi) (`mainnet/mainnet-<chain>-fusion/addresses.json`).
160
- A fuse must also be registered on the vault; `vault.get_fuses().call()` lists the registered ones.
160
+ A fuse must also be registered on the vault; `vault.get_fuses().call(ctx)` lists the registered ones.
161
161
 
162
162
  Amounts are raw on-chain integers (`Amount`, `Shares` in `ipor_fusion.types`); the SDK never scales by decimals.
163
163
 
164
164
  ### Read, send, or simulate
165
165
 
166
- Every wrapper method returns a `Call` instead of executing. The same `Call` powers all three modes:
166
+ Every wrapper method returns a `Call` instead of executing. Wrappers and `Call`s hold no
167
+ `Web3Context`: the context is a required argument wherever something runs, and methods that
168
+ must read the chain to answer are named `read_*` and take it first
169
+ (`vault.read_balance_fuses(ctx)`). The same `Call` powers all three modes:
167
170
 
168
171
  ```python
169
172
  from ipor_fusion import VaultSimulator
170
173
 
171
- total = vault.total_assets().call() # eth_call -> Amount
172
- receipt = vault.execute([action]).send() # signed tx -> TxReceipt
174
+ total = vault.total_assets().call(ctx) # eth_call -> Amount
175
+ receipt = vault.execute([action]).send(ctx) # signed tx -> TxReceipt
173
176
  payload = vault.execute([action]).calldata # raw bytes for an external signer
174
177
 
175
178
  # Simulate first via eth_simulateV1 (no local node); alpha is the account allowed to call execute()
@@ -312,7 +315,7 @@ Reverts on the deploy-and-configure path, keyed by selector so a failed transact
312
315
  | `0x9996b315` | `AddressEmptyCode(address)` | `execute()` touched a market with no balance fuse | `add_balance_fuse(market_id, balance_fuse)` before the first `execute` on that market |
313
316
  | `0x4bf4de4e` | `HandlerNotFound()` | A protocol called back into the vault during `execute()` (a flash loan) and no handler is registered for its address and selector | `update_callback_handler(handler, sender, selector)` before the first `execute` that triggers the callback |
314
317
  | `0x068ca9d8` | `AccessManagedUnauthorized(address)` | The caller lacks the role the function requires: `FUSE_MANAGER` for `add_fuses`, `grant_market_substrates`, `add_balance_fuse`, `update_callback_handler`; `ALPHA` for `execute`; `WHITELIST` for `deposit` and `mint` on a private vault | `AccessManager.grant_role(role, account, 0)` from the role's admin (`OWNER` grants `ATOMIST`, `ATOMIST` grants the rest); for a reverting `deposit`, whitelist the depositor or convert the vault to public |
315
- | `ValueError: Private key required for sending transactions` | SDK, before any transaction | `.send()` on a `Web3Context` without a key | `Web3Context(w3, chain_id, signer=..., private_key=...)` or `Web3Context.from_url(url, private_key=...)` |
318
+ | `ValueError: Private key required for sending transactions` | SDK, before any transaction | `.send(ctx)` on a `Web3Context` without a key | `Web3Context(w3, chain_id, signer=..., private_key=...)` or `Web3Context.from_url(url, private_key=...)` |
316
319
 
317
320
  `clone()` grants the owner only `Roles.OWNER_ROLE` (1). OWNER grants `Roles.ATOMIST_ROLE` (100), which administers `Roles.FUSE_MANAGER_ROLE` (300, configuration), `Roles.ALPHA_ROLE` (200, `execute`) and `Roles.WHITELIST_ROLE` (800, `deposit` on a private vault).
318
321
 
@@ -374,8 +377,8 @@ renaming a name in it, or changing a signature incompatibly, waits for a major
374
377
  release; adding one does not.
375
378
 
376
379
  - **Public subpackages** declare their own `__all__`, each a subset of the top
377
- level: `ipor_fusion.core`, `config`, `fuses`, `fuses.crosschain`, `readers`,
378
- `crosschain`, `crosschain.ccip` and `crosschain.stargate`. `ipor_fusion.guide`
380
+ level: `ipor_fusion.core`, `config`, `fuses`, `readers`, `crosschain`,
381
+ `crosschain.fuses`, `crosschain.ccip` and `crosschain.stargate`. `ipor_fusion.guide`
379
382
  (the packaged docs) keeps its names in its own namespace.
380
383
  - **Public modules** stay importable for the top-level names they define:
381
384
  `about`, `chains`, `dependency_graph`, `errors`, `explorer`, `field_docs`,
@@ -398,10 +401,10 @@ The SDK uses a **fuse adapter pattern**:
398
401
  - **Fuses** encode protocol-specific calls into `FuseAction` objects (pure calldata, no state)
399
402
  - **PlasmaVault** batches and executes `FuseAction` sequences on-chain via `execute()`; a batch is atomic
400
403
  - **Web3Context** manages provider connections, signing, and transaction dispatch
401
- - **Call** is the lazy result of every wrapper method: `.call()`, `.send()`, `.calldata`, or feed it to `VaultSimulator`
404
+ - **Call** is the lazy result of every wrapper method: `.call(ctx)`, `.send(ctx)`, `.calldata`, or feed it to `VaultSimulator`
402
405
 
403
406
  ```
404
- Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call --> .send() / simulate
407
+ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call --> .send(ctx) / simulate
405
408
  ```
406
409
 
407
410
  ### Core modules (`ipor_fusion.core`)
@@ -409,7 +412,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
409
412
  | Module | Purpose |
410
413
  |--------|---------|
411
414
  | `Web3Context` | Provider connection, signing, tx dispatch, gas estimation |
412
- | `Call` | Pre-encoded contract call; `.call()`, `.send()`, `.calldata`, `.build_transaction()` |
415
+ | `Call` | Pre-encoded contract call; `.call(ctx)`, `.send(ctx)`, `.calldata`, `.build_transaction(ctx)` |
413
416
  | `PlasmaVault` | ERC-4626 vault — execute, deposit, withdraw, fuse and market configuration |
414
417
  | `VaultSimulator` | Batch `execute` + reads through `eth_simulateV1`, multi-block, no local node |
415
418
  | `FusionFactory` | Deploy a new vault (`clone`, `clone_supervised`) |
@@ -93,8 +93,8 @@ ctx = Web3Context.from_url(
93
93
  private_key="0x...",
94
94
  )
95
95
 
96
- # 2. Wrap the PlasmaVault contract
97
- vault = PlasmaVault(ctx, Web3.to_checksum_address("0xVAULT_ADDRESS"))
96
+ # 2. Wrap the PlasmaVault contract (an address only; contexts are passed at execution)
97
+ vault = PlasmaVault(Web3.to_checksum_address("0xVAULT_ADDRESS"))
98
98
 
99
99
  # 3. Build a fuse action (e.g. supply USDC to Aave V3)
100
100
  fuse = AaveV3SupplyFuse(Web3.to_checksum_address("0xFUSE_ADDRESS"))
@@ -103,25 +103,28 @@ action = fuse.supply(
103
103
  amount=1_000_000, # 1 USDC (6 decimals)
104
104
  )
105
105
 
106
- # 4. Execute on-chain (execute() returns a Call; .send() signs and submits it)
107
- receipt = vault.execute([action]).send()
106
+ # 4. Execute on-chain (execute() returns a Call; .send(ctx) signs and submits it)
107
+ receipt = vault.execute([action]).send(ctx)
108
108
  ```
109
109
 
110
110
  Fuse, factory and manager addresses per chain are published in
111
111
  [ipor-abi](https://github.com/IPOR-Labs/ipor-abi) (`mainnet/mainnet-<chain>-fusion/addresses.json`).
112
- A fuse must also be registered on the vault; `vault.get_fuses().call()` lists the registered ones.
112
+ A fuse must also be registered on the vault; `vault.get_fuses().call(ctx)` lists the registered ones.
113
113
 
114
114
  Amounts are raw on-chain integers (`Amount`, `Shares` in `ipor_fusion.types`); the SDK never scales by decimals.
115
115
 
116
116
  ### Read, send, or simulate
117
117
 
118
- Every wrapper method returns a `Call` instead of executing. The same `Call` powers all three modes:
118
+ Every wrapper method returns a `Call` instead of executing. Wrappers and `Call`s hold no
119
+ `Web3Context`: the context is a required argument wherever something runs, and methods that
120
+ must read the chain to answer are named `read_*` and take it first
121
+ (`vault.read_balance_fuses(ctx)`). The same `Call` powers all three modes:
119
122
 
120
123
  ```python
121
124
  from ipor_fusion import VaultSimulator
122
125
 
123
- total = vault.total_assets().call() # eth_call -> Amount
124
- receipt = vault.execute([action]).send() # signed tx -> TxReceipt
126
+ total = vault.total_assets().call(ctx) # eth_call -> Amount
127
+ receipt = vault.execute([action]).send(ctx) # signed tx -> TxReceipt
125
128
  payload = vault.execute([action]).calldata # raw bytes for an external signer
126
129
 
127
130
  # Simulate first via eth_simulateV1 (no local node); alpha is the account allowed to call execute()
@@ -264,7 +267,7 @@ Reverts on the deploy-and-configure path, keyed by selector so a failed transact
264
267
  | `0x9996b315` | `AddressEmptyCode(address)` | `execute()` touched a market with no balance fuse | `add_balance_fuse(market_id, balance_fuse)` before the first `execute` on that market |
265
268
  | `0x4bf4de4e` | `HandlerNotFound()` | A protocol called back into the vault during `execute()` (a flash loan) and no handler is registered for its address and selector | `update_callback_handler(handler, sender, selector)` before the first `execute` that triggers the callback |
266
269
  | `0x068ca9d8` | `AccessManagedUnauthorized(address)` | The caller lacks the role the function requires: `FUSE_MANAGER` for `add_fuses`, `grant_market_substrates`, `add_balance_fuse`, `update_callback_handler`; `ALPHA` for `execute`; `WHITELIST` for `deposit` and `mint` on a private vault | `AccessManager.grant_role(role, account, 0)` from the role's admin (`OWNER` grants `ATOMIST`, `ATOMIST` grants the rest); for a reverting `deposit`, whitelist the depositor or convert the vault to public |
267
- | `ValueError: Private key required for sending transactions` | SDK, before any transaction | `.send()` on a `Web3Context` without a key | `Web3Context(w3, chain_id, signer=..., private_key=...)` or `Web3Context.from_url(url, private_key=...)` |
270
+ | `ValueError: Private key required for sending transactions` | SDK, before any transaction | `.send(ctx)` on a `Web3Context` without a key | `Web3Context(w3, chain_id, signer=..., private_key=...)` or `Web3Context.from_url(url, private_key=...)` |
268
271
 
269
272
  `clone()` grants the owner only `Roles.OWNER_ROLE` (1). OWNER grants `Roles.ATOMIST_ROLE` (100), which administers `Roles.FUSE_MANAGER_ROLE` (300, configuration), `Roles.ALPHA_ROLE` (200, `execute`) and `Roles.WHITELIST_ROLE` (800, `deposit` on a private vault).
270
273
 
@@ -326,8 +329,8 @@ renaming a name in it, or changing a signature incompatibly, waits for a major
326
329
  release; adding one does not.
327
330
 
328
331
  - **Public subpackages** declare their own `__all__`, each a subset of the top
329
- level: `ipor_fusion.core`, `config`, `fuses`, `fuses.crosschain`, `readers`,
330
- `crosschain`, `crosschain.ccip` and `crosschain.stargate`. `ipor_fusion.guide`
332
+ level: `ipor_fusion.core`, `config`, `fuses`, `readers`, `crosschain`,
333
+ `crosschain.fuses`, `crosschain.ccip` and `crosschain.stargate`. `ipor_fusion.guide`
331
334
  (the packaged docs) keeps its names in its own namespace.
332
335
  - **Public modules** stay importable for the top-level names they define:
333
336
  `about`, `chains`, `dependency_graph`, `errors`, `explorer`, `field_docs`,
@@ -350,10 +353,10 @@ The SDK uses a **fuse adapter pattern**:
350
353
  - **Fuses** encode protocol-specific calls into `FuseAction` objects (pure calldata, no state)
351
354
  - **PlasmaVault** batches and executes `FuseAction` sequences on-chain via `execute()`; a batch is atomic
352
355
  - **Web3Context** manages provider connections, signing, and transaction dispatch
353
- - **Call** is the lazy result of every wrapper method: `.call()`, `.send()`, `.calldata`, or feed it to `VaultSimulator`
356
+ - **Call** is the lazy result of every wrapper method: `.call(ctx)`, `.send(ctx)`, `.calldata`, or feed it to `VaultSimulator`
354
357
 
355
358
  ```
356
- Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call --> .send() / simulate
359
+ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call --> .send(ctx) / simulate
357
360
  ```
358
361
 
359
362
  ### Core modules (`ipor_fusion.core`)
@@ -361,7 +364,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
361
364
  | Module | Purpose |
362
365
  |--------|---------|
363
366
  | `Web3Context` | Provider connection, signing, tx dispatch, gas estimation |
364
- | `Call` | Pre-encoded contract call; `.call()`, `.send()`, `.calldata`, `.build_transaction()` |
367
+ | `Call` | Pre-encoded contract call; `.call(ctx)`, `.send(ctx)`, `.calldata`, `.build_transaction(ctx)` |
365
368
  | `PlasmaVault` | ERC-4626 vault — execute, deposit, withdraw, fuse and market configuration |
366
369
  | `VaultSimulator` | Batch `execute` + reads through `eth_simulateV1`, multi-block, no local node |
367
370
  | `FusionFactory` | Deploy a new vault (`clone`, `clone_supervised`) |
@@ -35,7 +35,7 @@ own examples.
35
35
  The default (and only) behavior is preview plus `eth_simulateV1` simulation. An example must not
36
36
  sign or broadcast a transaction, and must not require a private key to run.
37
37
 
38
- - Show the read-only path with `.call()`, the unsigned-payload path with `.calldata`, and simulate
38
+ - Show the read-only path with `.call(ctx)`, the unsigned-payload path with `.calldata`, and simulate
39
39
  the whole flow with `VaultSimulator`.
40
40
  - For "how to actually submit this on-chain", point the reader at their signer via `.calldata`, and
41
41
  at the deployment/operation repos -- do not add a runnable broadcast path.
@@ -104,7 +104,7 @@ default run also logs the unsigned `clone(...)` creation calldata.
104
104
 
105
105
  These examples only simulate; they never broadcast. To submit a flow on-chain, take
106
106
  the `.calldata` each builder produces and sign it with your own signer -- a multisig,
107
- hardware wallet, or key-management service. (`Call.send()` performs a direct send from a
107
+ hardware wallet, or key-management service. (`Call.send(ctx)` performs a direct send from a
108
108
  configured signer, but a production deployment should not sign with a hot key.) Always
109
109
  review production configuration independently before using it with real funds.
110
110
 
@@ -6,7 +6,7 @@ collateralized borrow. It shows, end to end and without reading the test suite,
6
6
  how to:
7
7
 
8
8
  1. connect a ``Web3Context`` to Base;
9
- 2. preview a vault deployment with ``FusionFactory.clone(...).call()`` and read
9
+ 2. preview a vault deployment with ``FusionFactory.clone(...).call(ctx)`` and read
10
10
  its state-dependent CREATE addresses for the atomic simulation, plus the
11
11
  unsigned creation ``.calldata``;
12
12
  3. bootstrap the vault's roles in the correct order;
@@ -222,7 +222,7 @@ def _euler_account(plasma_vault: ChecksumAddress) -> ChecksumAddress:
222
222
 
223
223
 
224
224
  def _evault_debt_of(
225
- ctx: Web3Context, euler_vault: ChecksumAddress, account: ChecksumAddress
225
+ euler_vault: ChecksumAddress, account: ChecksumAddress
226
226
  ) -> Call[int]:
227
227
  """Read an Euler eVault's outstanding debt for an account (underlying units).
228
228
 
@@ -234,7 +234,7 @@ def _evault_debt_of(
234
234
  data = function_signature_to_4byte_selector("debtOf(address)") + encode(
235
235
  ["address"], [account]
236
236
  )
237
- return Call(to=euler_vault, data=data, output_types=["uint256"], ctx=ctx)
237
+ return Call(to=euler_vault, data=data, output_types=["uint256"])
238
238
 
239
239
 
240
240
  # ── Pure builders (no chain access -- safe to import and unit-test offline) ───
@@ -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 on a wrapper with ctx=None, so it needs no provider. Hand these bytes to
265
- an external signer / multisig instead of calling ``.send()``. NOTE: the owner
264
+ Built from an address-only wrapper, so it needs no provider. Hand these bytes to
265
+ an external signer / multisig instead of calling ``.send(ctx)``. 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(None, BASE_FUSION_FACTORY).clone(**clone_args()).calldata
269
+ return FusionFactory(BASE_FUSION_FACTORY).clone(**clone_args()).calldata
270
270
 
271
271
 
272
272
  def market_substrates() -> list[bytes]:
@@ -369,10 +369,10 @@ def run_simulation(web3: Web3) -> SimulationResult:
369
369
  """Build, fund and run the full Euler V2 credit lifecycle in one batch."""
370
370
  ctx = Web3Context(web3=web3, chain_id=BASE_CHAIN_ID, signer=OWNER)
371
371
  ctx.default_block = PINNED_BLOCK
372
- factory = FusionFactory(ctx, BASE_FUSION_FACTORY)
372
+ factory = FusionFactory(BASE_FUSION_FACTORY)
373
373
 
374
374
  # 1. Preview the deterministic addresses (eth_call, no gas, no state change).
375
- preview = factory.clone(**clone_args()).call()
375
+ preview = factory.clone(**clone_args()).call(ctx)
376
376
  log.info(
377
377
  "predicted vault=%s access_manager=%s price_manager=%s (index=%d)",
378
378
  preview.plasma_vault,
@@ -383,19 +383,19 @@ def run_simulation(web3: Web3) -> SimulationResult:
383
383
  # 2. The unsigned creation calldata (external-signer path).
384
384
  log.info("unsigned clone calldata: 0x%s", unsigned_clone_calldata().hex())
385
385
 
386
- plasma_vault = PlasmaVault(ctx, preview.plasma_vault)
387
- access_manager = AccessManager(ctx, preview.access_manager)
388
- price_manager = PriceOracleMiddlewareManager(ctx, preview.price_manager)
389
- cbeth = ERC20(ctx, BASE_CBETH)
386
+ plasma_vault = PlasmaVault(preview.plasma_vault)
387
+ access_manager = AccessManager(preview.access_manager)
388
+ price_manager = PriceOracleMiddlewareManager(preview.price_manager)
389
+ cbeth = ERC20(BASE_CBETH)
390
390
  euler_account = _euler_account(preview.plasma_vault)
391
391
 
392
392
  # The whole flow runs as ONE eth_simulateV1 batch; nothing here is signed or
393
393
  # broadcast (see the module docstring). Each simulator primitive stands in for
394
394
  # a production action, read as "simulation -> production":
395
- # - sim.add_call(call=X, from_=OWNER) -> X.send() by the owner/atomist
395
+ # - sim.add_call(call=X, from_=OWNER) -> X.send(ctx) by the owner/atomist
396
396
  # (or X.calldata to a multisig / timelock).
397
- # - sim.execute([action]) -> plasma_vault.execute([action]).send() by the alpha.
398
- # - sim.observe("key", X) -> X.call(), a plain read.
397
+ # - sim.execute([action]) -> plasma_vault.execute([action]).send(ctx) by the alpha.
398
+ # - sim.observe("key", X) -> X.call(ctx), a plain read.
399
399
  sim = VaultSimulator(
400
400
  web3=web3, vault=preview.plasma_vault, alpha=ALPHA, block=PINNED_BLOCK
401
401
  )
@@ -499,7 +499,7 @@ def run_simulation(web3: Web3) -> SimulationResult:
499
499
 
500
500
  # Borrow WETH against the cbETH collateral; debt is now open.
501
501
  sim.execute([borrow_action(BORROW_AMOUNT)])
502
- sim.observe("debt_after_borrow", _evault_debt_of(ctx, EVAULT_WETH, euler_account))
502
+ sim.observe("debt_after_borrow", _evault_debt_of(EVAULT_WETH, euler_account))
503
503
  plan.append("borrow 1 WETH against the cbETH collateral")
504
504
 
505
505
  # Re-value the market while the position is open so the balance fuse's
@@ -512,7 +512,7 @@ def run_simulation(web3: Web3) -> SimulationResult:
512
512
  # advance in this run, interest is negligible and the borrowed WETH covers it.
513
513
  # (Advancing time would accrue interest, requiring a small extra WETH buffer.)
514
514
  sim.execute([repay_action(Amount(MAX_UINT256))])
515
- sim.observe("debt_after_repay", _evault_debt_of(ctx, EVAULT_WETH, euler_account))
515
+ sim.observe("debt_after_repay", _evault_debt_of(EVAULT_WETH, euler_account))
516
516
  plan.append("repay the full WETH debt")
517
517
 
518
518
  # Unwind in reverse of setup: disable the WETH controller, then the cbETH