ipor-fusion 4.0.0.dev49__tar.gz → 4.0.0.dev52__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 (287) hide show
  1. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/MIGRATION_4.md +24 -9
  2. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/__init__.py +4 -0
  5. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/vault_markets_view.py +6 -4
  6. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/context.py +3 -3
  7. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/dependency_graph.py +3 -1
  8. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/explorer.py +14 -4
  9. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/events.py +8 -1
  10. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/external_state_events.py +3 -3
  11. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/ramses_v2.py +3 -3
  12. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/uniswap_v3.py +4 -4
  13. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/hypercore.py +1 -1
  14. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/lending_health.py +1 -1
  15. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/substrates.py +19 -23
  16. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/types.py +25 -1
  17. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/validation.py +2 -2
  18. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/vault_data.py +16 -15
  19. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/vault_info.py +9 -4
  20. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_helpers.py +4 -4
  21. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_encoding.py +2 -2
  22. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_explorer.py +11 -2
  23. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_fuse_encoding.py +4 -4
  24. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_substrates.py +12 -12
  25. ipor_fusion-4.0.0.dev52/tests/test_types_and_events.py +122 -0
  26. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/typed_client/consumer.py +5 -0
  27. ipor_fusion-4.0.0.dev49/tests/test_types_and_events.py +0 -63
  28. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.claude-plugin/marketplace.json +0 -0
  29. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.claude-plugin/plugin.json +0 -0
  30. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.env.example +0 -0
  31. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.gemini/settings.json +0 -0
  32. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.git-blame-ignore-revs +0 -0
  33. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/cd.yml +0 -0
  34. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/ci.yml +0 -0
  35. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/commit-lint.yml +0 -0
  36. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/notify-slack.yml +0 -0
  37. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/python-build.yml +0 -0
  38. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/python-publish.yml +0 -0
  39. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/release.yml +0 -0
  40. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.github/workflows/report-slack.yml +0 -0
  41. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.gitignore +0 -0
  42. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.mcp.json +0 -0
  43. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.pre-commit-config.yaml +0 -0
  44. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/.python-version +0 -0
  45. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/AGENTS.md +0 -0
  46. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/CHANGELOG.md +0 -0
  47. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/CLAUDE.md +0 -0
  48. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/CODEOWNERS +0 -0
  49. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/LICENSE +0 -0
  50. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/README.md +0 -0
  51. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/build.sh +0 -0
  52. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/CONTRIBUTING.md +0 -0
  53. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/README.md +0 -0
  54. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  55. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  56. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  57. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  58. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/external_state_margin_leg_base.py +0 -0
  59. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/examples/simple_aave_v3_supply_base.py +0 -0
  60. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/pytest.ini +0 -0
  61. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/scripts/commit-check.sh +0 -0
  62. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/scripts/crosschain_readiness.py +0 -0
  63. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/skills/ipor-deploy-vault/SKILL.md +0 -0
  64. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/about.py +0 -0
  65. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cache.py +0 -0
  66. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/chains.py +0 -0
  67. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/__init__.py +0 -0
  68. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  69. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/config_cmd.py +0 -0
  70. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/config_store.py +0 -0
  71. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/main.py +0 -0
  72. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/market_cmd.py +0 -0
  73. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/options.py +0 -0
  74. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  75. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  76. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/vault_health.py +0 -0
  77. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  78. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  79. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/config/__init__.py +0 -0
  80. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/config/roles.py +0 -0
  81. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/__init__.py +0 -0
  82. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/access.py +0 -0
  83. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/address.py +0 -0
  84. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/contract.py +0 -0
  85. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/erc20.py +0 -0
  86. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/external_state_executor.py +0 -0
  87. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/fee_manager.py +0 -0
  88. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/fuse_action.py +0 -0
  89. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/fusion_factory.py +0 -0
  90. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/logs.py +0 -0
  91. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/multicall.py +0 -0
  92. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/oracle.py +0 -0
  93. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/plasma_vault.py +0 -0
  94. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/rewards_manager.py +0 -0
  95. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/simulation.py +0 -0
  96. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  97. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/__init__.py +0 -0
  98. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  99. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  100. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  101. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  102. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  103. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  104. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  105. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  106. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  107. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/contracts.py +0 -0
  108. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/discovery.py +0 -0
  109. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/errors.py +0 -0
  110. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/lane.py +0 -0
  111. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/logs.py +0 -0
  112. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/messages.py +0 -0
  113. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/simulation.py +0 -0
  114. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  115. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  116. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  117. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  118. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  119. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/crosschain/transport.py +0 -0
  120. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/entry_cli.py +0 -0
  121. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/entry_mcp.py +0 -0
  122. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/errors.py +0 -0
  123. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/field_docs.py +0 -0
  124. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/__init__.py +0 -0
  125. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  126. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/async_action.py +0 -0
  127. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/base.py +0 -0
  128. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  129. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  130. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  131. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  132. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  133. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/erc4626.py +0 -0
  134. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  135. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/external_state.py +0 -0
  136. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  137. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  138. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/hypercore.py +0 -0
  139. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  140. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/merkl.py +0 -0
  141. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/morpho.py +0 -0
  142. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/fuses/universal.py +0 -0
  143. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/guide/__init__.py +0 -0
  144. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/guide/architecture.md +0 -0
  145. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/guide/glossary.md +0 -0
  146. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/guide/invariants.md +0 -0
  147. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/guide/quickstart.md +0 -0
  148. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/market_ids.py +0 -0
  149. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/market_info.py +0 -0
  150. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/mcp/__init__.py +0 -0
  151. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/mcp/guide.py +0 -0
  152. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/mcp/models.py +0 -0
  153. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/mcp/server.py +0 -0
  154. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/morpho_api.py +0 -0
  155. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/py.typed +0 -0
  156. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/__init__.py +0 -0
  157. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/aave_v3.py +0 -0
  158. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/aave_v4.py +0 -0
  159. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/compound_v3.py +0 -0
  160. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  161. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/morpho.py +0 -0
  162. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  163. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/position_manager.py +0 -0
  164. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  165. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  166. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/src/ipor_fusion/readers/unpriceable.py +0 -0
  167. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/UniswapV3UniversalRouter.py +0 -0
  168. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_crosschain.py +0 -0
  169. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_crosschain_lifecycle.py +0 -0
  170. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_crosschain_recovery.py +0 -0
  171. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_euler_v2.py +0 -0
  172. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_foundry.py +0 -0
  173. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_guide.py +0 -0
  174. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_hypercore_model.py +0 -0
  175. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_hypercore_shadow.py +0 -0
  176. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_multicall.py +0 -0
  177. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_simulate.py +0 -0
  178. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/_validation.py +0 -0
  179. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/addresses.py +0 -0
  180. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/conftest.py +0 -0
  181. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/constants.py +0 -0
  182. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  183. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/fixtures/hypercore_market55.json +0 -0
  184. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/fixtures/vault_info_golden/clean.json +0 -0
  185. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  186. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/fixtures/vault_info_golden/rich.json +0 -0
  187. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  188. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_about.py +0 -0
  189. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_access_manager.py +0 -0
  190. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cache.py +0 -0
  191. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_chains.py +0 -0
  192. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_changelog.py +0 -0
  193. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_commands.py +0 -0
  194. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_config_store.py +0 -0
  195. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_hypercore.py +0 -0
  196. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_lending_health.py +0 -0
  197. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_market_morpho_blue.py +0 -0
  198. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_oracle_mapping.py +0 -0
  199. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_pending_requests_scan.py +0 -0
  200. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_role_accounts.py +0 -0
  201. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_vault_info_golden.py +0 -0
  202. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_vault_predates_getters.py +0 -0
  203. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_cli_vault_validate.py +0 -0
  204. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_contract_call.py +0 -0
  205. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_contract_encoder.py +0 -0
  206. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_ccip_events.py +0 -0
  207. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_ccip_indexer.py +0 -0
  208. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_ccip_quote.py +0 -0
  209. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_chainlink.py +0 -0
  210. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_contracts.py +0 -0
  211. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_errors.py +0 -0
  212. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_lane.py +0 -0
  213. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_layering.py +0 -0
  214. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_readiness.py +0 -0
  215. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_readiness_report.py +0 -0
  216. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_crosschain_transport.py +0 -0
  217. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_erc20.py +0 -0
  218. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_examples_composed_hypercore.py +0 -0
  219. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_examples_crosschain.py +0 -0
  220. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  221. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_examples_vaults.py +0 -0
  222. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_external_state_events.py +0 -0
  223. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_external_state_executor.py +0 -0
  224. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_fee_manager.py +0 -0
  225. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_guide.py +0 -0
  226. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_hypercore_deployment.py +0 -0
  227. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_hypercore_events.py +0 -0
  228. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_hypercore_flow.py +0 -0
  229. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_hypercore_live.py +0 -0
  230. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_hypercore_outcome.py +0 -0
  231. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_hypercore_readers.py +0 -0
  232. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_hypercore_simulation.py +0 -0
  233. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_logs.py +0 -0
  234. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_mcp_hypercore.py +0 -0
  235. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_mcp_models.py +0 -0
  236. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_mcp_server.py +0 -0
  237. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_mcp_vault_validate.py +0 -0
  238. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_multicall.py +0 -0
  239. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_oracle.py +0 -0
  240. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_oracle_mapping.py +0 -0
  241. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_plasma_vault.py +0 -0
  242. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_reader_encoding.py +0 -0
  243. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_readiness_selection.py +0 -0
  244. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_revert_decoding.py +0 -0
  245. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_rewards_manager.py +0 -0
  246. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_roles.py +0 -0
  247. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_sdk_layering.py +0 -0
  248. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  249. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  250. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  251. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_crosschain_canary.py +0 -0
  252. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_crosschain_hypercore.py +0 -0
  253. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  254. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  255. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  256. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  257. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  258. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  259. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  260. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_examples_arbitrum.py +0 -0
  261. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_examples_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_examples_crosschain.py +0 -0
  263. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_external_state_executor_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_morpho_blue.py +0 -0
  268. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_pilot_v3.py +0 -0
  269. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  270. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  271. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  272. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_readers_integration.py +0 -0
  274. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_release_funds.py +0 -0
  275. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_swap_on_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  277. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_solidity_compiler.py +0 -0
  278. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_solidity_mirrors.py +0 -0
  279. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_unpriceable_tokens.py +0 -0
  280. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_validation.py +0 -0
  281. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_vault_dep_graph.py +0 -0
  282. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_vault_info_onchain_base.py +0 -0
  283. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  284. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_vault_simulator_blocks.py +0 -0
  285. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_web3_context.py +0 -0
  286. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/tests/test_withdraw_manager.py +0 -0
  287. {ipor_fusion-4.0.0.dev49 → ipor_fusion-4.0.0.dev52}/uv.lock +0 -0
@@ -4,7 +4,7 @@
4
4
  vault/oracle/token addresses and transaction hashes a nominal type
5
5
  (`typing.NewType`) across the public API, and checks the SDK, the examples and a
6
6
  typed client with pyright's `reportArgumentType`. Other fields keep their types
7
- (e.g. `VaultData.access_manager` is still `str`).
7
+ (e.g. symbols, names and URLs stay `str`).
8
8
 
9
9
  The wrappers do not change values: `ChainId(1)` is `1` at runtime. The
10
10
  [behavior changes](#behavior-changes) below, however, apply to **every** caller,
@@ -15,7 +15,7 @@ type-checked or not. In particular, `fetch_market` and
15
15
  returns already carry their type and go back in unwrapped.
16
16
 
17
17
  All types are importable from `ipor_fusion` (and `ipor_fusion.types`);
18
- `ChecksumAddress`, `HexAddress` and `HexStr` come from `eth_typing`,
18
+ `ChecksumAddress` and `HexAddress` come from `eth_typing`,
19
19
  `BlockIdentifier` from `web3.types`, and `USDC_TOKEN_INDEX` / `hip3_action_asset` from
20
20
  `ipor_fusion.fuses.hypercore`. The wrappers label values; they do not validate byte
21
21
  widths or address spelling.
@@ -29,9 +29,9 @@ widths or address spelling.
29
29
  | `RoleId` | a `Roles` member (accepted as is), or `RoleId(n)`; `Roles.resolve(name)` returns one |
30
30
  | `Decimals`, `Period`, `Amount`, ... | `Decimals(8)`, `Period(0)`, `Amount(n)` (unchanged) |
31
31
  | `ChecksumAddress` | `Web3.to_checksum_address(value)` |
32
- | `HexAddress` | any `ChecksumAddress`, or `HexAddress(HexStr("0x..."))` for a lowercase address |
32
+ | `HexAddress` | any `ChecksumAddress`, or `parse_hex_address(value)` (20 bytes or hex in any spelling → lowercase `0x` form) |
33
33
  | `MorphoBlueMarketId` | `parse_morpho_market_id(value)`: 32 bytes or hex in any spelling, always `0x` + 64 lowercase digits |
34
- | `TxHash` | `TxHash(HexStr("0x..."))` |
34
+ | `TxHash` | `parse_tx_hash(value)` (32 bytes or hex in any spelling) |
35
35
  | crosschain ids | `CommandId(b)`, `MessageId(b)`, `AssetId(b)`, `OperationId(b)`, `TransferId(b)`, `ReturnRequestId(b)`, `PositionSetHash(b)` (bytes32); `ProposalId(n)`, `DeploymentRequestId(n)`, `StargateAssetId(n)` (int) |
36
36
  | HyperCore ids | `HyperCoreTokenIndex(n)` (or `USDC_TOKEN_INDEX`), `HyperCoreAsset(n)` (or `hip3_action_asset(dex, index)`), `HyperCoreOrderId(n)`, `HyperCoreClientOrderId(n)`, `MarginTableId(n)`, `HyperCoreActionId(n)`, `PayloadHash(b)` |
37
37
  | external state | `ProposalHash(b)` |
@@ -59,11 +59,18 @@ and the `dependency_graph` helpers (`compute_update_reach`, `compute_update_grou
59
59
  `find_orphan_fuse_markets`, `find_markets_missing_erc20_dependency`).
60
60
  A custom `VaultState` implementation also annotates `asset` as `ChecksumAddress` and
61
61
  its `unpriceable_priced_tokens` items' `market_id` as `MarketId`
62
- (`UnpriceableToken`). `VaultData.asset`, `VaultValidation.vault` / `asset` are
63
- `ChecksumAddress`,
62
+ (`UnpriceableToken`), and `price_oracle_addr` / `fuse_markets` keys as
63
+ `ChecksumAddress`. `VaultData.asset`, `access_manager`, `price_oracle_addr`,
64
+ `rewards_manager`, `withdraw_manager`, `fuses` and `instant_fuses` (and the
65
+ `fuse_markets` keys), `FeeData.fee_manager` / `ipor_dao_fee_recipient`, and
66
+ `VaultValidation.vault` / `asset` are `ChecksumAddress`; `VaultData.balance_fuses`
67
+ is `list[BalanceFuse]` and `token_prices_usd` is keyed by `HexAddress` (lowercase);
68
+ the Uniswap V3 / Ramses V2 position events' `version`, `sender` and `recipient` are
69
+ `ChecksumAddress`;
64
70
  `VaultValidation.chain_id` is `ChainId`; `LendingMarketHealth.market_id` and
65
- `MiddlewarePricedToken.market_id` are `MarketId`; `Erc20Holding.address` and
66
- `SubstrateInfo.address` are `HexAddress` (still `""` when a substrate has no address).
71
+ `MiddlewarePricedToken.market_id` are `MarketId`; `Erc20Holding.address` is
72
+ `HexAddress`, and `SubstrateInfo.address` is `HexAddress | None` (`None`, no longer
73
+ `""`, when a substrate carries no address).
67
74
  `LegacyPriceOracleMiddleware` takes `base_currency_decimals: Decimals`.
68
75
  `get_logs_adaptive(chain_id=)` and `LogSource.get_logs` take `ChainId`;
69
76
  `quote_ccip_native_fee(block=)` and `OracleMappingReader.asset_source_events` take a
@@ -112,7 +119,8 @@ takes a `ProposalHash`; `BalanceProposal.proposal_hash` and `NavMark.proposal_ha
112
119
  are `ProposalHash`; `ExternalStateEvent.transaction_hash` is a `TxHash`.
113
120
 
114
121
  **Errors and explorer.** `TransactionError(tx_hash=)` takes a `TxHash`,
115
- `LogScanError(address=)` a `ChecksumAddress`; `get_deployment_tx` returns a `TxHash`.
122
+ `LogScanError(address=)` a `ChecksumAddress`; `get_deployment_tx` returns a `TxHash`
123
+ (a malformed hash from Etherscan is reported as the error `malformed-tx-hash`).
116
124
 
117
125
  **MCP response models** (Python constructors only; the JSON schemas are unchanged
118
126
  except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
@@ -128,6 +136,13 @@ except below): `chain_id` fields are `ChainId`, `role_id` `RoleId`, `market_id`
128
136
 
129
137
  - The MCP `vault_info` document reports the vault address checksummed (the CLI
130
138
  already did).
139
+ - `SubstrateInfo.address` is `None` instead of `""` for a substrate without an
140
+ address. Truthiness checks are unaffected and the SDK's JSON documents already
141
+ omit it, but `dataclasses.asdict(info)` now gives `"address": None` and
142
+ `info.address == ""` is false.
143
+ - The Uniswap V3 / Ramses V2 position events (`extract_new_position_events`,
144
+ `extract_close_position_events`) return their addresses checksummed (they were the
145
+ lowercase ABI-decoded strings).
131
146
  - `fusion market meta-morpho --json` and the MCP `market_meta_morpho` tool report
132
147
  `null` instead of `""` for an absent curator, guardian or fee recipient; the MCP
133
148
  schema's `curator` is nullable.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev49
3
+ Version: 4.0.0.dev52
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev49"
3
+ version = "4.0.0.dev52"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -345,7 +345,9 @@ from ipor_fusion.types import (
345
345
  TokenId,
346
346
  TransferId,
347
347
  TxHash,
348
+ parse_hex_address,
348
349
  parse_morpho_market_id,
350
+ parse_tx_hash,
349
351
  )
350
352
  from ipor_fusion.validation import (
351
353
  Erc20Holding,
@@ -624,7 +626,9 @@ __all__ = [
624
626
  "PayloadHash",
625
627
  "ProposalHash",
626
628
  "TxHash",
629
+ "parse_hex_address",
627
630
  "parse_morpho_market_id",
631
+ "parse_tx_hash",
628
632
  "AssetId",
629
633
  "CommandId",
630
634
  "DeploymentRequestId",
@@ -20,7 +20,7 @@ from ipor_fusion.substrates import (
20
20
  decode_substrate,
21
21
  format_market_label,
22
22
  )
23
- from ipor_fusion.types import ChainId, MarketId
23
+ from ipor_fusion.types import ChainId, MarketId, parse_hex_address
24
24
  from ipor_fusion.vault_data import (
25
25
  GET_MARKET_SUBSTRATES,
26
26
  VaultData,
@@ -213,7 +213,7 @@ def _format_token_amount(
213
213
  ctx: Web3Context,
214
214
  raw: int,
215
215
  token_address: HexAddress,
216
- prices_usd: dict[str, float] | None = None,
216
+ prices_usd: dict[HexAddress, float] | None = None,
217
217
  ) -> str:
218
218
  """Format a raw on-chain amount using cached ERC-20 symbol + decimals.
219
219
 
@@ -226,7 +226,9 @@ def _format_token_amount(
226
226
  return f"{raw} raw ({symbol})"
227
227
  base = f"{_format_amount(raw, decimals)} {symbol}"
228
228
  if prices_usd:
229
- usd_suffix = _format_usd(raw, decimals, prices_usd.get(token_address.lower()))
229
+ usd_suffix = _format_usd(
230
+ raw, decimals, prices_usd.get(parse_hex_address(token_address))
231
+ )
230
232
  return f"{base}{usd_suffix}"
231
233
  return base
232
234
 
@@ -259,7 +261,7 @@ def _print_dependency_graph(data: VaultData) -> None:
259
261
  def _print_fuse_section(
260
262
  title: str,
261
263
  fuses: Sequence[ChecksumAddress],
262
- fuse_markets: dict[str, MarketId] | None,
264
+ fuse_markets: dict[ChecksumAddress, MarketId] | None,
263
265
  market_substrates: dict[MarketId, list[bytes]] | None,
264
266
  chain_id: ChainId,
265
267
  api_key: str | None,
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import logging
4
4
 
5
5
  from eth_account import Account
6
- from eth_typing import ChecksumAddress, HexStr
6
+ from eth_typing import ChecksumAddress
7
7
  from hexbytes import HexBytes
8
8
  from requests import ConnectionError as RequestsConnectionError
9
9
  from requests import HTTPError, Timeout
@@ -25,7 +25,7 @@ from ipor_fusion.core.logs import (
25
25
  get_logs_adaptive,
26
26
  )
27
27
  from ipor_fusion.errors import LogSourceUnavailable, TransactionError, get_revert_reason
28
- from ipor_fusion.types import ChainId, TxHash
28
+ from ipor_fusion.types import ChainId, parse_tx_hash
29
29
 
30
30
  # web3 retries every allowlisted method on any HTTP error, a 400 included;
31
31
  # a too-wide eth_getLogs answered with HTTP 400/413 or a query timeout would be
@@ -229,7 +229,7 @@ class Web3Context:
229
229
  reason = get_revert_reason(self.web3, tx_hash, receipt)
230
230
  raise TransactionError(
231
231
  "Transaction failed",
232
- tx_hash=TxHash(HexStr(tx_hash.to_0x_hex())),
232
+ tx_hash=parse_tx_hash(bytes(tx_hash)),
233
233
  revert_reason=reason,
234
234
  )
235
235
  return receipt
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from collections.abc import Mapping, Sequence, Set
6
6
 
7
+ from eth_typing import ChecksumAddress
8
+
7
9
  from ipor_fusion.market_ids import IporFusionMarkets
8
10
  from ipor_fusion.types import MarketId
9
11
 
@@ -38,7 +40,7 @@ _ERC20_DEP_NOT_REQUIRED: frozenset[MarketId] = frozenset(
38
40
 
39
41
 
40
42
  def find_orphan_fuse_markets(
41
- fuse_markets: Mapping[str, MarketId],
43
+ fuse_markets: Mapping[ChecksumAddress, MarketId],
42
44
  balance_fuse_market_ids: Set[MarketId],
43
45
  ) -> dict[MarketId, list[str]]:
44
46
  """Return markets claimed by action fuses but lacking a balance fuse.
@@ -10,10 +10,10 @@ import time
10
10
  from urllib.parse import urlencode
11
11
  from urllib.request import urlopen
12
12
 
13
- from eth_typing import HexAddress, HexStr
13
+ from eth_typing import HexAddress
14
14
 
15
15
  from ipor_fusion.cache import cache_key, load_contract_cache, update_contract_cache
16
- from ipor_fusion.types import ChainId, TxHash
16
+ from ipor_fusion.types import ChainId, TxHash, parse_tx_hash
17
17
 
18
18
  ETHERSCAN_V2_URL = "https://api.etherscan.io/v2/api"
19
19
 
@@ -67,6 +67,17 @@ def get_deployment_tx(
67
67
  return _fetch_contract_creation_tx(chain_id, address, api_key)
68
68
 
69
69
 
70
+ def _creation_tx_hash(raw: object) -> tuple[TxHash | None, str | None]:
71
+ if not raw:
72
+ return None, None
73
+ if not isinstance(raw, str):
74
+ return None, "malformed-tx-hash"
75
+ try:
76
+ return parse_tx_hash(raw), None
77
+ except ValueError:
78
+ return None, "malformed-tx-hash"
79
+
80
+
70
81
  def _fetch_contract_creation_tx(
71
82
  chain_id: ChainId, address: HexAddress, api_key: str | None = None
72
83
  ) -> tuple[TxHash | None, str | None]:
@@ -91,8 +102,7 @@ def _fetch_contract_creation_tx(
91
102
  with urlopen(url, timeout=10) as resp: # noqa: S310
92
103
  data = json.loads(resp.read().decode())
93
104
  if data.get("status") == "1" and data.get("result"):
94
- tx_hash = data["result"][0].get("txHash")
95
- return (TxHash(HexStr(tx_hash)) if tx_hash else None), None
105
+ return _creation_tx_hash(data["result"][0].get("txHash"))
96
106
  result_text = str(data.get("result", "")).lower()
97
107
  if "free api access is not supported" in result_text:
98
108
  return None, "etherscan-paid-tier-required"
@@ -14,5 +14,12 @@ def extract_events(
14
14
  for log in receipt["logs"]:
15
15
  if log["topics"][0] == sig_hash:
16
16
  decoded = decode(abi_types, log["data"])
17
- events.append(dataclass_type(*decoded))
17
+ events.append(
18
+ dataclass_type(
19
+ *(
20
+ Web3.to_checksum_address(value) if abi == "address" else value
21
+ for abi, value in zip(abi_types, decoded, strict=True)
22
+ )
23
+ )
24
+ )
18
25
  return events
@@ -28,11 +28,11 @@ from typing import Any
28
28
 
29
29
  from eth_abi import decode
30
30
  from eth_abi.exceptions import DecodingError
31
- from eth_typing import ChecksumAddress, HexStr
31
+ from eth_typing import ChecksumAddress
32
32
  from web3 import Web3
33
33
  from web3.types import TxReceipt
34
34
 
35
- from ipor_fusion.types import TxHash
35
+ from ipor_fusion.types import TxHash, parse_tx_hash
36
36
 
37
37
 
38
38
  @dataclass(frozen=True, slots=True)
@@ -196,7 +196,7 @@ def external_state_event_name(topic: bytes | str) -> str | None:
196
196
  def _hex(value: Any) -> TxHash | None:
197
197
  if value is None:
198
198
  return None
199
- return TxHash(HexStr("0x" + _as_bytes(value, "transactionHash").hex()))
199
+ return parse_tx_hash(_as_bytes(value, "transactionHash"))
200
200
 
201
201
 
202
202
  def decode_external_state_event(log: Mapping[str, Any]) -> ExternalStateEvent | None:
@@ -136,13 +136,13 @@ class RamsesClaimFuse(Fuse):
136
136
 
137
137
  @dataclass(slots=True)
138
138
  class RamsesNewPositionEvent:
139
- version: str
139
+ version: ChecksumAddress
140
140
  token_id: TokenId
141
141
  liquidity: Liquidity
142
142
  amount0: Amount
143
143
  amount1: Amount
144
- sender: str
145
- recipient: str
144
+ sender: ChecksumAddress
145
+ recipient: ChecksumAddress
146
146
  fee: Fee
147
147
  tick_lower: Tick
148
148
  tick_upper: Tick
@@ -140,13 +140,13 @@ class UniswapV3CollectFuse(Fuse):
140
140
 
141
141
  @dataclass(slots=True)
142
142
  class UniswapV3NewPositionEvent:
143
- version: str
143
+ version: ChecksumAddress
144
144
  token_id: TokenId
145
145
  liquidity: Liquidity
146
146
  amount0: Amount
147
147
  amount1: Amount
148
- sender: str
149
- recipient: str
148
+ sender: ChecksumAddress
149
+ recipient: ChecksumAddress
150
150
  fee: Fee
151
151
  tick_lower: Tick
152
152
  tick_upper: Tick
@@ -154,7 +154,7 @@ class UniswapV3NewPositionEvent:
154
154
 
155
155
  @dataclass(slots=True)
156
156
  class UniswapV3ClosePositionEvent:
157
- version: str
157
+ version: ChecksumAddress
158
158
  token_id: TokenId
159
159
 
160
160
 
@@ -499,7 +499,7 @@ def _market_layout(
499
499
  bitmap = 0
500
500
  for raw in substrates:
501
501
  info = decode_substrate(raw, HYPERCORE_MARKET)
502
- if info.type_label == "SPOT_TOKEN" and not info.is_error:
502
+ if info.type_label == "SPOT_TOKEN" and not info.is_error and info.address:
503
503
  index = HyperCoreTokenIndex(int(info.extra["token_index"]))
504
504
  if index in seen:
505
505
  continue
@@ -353,7 +353,7 @@ def _aave_v4_spokes(substrates: list[bytes]) -> list[ChecksumAddress]:
353
353
  spokes: dict[ChecksumAddress, None] = {}
354
354
  for sub in substrates:
355
355
  info = decode_substrate(sub, market_id=IporFusionMarkets.AAVE_V4)
356
- if info.type_label == "AAVE_V4_RESERVE":
356
+ if info.type_label == "AAVE_V4_RESERVE" and info.address:
357
357
  spokes[Web3.to_checksum_address(info.address)] = None
358
358
  return list(spokes)
359
359
 
@@ -15,7 +15,7 @@ from __future__ import annotations
15
15
  from collections.abc import Callable
16
16
  from dataclasses import dataclass, field
17
17
 
18
- from eth_typing import HexAddress, HexStr
18
+ from eth_typing import HexAddress
19
19
  from eth_utils import keccak
20
20
 
21
21
  from ipor_fusion.fuses.hypercore import (
@@ -25,12 +25,12 @@ from ipor_fusion.fuses.hypercore import (
25
25
  read_index_of,
26
26
  )
27
27
  from ipor_fusion.market_ids import IporFusionMarkets
28
- from ipor_fusion.types import HyperCoreAsset, MarketId
28
+ from ipor_fusion.types import HyperCoreAsset, MarketId, parse_hex_address
29
29
 
30
30
 
31
31
  @dataclass
32
32
  class SubstrateInfo:
33
- address: HexAddress = HexAddress(HexStr(""))
33
+ address: HexAddress | None = None
34
34
  raw_hex: str = ""
35
35
  type_label: str = ""
36
36
  is_error: bool = False
@@ -47,14 +47,10 @@ class SubstrateInfo:
47
47
  # Slippage variant: [2 type chars][62 value chars]
48
48
 
49
49
 
50
- def _hex_address(hex_digits: str) -> HexAddress:
51
- return HexAddress(HexStr(f"0x{hex_digits}"))
52
-
53
-
54
50
  def _decode_type_lshift160(hex_str: str, types: dict[int, str]) -> SubstrateInfo:
55
51
  """Decode type<<160 | address (Ebisu, Midas, Balancer, Velodrome)."""
56
52
  type_byte = int(hex_str[22:24], 16)
57
- addr = _hex_address(hex_str[24:])
53
+ addr = parse_hex_address(hex_str[24:])
58
54
  label = types.get(type_byte, f"type={type_byte}")
59
55
  return SubstrateInfo(address=addr, type_label=label)
60
56
 
@@ -67,13 +63,13 @@ def _decode_type_lshift248(hex_str: str, types: dict[int, str]) -> SubstrateInfo
67
63
  return SubstrateInfo(
68
64
  raw_hex=f"0x{hex_str}", type_label=label, extra={"slippage": str(value)}
69
65
  )
70
- addr = _hex_address(hex_str[24:])
66
+ addr = parse_hex_address(hex_str[24:])
71
67
  return SubstrateInfo(address=addr, type_label=label)
72
68
 
73
69
 
74
70
  def _decode_plain_address(hex_str: str) -> SubstrateInfo:
75
71
  """Decode zero-padded address: 12 zero bytes + 20 address bytes."""
76
- return SubstrateInfo(address=_hex_address(hex_str[24:]))
72
+ return SubstrateInfo(address=parse_hex_address(hex_str[24:]))
77
73
 
78
74
 
79
75
  def _decode_morpho(hex_str: str) -> SubstrateInfo:
@@ -88,14 +84,14 @@ def _decode_uniswap_v4(hex_str: str) -> SubstrateInfo:
88
84
  substrate-as-asset (12 zero bytes + address)."""
89
85
  if hex_str[:24] == "0" * 24:
90
86
  return SubstrateInfo(
91
- address=_hex_address(hex_str[24:]), type_label="uniswap_v4_token"
87
+ address=parse_hex_address(hex_str[24:]), type_label="uniswap_v4_token"
92
88
  )
93
89
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label="uniswap_v4_pool_id")
94
90
 
95
91
 
96
92
  def _decode_enso(hex_str: str) -> SubstrateInfo:
97
93
  """Decode address<<96 | selector<<64 (Enso)."""
98
- addr = _hex_address(hex_str[0:40])
94
+ addr = parse_hex_address(hex_str[0:40])
99
95
  selector = f"0x{hex_str[40:48]}"
100
96
  return SubstrateInfo(
101
97
  address=addr, type_label="ENSO_TARGET", extra={"selector": selector}
@@ -104,7 +100,7 @@ def _decode_enso(hex_str: str) -> SubstrateInfo:
104
100
 
105
101
  def _decode_dolomite(hex_str: str) -> SubstrateInfo:
106
102
  """Decode asset<<96 | subAccountId<<88 | canBorrow<<80 (Dolomite)."""
107
- addr = _hex_address(hex_str[0:40])
103
+ addr = parse_hex_address(hex_str[0:40])
108
104
  sub_account_id = int(hex_str[40:42], 16)
109
105
  can_borrow = (int(hex_str[42:44], 16) & 0x01) == 1
110
106
  return SubstrateInfo(
@@ -131,7 +127,7 @@ def _decode_aave_v4(hex_str: str) -> SubstrateInfo:
131
127
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label=label)
132
128
  flags = int(hex_str[50:52], 16)
133
129
  return SubstrateInfo(
134
- address=_hex_address(hex_str[2:42]),
130
+ address=parse_hex_address(hex_str[2:42]),
135
131
  type_label="AAVE_V4_RESERVE",
136
132
  extra={
137
133
  "reserve_id": str(int(hex_str[42:50], 16)),
@@ -149,7 +145,7 @@ def _decode_euler_v2(hex_str: str) -> SubstrateInfo:
149
145
  plain-address path silently produces a malformed address (last 20 bytes are
150
146
  flag bytes + zero padding).
151
147
  """
152
- addr = _hex_address(hex_str[0:40])
148
+ addr = parse_hex_address(hex_str[0:40])
153
149
  is_collateral = (int(hex_str[40:42], 16) & 0x01) == 1
154
150
  can_borrow = (int(hex_str[42:44], 16) & 0x01) == 1
155
151
  sub_account = f"0x{hex_str[44:46]}"
@@ -178,13 +174,13 @@ def _decode_async_action(hex_str: str) -> SubstrateInfo:
178
174
  type_byte = int(hex_str[0:2], 16)
179
175
  if type_byte == 0:
180
176
  return SubstrateInfo(
181
- address=_hex_address(hex_str[2:42]),
177
+ address=parse_hex_address(hex_str[2:42]),
182
178
  type_label="ALLOWED_AMOUNT_TO_OUTSIDE",
183
179
  extra={"amount": str(int(hex_str[42:64], 16))},
184
180
  )
185
181
  if type_byte == 1:
186
182
  return SubstrateInfo(
187
- address=_hex_address(hex_str[16:56]),
183
+ address=parse_hex_address(hex_str[16:56]),
188
184
  type_label="ALLOWED_TARGETS",
189
185
  extra={"selector": f"0x{hex_str[56:64]}"},
190
186
  )
@@ -229,10 +225,10 @@ def _decode_external_state(hex_str: str) -> SubstrateInfo:
229
225
  if type_byte == 0:
230
226
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label="UNDEFINED")
231
227
  if label := _EXTERNAL_STATE_ADDRESS_TYPES.get(type_byte):
232
- return SubstrateInfo(address=_hex_address(hex_str[24:]), type_label=label)
228
+ return SubstrateInfo(address=parse_hex_address(hex_str[24:]), type_label=label)
233
229
  if type_byte == 2:
234
230
  return SubstrateInfo(
235
- address=_hex_address(hex_str[24:]),
231
+ address=parse_hex_address(hex_str[24:]),
236
232
  type_label="TARGET",
237
233
  extra={"selector": f"0x{hex_str[16:24]}"},
238
234
  )
@@ -270,7 +266,7 @@ def _decode_crosschain(hex_str: str) -> SubstrateInfo:
270
266
  is_error=True,
271
267
  extra={"chain_id": str(chain_id)},
272
268
  )
273
- info = SubstrateInfo(address=_hex_address(hex_str[24:]), type_label=label)
269
+ info = SubstrateInfo(address=parse_hex_address(hex_str[24:]), type_label=label)
274
270
  if type_byte == 2:
275
271
  info.extra = {"chain_id": str(chain_id)}
276
272
  return info
@@ -330,7 +326,7 @@ def _decode_hypercore(hex_str: str) -> SubstrateInfo:
330
326
  return _hypercore_invalid(hex_str, label)
331
327
  if type_byte == 1:
332
328
  return SubstrateInfo(
333
- address=_hex_address(hex_str[24:]),
329
+ address=parse_hex_address(hex_str[24:]),
334
330
  type_label=label,
335
331
  extra={"token_index": str(int(hex_str[8:24], 16))},
336
332
  )
@@ -351,10 +347,10 @@ def _decode_hypercore(hex_str: str) -> SubstrateInfo:
351
347
  extra["read_index"] = str(read_index_of(asset))
352
348
  return SubstrateInfo(raw_hex=f"0x{hex_str}", type_label=label, extra=extra)
353
349
  if type_byte == 3:
354
- return SubstrateInfo(address=_hex_address(hex_str[24:]), type_label=label)
350
+ return SubstrateInfo(address=parse_hex_address(hex_str[24:]), type_label=label)
355
351
  if type_byte == 4:
356
352
  return SubstrateInfo(
357
- address=_hex_address(hex_str[24:]),
353
+ address=parse_hex_address(hex_str[24:]),
358
354
  type_label=label,
359
355
  extra={"max_fee_rate_decibps": str(int(hex_str[8:24], 16))},
360
356
  )
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from typing import ClassVar, Literal, NewType, TypeAlias
3
3
 
4
- from eth_typing import ChecksumAddress, HexStr
4
+ from eth_typing import ChecksumAddress, HexAddress, HexStr
5
5
 
6
6
  Amount = NewType("Amount", int)
7
7
  Shares = NewType("Shares", int)
@@ -131,3 +131,27 @@ def parse_morpho_market_id(value: str | bytes) -> MorphoBlueMarketId:
131
131
  if len(digits) != 64 or any(c not in "0123456789abcdef" for c in digits):
132
132
  raise ValueError(f"invalid Morpho market ID (expected 32-byte hex): {value}")
133
133
  return MorphoBlueMarketId("0x" + digits)
134
+
135
+
136
+ def _hex_digits(value: str | bytes, size: int, what: str) -> str:
137
+ if isinstance(value, bytes):
138
+ if len(value) != size:
139
+ raise ValueError(f"invalid {what} (expected {size} bytes): {value!r}")
140
+ return value.hex()
141
+ digits = value.strip().removeprefix("0x").removeprefix("0X").lower()
142
+ if len(digits) != 2 * size or any(c not in "0123456789abcdef" for c in digits):
143
+ raise ValueError(f"invalid {what} (expected {size}-byte hex): {value}")
144
+ return digits
145
+
146
+
147
+ def parse_hex_address(value: str | bytes) -> HexAddress:
148
+ """An address as ``0x`` and 40 lowercase hex digits, from 20 raw bytes or hex
149
+ in any spelling; raises ``ValueError`` on anything else. For a checksummed
150
+ address use ``Web3.to_checksum_address``."""
151
+ return HexAddress(HexStr("0x" + _hex_digits(value, 20, "address")))
152
+
153
+
154
+ def parse_tx_hash(value: str | bytes) -> TxHash:
155
+ """A transaction hash as ``0x`` and 64 lowercase hex digits, from 32 raw bytes
156
+ or hex in any spelling; raises ``ValueError`` on anything else."""
157
+ return TxHash(HexStr("0x" + _hex_digits(value, 32, "transaction hash")))
@@ -110,13 +110,13 @@ class VaultState(Protocol):
110
110
  @property
111
111
  def underlying_balance_on_vault(self) -> int: ...
112
112
  @property
113
- def price_oracle_addr(self) -> str: ...
113
+ def price_oracle_addr(self) -> ChecksumAddress: ...
114
114
  @property
115
115
  def balance_fuses(self) -> Sequence[BalanceFuse]: ...
116
116
  @property
117
117
  def market_substrates(self) -> Mapping[MarketId, Sequence[bytes]] | None: ...
118
118
  @property
119
- def fuse_markets(self) -> Mapping[str, MarketId] | None: ...
119
+ def fuse_markets(self) -> Mapping[ChecksumAddress, MarketId] | None: ...
120
120
  @property
121
121
  def dependency_graph(self) -> Mapping[MarketId, Sequence[MarketId]] | None: ...
122
122
  @property
@@ -22,7 +22,7 @@ from eth_typing import HexAddress
22
22
  from eth_utils import function_signature_to_4byte_selector
23
23
  from web3 import Web3
24
24
  from web3.exceptions import ContractLogicError, TimeExhausted, Web3RPCError
25
- from web3.types import ChecksumAddress, HexStr
25
+ from web3.types import ChecksumAddress
26
26
 
27
27
  from ipor_fusion.cache import (
28
28
  cache_key,
@@ -83,6 +83,7 @@ from ipor_fusion.types import (
83
83
  ChainId,
84
84
  MarketId,
85
85
  MorphoBlueMarketId,
86
+ parse_hex_address,
86
87
  parse_morpho_market_id,
87
88
  )
88
89
 
@@ -124,8 +125,8 @@ class FeeData:
124
125
  management_fee_vault_bps: int | None = None
125
126
  management_fee_last_update: int | None = None
126
127
  unrealized_management_fee: int | None = None
127
- fee_manager: str | None = None
128
- ipor_dao_fee_recipient: str | None = None
128
+ fee_manager: ChecksumAddress | None = None
129
+ ipor_dao_fee_recipient: ChecksumAddress | None = None
129
130
  deposit_fee_wad: int | None = None
130
131
  performance_fee_manager_bps: int | None = None
131
132
  management_fee_manager_bps: int | None = None
@@ -167,14 +168,14 @@ class VaultData:
167
168
  supply_cap: int
168
169
  asset: ChecksumAddress
169
170
  asset_symbol: str
170
- access_manager: str
171
- price_oracle_addr: str
172
- rewards_manager: str | None
173
- withdraw_manager: str | None
171
+ access_manager: ChecksumAddress
172
+ price_oracle_addr: ChecksumAddress
173
+ rewards_manager: ChecksumAddress | None
174
+ withdraw_manager: ChecksumAddress | None
174
175
  asset_price_usd: float | None
175
- fuses: list
176
- balance_fuses: list
177
- instant_fuses: list
176
+ fuses: list[ChecksumAddress]
177
+ balance_fuses: list[BalanceFuse]
178
+ instant_fuses: list[ChecksumAddress]
178
179
  # Underlying asset balance held directly on the vault (raw). Part of
179
180
  # totalAssets via ERC4626 base accounting; by design NOT tracked as an
180
181
  # ERC20_VAULT_BALANCE substrate, so it is read directly in
@@ -206,7 +207,7 @@ class VaultData:
206
207
  # breakdowns (Morpho loan/collateral, Aave reserve assets). Sourced from the
207
208
  # vault's PriceOracleMiddleware. Missing keys mean the oracle has no source
208
209
  # configured for that token.
209
- token_prices_usd: dict[str, float] | None = None
210
+ token_prices_usd: dict[HexAddress, float] | None = None
210
211
  # Middleware-priced tokens of granted substrates the PriceOracleMiddleware
211
212
  # cannot price (see readers.unpriceable). None when the check did not run.
212
213
  unpriceable_priced_tokens: list[MiddlewarePricedToken] | None = None
@@ -215,7 +216,7 @@ class VaultData:
215
216
  # are absent from the dict — used to detect orphan markets (action fuses
216
217
  # with no matching balance fuse, which silently drop positions from
217
218
  # totalAssets).
218
- fuse_markets: dict[str, MarketId] | None = None
219
+ fuse_markets: dict[ChecksumAddress, MarketId] | None = None
219
220
  # Substrates registered for each balance-fuse market. Allows the fuse
220
221
  # tables to report substrate counts per fuse without duplicating reads.
221
222
  market_substrates: dict[MarketId, list[bytes]] | None = None
@@ -665,7 +666,7 @@ def _fetch_breakdown_token_prices(
665
666
  ctx: Web3Context,
666
667
  oracle: PriceOracleMiddleware,
667
668
  addresses: set[ChecksumAddress],
668
- ) -> dict[str, float] | None:
669
+ ) -> dict[HexAddress, float] | None:
669
670
  """Fetch USD prices for breakdown tokens in one batch via the vault's oracle.
670
671
 
671
672
  Returns a dict keyed by lowercase token address. Tokens with no oracle
@@ -678,7 +679,7 @@ def _fetch_breakdown_token_prices(
678
679
  [oracle.get_asset_price(addr) for addr in ordered]
679
680
  )
680
681
  result = {
681
- addr.lower(): price.readable()
682
+ parse_hex_address(addr): price.readable()
682
683
  for addr, price in zip(ordered, prices, strict=True)
683
684
  if price is not None
684
685
  }
@@ -1064,7 +1065,7 @@ def fetch_deployment_info(
1064
1065
  return None, None, error
1065
1066
 
1066
1067
  try:
1067
- tx = ctx.web3.eth.get_transaction(HexStr(tx_hash))
1068
+ tx = ctx.web3.eth.get_transaction(tx_hash)
1068
1069
  block_number: int = tx["blockNumber"]
1069
1070
  block_info = ctx.web3.eth.get_block(block_number)
1070
1071
  timestamp: int = block_info["timestamp"]