ipor-fusion 4.0.0.dev60__tar.gz → 4.0.0.dev66__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.dev60 → ipor_fusion-4.0.0.dev66}/MIGRATION_4.md +12 -4
  2. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/vault_info_view.py +2 -2
  5. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/external_state_events.py +6 -1
  6. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/mcp/server.py +2 -2
  7. ipor_fusion-4.0.0.dev66/src/ipor_fusion/readers/oracle_mapping.py +484 -0
  8. ipor_fusion-4.0.0.dev66/src/ipor_fusion/readers/oracle_mapping_nodes.py +367 -0
  9. ipor_fusion-4.0.0.dev66/src/ipor_fusion/readers/oracle_mapping_reader.py +242 -0
  10. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/substrates.py +21 -16
  11. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/types.py +16 -9
  12. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_vault_info_golden.py +2 -2
  13. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_external_state_events.py +7 -0
  14. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_mcp_server.py +7 -5
  15. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_types_and_events.py +16 -5
  16. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/typed_client/consumer.py +1 -1
  17. ipor_fusion-4.0.0.dev60/src/ipor_fusion/readers/oracle_mapping.py +0 -1042
  18. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.claude-plugin/marketplace.json +0 -0
  19. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.claude-plugin/plugin.json +0 -0
  20. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.env.example +0 -0
  21. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.gemini/settings.json +0 -0
  22. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.git-blame-ignore-revs +0 -0
  23. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/cd.yml +0 -0
  24. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/ci.yml +0 -0
  25. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/commit-lint.yml +0 -0
  26. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/notify-slack.yml +0 -0
  27. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/python-build.yml +0 -0
  28. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/python-publish.yml +0 -0
  29. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/release.yml +0 -0
  30. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.github/workflows/report-slack.yml +0 -0
  31. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.gitignore +0 -0
  32. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.mcp.json +0 -0
  33. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.pre-commit-config.yaml +0 -0
  34. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/.python-version +0 -0
  35. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/AGENTS.md +0 -0
  36. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/CHANGELOG.md +0 -0
  37. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/CLAUDE.md +0 -0
  38. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/CODEOWNERS +0 -0
  39. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/LICENSE +0 -0
  40. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/README.md +0 -0
  41. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/build.sh +0 -0
  42. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/CONTRIBUTING.md +0 -0
  43. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/README.md +0 -0
  44. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  45. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  46. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  47. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  48. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/external_state_margin_leg_base.py +0 -0
  49. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/examples/simple_aave_v3_supply_base.py +0 -0
  50. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/pytest.ini +0 -0
  51. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/scripts/commit-check.sh +0 -0
  52. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/scripts/crosschain_readiness.py +0 -0
  53. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/skills/ipor-deploy-vault/SKILL.md +0 -0
  54. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/__init__.py +0 -0
  55. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/about.py +0 -0
  56. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cache.py +0 -0
  57. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/chains.py +0 -0
  58. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/__init__.py +0 -0
  59. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  60. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/config_cmd.py +0 -0
  61. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/config_store.py +0 -0
  62. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/main.py +0 -0
  63. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/market_cmd.py +0 -0
  64. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/options.py +0 -0
  65. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  66. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  67. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/vault_health.py +0 -0
  68. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  69. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  70. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/config/__init__.py +0 -0
  71. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/config/roles.py +0 -0
  72. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/__init__.py +0 -0
  73. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/access.py +0 -0
  74. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/address.py +0 -0
  75. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/context.py +0 -0
  76. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/contract.py +0 -0
  77. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/erc20.py +0 -0
  78. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/external_state_executor.py +0 -0
  79. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/fee_manager.py +0 -0
  80. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/fuse_action.py +0 -0
  81. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/fusion_factory.py +0 -0
  82. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/logs.py +0 -0
  83. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/multicall.py +0 -0
  84. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/oracle.py +0 -0
  85. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/plasma_vault.py +0 -0
  86. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/rewards_manager.py +0 -0
  87. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/simulation.py +0 -0
  88. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  89. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/__init__.py +0 -0
  90. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  91. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  92. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  93. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  94. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  95. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  96. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  97. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  98. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  99. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/contracts.py +0 -0
  100. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/discovery.py +0 -0
  101. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/errors.py +0 -0
  102. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/lane.py +0 -0
  103. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/logs.py +0 -0
  104. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/messages.py +0 -0
  105. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/simulation.py +0 -0
  106. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  107. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  108. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  109. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  110. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  111. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/crosschain/transport.py +0 -0
  112. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/dependency_graph.py +0 -0
  113. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/entry_cli.py +0 -0
  114. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/entry_mcp.py +0 -0
  115. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/errors.py +0 -0
  116. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/explorer.py +0 -0
  117. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/field_docs.py +0 -0
  118. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/__init__.py +0 -0
  119. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  120. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/async_action.py +0 -0
  121. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/base.py +0 -0
  122. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  123. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  124. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  125. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  126. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  127. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/erc4626.py +0 -0
  128. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  129. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/events.py +0 -0
  130. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/external_state.py +0 -0
  131. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  132. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  133. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/hypercore.py +0 -0
  134. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  135. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/merkl.py +0 -0
  136. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/morpho.py +0 -0
  137. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  138. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  139. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/fuses/universal.py +0 -0
  140. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/guide/__init__.py +0 -0
  141. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/guide/architecture.md +0 -0
  142. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/guide/glossary.md +0 -0
  143. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/guide/invariants.md +0 -0
  144. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/guide/quickstart.md +0 -0
  145. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/market_ids.py +0 -0
  146. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/market_info.py +0 -0
  147. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/mcp/__init__.py +0 -0
  148. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/mcp/guide.py +0 -0
  149. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/mcp/models.py +0 -0
  150. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/morpho_api.py +0 -0
  151. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/py.typed +0 -0
  152. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/__init__.py +0 -0
  153. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/aave_v3.py +0 -0
  154. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/aave_v4.py +0 -0
  155. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/compound_v3.py +0 -0
  156. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/hypercore.py +0 -0
  157. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  158. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/lending_health.py +0 -0
  159. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/morpho.py +0 -0
  160. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/position_manager.py +0 -0
  161. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  162. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  163. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/readers/unpriceable.py +0 -0
  164. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/validation.py +0 -0
  165. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/vault_data.py +0 -0
  166. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/vault_info.py +1 -1
  167. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/vault_managers.py +0 -0
  168. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/vault_positions.py +0 -0
  169. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/src/ipor_fusion/vault_reads.py +0 -0
  170. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/UniswapV3UniversalRouter.py +0 -0
  171. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_crosschain.py +0 -0
  172. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_crosschain_lifecycle.py +0 -0
  173. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_crosschain_recovery.py +0 -0
  174. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_euler_v2.py +0 -0
  175. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_foundry.py +0 -0
  176. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_guide.py +0 -0
  177. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_hypercore_model.py +0 -0
  178. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_hypercore_shadow.py +0 -0
  179. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_multicall.py +0 -0
  180. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_simulate.py +0 -0
  181. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/_validation.py +0 -0
  182. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/addresses.py +0 -0
  183. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/conftest.py +0 -0
  184. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/constants.py +0 -0
  185. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  186. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/fixtures/hypercore_market55.json +0 -0
  187. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/fixtures/vault_info_golden/clean.json +0 -0
  188. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  189. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/fixtures/vault_info_golden/rich.json +0 -0
  190. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  191. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_about.py +0 -0
  192. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_access_manager.py +0 -0
  193. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cache.py +0 -0
  194. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_chains.py +0 -0
  195. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_changelog.py +0 -0
  196. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_commands.py +0 -0
  197. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_config_store.py +0 -0
  198. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_helpers.py +0 -0
  199. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_hypercore.py +0 -0
  200. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_lending_health.py +0 -0
  201. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_market_morpho_blue.py +0 -0
  202. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_oracle_mapping.py +0 -0
  203. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_pending_requests_scan.py +0 -0
  204. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_role_accounts.py +0 -0
  205. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_vault_predates_getters.py +0 -0
  206. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_cli_vault_validate.py +0 -0
  207. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_contract_call.py +0 -0
  208. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_contract_encoder.py +0 -0
  209. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_ccip_events.py +0 -0
  210. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_ccip_indexer.py +0 -0
  211. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_ccip_quote.py +0 -0
  212. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_chainlink.py +0 -0
  213. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_contracts.py +0 -0
  214. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_encoding.py +0 -0
  215. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_errors.py +0 -0
  216. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_lane.py +0 -0
  217. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_layering.py +0 -0
  218. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_readiness.py +0 -0
  219. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_readiness_report.py +0 -0
  220. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_crosschain_transport.py +0 -0
  221. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_erc20.py +0 -0
  222. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_examples_composed_hypercore.py +0 -0
  223. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_examples_crosschain.py +0 -0
  224. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  225. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_examples_vaults.py +0 -0
  226. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_explorer.py +0 -0
  227. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_external_state_executor.py +0 -0
  228. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_fee_manager.py +0 -0
  229. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_fuse_encoding.py +0 -0
  230. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_guide.py +0 -0
  231. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_hypercore_deployment.py +0 -0
  232. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_hypercore_events.py +0 -0
  233. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_hypercore_flow.py +0 -0
  234. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_hypercore_live.py +0 -0
  235. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_hypercore_outcome.py +0 -0
  236. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_hypercore_readers.py +0 -0
  237. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_hypercore_simulation.py +0 -0
  238. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_logs.py +0 -0
  239. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_mcp_hypercore.py +0 -0
  240. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_mcp_models.py +0 -0
  241. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_mcp_vault_validate.py +0 -0
  242. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_multicall.py +0 -0
  243. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_oracle.py +0 -0
  244. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_oracle_mapping.py +0 -0
  245. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_plasma_vault.py +0 -0
  246. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_reader_encoding.py +0 -0
  247. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_readiness_selection.py +0 -0
  248. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_revert_decoding.py +0 -0
  249. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_rewards_manager.py +0 -0
  250. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_roles.py +0 -0
  251. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_sdk_layering.py +0 -0
  252. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  253. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  254. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  255. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_crosschain_canary.py +0 -0
  256. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_crosschain_hypercore.py +0 -0
  257. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  258. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  259. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  260. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  261. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_examples_arbitrum.py +0 -0
  265. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_examples_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_examples_crosschain.py +0 -0
  267. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_external_state_executor_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_morpho_blue.py +0 -0
  272. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_pilot_v3.py +0 -0
  273. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  274. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  275. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  276. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  277. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_readers_integration.py +0 -0
  278. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_release_funds.py +0 -0
  279. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_swap_on_base.py +0 -0
  280. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  281. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_solidity_compiler.py +0 -0
  282. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_solidity_mirrors.py +0 -0
  283. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_substrates.py +0 -0
  284. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_unpriceable_tokens.py +0 -0
  285. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_validation.py +0 -0
  286. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_vault_dep_graph.py +0 -0
  287. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_vault_info_onchain_base.py +0 -0
  288. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  289. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_vault_simulator_blocks.py +0 -0
  290. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_web3_context.py +0 -0
  291. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/tests/test_withdraw_manager.py +0 -0
  292. {ipor_fusion-4.0.0.dev60 → ipor_fusion-4.0.0.dev66}/uv.lock +0 -0
@@ -29,15 +29,22 @@ widths or address spelling.
29
29
  | `RoleId` | a `Roles` member (accepted as is), or `RoleId(n)`; `Roles.resolve(name)` returns one |
30
30
  | `Decimals`, `Period`, `Amount`, ... | `Decimals(8)`, `Period(0)`, `Amount(n)` (unchanged) |
31
31
  | `ChecksumAddress` | `Web3.to_checksum_address(value)` |
32
- | `HexAddress` | any `ChecksumAddress`, or `parse_hex_address(value)` (20 bytes or hex in any spelling → lowercase `0x` form) |
32
+ | `HexAddress` | any `ChecksumAddress`, or `parse_hex_address(value)` (20 bytes or `0x`/`0X`-prefixed hex in any case → lowercase `0x` form) |
33
33
  | `MorphoBlueMarketId` | `parse_morpho_market_id(value)`: 32 bytes or hex in any spelling, always `0x` + 64 lowercase digits |
34
- | `TxHash` | `parse_tx_hash(value)` (32 bytes or hex in any spelling) |
34
+ | `TxHash` | `parse_tx_hash(value)` (32 bytes or `0x`/`0X`-prefixed hex in any case) |
35
35
  | `PlasmaVaultAddress`, `FuseAddress`, `AccessManagerAddress` | from the SDK (`PlasmaVault.address`, `get_fuses()`, `get_access_manager_address()`, `FusionInstance`, discovery), or `parse_plasma_vault_address` / `parse_fuse_address` / `parse_access_manager_address(value)` for a raw address; they label the role and check nothing on chain |
36
36
  | crosschain ids | `CommandId(b)`, `MessageId(b)`, `AssetId(b)`, `OperationId(b)`, `TransferId(b)`, `ReturnRequestId(b)`, `PositionSetHash(b)` (bytes32); `ProposalId(n)`, `DeploymentRequestId(n)`, `StargateAssetId(n)` (int) |
37
37
  | HyperCore ids | `HyperCoreTokenIndex(n)` (or `USDC_TOKEN_INDEX`), `HyperCoreAsset(n)` (or `hip3_action_asset(dex, index)`), `HyperCoreOrderId(n)`, `HyperCoreClientOrderId(n)`, `MarginTableId(n)`, `HyperCoreActionId(n)`, `PayloadHash(b)` |
38
38
  | external state | `ProposalHash(b)` |
39
39
  | `BlockIdentifier` | a block number, `"latest"` / `"pending"` / ..., or a block hash |
40
40
 
41
+ The address and hash parsers (`parse_hex_address`, `parse_tx_hash`,
42
+ `parse_plasma_vault_address`, `parse_fuse_address`, `parse_access_manager_address`)
43
+ validate strictly: a string needs exactly one `0x`/`0X` prefix and the exact number of
44
+ hex digits, with no surrounding whitespace; anything else raises `ValueError`. Strip
45
+ user input yourself before parsing it. `parse_morpho_market_id` still accepts hex
46
+ without `0x`.
47
+
41
48
  `MessageId` covers both a CCIP `messageId` and a LayerZero `guid`. `AssetId` is the
42
49
  crosschain executor's `bytes32` asset id; a Stargate pool's `uint16` asset id is
43
50
  `StargateAssetId`. `DeploymentRequestId` is a factory's `uint256` dispatcher request;
@@ -49,8 +56,9 @@ a `bytes32` return (recall) request is `ReturnRequestId`.
49
56
  `build_vault_info_json`, `ensure_supported_chain` and `fetch_vault_lending_health`
50
57
  take `ChainId`. The vault is a `PlasmaVaultAddress` (see *Address roles*) in
51
58
  `validate_vault` and `fetch_deployment_info`; `build_vault_info_json(ctx, plasma_vault,
52
- data, chain_id, chain_label)` takes no address and labels the document with
53
- `plasma_vault.address`. `fetch_unpriceable_priced_tokens` takes the oracle and
59
+ data, *, chain_id, chain_label)` takes no address, takes the chain by keyword, and
60
+ labels the document with `plasma_vault.address` (a test that mocks `PlasmaVault`
61
+ sets `.address`). `fetch_unpriceable_priced_tokens` takes the oracle and
54
62
  underlying as `ChecksumAddress`. `resolve_token_symbol`, `resolve_token_decimals`,
55
63
  `get_contract_name` and `get_deployment_tx` take `HexAddress` (a checksum address
56
64
  fits), and the `resolve_name` / `resolve_symbol` callbacks receive one (a callback
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev60
3
+ Version: 4.0.0.dev66
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.dev60"
3
+ version = "4.0.0.dev66"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -98,8 +98,8 @@ def _print_vault_info(
98
98
  ctx,
99
99
  plasma_vault,
100
100
  data,
101
- chain_id,
102
- chain_label,
101
+ chain_id=chain_id,
102
+ chain_label=chain_label,
103
103
  resolve_name=partial(get_contract_name, chain_id, api_key=api_key),
104
104
  )
105
105
  click.echo(json.dumps(result, indent=2))
@@ -196,7 +196,12 @@ def external_state_event_name(topic: bytes | str) -> str | None:
196
196
  def _hex(value: Any) -> TxHash | None:
197
197
  if value is None:
198
198
  return None
199
- return parse_tx_hash(_as_bytes(value, "transactionHash"))
199
+ raw = _as_bytes(value, "transactionHash")
200
+ if len(raw) != 32:
201
+ raise ExternalStateEventDecodeError(
202
+ f"transactionHash: {len(raw)} bytes, expected 32"
203
+ )
204
+ return parse_tx_hash(raw)
200
205
 
201
206
 
202
207
  def decode_external_state_event(log: Mapping[str, Any]) -> ExternalStateEvent | None:
@@ -216,8 +216,8 @@ def vault_info(
216
216
  ctx,
217
217
  plasma_vault,
218
218
  data,
219
- chain_id,
220
- chain_label,
219
+ chain_id=chain_id,
220
+ chain_label=chain_label,
221
221
  resolve_name=partial(get_contract_name, chain_id, api_key=api_key),
222
222
  )
223
223
  return VaultInfoResponse.model_validate(result)
@@ -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
+ )