ipor-fusion 4.0.0.dev149__tar.gz → 4.0.0.dev153__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 (301) hide show
  1. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/AGENTS.md +21 -11
  2. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/MIGRATION_4.md +43 -0
  3. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/advanced_euler_v2_credit_market_base.py +2 -2
  5. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/advanced_euler_v2_looping_arbitrum.py +4 -4
  6. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +1 -2
  7. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +2 -2
  8. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/external_state_margin_leg_base.py +2 -2
  9. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/simple_aave_v3_supply_base.py +2 -2
  10. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/pyproject.toml +3 -2
  11. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/__init__.py +101 -1
  12. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_cmd.py +34 -3
  13. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_info_view.py +4 -3
  14. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_markets_view.py +7 -7
  15. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/__init__.py +7 -1
  16. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/access.py +12 -1
  17. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fusion_factory.py +34 -36
  18. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/events.py +5 -2
  19. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/indexer.py +22 -13
  20. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/transport.py +20 -17
  21. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/transport.py +2 -2
  22. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/external_state_events.py +14 -9
  23. ipor_fusion-4.0.0.dev153/src/ipor_fusion/market_info.py +396 -0
  24. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/models.py +8 -6
  25. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/morpho_api.py +192 -132
  26. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/hypercore.py +1 -1
  27. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_info.py +500 -90
  28. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_crosschain.py +3 -3
  29. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_euler_v2.py +2 -2
  30. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_helpers.py +11 -5
  31. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_market_morpho_blue.py +8 -0
  32. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_pending_requests_scan.py +3 -1
  33. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_vault_info_golden.py +5 -5
  34. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_ccip_events.py +11 -0
  35. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_transport.py +15 -0
  36. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_external_state_events.py +9 -0
  37. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_models.py +218 -112
  38. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_public_api.py +98 -0
  39. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_hyperevm.py +19 -7
  40. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_vault_from_scratch_base.py +2 -2
  41. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_unpriceable_tokens.py +2 -5
  42. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_validation.py +4 -4
  43. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/typed_client/consumer.py +40 -3
  44. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/typed_client/rejected.py +13 -0
  45. ipor_fusion-4.0.0.dev149/src/ipor_fusion/market_info.py +0 -223
  46. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.claude-plugin/marketplace.json +0 -0
  47. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.claude-plugin/plugin.json +0 -0
  48. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.env.example +0 -0
  49. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.gemini/settings.json +0 -0
  50. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.git-blame-ignore-revs +0 -0
  51. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/cd.yml +0 -0
  52. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/ci.yml +0 -0
  53. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/commit-lint.yml +0 -0
  54. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/notify-slack.yml +0 -0
  55. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/python-build.yml +0 -0
  56. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/python-publish.yml +0 -0
  57. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/release.yml +0 -0
  58. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.github/workflows/report-slack.yml +0 -0
  59. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.gitignore +0 -0
  60. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.mcp.json +0 -0
  61. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.pre-commit-config.yaml +0 -0
  62. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/.python-version +0 -0
  63. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/CHANGELOG.md +0 -0
  64. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/CLAUDE.md +0 -0
  65. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/CODEOWNERS +0 -0
  66. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/LICENSE +0 -0
  67. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/README.md +0 -0
  68. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/build.sh +0 -0
  69. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/CONTRIBUTING.md +0 -0
  70. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/examples/README.md +0 -0
  71. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/pytest.ini +0 -0
  72. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/scripts/commit-check.sh +0 -0
  73. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/scripts/crosschain_readiness.py +0 -0
  74. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/skills/ipor-deploy-vault/SKILL.md +0 -0
  75. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/about.py +0 -0
  76. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cache.py +0 -0
  77. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/chains.py +0 -0
  78. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/__init__.py +0 -0
  79. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  80. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/config_cmd.py +0 -0
  81. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/config_store.py +0 -0
  82. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/main.py +0 -0
  83. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/market_cmd.py +0 -0
  84. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/options.py +0 -0
  85. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  86. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_health.py +0 -0
  87. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  88. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/config/__init__.py +0 -0
  89. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/config/roles.py +0 -0
  90. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/address.py +0 -0
  91. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/context.py +0 -0
  92. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/contract.py +0 -0
  93. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/erc20.py +0 -0
  94. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/external_state_executor.py +0 -0
  95. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fee_manager.py +0 -0
  96. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fuse.py +0 -0
  97. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fuse_action.py +0 -0
  98. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/logs.py +0 -0
  99. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/multicall.py +0 -0
  100. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/oracle.py +0 -0
  101. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/plasma_vault.py +0 -0
  102. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/rewards_manager.py +0 -0
  103. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/simulation.py +0 -0
  104. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  105. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/__init__.py +0 -0
  106. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  107. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  108. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  109. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  110. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  111. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  112. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  113. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/contracts.py +0 -0
  114. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/discovery.py +0 -0
  115. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/errors.py +0 -0
  116. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  117. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  118. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  119. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  120. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/lane.py +0 -0
  121. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/logs.py +0 -0
  122. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/messages.py +0 -0
  123. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/simulation.py +0 -0
  124. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  125. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  126. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  127. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  128. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/dependency_graph.py +0 -0
  129. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/entry_cli.py +0 -0
  130. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/entry_mcp.py +0 -0
  131. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/errors.py +0 -0
  132. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/explorer.py +0 -0
  133. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/field_docs.py +0 -0
  134. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/__init__.py +0 -0
  135. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  136. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/async_action.py +0 -0
  137. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/base.py +0 -0
  138. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  139. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/erc4626.py +0 -0
  140. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  141. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/events.py +0 -0
  142. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/external_state.py +0 -0
  143. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  144. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  145. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/hypercore.py +0 -0
  146. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  147. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/merkl.py +0 -0
  148. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/morpho.py +0 -0
  149. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  150. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  151. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/universal.py +0 -0
  152. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/__init__.py +0 -0
  153. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/architecture.md +0 -0
  154. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/glossary.md +0 -0
  155. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/invariants.md +0 -0
  156. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/quickstart.md +0 -0
  157. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/market_ids.py +0 -0
  158. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/__init__.py +0 -0
  159. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/guide.py +0 -0
  160. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/server.py +0 -0
  161. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/py.typed +0 -0
  162. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/__init__.py +0 -0
  163. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/aave_v3.py +0 -0
  164. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/aave_v4.py +0 -0
  165. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/compound_v3.py +0 -0
  166. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  167. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/lending_health.py +0 -0
  168. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/morpho.py +0 -0
  169. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  170. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  171. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  172. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/position_manager.py +0 -0
  173. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  174. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  175. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/unpriceable.py +0 -0
  176. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/substrates.py +0 -0
  177. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/types.py +0 -0
  178. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/validation.py +0 -0
  179. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_data.py +0 -0
  180. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_erc20.py +0 -0
  181. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_managers.py +0 -0
  182. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_positions.py +0 -0
  183. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_reads.py +0 -0
  184. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/UniswapV3UniversalRouter.py +0 -0
  185. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_crosschain_lifecycle.py +0 -0
  186. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_crosschain_recovery.py +0 -0
  187. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_foundry.py +0 -0
  188. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_guide.py +0 -0
  189. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_hypercore_model.py +0 -0
  190. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_hypercore_shadow.py +0 -0
  191. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_multicall.py +0 -0
  192. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_simulate.py +0 -0
  193. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/_validation.py +0 -0
  194. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/addresses.py +0 -0
  195. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/conftest.py +0 -0
  196. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/constants.py +0 -0
  197. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  198. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/fixtures/hypercore_market55.json +0 -0
  199. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/clean.json +0 -0
  200. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  201. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/rich.json +0 -0
  202. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  203. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_about.py +0 -0
  204. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_access_manager.py +0 -0
  205. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cache.py +0 -0
  206. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_chains.py +0 -0
  207. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_changelog.py +0 -0
  208. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_commands.py +0 -0
  209. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_config_store.py +0 -0
  210. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_hypercore.py +0 -0
  211. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_lending_health.py +0 -0
  212. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_oracle_mapping.py +0 -0
  213. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_role_accounts.py +0 -0
  214. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_vault_predates_getters.py +0 -0
  215. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_cli_vault_validate.py +0 -0
  216. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_contract_call.py +0 -0
  217. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_contract_encoder.py +0 -0
  218. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_ccip_indexer.py +0 -0
  219. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_ccip_quote.py +0 -0
  220. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_chainlink.py +0 -0
  221. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_contracts.py +0 -0
  222. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_encoding.py +0 -0
  223. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_errors.py +0 -0
  224. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_lane.py +0 -0
  225. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_layering.py +0 -0
  226. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_readiness.py +0 -0
  227. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_readiness_report.py +0 -0
  228. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_docs_typing.py +0 -0
  229. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_erc20.py +0 -0
  230. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_error_hierarchy.py +0 -0
  231. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_examples_composed_hypercore.py +0 -0
  232. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_examples_crosschain.py +0 -0
  233. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  234. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_examples_vaults.py +0 -0
  235. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_explorer.py +0 -0
  236. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_external_state_executor.py +0 -0
  237. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_fee_manager.py +0 -0
  238. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_fuse_encoding.py +0 -0
  239. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_fusion_instance.py +0 -0
  240. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_guide.py +0 -0
  241. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_deployment.py +0 -0
  242. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_events.py +0 -0
  243. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_flow.py +0 -0
  244. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_live.py +0 -0
  245. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_outcome.py +0 -0
  246. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_readers.py +0 -0
  247. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_simulation.py +0 -0
  248. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_logs.py +0 -0
  249. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_hypercore.py +0 -0
  250. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_server.py +0 -0
  251. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_vault_validate.py +0 -0
  252. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_multicall.py +0 -0
  253. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_oracle.py +0 -0
  254. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_oracle_mapping.py +0 -0
  255. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_plasma_vault.py +0 -0
  256. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_public_type_reachability.py +0 -0
  257. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_reader_encoding.py +0 -0
  258. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_readiness_selection.py +0 -0
  259. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_revert_decoding.py +0 -0
  260. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_rewards_manager.py +0 -0
  261. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_roles.py +0 -0
  262. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_sdk_layering.py +0 -0
  263. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  264. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  266. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_canary.py +0 -0
  267. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_hypercore.py +0 -0
  268. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  269. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_examples_arbitrum.py +0 -0
  275. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_examples_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_examples_crosschain.py +0 -0
  277. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_external_state_executor_base.py +0 -0
  278. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  279. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  280. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  281. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_morpho_blue.py +0 -0
  282. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v3.py +0 -0
  283. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  284. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  285. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  286. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  287. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_readers_integration.py +0 -0
  288. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_release_funds.py +0 -0
  289. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_swap_on_base.py +0 -0
  290. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_solidity_compiler.py +0 -0
  291. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_solidity_mirrors.py +0 -0
  292. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_substrates.py +0 -0
  293. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_types_and_events.py +0 -0
  294. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_vault_data_block.py +0 -0
  295. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_vault_dep_graph.py +0 -0
  296. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_vault_info_onchain_base.py +0 -0
  297. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  298. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_vault_simulator_blocks.py +0 -0
  299. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_web3_context.py +0 -0
  300. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/tests/test_withdraw_manager.py +0 -0
  301. {ipor_fusion-4.0.0.dev149 → ipor_fusion-4.0.0.dev153}/uv.lock +0 -0
@@ -186,7 +186,7 @@ the lifecycle matrix follows that.
186
186
  | Python 3.12 runtime, 3.11 floor | `.python-version` + CI `python-version` default; `requires-python`, ruff `target-version`, pyright `pythonVersion` |
187
187
  | `IporFusionMarkets`, `Roles` | `market_ids.py`, `config/roles.py` mirror `IporFusionMarkets.sol`, `Roles.sol` in `ipor-fusion/contracts/libraries/`; drift-gated by `tests/test_solidity_mirrors.py` — bump its pinned ref in the same change that syncs the mirrors An id sourced from a contracts feature branch ahead of the pinned ref goes in the test's `_AHEAD_OF_UPSTREAM` allowlist and is removed when the ref catches up |
188
188
  | substrate decoders | `substrates.py` registry mirrors each market's `contracts/fuses/<protocol>/*SubstrateLib.sol` or `*FuseLib.sol` |
189
- | `vault_info` JSON shape | `build_vault_info_json` in `vault_info.py`, models in `mcp/models.py` (`extra="forbid"`), `_full_vault_info_dict` fixture in `test_mcp_models.py` |
189
+ | `vault_info` / `fusion market` JSON shapes | the builders and their TypedDicts (`VaultInfoJson`, `MorphoBlueMarketJson`, `MetaMorphoV1Json` / `MetaMorphoV2Json`) in `vault_info.py` / `market_info.py`, models in `mcp/models.py` (`extra="forbid"`, the published schema), the `VaultInfoJson`-typed `_full_vault_info_dict` fixture in `test_mcp_models.py` |
190
190
  | CLI command set | every CLI command has a matching tool in `mcp/server.py` (`changelog` maps to `server_info`) |
191
191
 
192
192
  ## Layout (`src/ipor_fusion/`)
@@ -213,8 +213,8 @@ the lifecycle matrix follows that.
213
213
  prices) and `vault_erc20.py` (the ERC20_VAULT_BALANCE holdings);
214
214
  `vault_info.py` — `build_vault_info_json(data)`, the `fusion vault info --json`
215
215
  document rendered from the snapshot alone (contract names via an injected
216
- `resolve_name`),
217
- and `health_check_lists`; `explorer.py` Etherscan V2 (single endpoint, needs API key);
216
+ `resolve_name`) and typed block by block (`VaultInfoJson` and its nested
217
+ TypedDicts), and `health_check_lists`; `explorer.py` Etherscan V2 (single endpoint, needs API key);
218
218
  `cache.py` the `ContractCache` protocol (`get`/`put` of immutable facts keyed
219
219
  `CacheKey(chain_id, address, field)`), `FileContractCache` (one JSON file, atomic
220
220
  replace, corrupt/unwritable degrades to memory) and `NullCache`; every SDK use goes
@@ -223,8 +223,14 @@ the lifecycle matrix follows that.
223
223
  `$XDG_CACHE_HOME/ipor-fusion/`) or `get_contract_name(cache=...)`. Never cache an
224
224
  absence (no code, no answer): it can change at a later block.
225
225
  `morpho_api.py` the Morpho public GraphQL API client (`fetch_market`, `fetch_vault`,
226
- `PUBLIC_ALLOCATOR_ADDRESSES`); `market_info.py` — `build_morpho_blue_market_json`,
227
- `build_meta_morpho_json`, the `fusion market` JSON documents.
226
+ `PUBLIC_ALLOCATOR_ADDRESSES`; it narrows the GraphQL body from `object`);
227
+ `market_info.py` — `build_morpho_blue_market_json`, `build_meta_morpho_json`, the
228
+ `fusion market` JSON documents (`MorphoBlueMarketJson`, `MetaMorphoV1Json` /
229
+ `MetaMorphoV2Json`). A JSON document is a TypedDict (`NotRequired` for a key only
230
+ some entries carry), never `dict[str, Any]`; JSON parsed from the network or a
231
+ caller enters as `object` / `Mapping[str, object]` and is narrowed with
232
+ `isinstance`. These two modules skip `from __future__ import annotations`, so a
233
+ TypedDict's `__optional_keys__` is right at run time.
228
234
  None of them imports `cli/` (`tests/test_sdk_layering.py`)
229
235
  - `config/roles.py` — `Roles` IntEnum
230
236
  - `core/` — `context` (`Web3Context`), `contract` (`Call`, `ContractWrapper`),
@@ -431,12 +437,16 @@ value sets are `Literal` enums. The docstring keeps only what has no
431
437
  structured home: a one-line summary, behavioral notes, cost warnings. Do not
432
438
  repeat schema content as prose; it drifts.
433
439
 
434
- `mcp/models.py` forbids extra keys. Adding a top-level field to
435
- `build_vault_info_json` requires adding it to the matching model (with a default
436
- for back-compat) and to the `_full_vault_info_dict` fixture; `test_mcp_models.py`
437
- enforces the mirror. Conditionally shaped blocks (`withdraw_manager_details`,
438
- `substrates`, `dependency_graph`) are `dict[str, Any]`, so their inner keys
439
- are not validated.
440
+ `mcp/models.py` forbids extra keys and stays the source of the published JSON
441
+ schema. Adding a field to `build_vault_info_json` (or a `fusion market` document)
442
+ means adding it to its TypedDict, to the matching model (with a default for
443
+ back-compat) and to the `_full_vault_info_dict` fixture;
444
+ `TestTypedDocumentsMirrorModels` in `test_mcp_models.py` holds every TypedDict to
445
+ its model block by block. The model keeps `dict[str, Any]` where the schema is
446
+ deliberately loose (`substrates`, `deployment`, `share_price`, `dependency_graph`,
447
+ `position_breakdown`): the SDK types those blocks, the schema does not validate
448
+ their inner keys. The substrate entries stay open-ended on both sides
449
+ (`SubstrateEntryJson`, `dict[str, str | bool]`): each decoder adds its own fields.
440
450
 
441
451
  ## Domain rules (verified against the contracts)
442
452
 
@@ -54,6 +54,10 @@ before it is fixed.
54
54
  nominal types. Wrap literals and raw values, `ChainId(8453)`, `Period(0)`,
55
55
  `Amount(n)`, `parse_fuse_address(...)`; values the SDK returns already carry
56
56
  their type. See [Nominal types](#nominal-types).
57
+ 9. **Typed JSON documents.** `build_vault_info_json` and the `fusion market`
58
+ builders return TypedDicts (`VaultInfoJson`, ...); code annotating them
59
+ `dict[str, Any]` switches to the TypedDict or `Mapping[str, object]`. See
60
+ [Typed JSON documents](#typed-json-documents).
57
61
 
58
62
  Then read [Behavior changes](#behavior-changes): they apply to every caller,
59
63
  type-checked or not.
@@ -721,6 +725,45 @@ string). Calling an MCP tool function
721
725
  directly from Python takes `ChainId`. Their amount, decimals and time fields stay
722
726
  `int`: they are JSON output models, and the SDK values above fit them unchanged.
723
727
 
728
+ ## Typed JSON documents
729
+
730
+ The JSON document builders return `TypedDict`s instead of `dict[str, Any]`; the
731
+ documents themselves (keys, values, order, the MCP schemas) are unchanged, so
732
+ untyped code needs none of this section.
733
+
734
+ | Builder | Returns |
735
+ |---|---|
736
+ | `build_vault_info_json` | `VaultInfoJson` |
737
+ | `build_morpho_blue_market_json` | `MorphoBlueMarketJson` |
738
+ | `build_meta_morpho_json` | `MetaMorphoV1Json \| MetaMorphoV2Json`, told apart by `doc["version"]` |
739
+ | `RoleAccount.to_dict` | `RoleAccountJson` |
740
+
741
+ Every nested block has its own TypedDict (`FeesJson`, `WithdrawManagerJson`,
742
+ `BalanceFuseJson`, `ReconciliationJson`, `HyperCoreJson`, `MorphoSupplyingVaultJson`,
743
+ ...), all importable from `ipor_fusion` and from `ipor_fusion.vault_info` /
744
+ `ipor_fusion.market_info` (`RoleAccountJson` from `ipor_fusion.core`). Values carry the
745
+ SDK's nominal types (`doc["vault"]` is a `PlasmaVaultAddress`, `doc["chain_id"]` a
746
+ `ChainId`). A block that can be `null` is `X | None`: check it before indexing
747
+ (`fees = doc["fees"]; if fees is not None: fees["management_fee_percent"]`). A key
748
+ that only some entries carry is `NotRequired` (a balance fuse's `pct_of_total` and
749
+ `position_breakdown`, the Morpho API keys of the market document): read it with
750
+ `.get`. A decoded substrate entry stays open-ended, `SubstrateEntryJson`
751
+ (`dict[str, str | bool]`), since each decoder adds its own fields.
752
+
753
+ - A variable or parameter annotated `dict[str, Any]` no longer accepts these
754
+ documents (a `TypedDict` is not a `dict[str, Any]`): annotate it with the TypedDict,
755
+ or `Mapping[str, object]` for code that only passes the document on (`json.dumps`
756
+ accepts either).
757
+ - Parsing entry points take `Mapping[str, object]` instead of `Mapping[str, Any]`:
758
+ `decode_external_state_event(log)` and `FusionInstance.from_dict(data)`.
759
+ `VerifierResult.message` is a `Mapping[str, object]`, and `OutboundMessage.raw` is
760
+ `object`: narrow it before use (`isinstance(message.raw, MessageV1)` for CCIP; a
761
+ Stargate `Packet`, or a `(Packet, TaxiMessage)` pair for a token transfer).
762
+ - The `vault info` document's `deployment` is `DeploymentJson` (block, timestamp,
763
+ age) or `DeploymentErrorJson` (`{"error": ...}`) or `None`; `share_price` is
764
+ `SharePriceJson` (`asset`, and `usd` when the asset is priced); `dependency_graph`
765
+ is `DependencyGraphJson` (`edges`, `update_reach`, `update_groups`).
766
+
724
767
  ## Behavior changes
725
768
 
726
769
  These apply to every caller, type-checked or not. Changes described in full above
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev149
3
+ Version: 4.0.0.dev153
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
@@ -72,7 +72,7 @@ from ipor_fusion.fuses import (
72
72
  euler_substrate,
73
73
  )
74
74
  from ipor_fusion.market_ids import IporFusionMarkets
75
- from ipor_fusion.types import MAX_UINT256, Amount, ChainId, MarketId, Period
75
+ from ipor_fusion.types import MAX_UINT256, Amount, ChainId, Period
76
76
 
77
77
  log = logging.getLogger("advanced_euler_v2_credit_market_base")
78
78
 
@@ -83,7 +83,7 @@ log = logging.getLogger("advanced_euler_v2_credit_market_base")
83
83
  # deployments -- see their note below.
84
84
 
85
85
  BASE_CHAIN_ID = ChainId(8453)
86
- EULER_MARKET = MarketId(IporFusionMarkets.EULER_V2)
86
+ EULER_MARKET = IporFusionMarkets.EULER_V2
87
87
 
88
88
  # IporFusionFactoryProxy on Base. Its clone(...) takes the six args in clone_args().
89
89
  BASE_FUSION_FACTORY = Web3.to_checksum_address(
@@ -61,7 +61,7 @@ from ipor_fusion.fuses import (
61
61
  euler_substrate,
62
62
  )
63
63
  from ipor_fusion.market_ids import IporFusionMarkets
64
- from ipor_fusion.types import Amount, ChainId, MarketId, Period
64
+ from ipor_fusion.types import Amount, ChainId, Period
65
65
 
66
66
  log = logging.getLogger("advanced_euler_v2_looping_arbitrum")
67
67
 
@@ -71,9 +71,9 @@ log = logging.getLogger("advanced_euler_v2_looping_arbitrum")
71
71
  # mainnet-arbitrum-fusion/addresses.json
72
72
 
73
73
  ARBITRUM_CHAIN_ID = ChainId(42161)
74
- EULER_MARKET = MarketId(IporFusionMarkets.EULER_V2)
75
- SWAPPER_MARKET = MarketId(IporFusionMarkets.UNIVERSAL_TOKEN_SWAPPER_V2)
76
- MORPHO_FLASH_MARKET = MarketId(IporFusionMarkets.MORPHO_FLASH_LOAN)
74
+ EULER_MARKET = IporFusionMarkets.EULER_V2
75
+ SWAPPER_MARKET = IporFusionMarkets.UNIVERSAL_TOKEN_SWAPPER_V2
76
+ MORPHO_FLASH_MARKET = IporFusionMarkets.MORPHO_FLASH_LOAN
77
77
 
78
78
  ARBITRUM_FUSION_FACTORY = Web3.to_checksum_address(
79
79
  "0x134fCAce7a2C7Ef3dF2479B62f03ddabAEa922d5"
@@ -113,7 +113,6 @@ from ipor_fusion.types import (
113
113
  Decimals,
114
114
  HyperCoreAsset,
115
115
  HyperCoreClientOrderId,
116
- MarketId,
117
116
  Period,
118
117
  Shares,
119
118
  )
@@ -162,7 +161,7 @@ HYPERCORE_BALANCE_FUSE = parse_fuse_address(
162
161
  "0xaC7404Ec2Bc3F2d3c0853633e0c3edfA25DC50F9"
163
162
  )
164
163
  HYPERCORE_MARKET = IporFusionMarkets.HYPERCORE
165
- CROSSCHAIN_MARKET = MarketId(int(IporFusionMarkets.CROSSCHAIN))
164
+ CROSSCHAIN_MARKET = IporFusionMarkets.CROSSCHAIN
166
165
 
167
166
  # ── Actors ───────────────────────────────────────────────────────────────────
168
167
  # One EOA holds the alpha role on both vaults in this test deployment; the two
@@ -86,7 +86,7 @@ from ipor_fusion import (
86
86
  parse_plasma_vault_address,
87
87
  )
88
88
  from ipor_fusion.market_ids import IporFusionMarkets
89
- from ipor_fusion.types import Amount, ChainId, Decimals, MarketId, Period, Shares
89
+ from ipor_fusion.types import Amount, ChainId, Decimals, Period, Shares
90
90
 
91
91
  log = logging.getLogger("crosschain_ccip_usdc_arbitrum_hyperevm")
92
92
 
@@ -123,7 +123,7 @@ EXECUTOR = Web3.to_checksum_address("0xcb83A78BD8e8c284d5704B62945EF2C454266dB0"
123
123
  SPOKE_VAULT = parse_plasma_vault_address("0xA342eBA00bD725799828A34EA55D25a55958f643")
124
124
  # ``IporFusionMarkets.CROSSCHAIN``: the market the four crosschain fuses are
125
125
  # registered under on the hub vault.
126
- CROSSCHAIN_MARKET = MarketId(int(IporFusionMarkets.CROSSCHAIN))
126
+ CROSSCHAIN_MARKET = IporFusionMarkets.CROSSCHAIN
127
127
 
128
128
  # ── Actors ───────────────────────────────────────────────────────────────────
129
129
  # Role separation: the alpha is an online, least-privilege key that can only
@@ -96,7 +96,7 @@ from ipor_fusion import (
96
96
  )
97
97
  from ipor_fusion.fuses import ExternalStateOperationFuse
98
98
  from ipor_fusion.market_ids import IporFusionMarkets
99
- from ipor_fusion.types import Amount, ChainId, MarketId, Period, ProposalHash, Shares
99
+ from ipor_fusion.types import Amount, ChainId, Period, ProposalHash, Shares
100
100
 
101
101
  log = logging.getLogger("external_state_margin_leg_base")
102
102
 
@@ -121,7 +121,7 @@ BASE_FUSION_FACTORY = Web3.to_checksum_address(
121
121
  BASE_USDC = Web3.to_checksum_address("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")
122
122
 
123
123
  # External-state market (IporFusionMarkets.EXTERNAL_STATE).
124
- EXTERNAL_STATE_MARKET = MarketId(IporFusionMarkets.EXTERNAL_STATE)
124
+ EXTERNAL_STATE_MARKET = IporFusionMarkets.EXTERNAL_STATE
125
125
 
126
126
  # External-state *operation* fuse (enter/exit) -- the functional fuse. It also
127
127
  # lazily deploys the per-vault ExternalStateExecutor on the first enter.
@@ -53,7 +53,7 @@ from ipor_fusion import (
53
53
  )
54
54
  from ipor_fusion.fuses import AaveV3SupplyFuse
55
55
  from ipor_fusion.market_ids import IporFusionMarkets
56
- from ipor_fusion.types import Amount, ChainId, MarketId, Period
56
+ from ipor_fusion.types import Amount, ChainId, Period
57
57
 
58
58
  log = logging.getLogger("simple_aave_v3_supply_base")
59
59
 
@@ -75,7 +75,7 @@ BASE_FUSION_FACTORY = Web3.to_checksum_address(
75
75
  BASE_USDC = Web3.to_checksum_address("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")
76
76
 
77
77
  # Aave V3 market (IporFusionMarkets.AAVE_V3).
78
- AAVE_MARKET = MarketId(IporFusionMarkets.AAVE_V3)
78
+ AAVE_MARKET = IporFusionMarkets.AAVE_V3
79
79
 
80
80
  # Aave V3 *supply* fuse (registry name SupplyFuseAaveV3) -- the functional fuse
81
81
  # that encodes supply/withdraw.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev149"
3
+ version = "4.0.0.dev153"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -111,7 +111,8 @@ executionEnvironments = [
111
111
  { root = "examples", reportArgumentType = "error" },
112
112
  # A negative case is a line that must fail to type-check, marked with
113
113
  # `# pyright: ignore[rule]`; an ignore that suppresses nothing is an error.
114
- { root = "tests/typed_client", reportArgumentType = "error", reportUnnecessaryTypeIgnoreComment = "error" },
114
+ # A client reads the SDK's NotRequired document keys with .get().
115
+ { root = "tests/typed_client", reportArgumentType = "error", reportUnnecessaryTypeIgnoreComment = "error", reportTypedDictNotRequiredAccess = "error" },
115
116
  ]
116
117
 
117
118
  [tool.coverage.run]
@@ -27,6 +27,7 @@ from ipor_fusion.config.roles import Roles
27
27
  from ipor_fusion.core.access import (
28
28
  AccessManager,
29
29
  RoleAccount,
30
+ RoleAccountJson,
30
31
  RoleStatus,
31
32
  resolve_access_manager,
32
33
  role_account_sort_key,
@@ -318,6 +319,21 @@ from ipor_fusion.fuses.external_state_events import (
318
319
  )
319
320
  from ipor_fusion.market_ids import IporFusionMarkets
320
321
  from ipor_fusion.market_info import (
322
+ MetaMorphoAdapterJson,
323
+ MetaMorphoAllocationJson,
324
+ MetaMorphoCapJson,
325
+ MetaMorphoFlowCapJson,
326
+ MetaMorphoPublicAllocatorJson,
327
+ MetaMorphoV1Json,
328
+ MetaMorphoV2Json,
329
+ MorphoAssetJson,
330
+ MorphoBlueMarketJson,
331
+ MorphoFlowCapConfigJson,
332
+ MorphoMarketParamsJson,
333
+ MorphoMarketRatesJson,
334
+ MorphoMarketStateJson,
335
+ MorphoSupplyingVaultJson,
336
+ MorphoVaultAssetJson,
321
337
  build_meta_morpho_json,
322
338
  build_morpho_blue_market_json,
323
339
  )
@@ -480,7 +496,42 @@ from ipor_fusion.vault_data import (
480
496
  resolve_token_decimals,
481
497
  resolve_token_symbol,
482
498
  )
483
- from ipor_fusion.vault_info import build_vault_info_json
499
+ from ipor_fusion.vault_info import (
500
+ AaveBreakdownJson,
501
+ AmountJson,
502
+ BalanceFuseJson,
503
+ BreakdownAmountJson,
504
+ DeltaJson,
505
+ DependencyGraphJson,
506
+ DeploymentErrorJson,
507
+ DeploymentJson,
508
+ Erc20BalanceJson,
509
+ FeesJson,
510
+ FuseJson,
511
+ HealthCheckJson,
512
+ HighWaterMarkJson,
513
+ HyperCoreJson,
514
+ HyperCorePendingJson,
515
+ HyperCorePerpLegJson,
516
+ HyperCorePerpMarketJson,
517
+ HyperCoreSpotLegJson,
518
+ LendingHealthJson,
519
+ LendingMarketJson,
520
+ ManagersJson,
521
+ MorphoBreakdownJson,
522
+ PendingRequestJson,
523
+ PricedAmountJson,
524
+ RecipientFeeJson,
525
+ ReconciliationJson,
526
+ SharePriceJson,
527
+ SubstrateEntryJson,
528
+ UsdAmountJson,
529
+ VaultAssetJson,
530
+ VaultInfoJson,
531
+ VaultLinksJson,
532
+ WithdrawManagerJson,
533
+ build_vault_info_json,
534
+ )
484
535
 
485
536
  __version__ = package_version()
486
537
 
@@ -526,6 +577,7 @@ __all__ = [
526
577
  "PlasmaVault",
527
578
  "AccessManager",
528
579
  "RoleAccount",
580
+ "RoleAccountJson",
529
581
  "RoleStatus",
530
582
  "resolve_access_manager",
531
583
  "role_account_sort_key",
@@ -919,6 +971,39 @@ __all__ = [
919
971
  "resolve_token_decimals",
920
972
  "resolve_token_symbol",
921
973
  "build_vault_info_json",
974
+ "AaveBreakdownJson",
975
+ "AmountJson",
976
+ "BalanceFuseJson",
977
+ "BreakdownAmountJson",
978
+ "DeltaJson",
979
+ "DependencyGraphJson",
980
+ "DeploymentErrorJson",
981
+ "DeploymentJson",
982
+ "Erc20BalanceJson",
983
+ "FeesJson",
984
+ "FuseJson",
985
+ "HealthCheckJson",
986
+ "HighWaterMarkJson",
987
+ "HyperCoreJson",
988
+ "HyperCorePendingJson",
989
+ "HyperCorePerpLegJson",
990
+ "HyperCorePerpMarketJson",
991
+ "HyperCoreSpotLegJson",
992
+ "LendingHealthJson",
993
+ "LendingMarketJson",
994
+ "ManagersJson",
995
+ "MorphoBreakdownJson",
996
+ "PendingRequestJson",
997
+ "PricedAmountJson",
998
+ "RecipientFeeJson",
999
+ "ReconciliationJson",
1000
+ "SharePriceJson",
1001
+ "SubstrateEntryJson",
1002
+ "UsdAmountJson",
1003
+ "VaultAssetJson",
1004
+ "VaultInfoJson",
1005
+ "VaultLinksJson",
1006
+ "WithdrawManagerJson",
922
1007
  "MorphoApiError",
923
1008
  "MorphoApiMarket",
924
1009
  "PUBLIC_ALLOCATOR_ADDRESSES",
@@ -933,4 +1018,19 @@ __all__ = [
933
1018
  "fetch_vault",
934
1019
  "build_meta_morpho_json",
935
1020
  "build_morpho_blue_market_json",
1021
+ "MetaMorphoAdapterJson",
1022
+ "MetaMorphoAllocationJson",
1023
+ "MetaMorphoCapJson",
1024
+ "MetaMorphoFlowCapJson",
1025
+ "MetaMorphoPublicAllocatorJson",
1026
+ "MetaMorphoV1Json",
1027
+ "MetaMorphoV2Json",
1028
+ "MorphoAssetJson",
1029
+ "MorphoBlueMarketJson",
1030
+ "MorphoFlowCapConfigJson",
1031
+ "MorphoMarketParamsJson",
1032
+ "MorphoMarketRatesJson",
1033
+ "MorphoMarketStateJson",
1034
+ "MorphoSupplyingVaultJson",
1035
+ "MorphoVaultAssetJson",
936
1036
  ]
@@ -2,10 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import sys
5
- from typing import Any
5
+ from typing import TypedDict
6
6
 
7
7
  import click
8
8
  from web3 import Web3
9
+ from web3.types import Timestamp
9
10
 
10
11
  from ipor_fusion.chains import CHAIN_NAMES
11
12
  from ipor_fusion.cli.config_store import (
@@ -41,7 +42,7 @@ from ipor_fusion.errors import (
41
42
  from ipor_fusion.readers.oracle_mapping import (
42
43
  build_oracle_mapping,
43
44
  )
44
- from ipor_fusion.types import ChainId, PlasmaVaultAddress
45
+ from ipor_fusion.types import ChainId, Check, PlasmaVaultAddress, Severity, Verdict
45
46
  from ipor_fusion.validation import (
46
47
  VaultValidation,
47
48
  validate_vault,
@@ -51,6 +52,10 @@ from ipor_fusion.vault_data import (
51
52
  fetch_vault_data,
52
53
  )
53
54
  from ipor_fusion.vault_info import (
55
+ Erc20BalanceJson,
56
+ LendingHealthJson,
57
+ ReconciliationJson,
58
+ VaultAssetJson,
54
59
  _erc20_balances_json,
55
60
  _lending_health_json,
56
61
  _reconciliation_json,
@@ -58,6 +63,32 @@ from ipor_fusion.vault_info import (
58
63
  )
59
64
 
60
65
 
66
+ class _FindingJson(TypedDict):
67
+ check: Check
68
+ severity: Severity
69
+ message: str
70
+
71
+
72
+ class _ValidationJson(TypedDict):
73
+ """The ``fusion vault validate --json`` document."""
74
+
75
+ vault: PlasmaVaultAddress
76
+ chain: str
77
+ chain_id: ChainId
78
+ block: int
79
+ block_timestamp: Timestamp
80
+ block_timestamp_utc: str
81
+ asset: VaultAssetJson
82
+ verdict: Verdict
83
+ criticals: list[str]
84
+ warnings: list[str]
85
+ ok: list[str]
86
+ findings: list[_FindingJson]
87
+ reconciliation: ReconciliationJson
88
+ erc20_balances: list[Erc20BalanceJson]
89
+ lending_health: LendingHealthJson | None
90
+
91
+
61
92
  def _auto_save_vault(
62
93
  cfg: FusionConfig,
63
94
  vault_address: PlasmaVaultAddress,
@@ -276,7 +307,7 @@ def validate(
276
307
  sys.exit(1)
277
308
 
278
309
 
279
- def _validation_json(validation: VaultValidation, chain_label: str) -> dict[str, Any]:
310
+ def _validation_json(validation: VaultValidation, chain_label: str) -> _ValidationJson:
280
311
  return {
281
312
  "vault": validation.vault,
282
313
  "chain": chain_label,
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ from collections.abc import Sequence
4
5
  from functools import partial
5
- from typing import Any
6
6
 
7
7
  import click
8
8
 
@@ -23,6 +23,7 @@ from ipor_fusion.cli.vault_rendering import (
23
23
  _format_usd,
24
24
  _print_table,
25
25
  )
26
+ from ipor_fusion.core.access import RoleAccountJson
26
27
  from ipor_fusion.core.context import Web3Context
27
28
  from ipor_fusion.core.fee_manager import RecipientFee
28
29
  from ipor_fusion.explorer import get_contract_name
@@ -48,7 +49,7 @@ from ipor_fusion.vault_info import (
48
49
  )
49
50
 
50
51
 
51
- def _print_role_accounts_table(role_accounts: list[dict[str, Any]]) -> None:
52
+ def _print_role_accounts_table(role_accounts: Sequence[RoleAccountJson]) -> None:
52
53
  _print_table(
53
54
  ("Account", "Role", "Role ID", "Delay (s)"),
54
55
  [
@@ -63,7 +64,7 @@ def _print_role_accounts_table(role_accounts: list[dict[str, Any]]) -> None:
63
64
  )
64
65
 
65
66
 
66
- def _print_role_accounts(role_accounts: list[dict[str, Any]] | None) -> None:
67
+ def _print_role_accounts(role_accounts: Sequence[RoleAccountJson] | None) -> None:
67
68
  click.echo("Role Accounts:")
68
69
  if role_accounts is None:
69
70
  click.echo(" (unavailable — the RoleGranted log scan did not complete)")
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from collections.abc import Callable, Sequence
4
4
  from concurrent.futures import Future, ThreadPoolExecutor
5
- from typing import Any
6
5
 
7
6
  import click
8
7
  from eth_typing import HexAddress
@@ -13,6 +12,7 @@ from ipor_fusion.cli.vault_rendering import (
13
12
  _print_table,
14
13
  _substrate_details,
15
14
  )
15
+ from ipor_fusion.readers.lending_health import LendingMarketHealth, VaultLendingHealth
16
16
  from ipor_fusion.substrates import (
17
17
  decode_substrate,
18
18
  format_market_label,
@@ -42,7 +42,7 @@ def _print_lending_health(data: VaultData) -> None: # noqa: C901
42
42
  click.echo("Position Breakdown:")
43
43
  morpho_health, aave_health = _index_lending_health(lh)
44
44
  consumed_morpho_subs: set[str] = set()
45
- consumed_aave_mids: set[int | str] = set()
45
+ consumed_aave_mids: set[MarketId | str] = set()
46
46
 
47
47
  for ipor_mid, positions in (data.morpho_positions or {}).items():
48
48
  click.echo(f" {format_market_label(ipor_mid)}:")
@@ -98,16 +98,16 @@ def _print_lending_health(data: VaultData) -> None: # noqa: C901
98
98
 
99
99
 
100
100
  def _index_lending_health(
101
- lh: Any,
102
- ) -> tuple[dict[str, Any], dict[int | str, Any]]:
101
+ lh: VaultLendingHealth | None,
102
+ ) -> tuple[dict[str, LendingMarketHealth], dict[MarketId | str, LendingMarketHealth]]:
103
103
  """Split lending health rows by protocol for fast lookup during rendering.
104
104
 
105
105
  Morpho rows are keyed by morpho substrate id (one row per substrate).
106
106
  Aave V3 rows are keyed by IPOR market id (account-aggregated, one row per
107
107
  market); Aave V4 rows by Spoke address (account-aggregated per Spoke).
108
108
  """
109
- morpho: dict[str, Any] = {}
110
- aave: dict[int | str, Any] = {}
109
+ morpho: dict[str, LendingMarketHealth] = {}
110
+ aave: dict[MarketId | str, LendingMarketHealth] = {}
111
111
  if lh is None:
112
112
  return morpho, aave
113
113
  for m in lh.markets:
@@ -120,7 +120,7 @@ def _index_lending_health(
120
120
  return morpho, aave
121
121
 
122
122
 
123
- def _print_health_lines(market: Any, indent: str) -> None:
123
+ def _print_health_lines(market: LendingMarketHealth, indent: str) -> None:
124
124
  """Render LTV / Health Factor / Status lines for a lending market.
125
125
 
126
126
  Status colors: green when safe, yellow at warning (HF < 1.10), red+bold
@@ -1,4 +1,9 @@
1
- from ipor_fusion.core.access import AccessManager, RoleAccount, RoleStatus
1
+ from ipor_fusion.core.access import (
2
+ AccessManager,
3
+ RoleAccount,
4
+ RoleAccountJson,
5
+ RoleStatus,
6
+ )
2
7
  from ipor_fusion.core.context import InFlightLimit, Web3Context
3
8
  from ipor_fusion.core.erc20 import ERC20
4
9
  from ipor_fusion.core.fee_manager import (
@@ -37,6 +42,7 @@ __all__ = [
37
42
  "PlasmaVault",
38
43
  "AccessManager",
39
44
  "RoleAccount",
45
+ "RoleAccountJson",
40
46
  "RoleStatus",
41
47
  "RewardsManager",
42
48
  "VestingData",
@@ -1,5 +1,6 @@
1
1
  from collections.abc import Callable
2
2
  from dataclasses import dataclass
3
+ from typing import TypedDict
3
4
 
4
5
  from eth_abi import decode
5
6
  from eth_abi.exceptions import InsufficientDataBytes
@@ -23,6 +24,16 @@ from ipor_fusion.errors import (
23
24
  from ipor_fusion.types import AccessManagerAddress, Period, PlasmaVaultAddress, RoleId
24
25
 
25
26
 
27
+ class RoleAccountJson(TypedDict):
28
+ """`RoleAccount.to_dict`: one role membership as JSON."""
29
+
30
+ account: ChecksumAddress
31
+ role_id: RoleId
32
+ role_name: str
33
+ is_member: bool
34
+ execution_delay: Period
35
+
36
+
26
37
  @dataclass(slots=True)
27
38
  class RoleStatus:
28
39
  """Result of a role membership check for an account."""
@@ -44,7 +55,7 @@ class RoleAccount:
44
55
  def role_name(self) -> str:
45
56
  return Roles.get_name(self.role_id)
46
57
 
47
- def to_dict(self) -> dict[str, str | int | bool]:
58
+ def to_dict(self) -> RoleAccountJson:
48
59
  """Canonical JSON-ready row shape, shared by the CLI surfaces."""
49
60
  return {
50
61
  "account": self.account,