ipor-fusion 4.0.0.dev35__tar.gz → 4.0.0.dev37__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.dev35 → ipor_fusion-4.0.0.dev37}/PKG-INFO +1 -1
  2. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/pyproject.toml +1 -1
  3. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/config_store.py +13 -2
  4. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/market_cmd.py +6 -6
  5. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/vault_cmd.py +1 -1
  6. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/mcp/models.py +1 -1
  7. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/mcp/server.py +5 -1
  8. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/morpho_api.py +65 -43
  9. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/validation.py +3 -3
  10. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/vault_data.py +1 -1
  11. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_mcp_models.py +19 -0
  12. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_mcp_server.py +14 -2
  13. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.claude-plugin/marketplace.json +0 -0
  14. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.claude-plugin/plugin.json +0 -0
  15. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.env.example +0 -0
  16. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.gemini/settings.json +0 -0
  17. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.git-blame-ignore-revs +0 -0
  18. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/cd.yml +0 -0
  19. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/ci.yml +0 -0
  20. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/commit-lint.yml +0 -0
  21. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/notify-slack.yml +0 -0
  22. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/python-build.yml +0 -0
  23. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/python-publish.yml +0 -0
  24. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/release.yml +0 -0
  25. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.github/workflows/report-slack.yml +0 -0
  26. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.gitignore +0 -0
  27. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.mcp.json +0 -0
  28. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.pre-commit-config.yaml +0 -0
  29. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/.python-version +0 -0
  30. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/AGENTS.md +0 -0
  31. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/CHANGELOG.md +0 -0
  32. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/CLAUDE.md +0 -0
  33. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/CODEOWNERS +0 -0
  34. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/LICENSE +0 -0
  35. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/README.md +0 -0
  36. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/build.sh +0 -0
  37. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/CONTRIBUTING.md +0 -0
  38. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/README.md +0 -0
  39. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  40. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  41. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  42. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  43. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/external_state_margin_leg_base.py +0 -0
  44. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/examples/simple_aave_v3_supply_base.py +0 -0
  45. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/pytest.ini +0 -0
  46. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/scripts/commit-check.sh +0 -0
  47. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/scripts/crosschain_readiness.py +0 -0
  48. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/skills/ipor-deploy-vault/SKILL.md +0 -0
  49. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/__init__.py +0 -0
  50. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/about.py +0 -0
  51. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cache.py +0 -0
  52. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/chains.py +0 -0
  53. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/__init__.py +0 -0
  54. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  55. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/config_cmd.py +0 -0
  56. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/main.py +0 -0
  57. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/options.py +0 -0
  58. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  59. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/vault_health.py +0 -0
  60. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  61. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  62. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  63. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/config/__init__.py +0 -0
  64. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/config/roles.py +0 -0
  65. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/__init__.py +0 -0
  66. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/access.py +0 -0
  67. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/address.py +0 -0
  68. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/context.py +0 -0
  69. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/contract.py +0 -0
  70. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/erc20.py +0 -0
  71. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/external_state_executor.py +0 -0
  72. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/fee_manager.py +0 -0
  73. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/fuse_action.py +0 -0
  74. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/fusion_factory.py +0 -0
  75. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/logs.py +0 -0
  76. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/multicall.py +0 -0
  77. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/oracle.py +0 -0
  78. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/plasma_vault.py +0 -0
  79. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/rewards_manager.py +0 -0
  80. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/simulation.py +0 -0
  81. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  82. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/__init__.py +0 -0
  83. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  84. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  85. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  86. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  87. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  88. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  89. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  90. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  91. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  92. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/contracts.py +0 -0
  93. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/discovery.py +0 -0
  94. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/errors.py +0 -0
  95. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/lane.py +0 -0
  96. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/logs.py +0 -0
  97. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/messages.py +0 -0
  98. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/simulation.py +0 -0
  99. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  100. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  101. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  102. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  103. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  104. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/crosschain/transport.py +0 -0
  105. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/dependency_graph.py +0 -0
  106. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/entry_cli.py +0 -0
  107. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/entry_mcp.py +0 -0
  108. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/errors.py +0 -0
  109. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/explorer.py +0 -0
  110. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/field_docs.py +0 -0
  111. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/__init__.py +0 -0
  112. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  113. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/async_action.py +0 -0
  114. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/base.py +0 -0
  115. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  116. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  117. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  118. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  119. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  120. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/erc4626.py +0 -0
  121. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  122. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/events.py +0 -0
  123. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/external_state.py +0 -0
  124. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  125. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  126. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  127. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/hypercore.py +0 -0
  128. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  129. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/merkl.py +0 -0
  130. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/morpho.py +0 -0
  131. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  132. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  133. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/fuses/universal.py +0 -0
  134. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/guide/__init__.py +0 -0
  135. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/guide/architecture.md +0 -0
  136. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/guide/glossary.md +0 -0
  137. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/guide/invariants.md +0 -0
  138. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/guide/quickstart.md +0 -0
  139. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/market_ids.py +0 -0
  140. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/market_info.py +0 -0
  141. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/mcp/__init__.py +0 -0
  142. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/mcp/guide.py +0 -0
  143. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/py.typed +0 -0
  144. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/__init__.py +0 -0
  145. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/aave_v3.py +0 -0
  146. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/aave_v4.py +0 -0
  147. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/compound_v3.py +0 -0
  148. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/hypercore.py +0 -0
  149. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  150. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/lending_health.py +0 -0
  151. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/morpho.py +0 -0
  152. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  153. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/position_manager.py +0 -0
  154. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  155. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  156. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/readers/unpriceable.py +0 -0
  157. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/substrates.py +0 -0
  158. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/types.py +0 -0
  159. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/src/ipor_fusion/vault_info.py +0 -0
  160. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/UniswapV3UniversalRouter.py +0 -0
  161. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_crosschain.py +0 -0
  162. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_crosschain_lifecycle.py +0 -0
  163. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_crosschain_recovery.py +0 -0
  164. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_euler_v2.py +0 -0
  165. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_foundry.py +0 -0
  166. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_guide.py +0 -0
  167. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_hypercore_model.py +0 -0
  168. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_hypercore_shadow.py +0 -0
  169. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_multicall.py +0 -0
  170. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_simulate.py +0 -0
  171. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/_validation.py +0 -0
  172. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/addresses.py +0 -0
  173. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/conftest.py +0 -0
  174. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/constants.py +0 -0
  175. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  176. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/fixtures/hypercore_market55.json +0 -0
  177. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/fixtures/vault_info_golden/clean.json +0 -0
  178. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  179. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/fixtures/vault_info_golden/rich.json +0 -0
  180. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  181. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_about.py +0 -0
  182. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_access_manager.py +0 -0
  183. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cache.py +0 -0
  184. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_chains.py +0 -0
  185. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_changelog.py +0 -0
  186. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_commands.py +0 -0
  187. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_config_store.py +0 -0
  188. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_helpers.py +0 -0
  189. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_hypercore.py +0 -0
  190. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_lending_health.py +0 -0
  191. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_market_morpho_blue.py +0 -0
  192. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_oracle_mapping.py +0 -0
  193. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_pending_requests_scan.py +0 -0
  194. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_role_accounts.py +0 -0
  195. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_vault_info_golden.py +0 -0
  196. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_vault_predates_getters.py +0 -0
  197. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_cli_vault_validate.py +0 -0
  198. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_contract_call.py +0 -0
  199. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_contract_encoder.py +0 -0
  200. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_ccip_events.py +0 -0
  201. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_ccip_indexer.py +0 -0
  202. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_ccip_quote.py +0 -0
  203. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_chainlink.py +0 -0
  204. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_contracts.py +0 -0
  205. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_encoding.py +0 -0
  206. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_errors.py +0 -0
  207. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_lane.py +0 -0
  208. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_layering.py +0 -0
  209. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_readiness.py +0 -0
  210. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_readiness_report.py +0 -0
  211. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_crosschain_transport.py +0 -0
  212. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_erc20.py +0 -0
  213. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_examples_composed_hypercore.py +0 -0
  214. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_examples_crosschain.py +0 -0
  215. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  216. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_examples_vaults.py +0 -0
  217. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_explorer.py +0 -0
  218. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_external_state_events.py +0 -0
  219. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_external_state_executor.py +0 -0
  220. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_fee_manager.py +0 -0
  221. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_fuse_encoding.py +0 -0
  222. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_guide.py +0 -0
  223. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_hypercore_deployment.py +0 -0
  224. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_hypercore_events.py +0 -0
  225. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_hypercore_flow.py +0 -0
  226. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_hypercore_live.py +0 -0
  227. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_hypercore_outcome.py +0 -0
  228. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_hypercore_readers.py +0 -0
  229. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_hypercore_simulation.py +0 -0
  230. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_logs.py +0 -0
  231. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_mcp_hypercore.py +0 -0
  232. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_mcp_vault_validate.py +0 -0
  233. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_multicall.py +0 -0
  234. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_oracle.py +0 -0
  235. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_oracle_mapping.py +0 -0
  236. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_plasma_vault.py +0 -0
  237. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_reader_encoding.py +0 -0
  238. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_readiness_selection.py +0 -0
  239. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_revert_decoding.py +0 -0
  240. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_rewards_manager.py +0 -0
  241. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_roles.py +0 -0
  242. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_sdk_layering.py +0 -0
  243. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  244. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  245. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  246. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_crosschain_canary.py +0 -0
  247. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_crosschain_hypercore.py +0 -0
  248. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  249. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  250. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  251. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  252. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  253. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  254. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  255. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_examples_arbitrum.py +0 -0
  256. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_examples_base.py +0 -0
  257. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_examples_crosschain.py +0 -0
  258. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_external_state_executor_base.py +0 -0
  259. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  260. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  261. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_morpho_blue.py +0 -0
  263. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_pilot_v3.py +0 -0
  264. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  265. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  266. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  267. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_readers_integration.py +0 -0
  269. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_release_funds.py +0 -0
  270. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_swap_on_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_solidity_compiler.py +0 -0
  273. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_solidity_mirrors.py +0 -0
  274. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_substrates.py +0 -0
  275. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_types_and_events.py +0 -0
  276. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_unpriceable_tokens.py +0 -0
  277. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_validation.py +0 -0
  278. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_vault_dep_graph.py +0 -0
  279. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_vault_info_onchain_base.py +0 -0
  280. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  281. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_vault_simulator_blocks.py +0 -0
  282. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_web3_context.py +0 -0
  283. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/test_withdraw_manager.py +0 -0
  284. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/tests/typed_client/consumer.py +0 -0
  285. {ipor_fusion-4.0.0.dev35 → ipor_fusion-4.0.0.dev37}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev35
3
+ Version: 4.0.0.dev37
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.dev35"
3
+ version = "4.0.0.dev37"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -6,7 +6,9 @@ from pathlib import Path
6
6
  from urllib.parse import urlsplit
7
7
 
8
8
  import click
9
- from pydantic import BaseModel, Field, ValidationError, model_validator
9
+ from eth_typing import ChecksumAddress
10
+ from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator
11
+ from web3 import Web3
10
12
 
11
13
  from ipor_fusion.types import ChainId
12
14
 
@@ -22,10 +24,19 @@ CONFIG_VERSION = 1
22
24
 
23
25
 
24
26
  class VaultEntry(BaseModel):
25
- address: str
27
+ address: ChecksumAddress
26
28
  label: str
27
29
  chain_id: ChainId
28
30
 
31
+ @field_validator("address", mode="before")
32
+ @classmethod
33
+ def _checksum(cls, value: object) -> object:
34
+ # Configs written before 4.0 may hold any spelling; a value that is not
35
+ # an address is left for the commands to report, not for every load to fail.
36
+ if isinstance(value, str) and Web3.is_address(value):
37
+ return Web3.to_checksum_address(value)
38
+ return value
39
+
29
40
 
30
41
  class FusionConfig(BaseModel):
31
42
  providers: dict[str, str] = Field(default_factory=dict)
@@ -389,12 +389,12 @@ def _print_vault_v2(
389
389
  click.echo("")
390
390
  click.echo("Roles:")
391
391
  click.echo(f" Owner: {vault.owner}")
392
- click.echo(f" Curator: {vault.curator}")
392
+ click.echo(f" Curator: {vault.curator or ''}")
393
393
  click.echo(
394
- f" Performance fee: {vault.performance_fee * 100:.2f}% → {vault.performance_fee_recipient}"
394
+ f" Performance fee: {vault.performance_fee * 100:.2f}% → {vault.performance_fee_recipient or ''}"
395
395
  )
396
396
  click.echo(
397
- f" Management fee: {vault.management_fee * 100:.2f}% → {vault.management_fee_recipient}"
397
+ f" Management fee: {vault.management_fee * 100:.2f}% → {vault.management_fee_recipient or ''}"
398
398
  )
399
399
  click.echo(f" Allocators ({len(vault.allocators)}):")
400
400
  for addr in vault.allocators or ["(none)"]:
@@ -515,13 +515,13 @@ def _print_vault_v1(
515
515
  f"({_format_amount(vault.total_assets, vault.asset_decimals)} total)"
516
516
  )
517
517
  click.echo(
518
- f" Performance fee: {vault.fee_wad / WAD * 100:.2f}% → {vault.fee_recipient}"
518
+ f" Performance fee: {vault.fee_wad / WAD * 100:.2f}% → {vault.fee_recipient or ''}"
519
519
  )
520
520
  click.echo("")
521
521
  click.echo("Roles:")
522
522
  click.echo(f" Owner: {vault.owner}")
523
- click.echo(f" Curator: {vault.curator}")
524
- click.echo(f" Guardian: {vault.guardian}")
523
+ click.echo(f" Curator: {vault.curator or ''}")
524
+ click.echo(f" Guardian: {vault.guardian or ''}")
525
525
  click.echo(f" Allocators ({len(vault.allocators)}):")
526
526
  for addr in vault.allocators or ["(none)"]:
527
527
  tag = (
@@ -95,7 +95,7 @@ def vault() -> None:
95
95
  default=None,
96
96
  help="Chain ID or name (auto-detected when only one provider is configured).",
97
97
  )
98
- def add(address: str, label: str | None, chain_id: ChainId | None) -> None:
98
+ def add(address: ChecksumAddress, label: str | None, chain_id: ChainId | None) -> None:
99
99
  """Save a vault to the config."""
100
100
  cfg = load_config()
101
101
 
@@ -944,7 +944,7 @@ class MetaMorphoVaultResponse(_Base):
944
944
  allocations: list[dict[str, Any]] | None = None
945
945
  # shared
946
946
  owner: str
947
- curator: str
947
+ curator: str | None = None
948
948
  allocators: list[str]
949
949
 
950
950
 
@@ -464,7 +464,11 @@ def vault_add(
464
464
  save_config(cfg)
465
465
  return ActionResult(message=f"Vault {label} ({address}) updated.")
466
466
 
467
- cfg.vaults.append(VaultEntry(address=address, label=label, chain_id=chain_id))
467
+ cfg.vaults.append(
468
+ VaultEntry(
469
+ address=Web3.to_checksum_address(address), label=label, chain_id=chain_id
470
+ )
471
+ )
468
472
  save_config(cfg)
469
473
  return ActionResult(message=f"Vault {label} ({address}) added.")
470
474
 
@@ -14,6 +14,7 @@ from urllib.error import URLError
14
14
  from urllib.request import Request, urlopen
15
15
 
16
16
  from eth_abi import decode, encode
17
+ from eth_typing import ChecksumAddress
17
18
  from eth_utils import keccak
18
19
  from web3 import Web3
19
20
 
@@ -97,7 +98,7 @@ class VaultFlowCap:
97
98
  class VaultAllocation:
98
99
  """A MetaMorpho vault's exposure to a single Morpho Blue market."""
99
100
 
100
- vault_address: str
101
+ vault_address: ChecksumAddress
101
102
  vault_name: str
102
103
  vault_symbol: str
103
104
  asset_symbol: str
@@ -115,10 +116,10 @@ class MorphoApiMarket:
115
116
 
116
117
  market_id: MorphoBlueMarketId
117
118
  lltv: int
118
- loan_token: str
119
+ loan_token: ChecksumAddress
119
120
  loan_symbol: str
120
121
  loan_decimals: int
121
- collateral_token: str
122
+ collateral_token: ChecksumAddress
122
123
  collateral_symbol: str
123
124
  collateral_decimals: int
124
125
  oracle: str
@@ -182,10 +183,10 @@ def _parse_market(raw: dict) -> MorphoApiMarket:
182
183
  return MorphoApiMarket(
183
184
  market_id=_api_market_id(raw.get("marketId")),
184
185
  lltv=int(raw["lltv"]),
185
- loan_token=raw["loanAsset"]["address"],
186
+ loan_token=_api_address(raw["loanAsset"]["address"]),
186
187
  loan_symbol=raw["loanAsset"]["symbol"],
187
188
  loan_decimals=int(raw["loanAsset"]["decimals"]),
188
- collateral_token=raw["collateralAsset"]["address"],
189
+ collateral_token=_api_address(raw["collateralAsset"]["address"]),
189
190
  collateral_symbol=raw["collateralAsset"]["symbol"],
190
191
  collateral_decimals=int(raw["collateralAsset"]["decimals"]),
191
192
  oracle=raw["oracle"]["address"],
@@ -281,8 +282,8 @@ class VaultV2Cap:
281
282
  relative_cap_wad: int
282
283
  allocation: int
283
284
  market_id: MorphoBlueMarketId | None
284
- loan_token: str | None
285
- collateral_token: str | None
285
+ loan_token: ChecksumAddress | None
286
+ collateral_token: ChecksumAddress | None
286
287
  oracle: str | None
287
288
  irm: str | None
288
289
  lltv: int | None
@@ -297,20 +298,22 @@ class VaultV2Cap:
297
298
  class VaultV2Adapter:
298
299
  """An adapter contract attached to a Vault V2 (e.g. Morpho adapter)."""
299
300
 
300
- address: str
301
+ address: ChecksumAddress
301
302
  adapter_type: str
302
303
  assets: int
303
- inner_vault: str | None # for MorphoVaultV2Adapter: the underlying market vault
304
+ inner_vault: (
305
+ ChecksumAddress | None
306
+ ) # for MorphoVaultV2Adapter: the underlying market vault
304
307
 
305
308
 
306
309
  @dataclass(slots=True)
307
310
  class VaultV2Info:
308
311
  """Snapshot of a Morpho Vault V2."""
309
312
 
310
- address: str
313
+ address: ChecksumAddress
311
314
  name: str
312
315
  symbol: str
313
- asset_address: str
316
+ asset_address: ChecksumAddress
314
317
  asset_symbol: str
315
318
  asset_decimals: int
316
319
  total_assets: int
@@ -319,13 +322,13 @@ class VaultV2Info:
319
322
  share_price: float
320
323
  max_apy: float
321
324
  performance_fee: float
322
- performance_fee_recipient: str
325
+ performance_fee_recipient: ChecksumAddress | None
323
326
  management_fee: float
324
- management_fee_recipient: str
325
- owner: str
326
- curator: str
327
- allocators: list[str]
328
- sentinels: list[str]
327
+ management_fee_recipient: ChecksumAddress | None
328
+ owner: ChecksumAddress
329
+ curator: ChecksumAddress | None
330
+ allocators: list[ChecksumAddress]
331
+ sentinels: list[ChecksumAddress]
329
332
  liquidity_adapter: VaultV2Adapter | None
330
333
  adapters: list[VaultV2Adapter]
331
334
  caps: list[VaultV2Cap] = field(default_factory=list)
@@ -355,19 +358,19 @@ class VaultV1MarketAllocation:
355
358
  class VaultV1Info:
356
359
  """Snapshot of a MetaMorpho V1 vault."""
357
360
 
358
- address: str
361
+ address: ChecksumAddress
359
362
  name: str
360
363
  symbol: str
361
- asset_address: str
364
+ asset_address: ChecksumAddress
362
365
  asset_symbol: str
363
366
  asset_decimals: int
364
367
  total_assets: int
365
368
  fee_wad: int
366
- owner: str
367
- curator: str
368
- guardian: str
369
- fee_recipient: str
370
- allocators: list[str]
369
+ owner: ChecksumAddress
370
+ curator: ChecksumAddress | None
371
+ guardian: ChecksumAddress | None
372
+ fee_recipient: ChecksumAddress | None
373
+ allocators: list[ChecksumAddress]
371
374
  allocations: list[VaultV1MarketAllocation]
372
375
  public_allocator: VaultFlowCap | None # vault-level (no flowCap filtering)
373
376
  public_allocator_flow_caps: dict[str, tuple[int, int]] # marketId -> (maxIn,maxOut)
@@ -431,10 +434,10 @@ def _parse_vault_v2(raw: dict) -> VaultV2Info:
431
434
  ]
432
435
  caps = [_parse_v2_cap(c) for c in (raw.get("caps") or {}).get("items", []) if c]
433
436
  return VaultV2Info(
434
- address=raw["address"],
437
+ address=_api_address(raw.get("address")),
435
438
  name=raw.get("name") or "",
436
439
  symbol=raw.get("symbol") or "",
437
- asset_address=asset.get("address") or "",
440
+ asset_address=_api_address(asset.get("address")),
438
441
  asset_symbol=asset.get("symbol") or "",
439
442
  asset_decimals=int(asset.get("decimals") or 0),
440
443
  total_assets=int(raw.get("totalAssets") or 0),
@@ -443,18 +446,22 @@ def _parse_vault_v2(raw: dict) -> VaultV2Info:
443
446
  share_price=float(raw.get("sharePrice") or 0.0),
444
447
  max_apy=float(raw.get("maxApy") or 0.0),
445
448
  performance_fee=float(raw.get("performanceFee") or 0.0),
446
- performance_fee_recipient=raw.get("performanceFeeRecipient") or "",
449
+ performance_fee_recipient=_api_optional_address(
450
+ raw.get("performanceFeeRecipient")
451
+ ),
447
452
  management_fee=float(raw.get("managementFee") or 0.0),
448
- management_fee_recipient=raw.get("managementFeeRecipient") or "",
449
- owner=(raw.get("owner") or {}).get("address") or "",
450
- curator=(raw.get("curator") or {}).get("address") or "",
453
+ management_fee_recipient=_api_optional_address(
454
+ raw.get("managementFeeRecipient")
455
+ ),
456
+ owner=_api_address((raw.get("owner") or {}).get("address")),
457
+ curator=_api_optional_address((raw.get("curator") or {}).get("address")),
451
458
  allocators=[
452
- a["allocator"]["address"]
459
+ _api_address(a["allocator"]["address"])
453
460
  for a in raw.get("allocators") or []
454
461
  if (a.get("allocator") or {}).get("address")
455
462
  ],
456
463
  sentinels=[
457
- s["sentinel"]["address"]
464
+ _api_address(s["sentinel"]["address"])
458
465
  for s in raw.get("sentinels") or []
459
466
  if (s.get("sentinel") or {}).get("address")
460
467
  ],
@@ -466,10 +473,10 @@ def _parse_vault_v2(raw: dict) -> VaultV2Info:
466
473
 
467
474
  def _parse_v2_adapter(raw: dict) -> VaultV2Adapter:
468
475
  return VaultV2Adapter(
469
- address=raw.get("address") or "",
476
+ address=_api_address(raw.get("address")),
470
477
  adapter_type=raw.get("type") or raw.get("__typename") or "",
471
478
  assets=int(raw.get("assets") or 0),
472
- inner_vault=(raw.get("metaMorpho") or {}).get("address"),
479
+ inner_vault=_api_optional_address((raw.get("metaMorpho") or {}).get("address")),
473
480
  )
474
481
 
475
482
 
@@ -500,7 +507,10 @@ def _parse_v2_cap(raw: dict) -> VaultV2Cap:
500
507
 
501
508
  def _decode_market_v1_id_data(
502
509
  id_data: str,
503
- ) -> tuple[str, str, str, str, int] | None:
510
+ ) -> (
511
+ tuple[ChecksumAddress, ChecksumAddress, ChecksumAddress, ChecksumAddress, int]
512
+ | None
513
+ ):
504
514
  """Decode a Vault V2 `MarketV1`-type cap idData into MarketParams.
505
515
 
506
516
  Vault V2 encodes the cap idData as `abi.encode(string idType, address adapter,
@@ -548,6 +558,16 @@ def _decode_market_v1_id_data(
548
558
  )
549
559
 
550
560
 
561
+ def _api_address(value: object) -> ChecksumAddress:
562
+ if not isinstance(value, str) or not Web3.is_address(value):
563
+ raise MorphoApiError(f"Morpho API returned an invalid address: {value!r}")
564
+ return Web3.to_checksum_address(value)
565
+
566
+
567
+ def _api_optional_address(value: object) -> ChecksumAddress | None:
568
+ return None if value is None or value == "" else _api_address(value)
569
+
570
+
551
571
  def _api_market_id(value: object) -> MorphoBlueMarketId:
552
572
  try:
553
573
  return parse_morpho_market_id(str(value or ""))
@@ -612,20 +632,22 @@ def _parse_vault_v1(raw: dict) -> VaultV1Info:
612
632
  flow_caps[mid] = (int(fc.get("maxIn") or 0), int(fc.get("maxOut") or 0))
613
633
 
614
634
  return VaultV1Info(
615
- address=raw["address"],
635
+ address=_api_address(raw.get("address")),
616
636
  name=raw.get("name") or "",
617
637
  symbol=raw.get("symbol") or "",
618
- asset_address=asset.get("address") or "",
638
+ asset_address=_api_address(asset.get("address")),
619
639
  asset_symbol=asset.get("symbol") or "",
620
640
  asset_decimals=int(asset.get("decimals") or 0),
621
641
  total_assets=int(state.get("totalAssets") or 0),
622
642
  fee_wad=int(state.get("fee") or 0),
623
- owner=state.get("owner") or "",
624
- curator=state.get("curator") or "",
625
- guardian=state.get("guardian") or "",
626
- fee_recipient=state.get("feeRecipient") or "",
643
+ owner=_api_address(state.get("owner")),
644
+ curator=_api_optional_address(state.get("curator")),
645
+ guardian=_api_optional_address(state.get("guardian")),
646
+ fee_recipient=_api_optional_address(state.get("feeRecipient")),
627
647
  allocators=[
628
- a["address"] for a in raw.get("allocators") or [] if a.get("address")
648
+ _api_address(a["address"])
649
+ for a in raw.get("allocators") or []
650
+ if a.get("address")
629
651
  ],
630
652
  allocations=allocations,
631
653
  public_allocator=flow_cap_summary,
@@ -667,7 +689,7 @@ def _parse_vault(raw: dict, market_id: MorphoBlueMarketId) -> VaultAllocation:
667
689
  a["address"] for a in (raw.get("allocators") or []) if a.get("address")
668
690
  ]
669
691
  return VaultAllocation(
670
- vault_address=raw["address"],
692
+ vault_address=_api_address(raw["address"]),
671
693
  vault_name=raw.get("name") or "",
672
694
  vault_symbol=raw.get("symbol") or "",
673
695
  asset_symbol=asset.get("symbol") or "",
@@ -98,7 +98,7 @@ class VaultState(Protocol):
98
98
  @property
99
99
  def block_timestamp(self) -> int: ...
100
100
  @property
101
- def asset(self) -> str: ...
101
+ def asset(self) -> ChecksumAddress: ...
102
102
  @property
103
103
  def asset_symbol(self) -> str: ...
104
104
  @property
@@ -219,11 +219,11 @@ class VaultValidation:
219
219
  found. Raw amounts are in the underlying ``asset``'s decimals.
220
220
  """
221
221
 
222
- vault: str
222
+ vault: ChecksumAddress
223
223
  chain_id: ChainId
224
224
  block_number: int
225
225
  block_timestamp: int
226
- asset: str
226
+ asset: ChecksumAddress
227
227
  asset_symbol: str
228
228
  asset_decimals: int
229
229
  asset_price_usd: float | None
@@ -164,7 +164,7 @@ class VaultData:
164
164
  total_assets: int
165
165
  total_supply: int
166
166
  supply_cap: int
167
- asset: str
167
+ asset: ChecksumAddress
168
168
  asset_symbol: str
169
169
  access_manager: str
170
170
  price_oracle_addr: str
@@ -666,6 +666,25 @@ class TestSimpleResponseContracts:
666
666
  assert result.version == "v2"
667
667
  assert result.fee_wad is None # v1-only stays unset
668
668
 
669
+ def test_meta_morpho_without_a_curator_validates(self):
670
+ d = {
671
+ "version": "v1",
672
+ "chain_id": 1,
673
+ "address": "0xVAULT",
674
+ "name": "v",
675
+ "symbol": "v",
676
+ "asset": {"address": "0xL", "symbol": "USDC", "decimals": 6},
677
+ "total_assets": "1000",
678
+ "owner": "0xOWNER",
679
+ "curator": None,
680
+ "guardian": None,
681
+ "fee_recipient": None,
682
+ "allocators": [],
683
+ }
684
+ result = MetaMorphoVaultResponse.model_validate(d)
685
+ assert result.curator is None
686
+ assert result.guardian is None
687
+
669
688
  def test_meta_morpho_v1_validates(self):
670
689
  d = {
671
690
  "version": "v1",
@@ -331,10 +331,22 @@ class TestVaultAdd:
331
331
  return_value=_config_with_provider(),
332
332
  )
333
333
  def test_add_with_label_and_chain(self, mock_load, mock_save):
334
- result = vault_add(address="0xDEF", label="New Vault", chain_id=1)
334
+ lowercase = "0x43ee0243ea8cf02f7087d8b16c8d2007cc9c7ca2"
335
+ result = vault_add(address=lowercase, label="New Vault", chain_id=1)
335
336
  assert "added" in result.message.lower()
336
337
  saved_cfg = mock_save.call_args[0][0]
337
- assert any(v.address == "0xDEF" for v in saved_cfg.vaults)
338
+ assert any(
339
+ v.address == Web3.to_checksum_address(lowercase) for v in saved_cfg.vaults
340
+ )
341
+
342
+ @patch("ipor_fusion.mcp.server.save_config")
343
+ @patch(
344
+ "ipor_fusion.mcp.server.load_config",
345
+ return_value=_config_with_provider(),
346
+ )
347
+ def test_add_rejects_a_non_address(self, mock_load, mock_save):
348
+ with pytest.raises(ValueError):
349
+ vault_add(address="0xDEF", label="New Vault", chain_id=1)
338
350
 
339
351
  @patch("ipor_fusion.mcp.server.save_config")
340
352
  @patch(