ipor-fusion 4.0.0.dev57__tar.gz → 4.0.0.dev60__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 (289) hide show
  1. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/AGENTS.md +4 -1
  2. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/PKG-INFO +1 -1
  3. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/pyproject.toml +1 -1
  4. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/vault_info_view.py +4 -8
  5. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/vault_data.py +25 -454
  6. ipor_fusion-4.0.0.dev60/src/ipor_fusion/vault_managers.py +185 -0
  7. ipor_fusion-4.0.0.dev60/src/ipor_fusion/vault_positions.py +238 -0
  8. ipor_fusion-4.0.0.dev60/src/ipor_fusion/vault_reads.py +81 -0
  9. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/conftest.py +4 -1
  10. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_commands.py +4 -4
  11. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_helpers.py +21 -22
  12. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_pending_requests_scan.py +4 -7
  13. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.claude-plugin/marketplace.json +0 -0
  14. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.claude-plugin/plugin.json +0 -0
  15. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.env.example +0 -0
  16. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.gemini/settings.json +0 -0
  17. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.git-blame-ignore-revs +0 -0
  18. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/cd.yml +0 -0
  19. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/ci.yml +0 -0
  20. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/commit-lint.yml +0 -0
  21. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/notify-slack.yml +0 -0
  22. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/python-build.yml +0 -0
  23. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/python-publish.yml +0 -0
  24. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/release.yml +0 -0
  25. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.github/workflows/report-slack.yml +0 -0
  26. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.gitignore +0 -0
  27. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.mcp.json +0 -0
  28. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.pre-commit-config.yaml +0 -0
  29. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/.python-version +0 -0
  30. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/CHANGELOG.md +0 -0
  31. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/CLAUDE.md +0 -0
  32. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/CODEOWNERS +0 -0
  33. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/LICENSE +0 -0
  34. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/MIGRATION_4.md +0 -0
  35. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/README.md +0 -0
  36. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/build.sh +0 -0
  37. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/CONTRIBUTING.md +0 -0
  38. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/README.md +0 -0
  39. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  40. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  41. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  42. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  43. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/external_state_margin_leg_base.py +0 -0
  44. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/examples/simple_aave_v3_supply_base.py +0 -0
  45. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/pytest.ini +0 -0
  46. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/scripts/commit-check.sh +0 -0
  47. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/scripts/crosschain_readiness.py +0 -0
  48. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/skills/ipor-deploy-vault/SKILL.md +0 -0
  49. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/__init__.py +0 -0
  50. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/about.py +0 -0
  51. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cache.py +0 -0
  52. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/chains.py +0 -0
  53. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/__init__.py +0 -0
  54. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  55. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/config_cmd.py +0 -0
  56. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/config_store.py +0 -0
  57. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/main.py +0 -0
  58. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/market_cmd.py +0 -0
  59. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/options.py +0 -0
  60. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  61. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  62. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/vault_health.py +0 -0
  63. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  64. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  65. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/config/__init__.py +0 -0
  66. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/config/roles.py +0 -0
  67. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/__init__.py +0 -0
  68. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/access.py +0 -0
  69. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/address.py +0 -0
  70. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/context.py +0 -0
  71. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/contract.py +0 -0
  72. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/erc20.py +0 -0
  73. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/external_state_executor.py +0 -0
  74. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/fee_manager.py +0 -0
  75. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/fuse_action.py +0 -0
  76. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/fusion_factory.py +0 -0
  77. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/logs.py +0 -0
  78. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/multicall.py +0 -0
  79. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/oracle.py +0 -0
  80. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/plasma_vault.py +0 -0
  81. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/rewards_manager.py +0 -0
  82. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/simulation.py +0 -0
  83. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  84. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/__init__.py +0 -0
  85. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  86. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  87. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  88. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  89. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  90. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  91. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  92. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  93. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  94. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/contracts.py +0 -0
  95. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/discovery.py +0 -0
  96. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/errors.py +0 -0
  97. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/lane.py +0 -0
  98. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/logs.py +0 -0
  99. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/messages.py +0 -0
  100. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/simulation.py +0 -0
  101. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  102. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  103. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  104. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  105. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  106. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/crosschain/transport.py +0 -0
  107. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/dependency_graph.py +0 -0
  108. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/entry_cli.py +0 -0
  109. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/entry_mcp.py +0 -0
  110. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/errors.py +0 -0
  111. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/explorer.py +0 -0
  112. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/field_docs.py +0 -0
  113. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/__init__.py +0 -0
  114. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  115. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/async_action.py +0 -0
  116. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/base.py +0 -0
  117. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  118. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  119. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  120. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  121. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  122. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/erc4626.py +0 -0
  123. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  124. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/events.py +0 -0
  125. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/external_state.py +0 -0
  126. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  127. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  128. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  129. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/hypercore.py +0 -0
  130. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  131. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/merkl.py +0 -0
  132. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/morpho.py +0 -0
  133. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  134. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  135. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/fuses/universal.py +0 -0
  136. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/guide/__init__.py +0 -0
  137. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/guide/architecture.md +0 -0
  138. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/guide/glossary.md +0 -0
  139. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/guide/invariants.md +0 -0
  140. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/guide/quickstart.md +0 -0
  141. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/market_ids.py +0 -0
  142. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/market_info.py +0 -0
  143. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/mcp/__init__.py +0 -0
  144. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/mcp/guide.py +0 -0
  145. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/mcp/models.py +0 -0
  146. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/mcp/server.py +0 -0
  147. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/morpho_api.py +0 -0
  148. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/py.typed +0 -0
  149. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/__init__.py +0 -0
  150. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/aave_v3.py +0 -0
  151. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/aave_v4.py +0 -0
  152. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/compound_v3.py +0 -0
  153. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/hypercore.py +0 -0
  154. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  155. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/lending_health.py +0 -0
  156. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/morpho.py +0 -0
  157. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  158. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/position_manager.py +0 -0
  159. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  160. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  161. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/readers/unpriceable.py +0 -0
  162. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/substrates.py +0 -0
  163. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/types.py +0 -0
  164. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/validation.py +0 -0
  165. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/src/ipor_fusion/vault_info.py +0 -0
  166. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/UniswapV3UniversalRouter.py +0 -0
  167. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_crosschain.py +0 -0
  168. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_crosschain_lifecycle.py +0 -0
  169. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_crosschain_recovery.py +0 -0
  170. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_euler_v2.py +0 -0
  171. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_foundry.py +0 -0
  172. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_guide.py +0 -0
  173. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_hypercore_model.py +0 -0
  174. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_hypercore_shadow.py +0 -0
  175. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_multicall.py +0 -0
  176. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_simulate.py +0 -0
  177. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/_validation.py +0 -0
  178. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/addresses.py +0 -0
  179. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/constants.py +0 -0
  180. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  181. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/fixtures/hypercore_market55.json +0 -0
  182. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/fixtures/vault_info_golden/clean.json +0 -0
  183. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  184. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/fixtures/vault_info_golden/rich.json +0 -0
  185. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  186. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_about.py +0 -0
  187. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_access_manager.py +0 -0
  188. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cache.py +0 -0
  189. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_chains.py +0 -0
  190. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_changelog.py +0 -0
  191. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_config_store.py +0 -0
  192. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_hypercore.py +0 -0
  193. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_lending_health.py +0 -0
  194. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_market_morpho_blue.py +0 -0
  195. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_oracle_mapping.py +0 -0
  196. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_role_accounts.py +0 -0
  197. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_vault_info_golden.py +0 -0
  198. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_vault_predates_getters.py +0 -0
  199. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_cli_vault_validate.py +0 -0
  200. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_contract_call.py +0 -0
  201. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_contract_encoder.py +0 -0
  202. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_ccip_events.py +0 -0
  203. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_ccip_indexer.py +0 -0
  204. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_ccip_quote.py +0 -0
  205. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_chainlink.py +0 -0
  206. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_contracts.py +0 -0
  207. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_encoding.py +0 -0
  208. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_errors.py +0 -0
  209. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_lane.py +0 -0
  210. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_layering.py +0 -0
  211. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_readiness.py +0 -0
  212. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_readiness_report.py +0 -0
  213. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_crosschain_transport.py +0 -0
  214. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_erc20.py +0 -0
  215. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_examples_composed_hypercore.py +0 -0
  216. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_examples_crosschain.py +0 -0
  217. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  218. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_examples_vaults.py +0 -0
  219. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_explorer.py +0 -0
  220. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_external_state_events.py +0 -0
  221. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_external_state_executor.py +0 -0
  222. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_fee_manager.py +0 -0
  223. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_fuse_encoding.py +0 -0
  224. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_guide.py +0 -0
  225. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_hypercore_deployment.py +0 -0
  226. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_hypercore_events.py +0 -0
  227. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_hypercore_flow.py +0 -0
  228. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_hypercore_live.py +0 -0
  229. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_hypercore_outcome.py +0 -0
  230. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_hypercore_readers.py +0 -0
  231. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_hypercore_simulation.py +0 -0
  232. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_logs.py +0 -0
  233. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_mcp_hypercore.py +0 -0
  234. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_mcp_models.py +0 -0
  235. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_mcp_server.py +0 -0
  236. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_mcp_vault_validate.py +0 -0
  237. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_multicall.py +0 -0
  238. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_oracle.py +0 -0
  239. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_oracle_mapping.py +0 -0
  240. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_plasma_vault.py +0 -0
  241. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_reader_encoding.py +0 -0
  242. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_readiness_selection.py +0 -0
  243. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_revert_decoding.py +0 -0
  244. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_rewards_manager.py +0 -0
  245. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_roles.py +0 -0
  246. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_sdk_layering.py +0 -0
  247. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  248. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  249. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  250. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_crosschain_canary.py +0 -0
  251. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_crosschain_hypercore.py +0 -0
  252. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  253. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  254. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  255. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  256. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  257. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  258. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  259. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_examples_arbitrum.py +0 -0
  260. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_examples_base.py +0 -0
  261. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_examples_crosschain.py +0 -0
  262. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_external_state_executor_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_morpho_blue.py +0 -0
  267. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_pilot_v3.py +0 -0
  268. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  269. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  270. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  271. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_readers_integration.py +0 -0
  273. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_release_funds.py +0 -0
  274. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_swap_on_base.py +0 -0
  275. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_solidity_compiler.py +0 -0
  277. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_solidity_mirrors.py +0 -0
  278. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_substrates.py +0 -0
  279. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_types_and_events.py +0 -0
  280. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_unpriceable_tokens.py +0 -0
  281. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_validation.py +0 -0
  282. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_vault_dep_graph.py +0 -0
  283. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_vault_info_onchain_base.py +0 -0
  284. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  285. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_vault_simulator_blocks.py +0 -0
  286. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_web3_context.py +0 -0
  287. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/test_withdraw_manager.py +0 -0
  288. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/tests/typed_client/consumer.py +0 -0
  289. {ipor_fusion-4.0.0.dev57 → ipor_fusion-4.0.0.dev60}/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.dev60
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.dev60"
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))
@@ -10,18 +10,14 @@ Token symbols, decimals and deployment blocks are cached on disk
10
10
  from __future__ import annotations
11
11
 
12
12
  import logging
13
- from collections.abc import Callable, Sequence
14
- from concurrent.futures import Future, ThreadPoolExecutor
13
+ from concurrent.futures import ThreadPoolExecutor
15
14
  from dataclasses import dataclass, field
16
15
  from functools import partial
17
16
  from typing import Any, TypeVar
18
17
 
19
- import requests
20
- from eth_abi.exceptions import DecodingError
21
18
  from eth_typing import HexAddress
22
19
  from eth_utils import function_signature_to_4byte_selector
23
20
  from web3 import Web3
24
- from web3.exceptions import ContractLogicError, TimeExhausted, Web3RPCError
25
21
  from web3.types import ChecksumAddress
26
22
 
27
23
  from ipor_fusion.cache import (
@@ -36,28 +32,16 @@ from ipor_fusion.core.access import AccessManager, RoleAccount, role_account_sor
36
32
  from ipor_fusion.core.context import Web3Context
37
33
  from ipor_fusion.core.contract import Call
38
34
  from ipor_fusion.core.erc20 import ERC20
39
- from ipor_fusion.core.fee_manager import (
40
- FeeAccount,
41
- FeeManager,
42
- HighWaterMarkPerformanceFee,
43
- RecipientFee,
44
- )
45
- from ipor_fusion.core.multicall import Multicall3
46
- from ipor_fusion.core.oracle import PriceOracleMiddleware, price_oracle_middleware
35
+ from ipor_fusion.core.oracle import price_oracle_middleware
47
36
  from ipor_fusion.core.plasma_vault import BalanceFuse, PlasmaVault
48
- from ipor_fusion.core.withdraw_manager import AccountRequest, WithdrawManager
49
37
  from ipor_fusion.errors import (
50
38
  EmptyCallResultError,
51
- LogScanError,
52
39
  UnsupportedVaultVersionError,
53
40
  )
54
41
  from ipor_fusion.explorer import get_deployment_tx
55
42
  from ipor_fusion.market_ids import IporFusionMarkets
56
43
  from ipor_fusion.readers.aave_v3 import (
57
- AaveV3FuseReader,
58
- AaveV3PoolAddressesProvider,
59
44
  AaveV3PositionBreakdown,
60
- AaveV3Reader,
61
45
  )
62
46
  from ipor_fusion.readers.hypercore import (
63
47
  HYPEREVM_CHAIN_ID,
@@ -65,15 +49,11 @@ from ipor_fusion.readers.hypercore import (
65
49
  read_hypercore_vault_state,
66
50
  )
67
51
  from ipor_fusion.readers.lending_health import (
68
- AAVE_V3_MARKET_IDS,
69
- MORPHO_MARKET_IDS,
70
52
  VaultLendingHealth,
71
53
  fetch_vault_lending_health,
72
54
  )
73
55
  from ipor_fusion.readers.morpho import (
74
56
  MorphoPositionBreakdown,
75
- MorphoReader,
76
- morpho_blue_address,
77
57
  )
78
58
  from ipor_fusion.readers.unpriceable import (
79
59
  MiddlewarePricedToken,
@@ -84,10 +64,30 @@ from ipor_fusion.types import (
84
64
  ChainId,
85
65
  FuseAddress,
86
66
  MarketId,
87
- MorphoBlueMarketId,
88
67
  PlasmaVaultAddress,
89
- parse_hex_address,
90
- parse_morpho_market_id,
68
+ )
69
+ from ipor_fusion.vault_managers import (
70
+ FeeData as FeeData,
71
+ )
72
+ from ipor_fusion.vault_managers import (
73
+ WithdrawManagerData as WithdrawManagerData,
74
+ )
75
+ from ipor_fusion.vault_managers import (
76
+ _fetch_fee_data,
77
+ _fetch_withdraw_manager,
78
+ )
79
+ from ipor_fusion.vault_positions import (
80
+ _collect_breakdown_token_addresses,
81
+ _fetch_aave_pools,
82
+ _fetch_aave_positions,
83
+ _fetch_breakdown_token_prices,
84
+ _fetch_morpho_positions,
85
+ )
86
+ from ipor_fusion.vault_reads import (
87
+ _ROLE_SCAN_ERRORS,
88
+ _read_batch,
89
+ _safe_call,
90
+ _unless_unimplemented,
91
91
  )
92
92
 
93
93
  T = TypeVar("T")
@@ -103,55 +103,6 @@ _logger = logging.getLogger(__name__)
103
103
  _NO_CONTRACT = "no contract"
104
104
  _NO_CHAIN = ChainId(0)
105
105
 
106
- _ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
107
-
108
-
109
- @dataclass
110
- class FeeData:
111
- """Vault fee configuration: vault-level fee data plus FeeManager state.
112
-
113
- Every field is optional because each degrades independently: a vault may
114
- have no fee account configured (no FeeManager at all), and FeeManagers
115
- predating the deposit fee / high-water mark revert on those getters.
116
- Scales differ per source, so every field names its own: `*_bps` is basis
117
- points (10000 = 100%), `*_wad` is WAD (1e18 = 100%). Pair each with the
118
- matching converter — `_bps_to_percent` / `_wad_to_percent` — and a
119
- mismatch reads wrong at the call site.
120
-
121
- `*_vault_bps` and `*_manager_bps` are the SAME fee read from two places:
122
- the vault charges the former, the FeeManager believes it configured the
123
- latter. They agree in a healthy deployment; a difference means the two
124
- configurations have drifted.
125
- """
126
-
127
- performance_fee_vault_bps: int | None = None
128
- management_fee_vault_bps: int | None = None
129
- management_fee_last_update: int | None = None
130
- unrealized_management_fee: int | None = None
131
- fee_manager: ChecksumAddress | None = None
132
- ipor_dao_fee_recipient: ChecksumAddress | None = None
133
- deposit_fee_wad: int | None = None
134
- performance_fee_manager_bps: int | None = None
135
- management_fee_manager_bps: int | None = None
136
- performance_fee_recipients: list[RecipientFee] | None = None
137
- management_fee_recipients: list[RecipientFee] | None = None
138
- high_water_mark: HighWaterMarkPerformanceFee | None = None
139
-
140
-
141
- @dataclass
142
- class WithdrawManagerData:
143
- """On-chain state snapshot from the WithdrawManager contract."""
144
-
145
- withdraw_window: int
146
- # None (not 0) when the getter failed: a zero fee and an unreadable fee
147
- # are different facts, and the fees section reports both.
148
- request_fee: int | None
149
- withdraw_fee: int | None
150
- shares_to_release: int
151
- last_release_funds_timestamp: int
152
- # None when the WithdrawRequestUpdated scan or its reads failed.
153
- pending_requests: list[AccountRequest] | None
154
-
155
106
 
156
107
  @dataclass
157
108
  class VaultData:
@@ -234,36 +185,6 @@ class VaultData:
234
185
  unimplemented_getters: list[str] = field(default_factory=list)
235
186
 
236
187
 
237
- # Failure modes of the event scans behind optional data (role holders, pending
238
- # withdraw requests): a log scan the provider would not let complete
239
- # (LogScanError), plus JSON-RPC and transport errors from the reads that follow.
240
- _ROLE_SCAN_ERRORS = (
241
- LogScanError,
242
- ContractLogicError,
243
- Web3RPCError,
244
- TimeExhausted,
245
- requests.RequestException,
246
- )
247
-
248
-
249
- def _scan_or_none(func: Callable[[], T]) -> T | None:
250
- """An event scan behind optional data; None (never a partial result) when
251
- it cannot complete."""
252
- try:
253
- return func()
254
- except _ROLE_SCAN_ERRORS as exc:
255
- _logger.debug("scan unavailable: %s: %s", type(exc).__name__, exc)
256
- return None
257
-
258
-
259
- def _safe_call(func: Callable[[], T]) -> T | None:
260
- try:
261
- return func()
262
- except (ContractLogicError, Web3RPCError, TimeExhausted) as exc:
263
- _logger.debug("_safe_call suppressed %s: %s", type(exc).__name__, exc)
264
- return None
265
-
266
-
267
188
  _MARKET_ID_SELECTOR = function_signature_to_4byte_selector("MARKET_ID()")
268
189
 
269
190
 
@@ -285,18 +206,6 @@ def _fuse_market_id_call(ctx: Web3Context, fuse: ChecksumAddress) -> Call[int]:
285
206
  _UNCAPPED = 2**256 - 1
286
207
 
287
208
 
288
- def _unless_unimplemented(value: T | None, call: Call[T]) -> T | None:
289
- """An optional batch read, re-run when it failed: ``None`` only when the
290
- contract returned no data (it predates the getter); a revert or RPC error
291
- raises exactly what `Call.call()` would, so real failures stay loud."""
292
- if value is not None:
293
- return value
294
- try:
295
- return call.call()
296
- except EmptyCallResultError:
297
- return None
298
-
299
-
300
209
  def _missing_required_getter(
301
210
  plasma_vault: PlasmaVault, exc: EmptyCallResultError
302
211
  ) -> UnsupportedVaultVersionError:
@@ -308,24 +217,6 @@ def _missing_required_getter(
308
217
  )
309
218
 
310
219
 
311
- def _read_batch(
312
- ctx: Web3Context,
313
- required: Sequence[Call[Any]],
314
- optional: Sequence[Call[Any]] = (),
315
- ) -> tuple[list[Any], list[Any]]:
316
- """Read `required` + `optional` in one Multicall3 round trip.
317
-
318
- A failing optional read is ``None``. A failing required read is re-run on
319
- its own, so it raises exactly what `Call.call()` would.
320
- """
321
- results = Multicall3(ctx).try_aggregate([*required, *optional])
322
- head = [
323
- value if value is not None else call.call()
324
- for value, call in zip(results, required, strict=False)
325
- ]
326
- return head, results[len(required) :]
327
-
328
-
329
220
  def resolve_token_symbol(ctx: Web3Context, address: HexAddress) -> str:
330
221
  """ERC-20 symbol, cached; ``"no contract"`` when nothing is deployed at
331
222
  ``address`` and ``""`` when the symbol cannot be read."""
@@ -369,326 +260,6 @@ def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> int | None:
369
260
  return decimals
370
261
 
371
262
 
372
- def _fetch_withdraw_manager(
373
- ctx: Web3Context, plasma_vault: PlasmaVault
374
- ) -> tuple[ChecksumAddress | None, WithdrawManagerData | None]:
375
- """Resolve the vault's WithdrawManager and snapshot its state."""
376
- address = plasma_vault.withdraw_manager_address()
377
- if not address:
378
- return None, None
379
- wm = WithdrawManager(ctx, address)
380
- _, (window, request_fee, withdraw_fee, shares, last_ts) = _read_batch(
381
- ctx,
382
- [],
383
- [
384
- wm.get_withdraw_window(),
385
- wm.get_request_fee(),
386
- wm.get_withdraw_fee(),
387
- wm.get_shares_to_release(),
388
- wm.get_last_release_funds_timestamp(),
389
- ],
390
- )
391
- return address, WithdrawManagerData(
392
- withdraw_window=window or 0,
393
- request_fee=request_fee,
394
- withdraw_fee=withdraw_fee,
395
- shares_to_release=shares or 0,
396
- last_release_funds_timestamp=last_ts or 0,
397
- pending_requests=_scan_or_none(wm.get_pending_requests),
398
- )
399
-
400
-
401
- def _fetch_fee_manager_address(
402
- ctx: Web3Context, fee_account: ChecksumAddress
403
- ) -> ChecksumAddress | None:
404
- """Resolve FeeAccount.FEE_MANAGER(); None when it is not a FeeAccount.
405
-
406
- The fee account reported by a vault is not guaranteed to be a FeeAccount
407
- contract — older deployments can name a plain recipient address. An EOA
408
- answers eth_call with empty data, which fails ABI decoding instead of
409
- reverting, so `_safe_call` alone does not cover it.
410
- """
411
- try:
412
- return _safe_call(FeeAccount(ctx, fee_account).fee_manager().call)
413
- except DecodingError:
414
- return None
415
-
416
-
417
- def _fetch_fee_data(ctx: Web3Context, plasma_vault: PlasmaVault) -> FeeData:
418
- """Fetch the vault's fee configuration, including the FeeManager hop.
419
-
420
- Three dependent round trips: vault-level fee data, then
421
- FeeAccount.FEE_MANAGER() to discover the FeeManager, then the FeeManager
422
- getters. Nothing raises on a revert — every read degrades to ``None``, so
423
- vaults without a fee account and FeeManagers predating the deposit fee or
424
- high-water mark still produce a usable snapshot.
425
- """
426
- _, (perf, mgmt, unrealized) = _read_batch(
427
- ctx,
428
- [],
429
- [
430
- plasma_vault.get_performance_fee_data(),
431
- plasma_vault.get_management_fee_data(),
432
- plasma_vault.get_unrealized_management_fee(),
433
- ],
434
- )
435
- result = FeeData(
436
- performance_fee_vault_bps=perf.fee_in_percentage if perf else None,
437
- management_fee_vault_bps=mgmt.fee_in_percentage if mgmt else None,
438
- management_fee_last_update=mgmt.last_update_timestamp if mgmt else None,
439
- unrealized_management_fee=unrealized,
440
- )
441
-
442
- # A FeeManager deploys two separate escrow accounts (performance and
443
- # management) but is itself the FEE_MANAGER of both, so either hop
444
- # resolves to the same address; take whichever is configured.
445
- fee_account = next(
446
- (
447
- data.fee_account
448
- for data in (perf, mgmt)
449
- if data is not None and data.fee_account != _ZERO_ADDRESS
450
- ),
451
- None,
452
- )
453
- if fee_account is None:
454
- return result
455
-
456
- fee_manager_addr = _fetch_fee_manager_address(ctx, fee_account)
457
- if not fee_manager_addr or fee_manager_addr == _ZERO_ADDRESS:
458
- return result
459
- result.fee_manager = fee_manager_addr
460
-
461
- fee_manager = FeeManager(ctx, fee_manager_addr)
462
- (
463
- _,
464
- (
465
- result.deposit_fee_wad,
466
- result.performance_fee_manager_bps,
467
- result.management_fee_manager_bps,
468
- result.performance_fee_recipients,
469
- result.management_fee_recipients,
470
- result.ipor_dao_fee_recipient,
471
- result.high_water_mark,
472
- ),
473
- ) = _read_batch(
474
- ctx,
475
- [],
476
- [
477
- fee_manager.get_deposit_fee(),
478
- fee_manager.get_total_performance_fee(),
479
- fee_manager.get_total_management_fee(),
480
- fee_manager.get_performance_fee_recipients(),
481
- fee_manager.get_management_fee_recipients(),
482
- fee_manager.get_ipor_dao_fee_recipient_address(),
483
- fee_manager.get_plasma_vault_high_water_mark_performance_fee(),
484
- ],
485
- )
486
- return result
487
-
488
-
489
- def _collect_morpho_substrates(
490
- market_substrates: dict[MarketId, list[bytes]],
491
- ) -> dict[MarketId, list[MorphoBlueMarketId]]:
492
- """Filter + parse Morpho Blue market IDs from raw substrate bytes."""
493
- result: dict[MarketId, list[MorphoBlueMarketId]] = {}
494
- for mid, subs in market_substrates.items():
495
- if mid not in MORPHO_MARKET_IDS:
496
- continue
497
- morpho_ids = [parse_morpho_market_id(sub) for sub in subs if len(sub) == 32]
498
- if morpho_ids:
499
- result[mid] = morpho_ids
500
- return result
501
-
502
-
503
- def _fetch_morpho_positions(
504
- ctx: Web3Context,
505
- pool: ThreadPoolExecutor,
506
- vault_addr: ChecksumAddress,
507
- market_substrates: dict[MarketId, list[bytes]],
508
- ) -> dict[MarketId, list[MorphoPositionBreakdown]] | None:
509
- """Fetch collateral / borrow / supply breakdown for every Morpho substrate.
510
-
511
- Each IPOR Morpho market (MORPHO_MARKET_IDS) can hold multiple morpho
512
- market_id substrates; each substrate has an independent position. The
513
- on-chain balance fuse reports a single netted number per IPOR market —
514
- this helper exposes the three-way decomposition behind that number.
515
- """
516
- per_market_substrates = _collect_morpho_substrates(market_substrates)
517
- if not per_market_substrates:
518
- return None
519
-
520
- reader = MorphoReader(ctx, morpho_blue_address(ctx.chain_id))
521
- futures: dict[MarketId, list[Future]] = {
522
- mid: [
523
- pool.submit(
524
- _safe_call,
525
- lambda mid_hex=morpho_mid: reader.position_breakdown(
526
- mid_hex, vault_addr
527
- ),
528
- )
529
- for morpho_mid in morpho_mids
530
- ]
531
- for mid, morpho_mids in per_market_substrates.items()
532
- }
533
-
534
- result: dict[MarketId, list[MorphoPositionBreakdown]] = {}
535
- for mid, per_market in futures.items():
536
- breakdowns = [bd for fut in per_market if (bd := fut.result()) is not None]
537
- if breakdowns:
538
- result[mid] = breakdowns
539
- return result or None
540
-
541
-
542
- def _collect_aave_substrate_assets(
543
- market_substrates: dict[MarketId, list[bytes]],
544
- ) -> dict[MarketId, list[ChecksumAddress]]:
545
- """Filter + parse asset addresses from Aave V3 substrate bytes.
546
-
547
- Aave substrates are zero-padded plain addresses (12 zero bytes + 20 address
548
- bytes). Returns a per-market list of checksummed asset addresses.
549
- """
550
- result: dict[MarketId, list[ChecksumAddress]] = {}
551
- for mid, subs in market_substrates.items():
552
- if mid not in AAVE_V3_MARKET_IDS:
553
- continue
554
- assets: list[ChecksumAddress] = []
555
- for sub in subs:
556
- hex_str = sub.hex()
557
- if len(hex_str) != 64:
558
- continue
559
- assets.append(Web3.to_checksum_address("0x" + hex_str[24:]))
560
- if assets:
561
- result[mid] = assets
562
- return result
563
-
564
-
565
- def _fetch_aave_pools(
566
- ctx: Web3Context, balance_fuses: list[BalanceFuse]
567
- ) -> dict[MarketId, ChecksumAddress]:
568
- """Map every Aave V3 market to the Pool its balance fuse reads.
569
-
570
- Aave V3 Core, Aave V3 Prime and SparkLend share the fuse contracts but not
571
- the Pool, so the Pool is resolved per market rather than per chain: the
572
- fuse's PoolAddressesProvider, then its Pool, one batch each. A balance
573
- fuse that is not an Aave V3 fuse reverts or answers with empty data and is
574
- left out.
575
- """
576
- fuses = {
577
- bf.market_id: bf.fuse
578
- for bf in balance_fuses
579
- if bf.market_id in AAVE_V3_MARKET_IDS
580
- }
581
- if not fuses:
582
- return {}
583
- multicall = Multicall3(ctx)
584
- providers = multicall.try_aggregate(
585
- [
586
- AaveV3FuseReader(ctx, fuse).pool_addresses_provider()
587
- for fuse in fuses.values()
588
- ]
589
- )
590
- resolved = {
591
- mid: provider
592
- for mid, provider in zip(fuses, providers, strict=True)
593
- if provider is not None
594
- }
595
- pools = multicall.try_aggregate(
596
- [AaveV3PoolAddressesProvider(ctx, p).get_pool() for p in resolved.values()]
597
- )
598
- return {
599
- mid: pool for mid, pool in zip(resolved, pools, strict=True) if pool is not None
600
- }
601
-
602
-
603
- def _fetch_aave_positions(
604
- ctx: Web3Context,
605
- pool: ThreadPoolExecutor,
606
- vault_addr: ChecksumAddress,
607
- aave_pools: dict[MarketId, ChecksumAddress],
608
- market_substrates: dict[MarketId, list[bytes]],
609
- ) -> dict[MarketId, list[AaveV3PositionBreakdown]] | None:
610
- """Fetch supply / variable / stable debt per Aave V3 asset substrate.
611
-
612
- The on-chain `getUserAccountData` aggregates all reserves into a single
613
- base-currency total — this helper exposes the per-asset decomposition.
614
- Each market is read from its own Pool (`aave_pools`); markets without one
615
- are skipped. Empty positions (vault allows the asset but holds none of it)
616
- are dropped.
617
- """
618
- per_market_assets = {
619
- mid: assets
620
- for mid, assets in _collect_aave_substrate_assets(market_substrates).items()
621
- if mid in aave_pools
622
- }
623
- if not per_market_assets:
624
- return None
625
-
626
- readers = {mid: AaveV3Reader(ctx, aave_pools[mid]) for mid in per_market_assets}
627
- futures: dict[MarketId, list[Future]] = {
628
- mid: [
629
- pool.submit(
630
- _safe_call, partial(readers[mid].position_breakdown, asset, vault_addr)
631
- )
632
- for asset in assets
633
- ]
634
- for mid, assets in per_market_assets.items()
635
- }
636
-
637
- result: dict[MarketId, list[AaveV3PositionBreakdown]] = {}
638
- for mid, per_market in futures.items():
639
- breakdowns = [
640
- bd
641
- for fut in per_market
642
- if (bd := fut.result()) is not None and not bd.is_empty
643
- ]
644
- if breakdowns:
645
- result[mid] = breakdowns
646
- return result or None
647
-
648
-
649
- def _collect_breakdown_token_addresses(
650
- morpho_positions: dict[MarketId, list[MorphoPositionBreakdown]] | None,
651
- aave_positions: dict[MarketId, list[AaveV3PositionBreakdown]] | None,
652
- ) -> set[ChecksumAddress]:
653
- """Return the set of unique token addresses appearing in any breakdown.
654
-
655
- Used to batch-fetch USD prices via the price oracle in one parallel pass.
656
- """
657
- addrs: set[ChecksumAddress] = set()
658
- for morpho_list in (morpho_positions or {}).values():
659
- for pb in morpho_list:
660
- addrs.add(pb.loan_token)
661
- addrs.add(pb.collateral_token)
662
- for aave_list in (aave_positions or {}).values():
663
- for ab in aave_list:
664
- addrs.add(ab.asset)
665
- return addrs
666
-
667
-
668
- def _fetch_breakdown_token_prices(
669
- ctx: Web3Context,
670
- oracle: PriceOracleMiddleware,
671
- addresses: set[ChecksumAddress],
672
- ) -> dict[HexAddress, float] | None:
673
- """Fetch USD prices for breakdown tokens in one batch via the vault's oracle.
674
-
675
- Returns a dict keyed by lowercase token address. Tokens with no oracle
676
- source configured are simply omitted (callers treat absence as "no price").
677
- """
678
- if not addresses:
679
- return None
680
- ordered = sorted(addresses)
681
- prices = Multicall3(ctx).try_aggregate(
682
- [oracle.get_asset_price(addr) for addr in ordered]
683
- )
684
- result = {
685
- parse_hex_address(addr): price.readable()
686
- for addr, price in zip(ordered, prices, strict=True)
687
- if price is not None
688
- }
689
- return result or None
690
-
691
-
692
263
  def _fetch_block(ctx: Web3Context, block_number: int | None) -> tuple[int, int]:
693
264
  """``(block_number, timestamp)`` of the requested block, latest by default."""
694
265
  resolved = ctx.web3.eth.block_number if block_number is None else block_number