ipor-fusion 4.0.0.dev57__tar.gz → 4.0.0.dev63__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 (292) hide show
  1. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/AGENTS.md +4 -1
  2. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_info_view.py +4 -8
  5. ipor_fusion-4.0.0.dev63/src/ipor_fusion/readers/oracle_mapping.py +484 -0
  6. ipor_fusion-4.0.0.dev63/src/ipor_fusion/readers/oracle_mapping_nodes.py +367 -0
  7. ipor_fusion-4.0.0.dev63/src/ipor_fusion/readers/oracle_mapping_reader.py +242 -0
  8. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/vault_data.py +25 -454
  9. ipor_fusion-4.0.0.dev63/src/ipor_fusion/vault_managers.py +185 -0
  10. ipor_fusion-4.0.0.dev63/src/ipor_fusion/vault_positions.py +238 -0
  11. ipor_fusion-4.0.0.dev63/src/ipor_fusion/vault_reads.py +81 -0
  12. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/conftest.py +4 -1
  13. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_commands.py +4 -4
  14. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_helpers.py +21 -22
  15. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_pending_requests_scan.py +4 -7
  16. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_server.py +7 -5
  17. ipor_fusion-4.0.0.dev57/src/ipor_fusion/readers/oracle_mapping.py +0 -1042
  18. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.claude-plugin/marketplace.json +0 -0
  19. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.claude-plugin/plugin.json +0 -0
  20. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.env.example +0 -0
  21. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.gemini/settings.json +0 -0
  22. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.git-blame-ignore-revs +0 -0
  23. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/cd.yml +0 -0
  24. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/ci.yml +0 -0
  25. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/commit-lint.yml +0 -0
  26. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/notify-slack.yml +0 -0
  27. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/python-build.yml +0 -0
  28. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/python-publish.yml +0 -0
  29. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/release.yml +0 -0
  30. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.github/workflows/report-slack.yml +0 -0
  31. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.gitignore +0 -0
  32. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.mcp.json +0 -0
  33. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.pre-commit-config.yaml +0 -0
  34. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/.python-version +0 -0
  35. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/CHANGELOG.md +0 -0
  36. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/CLAUDE.md +0 -0
  37. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/CODEOWNERS +0 -0
  38. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/LICENSE +0 -0
  39. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/MIGRATION_4.md +0 -0
  40. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/README.md +0 -0
  41. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/build.sh +0 -0
  42. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/CONTRIBUTING.md +0 -0
  43. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/README.md +0 -0
  44. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  45. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  46. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  47. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  48. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/external_state_margin_leg_base.py +0 -0
  49. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/examples/simple_aave_v3_supply_base.py +0 -0
  50. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/pytest.ini +0 -0
  51. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/scripts/commit-check.sh +0 -0
  52. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/scripts/crosschain_readiness.py +0 -0
  53. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/skills/ipor-deploy-vault/SKILL.md +0 -0
  54. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/__init__.py +0 -0
  55. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/about.py +0 -0
  56. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cache.py +0 -0
  57. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/chains.py +0 -0
  58. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/__init__.py +0 -0
  59. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  60. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/config_cmd.py +0 -0
  61. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/config_store.py +0 -0
  62. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/main.py +0 -0
  63. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/market_cmd.py +0 -0
  64. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/options.py +0 -0
  65. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  66. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  67. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_health.py +0 -0
  68. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  69. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  70. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/config/__init__.py +0 -0
  71. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/config/roles.py +0 -0
  72. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/__init__.py +0 -0
  73. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/access.py +0 -0
  74. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/address.py +0 -0
  75. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/context.py +0 -0
  76. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/contract.py +0 -0
  77. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/erc20.py +0 -0
  78. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/external_state_executor.py +0 -0
  79. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/fee_manager.py +0 -0
  80. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/fuse_action.py +0 -0
  81. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/fusion_factory.py +0 -0
  82. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/logs.py +0 -0
  83. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/multicall.py +0 -0
  84. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/oracle.py +0 -0
  85. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/plasma_vault.py +0 -0
  86. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/rewards_manager.py +0 -0
  87. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/simulation.py +0 -0
  88. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  89. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/__init__.py +0 -0
  90. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  91. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  92. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  93. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  94. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  95. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  96. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  97. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  98. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  99. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/contracts.py +0 -0
  100. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/discovery.py +0 -0
  101. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/errors.py +0 -0
  102. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/lane.py +0 -0
  103. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/logs.py +0 -0
  104. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/messages.py +0 -0
  105. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/simulation.py +0 -0
  106. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  107. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  108. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  109. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  110. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  111. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/crosschain/transport.py +0 -0
  112. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/dependency_graph.py +0 -0
  113. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/entry_cli.py +0 -0
  114. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/entry_mcp.py +0 -0
  115. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/errors.py +0 -0
  116. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/explorer.py +0 -0
  117. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/field_docs.py +0 -0
  118. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/__init__.py +0 -0
  119. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  120. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/async_action.py +0 -0
  121. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/base.py +0 -0
  122. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  123. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  124. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  125. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  126. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  127. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/erc4626.py +0 -0
  128. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  129. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/events.py +0 -0
  130. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/external_state.py +0 -0
  131. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  132. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  133. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  134. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/hypercore.py +0 -0
  135. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  136. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/merkl.py +0 -0
  137. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/morpho.py +0 -0
  138. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  139. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  140. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/fuses/universal.py +0 -0
  141. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/__init__.py +0 -0
  142. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/architecture.md +0 -0
  143. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/glossary.md +0 -0
  144. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/invariants.md +0 -0
  145. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/guide/quickstart.md +0 -0
  146. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/market_ids.py +0 -0
  147. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/market_info.py +0 -0
  148. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/__init__.py +0 -0
  149. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/guide.py +0 -0
  150. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/models.py +0 -0
  151. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/mcp/server.py +0 -0
  152. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/morpho_api.py +0 -0
  153. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/py.typed +0 -0
  154. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/__init__.py +0 -0
  155. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/aave_v3.py +0 -0
  156. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/aave_v4.py +0 -0
  157. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/compound_v3.py +0 -0
  158. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/hypercore.py +0 -0
  159. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  160. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/lending_health.py +0 -0
  161. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/morpho.py +0 -0
  162. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/position_manager.py +0 -0
  163. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  164. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  165. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/readers/unpriceable.py +0 -0
  166. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/substrates.py +0 -0
  167. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/types.py +0 -0
  168. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/validation.py +0 -0
  169. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/src/ipor_fusion/vault_info.py +0 -0
  170. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/UniswapV3UniversalRouter.py +0 -0
  171. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_crosschain.py +0 -0
  172. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_crosschain_lifecycle.py +0 -0
  173. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_crosschain_recovery.py +0 -0
  174. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_euler_v2.py +0 -0
  175. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_foundry.py +0 -0
  176. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_guide.py +0 -0
  177. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_hypercore_model.py +0 -0
  178. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_hypercore_shadow.py +0 -0
  179. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_multicall.py +0 -0
  180. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_simulate.py +0 -0
  181. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/_validation.py +0 -0
  182. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/addresses.py +0 -0
  183. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/constants.py +0 -0
  184. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  185. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/fixtures/hypercore_market55.json +0 -0
  186. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/clean.json +0 -0
  187. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  188. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/rich.json +0 -0
  189. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  190. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_about.py +0 -0
  191. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_access_manager.py +0 -0
  192. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cache.py +0 -0
  193. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_chains.py +0 -0
  194. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_changelog.py +0 -0
  195. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_config_store.py +0 -0
  196. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_hypercore.py +0 -0
  197. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_lending_health.py +0 -0
  198. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_market_morpho_blue.py +0 -0
  199. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_oracle_mapping.py +0 -0
  200. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_role_accounts.py +0 -0
  201. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_vault_info_golden.py +0 -0
  202. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_vault_predates_getters.py +0 -0
  203. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_cli_vault_validate.py +0 -0
  204. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_contract_call.py +0 -0
  205. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_contract_encoder.py +0 -0
  206. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_ccip_events.py +0 -0
  207. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_ccip_indexer.py +0 -0
  208. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_ccip_quote.py +0 -0
  209. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_chainlink.py +0 -0
  210. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_contracts.py +0 -0
  211. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_encoding.py +0 -0
  212. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_errors.py +0 -0
  213. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_lane.py +0 -0
  214. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_layering.py +0 -0
  215. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_readiness.py +0 -0
  216. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_readiness_report.py +0 -0
  217. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_crosschain_transport.py +0 -0
  218. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_erc20.py +0 -0
  219. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_examples_composed_hypercore.py +0 -0
  220. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_examples_crosschain.py +0 -0
  221. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  222. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_examples_vaults.py +0 -0
  223. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_explorer.py +0 -0
  224. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_external_state_events.py +0 -0
  225. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_external_state_executor.py +0 -0
  226. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_fee_manager.py +0 -0
  227. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_fuse_encoding.py +0 -0
  228. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_guide.py +0 -0
  229. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_deployment.py +0 -0
  230. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_events.py +0 -0
  231. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_flow.py +0 -0
  232. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_live.py +0 -0
  233. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_outcome.py +0 -0
  234. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_readers.py +0 -0
  235. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_hypercore_simulation.py +0 -0
  236. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_logs.py +0 -0
  237. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_hypercore.py +0 -0
  238. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_models.py +0 -0
  239. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_mcp_vault_validate.py +0 -0
  240. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_multicall.py +0 -0
  241. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_oracle.py +0 -0
  242. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_oracle_mapping.py +0 -0
  243. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_plasma_vault.py +0 -0
  244. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_reader_encoding.py +0 -0
  245. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_readiness_selection.py +0 -0
  246. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_revert_decoding.py +0 -0
  247. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_rewards_manager.py +0 -0
  248. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_roles.py +0 -0
  249. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_sdk_layering.py +0 -0
  250. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  251. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  252. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  253. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_canary.py +0 -0
  254. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_hypercore.py +0 -0
  255. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  256. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  257. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  258. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  259. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  260. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  261. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_examples_arbitrum.py +0 -0
  263. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_examples_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_examples_crosschain.py +0 -0
  265. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_external_state_executor_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_morpho_blue.py +0 -0
  270. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v3.py +0 -0
  271. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  272. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  273. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  274. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  275. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_readers_integration.py +0 -0
  276. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_release_funds.py +0 -0
  277. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_swap_on_base.py +0 -0
  278. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  279. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_solidity_compiler.py +0 -0
  280. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_solidity_mirrors.py +0 -0
  281. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_substrates.py +0 -0
  282. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_types_and_events.py +0 -0
  283. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_unpriceable_tokens.py +0 -0
  284. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_validation.py +0 -0
  285. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_vault_dep_graph.py +0 -0
  286. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_vault_info_onchain_base.py +0 -0
  287. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  288. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_vault_simulator_blocks.py +0 -0
  289. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_web3_context.py +0 -0
  290. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/test_withdraw_manager.py +0 -0
  291. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/tests/typed_client/consumer.py +0 -0
  292. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev63}/uv.lock +0 -0
@@ -184,7 +184,10 @@ the lifecycle matrix follows that.
184
184
  balance-graph checks
185
185
  - `vault_data.py` — public vault read: `fetch_vault_data` → `VaultData` (with `FeeData`,
186
186
  `WithdrawManagerData`), `fetch_deployment_info`, cached `resolve_token_symbol` /
187
- `resolve_token_decimals`; `vault_info.py` — `build_vault_info_json`, the
187
+ `resolve_token_decimals`; it composes `vault_reads.py` (the shared Multicall3 batch
188
+ and degrade-to-``None`` helpers), `vault_managers.py` (fee and WithdrawManager
189
+ snapshots) and `vault_positions.py` (Morpho/Aave position breakdowns and their token
190
+ prices); `vault_info.py` — `build_vault_info_json`, the
188
191
  `fusion vault info --json` document (contract names via an injected `resolve_name`),
189
192
  and `health_check_lists`; `explorer.py` Etherscan V2 (single endpoint, needs API key);
190
193
  `cache.py` the on-disk contract and deployment caches (`~/.cache/ipor-fusion/`).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev57
3
+ Version: 4.0.0.dev63
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.dev57"
3
+ version = "4.0.0.dev63"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -32,16 +32,11 @@ from ipor_fusion.readers.hypercore import HyperCorePendingState
32
32
  from ipor_fusion.substrates import (
33
33
  format_market_label,
34
34
  )
35
- from ipor_fusion.types import ChainId, PlasmaVaultAddress
35
+ from ipor_fusion.types import ZERO_ADDRESS, ChainId, PlasmaVaultAddress
36
36
  from ipor_fusion.validation import (
37
37
  validate_vault_state,
38
38
  )
39
- from ipor_fusion.vault_data import (
40
- _ZERO_ADDRESS,
41
- VaultData,
42
- _safe_call,
43
- fetch_deployment_info,
44
- )
39
+ from ipor_fusion.vault_data import VaultData, fetch_deployment_info
45
40
  from ipor_fusion.vault_info import (
46
41
  BLOCK_EXPLORER_URLS,
47
42
  IPOR_APP_URL,
@@ -53,6 +48,7 @@ from ipor_fusion.vault_info import (
53
48
  _wad_to_usd,
54
49
  build_vault_info_json,
55
50
  )
51
+ from ipor_fusion.vault_reads import _safe_call
56
52
 
57
53
 
58
54
  def _print_role_accounts_table(role_accounts: list[dict[str, Any]]) -> None:
@@ -275,7 +271,7 @@ def _print_fees(data: VaultData) -> None:
275
271
  click.echo("Fees:")
276
272
  click.echo(f" Fee Manager: {fees.fee_manager or 'N/A'}")
277
273
  dao_payee = fees.ipor_dao_fee_recipient
278
- if not dao_payee or dao_payee == _ZERO_ADDRESS:
274
+ if not dao_payee or dao_payee == ZERO_ADDRESS:
279
275
  dao_payee = "N/A"
280
276
  click.echo(f" IPOR DAO payee: {dao_payee}")
281
277
  onboarding_fee = _format_fee_percent(_wad_to_percent(fees.deposit_fee_wad))
@@ -0,0 +1,484 @@
1
+ """Resolve how a Plasma Vault prices every configured asset at a given block.
2
+
3
+ Powers the ``fusion vault oracle-mapping`` CLI command and the
4
+ ``vault_oracle_mapping`` MCP tool. For each asset the vault's price oracle
5
+ knows about, this walks the *source* price feed, classifies its type by
6
+ interface probing, reads the effective price, and recursively resolves any
7
+ feed that derives its price from another asset (ERC4626 wrappers, Morpho
8
+ collateral feeds). Unknown feeds are reported as partial, never dropped.
9
+ A zero source is not a dead end: the oracle may be a manager variant that
10
+ delegates such assets to an underlying global middleware — that delegation is
11
+ followed and reported as ``middleware_fallback``. Every aggregator-compatible
12
+ read (Chainlink-tier leaves, dual-cross-reference component feeds) carries
13
+ ``description()`` and the full ``latestRoundData()`` round in
14
+ ``source_detail`` — raw values plus ISO-8601 UTC twins of the timestamps,
15
+ no staleness judgment; ``description`` is null when the feed does not
16
+ implement it.
17
+
18
+ Chainlink leaves are graded by on-chain evidence: ``ChainlinkAggregator``
19
+ only when the full AggregatorV3Interface answers (``latestRoundData`` +
20
+ ``decimals`` + ``description`` + ``version``) and nothing contradicts it;
21
+ ``chainlink_style`` when the feed merely quacks like an aggregator — verify
22
+ the address yourself if identity matters. Grading only ever withholds the
23
+ confirmed label; it cannot prove a contract's identity or operator.
24
+
25
+ Statuses: a node is ``resolved`` (own feed explained and every dependency
26
+ resolved), ``partially_resolved`` (own feed explained, but some descendant is
27
+ not resolved), or ``partial`` (the node's own resolution is incomplete —
28
+ ``reason`` says why). The mapping-level ``status`` rolls up the roots:
29
+ ``resolved`` when every configured asset resolved (vacuously so for zero
30
+ assets), ``unresolved`` when every root is ``partial`` (total failure),
31
+ ``partially_resolved`` otherwise. The ``unresolved`` array mirrors
32
+ ``partial`` nodes only; demoted parents self-describe.
33
+
34
+ Design:
35
+ - All network access goes through :class:`OracleMappingReader` (the single
36
+ seam). Every read is "safe": a revert/decode failure yields ``None`` so one
37
+ bad asset never aborts the whole map. The pure logic (classification,
38
+ recursion, event collapse) takes a reader and is unit-testable with a fake.
39
+ - SDK primitives are reused for the reads that already exist
40
+ (``PriceOracleMiddleware``, ``PriceOracleMiddlewareManager``, ``ERC20``,
41
+ ``PlasmaVault``); only the feed-probe reads are added here.
42
+
43
+ Adding a feed type:
44
+
45
+ 1. Wrapper class (``ContractWrapper``) exposing its getters.
46
+ 2. Revert-safe reader probes on :class:`OracleMappingReader` (``None`` on
47
+ failure); both live in ``oracle_mapping_reader``.
48
+ 3. ``TYPE_*`` constant (``oracle_mapping_nodes``) + ``_resolve_*`` function
49
+ (``oracle_mapping_nodes`` for a leaf, here when it recurses); recurse only into
50
+ middleware assets — component *feeds* belong in ``source_detail``.
51
+ 4. Dispatch entry in ``_classify_and_resolve``, ordered by specificity,
52
+ above the generic Chainlink-tier fallback. Gate on the type-defining
53
+ reads, not a single common-named getter (false-matcher magnet); an
54
+ incomplete gate falls through with no type claim.
55
+ 5. External doc touchpoints: ``cli/vault_cmd.py`` (command docstring +
56
+ rendering), ``mcp/server.py`` tool docstring and ``mcp/models.py``
57
+ ``source_type`` description (the last two are tripwire-tested).
58
+ 6. Tests against the fake reader: precedence over the generic aggregator
59
+ fallback, happy path, per-probe degradation to ``partial``, incomplete
60
+ gate falling through, and ABI-decode cases for the new reader probes.
61
+ """
62
+
63
+ from __future__ import annotations
64
+
65
+ from eth_typing import ChecksumAddress
66
+
67
+ from ipor_fusion.core.context import Web3Context
68
+ from ipor_fusion.core.plasma_vault import PlasmaVault
69
+ from ipor_fusion.readers.oracle_mapping_nodes import (
70
+ TYPE_CHAINLINK,
71
+ TYPE_CHAINLINK_STYLE,
72
+ TYPE_DUAL_XREF,
73
+ TYPE_ERC4626,
74
+ TYPE_MIDDLEWARE_FALLBACK,
75
+ TYPE_MORPHO,
76
+ TYPE_UNKNOWN,
77
+ WAD_DECIMALS,
78
+ ZERO_ADDRESS,
79
+ OracleAsset,
80
+ OracleMapping,
81
+ OracleNode,
82
+ OraclePrice,
83
+ _apply_dependency_status,
84
+ _collect_unresolved,
85
+ _mapping_status,
86
+ _partial,
87
+ _price_block,
88
+ _resolve_chainlink,
89
+ _resolve_dual_xref,
90
+ collapse_sources,
91
+ normalize_wad,
92
+ )
93
+ from ipor_fusion.readers.oracle_mapping_reader import (
94
+ ASSET_PRICE_SOURCE_UPDATED_TOPIC,
95
+ OracleMappingReader,
96
+ )
97
+ from ipor_fusion.types import (
98
+ AssetSource,
99
+ PlasmaVaultAddress,
100
+ Price,
101
+ )
102
+
103
+ __all__ = [
104
+ "ASSET_PRICE_SOURCE_UPDATED_TOPIC",
105
+ "TYPE_CHAINLINK",
106
+ "TYPE_CHAINLINK_STYLE",
107
+ "TYPE_DUAL_XREF",
108
+ "TYPE_ERC4626",
109
+ "TYPE_MIDDLEWARE_FALLBACK",
110
+ "TYPE_MORPHO",
111
+ "TYPE_UNKNOWN",
112
+ "WAD_DECIMALS",
113
+ "ZERO_ADDRESS",
114
+ "OracleAsset",
115
+ "OracleMapping",
116
+ "OracleMappingReader",
117
+ "OracleNode",
118
+ "OraclePrice",
119
+ "build_oracle_mapping",
120
+ "collapse_sources",
121
+ "normalize_wad",
122
+ "resolve_asset",
123
+ ]
124
+
125
+
126
+ def _resolve_erc4626(
127
+ reader: OracleMappingReader,
128
+ node: OracleNode,
129
+ label: str,
130
+ vault: ChecksumAddress,
131
+ underlying: ChecksumAddress,
132
+ depth: int,
133
+ visited: set[str],
134
+ max_depth: int,
135
+ ) -> OracleNode:
136
+ """Resolve an ERC4626 feed: share→underlying rate, then recurse on the underlying."""
137
+ share_decimals = reader.vault_decimals(vault)
138
+ rate = (
139
+ reader.vault_convert_to_assets(vault, 10**share_decimals)
140
+ if share_decimals is not None
141
+ else None
142
+ )
143
+ node.source_type = TYPE_ERC4626
144
+ node.source_detail = {
145
+ "vault": vault,
146
+ "underlying": underlying,
147
+ "share_decimals": share_decimals,
148
+ "rate": str(rate) if rate is not None else None,
149
+ }
150
+ dep = _resolve(reader, underlying, depth + 1, visited, max_depth)
151
+ node.dependencies = [dep]
152
+ node.path = [label, "convertToAssets(1 share)", *dep.path]
153
+ if rate is None:
154
+ # No derived price without the share→asset rate — same "complete data
155
+ # or partial" standard as dual-xref; the dep is still attached.
156
+ node.status = "partial"
157
+ node.reason = "erc4626_rate_unreadable"
158
+ return node
159
+ node.status = "resolved"
160
+ return node
161
+
162
+
163
+ def _resolve_morpho(
164
+ reader: OracleMappingReader,
165
+ node: OracleNode,
166
+ label: str,
167
+ source: ChecksumAddress,
168
+ morpho_oracle: ChecksumAddress,
169
+ loan: ChecksumAddress,
170
+ depth: int,
171
+ visited: set[str],
172
+ max_depth: int,
173
+ ) -> OracleNode:
174
+ """Resolve a Morpho collateral feed: market price, then recurse on the loan token."""
175
+ collateral = reader.feed_collateral_token(source)
176
+ price = reader.morpho_oracle_price(morpho_oracle)
177
+ node.source_type = TYPE_MORPHO
178
+ node.source_detail = {
179
+ "morpho_oracle": morpho_oracle,
180
+ "collateral_token": collateral,
181
+ "loan_token": loan,
182
+ "morpho_price": str(price) if price is not None else None,
183
+ }
184
+ dep = _resolve(reader, loan, depth + 1, visited, max_depth)
185
+ node.dependencies = [dep]
186
+ node.path = [label, "Morpho collateral/loan oracle", *dep.path]
187
+ node.status = "resolved"
188
+ return node
189
+
190
+
191
+ def _resolve_middleware_fallback(
192
+ reader: OracleMappingReader,
193
+ asset: ChecksumAddress,
194
+ symbol: str | None,
195
+ decimals: int | None,
196
+ source: ChecksumAddress | None,
197
+ label: str,
198
+ price: Price | None,
199
+ depth: int,
200
+ visited: set[str],
201
+ max_depth: int,
202
+ ) -> OracleNode:
203
+ """Explain a zero/unreadable source instead of declaring the asset dead.
204
+
205
+ On a ``PriceOracleMiddlewareManager`` a zero source means "no per-vault
206
+ override" — the price comes from the underlying global middleware, so the
207
+ asset is re-resolved there to surface the real feed. Without a manager, a
208
+ readable price means the middleware priced the asset through its own
209
+ internal fallback (the Chainlink FeedRegistry on mainnet).
210
+ """
211
+ underlying_middleware = reader.underlying_middleware()
212
+ if (
213
+ underlying_middleware is not None
214
+ and underlying_middleware.lower() == ZERO_ADDRESS
215
+ ):
216
+ underlying_middleware = None
217
+ if underlying_middleware is None and price is None:
218
+ return _partial(
219
+ asset,
220
+ symbol,
221
+ decimals,
222
+ source,
223
+ TYPE_UNKNOWN,
224
+ "no_source_configured",
225
+ [label],
226
+ price,
227
+ )
228
+ node = OracleNode(
229
+ asset=asset,
230
+ symbol=symbol,
231
+ decimals=decimals,
232
+ source=source,
233
+ price=_price_block(price),
234
+ )
235
+ node.source_type = TYPE_MIDDLEWARE_FALLBACK
236
+ node.status = "resolved"
237
+ if underlying_middleware is not None:
238
+ # Same asset, different oracle — must not be in `visited` yet; runaway
239
+ # manager→manager chains are bounded by max_depth instead.
240
+ dep = _resolve(
241
+ reader.delegate(underlying_middleware),
242
+ asset,
243
+ depth + 1,
244
+ visited,
245
+ max_depth,
246
+ )
247
+ node.dependencies = [dep]
248
+ # dep re-resolves the same asset, so drop its leading label from the path.
249
+ node.path = [label, "middleware fallback", *dep.path[1:]]
250
+ node.source_detail = {
251
+ "delegated_to": underlying_middleware,
252
+ "chainlink_feed_registry": None,
253
+ }
254
+ if price is None:
255
+ # On-chain the manager's zero-source getAssetPrice IS this
256
+ # delegation, so its own read failing while the hop answers means
257
+ # an anomaly (transient read, or a feed answering 0/negative) —
258
+ # surface it instead of reporting resolved with a null price.
259
+ node.status = "partial"
260
+ node.reason = "manager_price_unreadable"
261
+ return node
262
+ # Not a manager, yet the price read succeeded (else the no_source_configured
263
+ # early return above would have fired): the middleware's internal fallback
264
+ # (FeedRegistry on mainnet) priced it — report a leaf.
265
+ registry = reader.chainlink_feed_registry()
266
+ if registry is not None and registry.lower() == ZERO_ADDRESS:
267
+ registry = None
268
+ node.path = [label, "middleware fallback"]
269
+ node.source_detail = {"delegated_to": None, "chainlink_feed_registry": registry}
270
+ return node
271
+
272
+
273
+ def _classify_and_resolve(
274
+ reader: OracleMappingReader,
275
+ node: OracleNode,
276
+ label: str,
277
+ source: ChecksumAddress,
278
+ depth: int,
279
+ visited: set[str],
280
+ max_depth: int,
281
+ ) -> OracleNode:
282
+ """Classify the source by interface probing and dispatch to its type resolver."""
283
+ # Probe most specific first; Chainlink's interface is shared by the others.
284
+ # Partial getter match = wrong type hypothesis, not a broken feed — fall
285
+ # through (a foreign getter answering caps the leaf at chainlink_style).
286
+ # New types: see the "Adding a feed type" checklist in the module docstring.
287
+ asset_x = reader.feed_asset_x(source)
288
+ if asset_x is not None:
289
+ xy_feed = reader.feed_asset_x_asset_y_feed(source)
290
+ y_usd_feed = reader.feed_asset_y_usd_feed(source)
291
+ if xy_feed is not None and y_usd_feed is not None:
292
+ return _resolve_dual_xref(reader, node, label, asset_x, xy_feed, y_usd_feed)
293
+ morpho_oracle = reader.feed_morpho_oracle(source)
294
+ if morpho_oracle is not None:
295
+ # gate: the type-defining loanToken() must answer too — a lone
296
+ # morphoOracle() hit is a false matcher, not a broken Morpho feed
297
+ loan = reader.feed_loan_token(source)
298
+ if loan is not None:
299
+ return _resolve_morpho(
300
+ reader,
301
+ node,
302
+ label,
303
+ source,
304
+ morpho_oracle,
305
+ loan,
306
+ depth,
307
+ visited,
308
+ max_depth,
309
+ )
310
+ vault = reader.feed_vault(source)
311
+ if vault is not None:
312
+ # gate: vault() is a common method name — only a vault answering
313
+ # asset() defines the ERC4626 hypothesis
314
+ underlying = reader.vault_asset(vault)
315
+ if underlying is not None:
316
+ return _resolve_erc4626(
317
+ reader, node, label, vault, underlying, depth, visited, max_depth
318
+ )
319
+ rnd = reader.feed_latest_round_data(source)
320
+ if rnd is not None:
321
+ has_foreign_getter = (
322
+ asset_x is not None or morpho_oracle is not None or vault is not None
323
+ )
324
+ return _resolve_chainlink(
325
+ reader, node, label, source, rnd, has_foreign_getter=has_foreign_getter
326
+ )
327
+ node.source_type = TYPE_UNKNOWN
328
+ node.status = "partial"
329
+ node.reason = "unsupported_custom_feed"
330
+ node.path = [label, "custom feed (unresolved)"]
331
+ return node
332
+
333
+
334
+ def _resolve(
335
+ reader: OracleMappingReader,
336
+ asset: ChecksumAddress,
337
+ depth: int,
338
+ visited: set[str],
339
+ max_depth: int,
340
+ ) -> OracleNode:
341
+ """Resolve one asset into a node (recursively for derived feeds)."""
342
+ symbol = reader.symbol(asset)
343
+ decimals = reader.token_decimals(asset)
344
+ label = symbol or asset
345
+ key = asset.lower()
346
+
347
+ # The oracle's getAssetPrice is authoritative and independent of how far we
348
+ # can explain the feed, so read it up front — even cycle/depth-capped nodes
349
+ # carry the real price.
350
+ price = reader.asset_price(asset)
351
+
352
+ if key in visited:
353
+ return _partial(
354
+ asset,
355
+ symbol,
356
+ decimals,
357
+ None,
358
+ TYPE_UNKNOWN,
359
+ "cycle_detected",
360
+ [label],
361
+ price,
362
+ )
363
+ if depth > max_depth:
364
+ return _partial(
365
+ asset,
366
+ symbol,
367
+ decimals,
368
+ None,
369
+ TYPE_UNKNOWN,
370
+ "max_depth_exceeded",
371
+ [label],
372
+ price,
373
+ )
374
+
375
+ source = reader.source_of(asset)
376
+ if source is None or source.lower() == ZERO_ADDRESS:
377
+ return _apply_dependency_status(
378
+ _resolve_middleware_fallback(
379
+ reader,
380
+ asset,
381
+ symbol,
382
+ decimals,
383
+ source,
384
+ label,
385
+ price,
386
+ depth,
387
+ visited,
388
+ max_depth,
389
+ )
390
+ )
391
+
392
+ node = OracleNode(
393
+ asset=asset,
394
+ symbol=symbol,
395
+ decimals=decimals,
396
+ source=source,
397
+ price=_price_block(price),
398
+ )
399
+ visited.add(key)
400
+ try:
401
+ return _apply_dependency_status(
402
+ _classify_and_resolve(
403
+ reader, node, label, source, depth, visited, max_depth
404
+ )
405
+ )
406
+ finally:
407
+ visited.discard(key)
408
+
409
+
410
+ def resolve_asset(
411
+ reader: OracleMappingReader, asset: ChecksumAddress, max_depth: int
412
+ ) -> OracleNode:
413
+ """Public entry: resolve a single asset into a node tree."""
414
+ return _resolve(reader, asset, 0, set(), max_depth)
415
+
416
+
417
+ def _enumerate_assets(
418
+ reader: OracleMappingReader, effective_block: int
419
+ ) -> tuple[list[ChecksumAddress], AssetSource]:
420
+ """Configured assets via ``getConfiguredAssets`` or event-replay fallback."""
421
+ assets = reader.configured_assets()
422
+ if assets is not None:
423
+ return assets, "getConfiguredAssets"
424
+ events = reader.asset_source_events(effective_block)
425
+ return list(collapse_sources(events)), "events"
426
+
427
+
428
+ def build_oracle_mapping(
429
+ ctx: Web3Context,
430
+ vault_address: PlasmaVaultAddress,
431
+ effective_block: int,
432
+ max_depth: int = 6,
433
+ ) -> OracleMapping:
434
+ """Build the full oracle mapping for ``vault_address``.
435
+
436
+ ``ctx`` must already be pinned to ``effective_block`` (the caller sets
437
+ ``ctx.default_block``); the block is echoed in the output. Raises
438
+ `UnsupportedVaultVersionError` for a vault that exposes no price-oracle
439
+ getter.
440
+ """
441
+ vault = PlasmaVault(ctx, vault_address)
442
+ oracle_addr = vault.price_oracle_address()
443
+ asset_addr = vault.underlying_asset_address().call()
444
+
445
+ try:
446
+ vault_name = vault.name().call()
447
+ except Exception:
448
+ vault_name = None
449
+
450
+ reader = OracleMappingReader(ctx, oracle_addr)
451
+ assets, asset_source = _enumerate_assets(reader, effective_block)
452
+
453
+ configured: list[OracleNode] = []
454
+ for asset in assets:
455
+ try:
456
+ configured.append(resolve_asset(reader, asset, max_depth))
457
+ except Exception:
458
+ configured.append(
459
+ _partial(
460
+ asset,
461
+ reader.symbol(asset),
462
+ reader.token_decimals(asset),
463
+ None,
464
+ TYPE_UNKNOWN,
465
+ "resolution_error",
466
+ [asset],
467
+ )
468
+ )
469
+
470
+ return OracleMapping(
471
+ vault=vault_address,
472
+ vault_name=vault_name,
473
+ asset=OracleAsset(
474
+ address=asset_addr,
475
+ symbol=reader.symbol(asset_addr),
476
+ decimals=reader.token_decimals(asset_addr),
477
+ ),
478
+ price_oracle=oracle_addr,
479
+ block_number=effective_block,
480
+ asset_source=asset_source,
481
+ status=_mapping_status(configured),
482
+ configured_assets=configured,
483
+ unresolved=_collect_unresolved(configured),
484
+ )