ipor-fusion 4.0.0.dev151__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.dev151 → ipor_fusion-4.0.0.dev153}/AGENTS.md +21 -11
  2. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/MIGRATION_4.md +43 -0
  3. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/pyproject.toml +3 -2
  5. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/__init__.py +101 -1
  6. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_cmd.py +34 -3
  7. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_info_view.py +4 -3
  8. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_markets_view.py +7 -7
  9. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/__init__.py +7 -1
  10. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/access.py +12 -1
  11. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fusion_factory.py +34 -36
  12. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/events.py +5 -2
  13. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/indexer.py +22 -13
  14. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/transport.py +20 -17
  15. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/transport.py +2 -2
  16. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/external_state_events.py +14 -9
  17. ipor_fusion-4.0.0.dev153/src/ipor_fusion/market_info.py +396 -0
  18. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/models.py +8 -6
  19. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/morpho_api.py +192 -132
  20. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_info.py +500 -90
  21. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_helpers.py +10 -4
  22. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_market_morpho_blue.py +8 -0
  23. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_pending_requests_scan.py +3 -1
  24. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_ccip_events.py +11 -0
  25. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_transport.py +15 -0
  26. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_external_state_events.py +9 -0
  27. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_models.py +218 -112
  28. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_public_api.py +98 -0
  29. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_hyperevm.py +19 -7
  30. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/typed_client/consumer.py +40 -3
  31. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/typed_client/rejected.py +13 -0
  32. ipor_fusion-4.0.0.dev151/src/ipor_fusion/market_info.py +0 -223
  33. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.claude-plugin/marketplace.json +0 -0
  34. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.claude-plugin/plugin.json +0 -0
  35. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.env.example +0 -0
  36. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.gemini/settings.json +0 -0
  37. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.git-blame-ignore-revs +0 -0
  38. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/cd.yml +0 -0
  39. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/ci.yml +0 -0
  40. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/commit-lint.yml +0 -0
  41. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/notify-slack.yml +0 -0
  42. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/python-build.yml +0 -0
  43. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/python-publish.yml +0 -0
  44. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/release.yml +0 -0
  45. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.github/workflows/report-slack.yml +0 -0
  46. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.gitignore +0 -0
  47. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.mcp.json +0 -0
  48. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.pre-commit-config.yaml +0 -0
  49. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/.python-version +0 -0
  50. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/CHANGELOG.md +0 -0
  51. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/CLAUDE.md +0 -0
  52. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/CODEOWNERS +0 -0
  53. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/LICENSE +0 -0
  54. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/README.md +0 -0
  55. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/build.sh +0 -0
  56. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/CONTRIBUTING.md +0 -0
  57. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/README.md +0 -0
  58. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  59. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  60. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  61. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  62. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/external_state_margin_leg_base.py +0 -0
  63. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/examples/simple_aave_v3_supply_base.py +0 -0
  64. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/pytest.ini +0 -0
  65. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/scripts/commit-check.sh +0 -0
  66. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/scripts/crosschain_readiness.py +0 -0
  67. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/skills/ipor-deploy-vault/SKILL.md +0 -0
  68. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/about.py +0 -0
  69. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cache.py +0 -0
  70. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/chains.py +0 -0
  71. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/__init__.py +0 -0
  72. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  73. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/config_cmd.py +0 -0
  74. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/config_store.py +0 -0
  75. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/main.py +0 -0
  76. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/market_cmd.py +0 -0
  77. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/options.py +0 -0
  78. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  79. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_health.py +0 -0
  80. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  81. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/config/__init__.py +0 -0
  82. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/config/roles.py +0 -0
  83. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/address.py +0 -0
  84. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/context.py +0 -0
  85. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/contract.py +0 -0
  86. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/erc20.py +0 -0
  87. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/external_state_executor.py +0 -0
  88. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fee_manager.py +0 -0
  89. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fuse.py +0 -0
  90. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/fuse_action.py +0 -0
  91. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/logs.py +0 -0
  92. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/multicall.py +0 -0
  93. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/oracle.py +0 -0
  94. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/plasma_vault.py +0 -0
  95. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/rewards_manager.py +0 -0
  96. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/simulation.py +0 -0
  97. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  98. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/__init__.py +0 -0
  99. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  100. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  101. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  102. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  103. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  104. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  105. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  106. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/contracts.py +0 -0
  107. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/discovery.py +0 -0
  108. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/errors.py +0 -0
  109. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  110. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  111. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  112. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  113. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/lane.py +0 -0
  114. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/logs.py +0 -0
  115. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/messages.py +0 -0
  116. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/simulation.py +0 -0
  117. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  118. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  119. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  120. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  121. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/dependency_graph.py +0 -0
  122. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/entry_cli.py +0 -0
  123. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/entry_mcp.py +0 -0
  124. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/errors.py +0 -0
  125. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/explorer.py +0 -0
  126. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/field_docs.py +0 -0
  127. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/__init__.py +0 -0
  128. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  129. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/async_action.py +0 -0
  130. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/base.py +0 -0
  131. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  132. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/erc4626.py +0 -0
  133. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  134. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/events.py +0 -0
  135. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/external_state.py +0 -0
  136. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  137. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  138. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/hypercore.py +0 -0
  139. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  140. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/merkl.py +0 -0
  141. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/morpho.py +0 -0
  142. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  143. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  144. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/fuses/universal.py +0 -0
  145. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/__init__.py +0 -0
  146. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/architecture.md +0 -0
  147. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/glossary.md +0 -0
  148. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/invariants.md +0 -0
  149. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/guide/quickstart.md +0 -0
  150. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/market_ids.py +0 -0
  151. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/__init__.py +0 -0
  152. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/guide.py +0 -0
  153. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/mcp/server.py +0 -0
  154. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/py.typed +0 -0
  155. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/__init__.py +0 -0
  156. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/aave_v3.py +0 -0
  157. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/aave_v4.py +0 -0
  158. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/compound_v3.py +0 -0
  159. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/hypercore.py +0 -0
  160. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  161. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/lending_health.py +0 -0
  162. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/morpho.py +0 -0
  163. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  164. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  165. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  166. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/position_manager.py +0 -0
  167. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  168. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  169. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/readers/unpriceable.py +0 -0
  170. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/substrates.py +0 -0
  171. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/types.py +0 -0
  172. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/validation.py +0 -0
  173. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_data.py +0 -0
  174. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_erc20.py +0 -0
  175. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_managers.py +0 -0
  176. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_positions.py +0 -0
  177. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/src/ipor_fusion/vault_reads.py +0 -0
  178. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/UniswapV3UniversalRouter.py +0 -0
  179. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_crosschain.py +0 -0
  180. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_crosschain_lifecycle.py +0 -0
  181. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_crosschain_recovery.py +0 -0
  182. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_euler_v2.py +0 -0
  183. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_foundry.py +0 -0
  184. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_guide.py +0 -0
  185. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_hypercore_model.py +0 -0
  186. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_hypercore_shadow.py +0 -0
  187. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_multicall.py +0 -0
  188. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_simulate.py +0 -0
  189. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/_validation.py +0 -0
  190. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/addresses.py +0 -0
  191. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/conftest.py +0 -0
  192. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/constants.py +0 -0
  193. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  194. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/fixtures/hypercore_market55.json +0 -0
  195. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/clean.json +0 -0
  196. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  197. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/rich.json +0 -0
  198. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  199. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_about.py +0 -0
  200. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_access_manager.py +0 -0
  201. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cache.py +0 -0
  202. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_chains.py +0 -0
  203. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_changelog.py +0 -0
  204. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_commands.py +0 -0
  205. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_config_store.py +0 -0
  206. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_hypercore.py +0 -0
  207. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_lending_health.py +0 -0
  208. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_oracle_mapping.py +0 -0
  209. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_role_accounts.py +0 -0
  210. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_vault_info_golden.py +0 -0
  211. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_vault_predates_getters.py +0 -0
  212. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_cli_vault_validate.py +0 -0
  213. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_contract_call.py +0 -0
  214. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_contract_encoder.py +0 -0
  215. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_ccip_indexer.py +0 -0
  216. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_ccip_quote.py +0 -0
  217. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_chainlink.py +0 -0
  218. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_contracts.py +0 -0
  219. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_encoding.py +0 -0
  220. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_errors.py +0 -0
  221. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_lane.py +0 -0
  222. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_layering.py +0 -0
  223. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_readiness.py +0 -0
  224. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_crosschain_readiness_report.py +0 -0
  225. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_docs_typing.py +0 -0
  226. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_erc20.py +0 -0
  227. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_error_hierarchy.py +0 -0
  228. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_examples_composed_hypercore.py +0 -0
  229. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_examples_crosschain.py +0 -0
  230. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  231. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_examples_vaults.py +0 -0
  232. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_explorer.py +0 -0
  233. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_external_state_executor.py +0 -0
  234. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_fee_manager.py +0 -0
  235. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_fuse_encoding.py +0 -0
  236. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_fusion_instance.py +0 -0
  237. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_guide.py +0 -0
  238. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_deployment.py +0 -0
  239. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_events.py +0 -0
  240. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_flow.py +0 -0
  241. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_live.py +0 -0
  242. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_outcome.py +0 -0
  243. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_readers.py +0 -0
  244. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_hypercore_simulation.py +0 -0
  245. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_logs.py +0 -0
  246. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_hypercore.py +0 -0
  247. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_server.py +0 -0
  248. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_mcp_vault_validate.py +0 -0
  249. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_multicall.py +0 -0
  250. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_oracle.py +0 -0
  251. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_oracle_mapping.py +0 -0
  252. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_plasma_vault.py +0 -0
  253. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_public_type_reachability.py +0 -0
  254. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_reader_encoding.py +0 -0
  255. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_readiness_selection.py +0 -0
  256. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_revert_decoding.py +0 -0
  257. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_rewards_manager.py +0 -0
  258. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_roles.py +0 -0
  259. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_sdk_layering.py +0 -0
  260. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  261. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  263. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_canary.py +0 -0
  264. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_hypercore.py +0 -0
  265. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  266. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_examples_arbitrum.py +0 -0
  272. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_examples_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_examples_crosschain.py +0 -0
  274. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_external_state_executor_base.py +0 -0
  275. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  277. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  278. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_morpho_blue.py +0 -0
  279. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v3.py +0 -0
  280. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  281. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  282. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  283. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  284. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_readers_integration.py +0 -0
  285. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_release_funds.py +0 -0
  286. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_swap_on_base.py +0 -0
  287. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  288. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_solidity_compiler.py +0 -0
  289. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_solidity_mirrors.py +0 -0
  290. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_substrates.py +0 -0
  291. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_types_and_events.py +0 -0
  292. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_unpriceable_tokens.py +0 -0
  293. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_validation.py +0 -0
  294. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_vault_data_block.py +0 -0
  295. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_vault_dep_graph.py +0 -0
  296. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_vault_info_onchain_base.py +0 -0
  297. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  298. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_vault_simulator_blocks.py +0 -0
  299. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_web3_context.py +0 -0
  300. {ipor_fusion-4.0.0.dev151 → ipor_fusion-4.0.0.dev153}/tests/test_withdraw_manager.py +0 -0
  301. {ipor_fusion-4.0.0.dev151 → 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.dev151
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev151"
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,
@@ -22,9 +22,8 @@ For external-signer flows, grab the bytes directly:
22
22
 
23
23
  from __future__ import annotations
24
24
 
25
- from collections.abc import Callable, Mapping
26
- from dataclasses import dataclass
27
- from typing import Any
25
+ from collections.abc import Mapping
26
+ from dataclasses import dataclass, fields
28
27
 
29
28
  from eth_abi import decode as abi_decode
30
29
  from eth_typing import ChecksumAddress
@@ -80,61 +79,60 @@ class FusionInstance:
80
79
  price_manager: ChecksumAddress
81
80
 
82
81
  @classmethod
83
- def from_dict(
84
- cls, data: Mapping[str, Any]
85
- ) -> FusionInstance: # proctor: ignore[primitives-untyped-dict]
82
+ def from_dict( # proctor: ignore[primitives-untyped-dict]
83
+ cls, data: Mapping[str, object]
84
+ ) -> FusionInstance:
86
85
  """Rebuild an instance from its JSON form (``dataclasses.asdict``),
87
86
  parsing every field: role addresses through their parsers, the rest
88
87
  checksummed, integers and strings type-checked. Raises ``ValueError``
89
88
  on a missing, extra or malformed field."""
90
- missing = _INSTANCE_FIELDS.keys() - data.keys()
91
- extra = data.keys() - _INSTANCE_FIELDS.keys()
89
+ names = {f.name for f in fields(cls)}
90
+ missing = names - data.keys()
91
+ extra = data.keys() - names
92
92
  if missing or extra:
93
93
  raise ValueError(
94
94
  f"FusionInstance fields: missing {sorted(missing)}, extra {sorted(extra)}"
95
95
  )
96
96
  return cls(
97
- **{name: parse(data[name]) for name, parse in _INSTANCE_FIELDS.items()}
97
+ index=_integer(data["index"]),
98
+ version=_integer(data["version"]),
99
+ asset_name=_text(data["asset_name"]),
100
+ asset_symbol=_text(data["asset_symbol"]),
101
+ asset_decimals=Decimals(_integer(data["asset_decimals"])),
102
+ underlying_token=_address(data["underlying_token"]),
103
+ underlying_token_symbol=_text(data["underlying_token_symbol"]),
104
+ underlying_token_decimals=Decimals(
105
+ _integer(data["underlying_token_decimals"])
106
+ ),
107
+ initial_owner=_address(data["initial_owner"]),
108
+ plasma_vault=parse_plasma_vault_address(_text(data["plasma_vault"])),
109
+ plasma_vault_base=_address(data["plasma_vault_base"]),
110
+ access_manager=parse_access_manager_address(_text(data["access_manager"])),
111
+ fee_manager=_address(data["fee_manager"]),
112
+ rewards_manager=_address(data["rewards_manager"]),
113
+ withdraw_manager=_address(data["withdraw_manager"]),
114
+ context_manager=_address(data["context_manager"]),
115
+ price_manager=_address(data["price_manager"]),
98
116
  )
99
117
 
100
118
 
101
- def _integer(value: Any) -> int:
102
- if type(value) is not int:
103
- raise ValueError(f"expected an integer: {value!r}")
104
- return value
119
+ def _integer(value: object) -> int:
120
+ # Exactly int: a bool or an IntEnum is not a JSON integer field.
121
+ if type(value) is int:
122
+ return value
123
+ raise ValueError(f"expected an integer: {value!r}")
105
124
 
106
125
 
107
- def _text(value: Any) -> str:
126
+ def _text(value: object) -> str:
108
127
  if not isinstance(value, str):
109
128
  raise ValueError(f"expected a string: {value!r}")
110
129
  return value
111
130
 
112
131
 
113
- def _address(value: Any) -> ChecksumAddress:
132
+ def _address(value: object) -> ChecksumAddress:
114
133
  return Web3.to_checksum_address(_text(value))
115
134
 
116
135
 
117
- _INSTANCE_FIELDS: dict[str, Callable[[Any], Any]] = {
118
- "index": _integer,
119
- "version": _integer,
120
- "asset_name": _text,
121
- "asset_symbol": _text,
122
- "asset_decimals": lambda value: Decimals(_integer(value)),
123
- "underlying_token": _address,
124
- "underlying_token_symbol": _text,
125
- "underlying_token_decimals": lambda value: Decimals(_integer(value)),
126
- "initial_owner": _address,
127
- "plasma_vault": lambda value: parse_plasma_vault_address(_text(value)),
128
- "plasma_vault_base": _address,
129
- "access_manager": lambda value: parse_access_manager_address(_text(value)),
130
- "fee_manager": _address,
131
- "rewards_manager": _address,
132
- "withdraw_manager": _address,
133
- "context_manager": _address,
134
- "price_manager": _address,
135
- }
136
-
137
-
138
136
  _FUSION_INSTANCE_OUTPUT_TYPES: list[str] = [
139
137
  "uint256", # index
140
138
  "uint256", # version
@@ -19,7 +19,6 @@ from __future__ import annotations
19
19
  from collections.abc import Iterable, Mapping
20
20
  from dataclasses import dataclass
21
21
  from types import MappingProxyType
22
- from typing import Any
23
22
 
24
23
  from eth_abi import decode
25
24
  from eth_typing import ChecksumAddress
@@ -401,12 +400,16 @@ CCIP_EVENT_TOPICS: dict[bytes, CcipEventSpec] = {
401
400
  }
402
401
 
403
402
 
404
- def _python_value(abi_type: str, value: Any) -> object:
403
+ def _python_value(abi_type: str, value: object) -> object:
405
404
  """Checksum an address, also inside a struct; ``int``, ``bool`` and
406
405
  ``bytes32`` values already decode to the Python type."""
407
406
  if abi_type == "address":
407
+ if not isinstance(value, str):
408
+ raise TypeError(f"address decoded as {type(value).__name__}")
408
409
  return Web3.to_checksum_address(value)
409
410
  if abi_type.startswith("("):
411
+ if not isinstance(value, tuple):
412
+ raise TypeError(f"struct decoded as {type(value).__name__}")
410
413
  # Flat split: no CCIP event nests a struct inside a struct.
411
414
  return tuple(map(_python_value, abi_type[1:-1].split(","), value))
412
415
  return value