ipor-fusion 4.0.0.dev30__tar.gz → 4.0.0.dev32__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 (285) hide show
  1. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/AGENTS.md +3 -1
  2. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/__init__.py +2 -0
  5. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/market_cmd.py +18 -26
  6. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/market_info.py +3 -3
  7. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/mcp/server.py +4 -9
  8. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/morpho_api.py +20 -13
  9. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/lending_health.py +8 -4
  10. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/types.py +16 -1
  11. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/vault_data.py +7 -4
  12. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_market_morpho_blue.py +3 -1
  13. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/typed_client/consumer.py +3 -1
  14. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.claude-plugin/marketplace.json +0 -0
  15. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.claude-plugin/plugin.json +0 -0
  16. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.env.example +0 -0
  17. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.gemini/settings.json +0 -0
  18. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.git-blame-ignore-revs +0 -0
  19. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/cd.yml +0 -0
  20. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/ci.yml +0 -0
  21. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/commit-lint.yml +0 -0
  22. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/notify-slack.yml +0 -0
  23. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/python-build.yml +0 -0
  24. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/python-publish.yml +0 -0
  25. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/release.yml +0 -0
  26. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.github/workflows/report-slack.yml +0 -0
  27. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.gitignore +0 -0
  28. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.mcp.json +0 -0
  29. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.pre-commit-config.yaml +0 -0
  30. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/.python-version +0 -0
  31. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/CHANGELOG.md +0 -0
  32. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/CLAUDE.md +0 -0
  33. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/CODEOWNERS +0 -0
  34. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/LICENSE +0 -0
  35. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/README.md +0 -0
  36. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/build.sh +0 -0
  37. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/CONTRIBUTING.md +0 -0
  38. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/README.md +0 -0
  39. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  40. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  41. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  42. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  43. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/external_state_margin_leg_base.py +0 -0
  44. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/examples/simple_aave_v3_supply_base.py +0 -0
  45. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/pytest.ini +0 -0
  46. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/scripts/commit-check.sh +0 -0
  47. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/scripts/crosschain_readiness.py +0 -0
  48. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/skills/ipor-deploy-vault/SKILL.md +0 -0
  49. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/about.py +0 -0
  50. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cache.py +0 -0
  51. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/chains.py +0 -0
  52. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/__init__.py +0 -0
  53. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  54. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/config_cmd.py +0 -0
  55. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/config_store.py +0 -0
  56. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/main.py +0 -0
  57. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/options.py +0 -0
  58. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  59. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  60. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/vault_health.py +0 -0
  61. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  62. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  63. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  64. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/config/__init__.py +0 -0
  65. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/config/roles.py +0 -0
  66. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/__init__.py +0 -0
  67. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/access.py +0 -0
  68. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/address.py +0 -0
  69. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/context.py +0 -0
  70. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/contract.py +0 -0
  71. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/erc20.py +0 -0
  72. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/external_state_executor.py +0 -0
  73. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/fee_manager.py +0 -0
  74. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/fuse_action.py +0 -0
  75. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/fusion_factory.py +0 -0
  76. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/logs.py +0 -0
  77. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/multicall.py +0 -0
  78. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/oracle.py +0 -0
  79. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/plasma_vault.py +0 -0
  80. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/rewards_manager.py +0 -0
  81. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/simulation.py +0 -0
  82. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  83. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/__init__.py +0 -0
  84. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  85. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  86. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  87. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  88. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  89. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  90. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  91. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  92. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  93. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/contracts.py +0 -0
  94. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/discovery.py +0 -0
  95. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/errors.py +0 -0
  96. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/lane.py +0 -0
  97. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/logs.py +0 -0
  98. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/messages.py +0 -0
  99. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/simulation.py +0 -0
  100. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  101. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  102. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  103. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  104. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  105. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/crosschain/transport.py +0 -0
  106. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/dependency_graph.py +0 -0
  107. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/entry_cli.py +0 -0
  108. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/entry_mcp.py +0 -0
  109. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/errors.py +0 -0
  110. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/explorer.py +0 -0
  111. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/field_docs.py +0 -0
  112. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/__init__.py +0 -0
  113. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  114. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/async_action.py +0 -0
  115. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/base.py +0 -0
  116. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  117. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  118. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  119. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  120. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  121. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/erc4626.py +0 -0
  122. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  123. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/events.py +0 -0
  124. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/external_state.py +0 -0
  125. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  126. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  127. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  128. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/hypercore.py +0 -0
  129. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  130. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/merkl.py +0 -0
  131. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/morpho.py +0 -0
  132. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  133. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  134. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/fuses/universal.py +0 -0
  135. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/guide/__init__.py +0 -0
  136. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/guide/architecture.md +0 -0
  137. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/guide/glossary.md +0 -0
  138. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/guide/invariants.md +0 -0
  139. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/guide/quickstart.md +0 -0
  140. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/market_ids.py +0 -0
  141. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/mcp/__init__.py +0 -0
  142. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/mcp/guide.py +0 -0
  143. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/mcp/models.py +0 -0
  144. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/py.typed +0 -0
  145. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/__init__.py +0 -0
  146. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/aave_v3.py +0 -0
  147. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/aave_v4.py +0 -0
  148. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/compound_v3.py +0 -0
  149. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/hypercore.py +0 -0
  150. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  151. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/morpho.py +0 -0
  152. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  153. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/position_manager.py +0 -0
  154. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  155. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  156. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/readers/unpriceable.py +0 -0
  157. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/substrates.py +0 -0
  158. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/validation.py +0 -0
  159. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/src/ipor_fusion/vault_info.py +0 -0
  160. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/UniswapV3UniversalRouter.py +0 -0
  161. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_crosschain.py +0 -0
  162. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_crosschain_lifecycle.py +0 -0
  163. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_crosschain_recovery.py +0 -0
  164. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_euler_v2.py +0 -0
  165. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_foundry.py +0 -0
  166. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_guide.py +0 -0
  167. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_hypercore_model.py +0 -0
  168. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_hypercore_shadow.py +0 -0
  169. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_multicall.py +0 -0
  170. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_simulate.py +0 -0
  171. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/_validation.py +0 -0
  172. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/addresses.py +0 -0
  173. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/conftest.py +0 -0
  174. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/constants.py +0 -0
  175. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  176. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/fixtures/hypercore_market55.json +0 -0
  177. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/fixtures/vault_info_golden/clean.json +0 -0
  178. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  179. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/fixtures/vault_info_golden/rich.json +0 -0
  180. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  181. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_about.py +0 -0
  182. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_access_manager.py +0 -0
  183. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cache.py +0 -0
  184. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_chains.py +0 -0
  185. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_changelog.py +0 -0
  186. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_commands.py +0 -0
  187. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_config_store.py +0 -0
  188. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_helpers.py +0 -0
  189. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_hypercore.py +0 -0
  190. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_lending_health.py +0 -0
  191. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_oracle_mapping.py +0 -0
  192. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_pending_requests_scan.py +0 -0
  193. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_role_accounts.py +0 -0
  194. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_vault_info_golden.py +0 -0
  195. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_vault_predates_getters.py +0 -0
  196. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_cli_vault_validate.py +0 -0
  197. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_contract_call.py +0 -0
  198. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_contract_encoder.py +0 -0
  199. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_ccip_events.py +0 -0
  200. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_ccip_indexer.py +0 -0
  201. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_ccip_quote.py +0 -0
  202. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_chainlink.py +0 -0
  203. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_contracts.py +0 -0
  204. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_encoding.py +0 -0
  205. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_errors.py +0 -0
  206. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_lane.py +0 -0
  207. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_layering.py +0 -0
  208. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_readiness.py +0 -0
  209. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_readiness_report.py +0 -0
  210. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_crosschain_transport.py +0 -0
  211. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_erc20.py +0 -0
  212. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_examples_composed_hypercore.py +0 -0
  213. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_examples_crosschain.py +0 -0
  214. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  215. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_examples_vaults.py +0 -0
  216. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_explorer.py +0 -0
  217. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_external_state_events.py +0 -0
  218. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_external_state_executor.py +0 -0
  219. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_fee_manager.py +0 -0
  220. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_fuse_encoding.py +0 -0
  221. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_guide.py +0 -0
  222. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_hypercore_deployment.py +0 -0
  223. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_hypercore_events.py +0 -0
  224. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_hypercore_flow.py +0 -0
  225. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_hypercore_live.py +0 -0
  226. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_hypercore_outcome.py +0 -0
  227. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_hypercore_readers.py +0 -0
  228. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_hypercore_simulation.py +0 -0
  229. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_logs.py +0 -0
  230. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_mcp_hypercore.py +0 -0
  231. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_mcp_models.py +0 -0
  232. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_mcp_server.py +0 -0
  233. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_mcp_vault_validate.py +0 -0
  234. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_multicall.py +0 -0
  235. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_oracle.py +0 -0
  236. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_oracle_mapping.py +0 -0
  237. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_plasma_vault.py +0 -0
  238. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_reader_encoding.py +0 -0
  239. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_readiness_selection.py +0 -0
  240. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_revert_decoding.py +0 -0
  241. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_rewards_manager.py +0 -0
  242. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_roles.py +0 -0
  243. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_sdk_layering.py +0 -0
  244. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  245. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  246. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  247. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_crosschain_canary.py +0 -0
  248. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_crosschain_hypercore.py +0 -0
  249. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  250. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  251. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  252. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  253. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  254. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  255. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  256. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_examples_arbitrum.py +0 -0
  257. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_examples_base.py +0 -0
  258. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_examples_crosschain.py +0 -0
  259. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_external_state_executor_base.py +0 -0
  260. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  261. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_morpho_blue.py +0 -0
  264. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_pilot_v3.py +0 -0
  265. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  266. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  267. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  268. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_readers_integration.py +0 -0
  270. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_release_funds.py +0 -0
  271. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_swap_on_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_solidity_compiler.py +0 -0
  274. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_solidity_mirrors.py +0 -0
  275. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_substrates.py +0 -0
  276. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_types_and_events.py +0 -0
  277. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_unpriceable_tokens.py +0 -0
  278. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_validation.py +0 -0
  279. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_vault_dep_graph.py +0 -0
  280. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_vault_info_onchain_base.py +0 -0
  281. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  282. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_vault_simulator_blocks.py +0 -0
  283. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_web3_context.py +0 -0
  284. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/tests/test_withdraw_manager.py +0 -0
  285. {ipor_fusion-4.0.0.dev30 → ipor_fusion-4.0.0.dev32}/uv.lock +0 -0
@@ -285,7 +285,9 @@ or examples.
285
285
  `MarketId`, ...) name the unit, and nothing scales by decimals.
286
286
  - Ids and addresses are nominal in every signature: `ChainId`, `MarketId` (each
287
287
  `IporFusionMarkets` member is one), `RoleId | Roles`, `ChecksumAddress`;
288
- `HexAddress` only for the lowercase addresses decoded from substrates. Raw input
288
+ `HexAddress` only for the lowercase addresses decoded from substrates;
289
+ `MorphoBlueMarketId` is always `0x` + 64 lowercase hex, built by
290
+ `parse_morpho_market_id`. Raw input
289
291
  is parsed once at the boundary (click param types, MCP tools, the user config).
290
292
  `src/`, `examples/` and `tests/typed_client/` are checked with
291
293
  `reportArgumentType` on, so a bare `int`/`str` passed for one fails pyright; the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev30
3
+ Version: 4.0.0.dev32
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.dev30"
3
+ version = "4.0.0.dev32"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -336,6 +336,7 @@ from ipor_fusion.types import (
336
336
  Tick,
337
337
  TokenId,
338
338
  TransferId,
339
+ parse_morpho_market_id,
339
340
  )
340
341
  from ipor_fusion.validation import (
341
342
  Erc20Holding,
@@ -605,6 +606,7 @@ __all__ = [
605
606
  "RoleId",
606
607
  "ChainId",
607
608
  "MorphoBlueMarketId",
609
+ "parse_morpho_market_id",
608
610
  "AssetId",
609
611
  "CommandId",
610
612
  "DeploymentRequestId",
@@ -42,7 +42,7 @@ from ipor_fusion.readers.morpho import (
42
42
  MorphoReader,
43
43
  morpho_blue_address,
44
44
  )
45
- from ipor_fusion.types import ChainId, MorphoBlueMarketId
45
+ from ipor_fusion.types import ChainId, MorphoBlueMarketId, parse_morpho_market_id
46
46
  from ipor_fusion.vault_info import BLOCK_EXPLORER_URLS
47
47
 
48
48
 
@@ -54,12 +54,10 @@ class MarketIdType(click.ParamType):
54
54
  def convert(self, value, param, ctx): # type: ignore[override]
55
55
  if not isinstance(value, str):
56
56
  self.fail(f"expected string, got {type(value).__name__}", param, ctx)
57
- raw = value.strip().removeprefix("0x").removeprefix("0X")
58
- if len(raw) != 64 or not all(c in "0123456789abcdefABCDEF" for c in raw):
59
- self.fail(
60
- f"invalid Morpho market ID (expected 32-byte hex): {value}", param, ctx
61
- )
62
- return raw.lower()
57
+ try:
58
+ return parse_morpho_market_id(value)
59
+ except ValueError as exc:
60
+ self.fail(str(exc), param, ctx)
63
61
 
64
62
 
65
63
  MARKET_ID = MarketIdType()
@@ -106,7 +104,7 @@ def market() -> None:
106
104
  help="Emit machine-readable JSON instead of a table.",
107
105
  )
108
106
  def morpho_blue(
109
- market_id: str,
107
+ market_id: MorphoBlueMarketId,
110
108
  chain_id: ChainId,
111
109
  block: int | None,
112
110
  no_api: bool,
@@ -127,7 +125,7 @@ def morpho_blue(
127
125
  if block is not None:
128
126
  ctx.default_block = block
129
127
  reader = MorphoReader(ctx, morpho_blue_address(chain_id))
130
- mid = MorphoBlueMarketId(market_id)
128
+ mid = market_id
131
129
  params, state = reader.require_market(mid)
132
130
  rates = reader.rates_from(state, params)
133
131
 
@@ -171,7 +169,7 @@ def morpho_blue(
171
169
 
172
170
 
173
171
  def _print_market(
174
- market_id: str,
172
+ market_id: MorphoBlueMarketId,
175
173
  chain_id: ChainId,
176
174
  params: MorphoMarketParams,
177
175
  state: MorphoMarket,
@@ -181,11 +179,11 @@ def _print_market(
181
179
  chain_name = CHAIN_NAMES.get(chain_id, str(chain_id))
182
180
  explorer = BLOCK_EXPLORER_URLS.get(chain_id, "")
183
181
  liquidity = state.total_supply_assets - state.total_borrow_assets
184
- click.secho(f"Morpho Blue market 0x{market_id}", bold=True)
182
+ click.secho(f"Morpho Blue market {market_id}", bold=True)
185
183
  click.echo(f" Chain: {chain_name} (id {chain_id})")
186
184
  if explorer:
187
185
  click.echo(
188
- f" App: https://app.morpho.org/{chain_name}/market/0x{market_id}"
186
+ f" App: https://app.morpho.org/{chain_name}/market/{market_id}"
189
187
  )
190
188
  click.echo(f" Loan token: {params.loan_token}")
191
189
  click.echo(f" Collateral: {params.collateral_token}")
@@ -332,7 +330,7 @@ def _print_reallocate_hint(params: MorphoMarketParams) -> None:
332
330
  @click.option(
333
331
  "--market",
334
332
  "market_filter",
335
- type=str,
333
+ type=MARKET_ID,
336
334
  default=None,
337
335
  help="Filter caps/allocations to a single Morpho Blue market ID.",
338
336
  )
@@ -346,7 +344,7 @@ def _print_reallocate_hint(params: MorphoMarketParams) -> None:
346
344
  def meta_morpho(
347
345
  vault_address: ChecksumAddress,
348
346
  chain_id: ChainId,
349
- market_filter: str | None,
347
+ market_filter: MorphoBlueMarketId | None,
350
348
  json_output: bool,
351
349
  ) -> None:
352
350
  """Inspect a MetaMorpho V1 or Morpho Vault V2 by address.
@@ -361,22 +359,18 @@ def meta_morpho(
361
359
  except MorphoApiError as exc:
362
360
  raise click.UsageError(str(exc)) from exc
363
361
 
364
- market_filter_norm = (
365
- ("0x" + market_filter.removeprefix("0x").lower()) if market_filter else None
366
- )
367
-
368
362
  if json_output:
369
363
  click.echo(json_lib.dumps(build_meta_morpho_json(info, chain_id), indent=2))
370
364
  return
371
365
 
372
366
  if isinstance(info, VaultV2Info):
373
- _print_vault_v2(info, chain_id, market_filter_norm)
367
+ _print_vault_v2(info, chain_id, market_filter)
374
368
  else:
375
- _print_vault_v1(info, chain_id, market_filter_norm)
369
+ _print_vault_v1(info, chain_id, market_filter)
376
370
 
377
371
 
378
372
  def _print_vault_v2(
379
- vault: VaultV2Info, chain_id: ChainId, market_filter: str | None
373
+ vault: VaultV2Info, chain_id: ChainId, market_filter: MorphoBlueMarketId | None
380
374
  ) -> None:
381
375
  chain_name = CHAIN_NAMES.get(chain_id, str(chain_id))
382
376
  click.secho(f"Morpho Vault V2 — {vault.name} ({vault.symbol})", bold=True)
@@ -433,9 +427,7 @@ def _print_vault_v2(
433
427
  market_caps = [c for c in vault.caps if c.cap_type == "MarketV1"]
434
428
  other_caps = [c for c in vault.caps if c.cap_type != "MarketV1"]
435
429
  if market_filter:
436
- market_caps = [
437
- c for c in market_caps if (c.market_id or "").lower() == market_filter
438
- ]
430
+ market_caps = [c for c in market_caps if c.market_id == market_filter]
439
431
 
440
432
  click.secho(f"Morpho Blue market caps ({len(market_caps)}):", bold=True)
441
433
  _print_market_caps(market_caps, vault.asset_symbol, vault.asset_decimals)
@@ -508,7 +500,7 @@ def _print_other_caps(
508
500
 
509
501
 
510
502
  def _print_vault_v1(
511
- vault: VaultV1Info, chain_id: ChainId, market_filter: str | None
503
+ vault: VaultV1Info, chain_id: ChainId, market_filter: MorphoBlueMarketId | None
512
504
  ) -> None:
513
505
  chain_name = CHAIN_NAMES.get(chain_id, str(chain_id))
514
506
  public_allocator = PUBLIC_ALLOCATOR_ADDRESSES.get(chain_id)
@@ -552,7 +544,7 @@ def _print_vault_v1(
552
544
 
553
545
  allocations = vault.allocations
554
546
  if market_filter:
555
- allocations = [a for a in allocations if a.market_id.lower() == market_filter]
547
+ allocations = [a for a in allocations if a.market_id == market_filter]
556
548
  click.secho(f"Allocations ({len(allocations)}):", bold=True)
557
549
  if not allocations:
558
550
  click.echo(" (none)")
@@ -17,11 +17,11 @@ from ipor_fusion.readers.morpho import (
17
17
  MorphoMarketParams,
18
18
  MorphoMarketRates,
19
19
  )
20
- from ipor_fusion.types import ChainId
20
+ from ipor_fusion.types import ChainId, MorphoBlueMarketId
21
21
 
22
22
 
23
23
  def build_morpho_blue_market_json(
24
- market_id: str,
24
+ market_id: MorphoBlueMarketId,
25
25
  chain_id: ChainId,
26
26
  params: MorphoMarketParams,
27
27
  state: MorphoMarket,
@@ -36,7 +36,7 @@ def build_morpho_blue_market_json(
36
36
  given, or ``api_error`` when that lookup failed. ``market_id`` is the
37
37
  64-hex-char id without ``0x``."""
38
38
  out: dict = {
39
- "market_id": "0x" + market_id,
39
+ "market_id": market_id,
40
40
  "chain_id": chain_id,
41
41
  "public_allocator": public_allocator,
42
42
  "market_params": {
@@ -55,7 +55,7 @@ from ipor_fusion.morpho_api import (
55
55
  )
56
56
  from ipor_fusion.readers.morpho import MorphoReader, morpho_blue_address
57
57
  from ipor_fusion.readers.oracle_mapping import build_oracle_mapping
58
- from ipor_fusion.types import ChainId, MorphoBlueMarketId
58
+ from ipor_fusion.types import ChainId, parse_morpho_market_id
59
59
  from ipor_fusion.validation import validate_vault
60
60
  from ipor_fusion.vault_data import (
61
61
  fetch_deployment_info,
@@ -580,16 +580,11 @@ def market_morpho_blue(
580
580
  block_number: Block number for on-chain reads (latest if 0).
581
581
  no_api: Skip Morpho API call (no vault discovery / PublicAllocator info).
582
582
  """
583
- raw = market_id.strip().removeprefix("0x").removeprefix("0X").lower()
584
- if len(raw) != 64 or not all(c in "0123456789abcdef" for c in raw):
585
- raise ValueError(
586
- f"invalid Morpho market ID (expected 32-byte hex): {market_id}"
587
- )
583
+ mid = parse_morpho_market_id(market_id)
588
584
 
589
585
  cfg = load_config()
590
586
  ctx, _ = _build_ctx(cfg, chain_id, block_number)
591
587
  reader = MorphoReader(ctx, morpho_blue_address(chain_id))
592
- mid = MorphoBlueMarketId(raw)
593
588
  params, state = reader.require_market(mid)
594
589
  rates = reader.rates_from(state, params)
595
590
 
@@ -597,13 +592,13 @@ def market_morpho_blue(
597
592
  api_error: str | None = None
598
593
  if not no_api:
599
594
  try:
600
- api_market = fetch_market(raw, chain_id)
595
+ api_market = fetch_market(mid, chain_id)
601
596
  except MorphoApiError as exc:
602
597
  api_error = str(exc)
603
598
 
604
599
  public_allocator = PUBLIC_ALLOCATOR_ADDRESSES.get(chain_id)
605
600
  payload = build_morpho_blue_market_json(
606
- raw, chain_id, params, state, rates, api_market, api_error, public_allocator
601
+ mid, chain_id, params, state, rates, api_market, api_error, public_allocator
607
602
  )
608
603
  return MorphoBlueMarketResponse.model_validate(payload)
609
604
 
@@ -17,7 +17,7 @@ from eth_abi import decode, encode
17
17
  from eth_utils import keccak
18
18
  from web3 import Web3
19
19
 
20
- from ipor_fusion.types import ChainId
20
+ from ipor_fusion.types import ChainId, MorphoBlueMarketId, parse_morpho_market_id
21
21
 
22
22
  MORPHO_API_URL = "https://blue-api.morpho.org/graphql"
23
23
 
@@ -113,7 +113,7 @@ class VaultAllocation:
113
113
  class MorphoApiMarket:
114
114
  """Snapshot of a Morpho Blue market plus the vaults supplying to it."""
115
115
 
116
- market_id: str
116
+ market_id: MorphoBlueMarketId
117
117
  lltv: int
118
118
  loan_token: str
119
119
  loan_symbol: str
@@ -139,7 +139,7 @@ class MorphoApiError(RuntimeError):
139
139
 
140
140
 
141
141
  def fetch_market(
142
- market_id: str, chain_id: ChainId, *, timeout: float = 10.0
142
+ market_id: MorphoBlueMarketId, chain_id: ChainId, *, timeout: float = 10.0
143
143
  ) -> MorphoApiMarket:
144
144
  """Fetch a Morpho Blue market with its supplying vaults from the public API.
145
145
 
@@ -147,8 +147,6 @@ def fetch_market(
147
147
  ID (1 = mainnet, 8453 = base, ...). Raises `MorphoApiError` on network failure
148
148
  or if the market is not indexed.
149
149
  """
150
- if not market_id.startswith("0x"):
151
- market_id = "0x" + market_id
152
150
  payload = json.dumps(
153
151
  {
154
152
  "query": _MARKET_QUERY,
@@ -182,7 +180,7 @@ def fetch_market(
182
180
  def _parse_market(raw: dict) -> MorphoApiMarket:
183
181
  state = raw.get("state") or {}
184
182
  return MorphoApiMarket(
185
- market_id=raw["marketId"],
183
+ market_id=_api_market_id(raw.get("marketId")),
186
184
  lltv=int(raw["lltv"]),
187
185
  loan_token=raw["loanAsset"]["address"],
188
186
  loan_symbol=raw["loanAsset"]["symbol"],
@@ -282,7 +280,7 @@ class VaultV2Cap:
282
280
  absolute_cap: int
283
281
  relative_cap_wad: int
284
282
  allocation: int
285
- market_id: str | None
283
+ market_id: MorphoBlueMarketId | None
286
284
  loan_token: str | None
287
285
  collateral_token: str | None
288
286
  oracle: str | None
@@ -337,7 +335,7 @@ class VaultV2Info:
337
335
  class VaultV1MarketAllocation:
338
336
  """A MetaMorpho V1 allocation entry."""
339
337
 
340
- market_id: str
338
+ market_id: MorphoBlueMarketId
341
339
  lltv: int
342
340
  loan_symbol: str
343
341
  loan_decimals: int
@@ -550,9 +548,18 @@ def _decode_market_v1_id_data(
550
548
  )
551
549
 
552
550
 
551
+ def _api_market_id(value: object) -> MorphoBlueMarketId:
552
+ try:
553
+ return parse_morpho_market_id(str(value or ""))
554
+ except ValueError as exc:
555
+ raise MorphoApiError(
556
+ f"Morpho API returned an invalid market id: {value!r}"
557
+ ) from exc
558
+
559
+
553
560
  def _morpho_blue_market_id(
554
561
  loan: str, collateral: str, oracle: str, irm: str, lltv: int
555
- ) -> str:
562
+ ) -> MorphoBlueMarketId:
556
563
  """Compute a Morpho Blue market unique key from MarketParams.
557
564
 
558
565
  `marketId = keccak256(abi.encode(loanToken, collateralToken, oracle, irm, lltv))`
@@ -562,7 +569,7 @@ def _morpho_blue_market_id(
562
569
  ["address", "address", "address", "address", "uint256"],
563
570
  [loan, collateral, oracle, irm, lltv],
564
571
  )
565
- return "0x" + keccak(encoded).hex()
572
+ return parse_morpho_market_id(keccak(encoded))
566
573
 
567
574
 
568
575
  def _parse_vault_v1(raw: dict) -> VaultV1Info:
@@ -576,7 +583,7 @@ def _parse_vault_v1(raw: dict) -> VaultV1Info:
576
583
  collat_asset = market.get("collateralAsset") or {}
577
584
  allocations.append(
578
585
  VaultV1MarketAllocation(
579
- market_id=market.get("marketId") or "",
586
+ market_id=_api_market_id(market.get("marketId")),
580
587
  lltv=int(market.get("lltv") or 0),
581
588
  loan_symbol=loan_asset.get("symbol") or "",
582
589
  loan_decimals=int(loan_asset.get("decimals") or 0),
@@ -626,14 +633,14 @@ def _parse_vault_v1(raw: dict) -> VaultV1Info:
626
633
  )
627
634
 
628
635
 
629
- def _parse_vault(raw: dict, market_id: str) -> VaultAllocation:
636
+ def _parse_vault(raw: dict, market_id: MorphoBlueMarketId) -> VaultAllocation:
630
637
  asset = raw.get("asset") or {}
631
638
  state = raw.get("state") or {}
632
639
  allocation_for_market: dict = next(
633
640
  (
634
641
  a
635
642
  for a in (state.get("allocation") or [])
636
- if (a.get("market") or {}).get("marketId", "").lower() == market_id.lower()
643
+ if (a.get("market") or {}).get("marketId", "").lower() == market_id
637
644
  ),
638
645
  {},
639
646
  )
@@ -29,7 +29,12 @@ from ipor_fusion.readers.morpho import (
29
29
  morpho_blue_address,
30
30
  )
31
31
  from ipor_fusion.substrates import decode_substrate, market_name
32
- from ipor_fusion.types import ChainId, MarketId, MorphoBlueMarketId
32
+ from ipor_fusion.types import (
33
+ ChainId,
34
+ MarketId,
35
+ MorphoBlueMarketId,
36
+ parse_morpho_market_id,
37
+ )
33
38
 
34
39
  _logger = logging.getLogger(__name__)
35
40
 
@@ -374,9 +379,8 @@ def _collect_lending_markets(
374
379
  if mid in MORPHO_MARKET_IDS:
375
380
  substrates = market_substrates.get(mid, [])
376
381
  for sub in substrates:
377
- hex_str = sub.hex()
378
- if len(hex_str) == 64:
379
- markets.morpho.append((mid, name, MorphoBlueMarketId(hex_str)))
382
+ if len(sub) == 32:
383
+ markets.morpho.append((mid, name, parse_morpho_market_id(sub)))
380
384
  elif mid in AAVE_V3_MARKET_IDS:
381
385
  if aave_pool := aave_pools.get(mid):
382
386
  markets.aave_v3.append((mid, name, aave_pool))
@@ -13,7 +13,9 @@ Tick = NewType("Tick", int)
13
13
  Liquidity = NewType("Liquidity", int)
14
14
  RoleId = NewType("RoleId", int)
15
15
  ChainId = NewType("ChainId", int)
16
- # Hex string without 0x prefix (e.g. "abc123..." not "0xabc123...")
16
+ # A Morpho Blue market id (bytes32) in its canonical spelling: "0x" and 64
17
+ # lowercase hex digits, as the Morpho API reports it. Build one with
18
+ # parse_morpho_market_id.
17
19
  MorphoBlueMarketId = NewType("MorphoBlueMarketId", str)
18
20
  # Crosschain identifiers (bytes32 unless noted), named after the contracts' fields.
19
21
  CommandId = NewType("CommandId", bytes)
@@ -98,3 +100,16 @@ Period.MINUTE = Period(60 * Period.SECOND)
98
100
  Period.HOUR = Period(60 * Period.MINUTE)
99
101
  Period.DAY = Period(24 * Period.HOUR)
100
102
  Period.WEEK = Period(7 * Period.DAY)
103
+
104
+
105
+ def parse_morpho_market_id(value: str | bytes) -> MorphoBlueMarketId:
106
+ """A Morpho Blue market id from 32 raw bytes or from hex with or without
107
+ ``0x``, in any case; raises ``ValueError`` on anything else."""
108
+ if isinstance(value, bytes):
109
+ if len(value) != 32:
110
+ raise ValueError(f"invalid Morpho market ID (expected 32 bytes): {value!r}")
111
+ return MorphoBlueMarketId("0x" + value.hex())
112
+ digits = value.strip().removeprefix("0x").removeprefix("0X").lower()
113
+ if len(digits) != 64 or any(c not in "0123456789abcdef" for c in digits):
114
+ raise ValueError(f"invalid Morpho market ID (expected 32-byte hex): {value}")
115
+ return MorphoBlueMarketId("0x" + digits)
@@ -78,7 +78,12 @@ from ipor_fusion.readers.unpriceable import (
78
78
  MiddlewarePricedToken,
79
79
  fetch_unpriceable_priced_tokens,
80
80
  )
81
- from ipor_fusion.types import ChainId, MarketId, MorphoBlueMarketId
81
+ from ipor_fusion.types import (
82
+ ChainId,
83
+ MarketId,
84
+ MorphoBlueMarketId,
85
+ parse_morpho_market_id,
86
+ )
82
87
 
83
88
  T = TypeVar("T")
84
89
 
@@ -484,9 +489,7 @@ def _collect_morpho_substrates(
484
489
  for mid, subs in market_substrates.items():
485
490
  if mid not in MORPHO_MARKET_IDS:
486
491
  continue
487
- morpho_ids = [
488
- MorphoBlueMarketId(sub.hex()) for sub in subs if len(sub.hex()) == 64
489
- ]
492
+ morpho_ids = [parse_morpho_market_id(sub) for sub in subs if len(sub) == 32]
490
493
  if morpho_ids:
491
494
  result[mid] = morpho_ids
492
495
  return result
@@ -196,7 +196,9 @@ class TestMorphoBlueCommand:
196
196
 
197
197
  assert result.exit_code != 0
198
198
  assert isinstance(result.exception, MorphoMarketNotFoundError)
199
- assert f"market {MARKET_ID} does not exist on chain 1" in str(result.exception)
199
+ assert f"market 0x{MARKET_ID} does not exist on chain 1" in str(
200
+ result.exception
201
+ )
200
202
  mock_fetch.assert_not_called()
201
203
 
202
204
  def test_no_api_skips_vault_section(
@@ -19,6 +19,7 @@ from ipor_fusion import (
19
19
  fetch_vault_data,
20
20
  format_market_label,
21
21
  market_name,
22
+ parse_morpho_market_id,
22
23
  validate_vault,
23
24
  )
24
25
  from ipor_fusion.chains import ensure_supported_chain
@@ -31,7 +32,7 @@ from ipor_fusion.crosschain.contracts import (
31
32
  CrosschainFactory,
32
33
  )
33
34
  from ipor_fusion.explorer import get_contract_name
34
- from ipor_fusion.morpho_api import fetch_vault
35
+ from ipor_fusion.morpho_api import fetch_market, fetch_vault
35
36
  from ipor_fusion.readers.morpho import morpho_blue_address
36
37
  from ipor_fusion.readers.unpriceable import fetch_unpriceable_priced_tokens
37
38
  from ipor_fusion.types import (
@@ -74,6 +75,7 @@ def lookups() -> None:
74
75
  get_contract_name(ETHEREUM, VAULT)
75
76
  morpho_blue_address(ETHEREUM)
76
77
  fetch_vault(VAULT, ETHEREUM)
78
+ fetch_market(parse_morpho_market_id("0x" + "ab" * 32), ETHEREUM)
77
79
 
78
80
 
79
81
  def crosschain(