ipor-fusion 4.1.0.dev9__tar.gz → 4.1.0.dev14__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 (357) hide show
  1. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/PKG-INFO +2 -2
  2. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/README.md +1 -1
  3. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/pyproject.toml +1 -1
  4. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/vault_markets_view.py +5 -2
  5. ipor_fusion-4.1.0.dev14/src/ipor_fusion/readers/euler_v2.py +111 -0
  6. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/hypercore.py +5 -4
  7. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/lending_health.py +192 -11
  8. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/vault_data.py +1 -1
  9. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_helpers.py +15 -0
  10. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_lending_health.py +243 -3
  11. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_hypercore_readers.py +2 -2
  12. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.claude-plugin/marketplace.json +0 -0
  13. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.claude-plugin/plugin.json +0 -0
  14. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.env.example +0 -0
  15. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.gemini/settings.json +0 -0
  16. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.git-blame-ignore-revs +0 -0
  17. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/cd.yml +0 -0
  18. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/ci.yml +0 -0
  19. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/commit-lint.yml +0 -0
  20. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/notify-slack.yml +0 -0
  21. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/python-build.yml +0 -0
  22. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/python-publish.yml +0 -0
  23. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/release.yml +0 -0
  24. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.github/workflows/report-slack.yml +0 -0
  25. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.gitignore +0 -0
  26. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.mcp.json +0 -0
  27. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.pre-commit-config.yaml +0 -0
  28. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/.python-version +0 -0
  29. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/AGENTS.md +0 -0
  30. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/CHANGELOG.md +0 -0
  31. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/CLAUDE.md +0 -0
  32. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/CODEOWNERS +0 -0
  33. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/LICENSE +0 -0
  34. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/MIGRATION_4.md +0 -0
  35. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/build.sh +0 -0
  36. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/CONTRIBUTING.md +0 -0
  37. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/README.md +0 -0
  38. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  39. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  40. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  41. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/external_state_margin_leg_base.py +0 -0
  42. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
  43. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/examples/simple_aave_v3_supply_base.py +0 -0
  44. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/pytest.ini +0 -0
  45. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/scripts/commit-check.sh +0 -0
  46. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/scripts/fusion_error_signatures.py +0 -0
  47. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/skills/ipor-deploy-vault/SKILL.md +0 -0
  48. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/__init__.py +0 -0
  49. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/about.py +0 -0
  50. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cache.py +0 -0
  51. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/chains.py +0 -0
  52. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/__init__.py +0 -0
  53. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/address_cmd.py +0 -0
  54. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  55. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/config_cmd.py +0 -0
  56. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/config_store.py +0 -0
  57. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/main.py +0 -0
  58. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/market_cmd.py +0 -0
  59. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/options.py +0 -0
  60. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  61. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
  62. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  63. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/vault_health.py +0 -0
  64. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  65. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  66. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/config/__init__.py +0 -0
  67. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/config/roles.py +0 -0
  68. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/__init__.py +0 -0
  69. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/access.py +0 -0
  70. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/address.py +0 -0
  71. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/context.py +0 -0
  72. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/context_manager.py +0 -0
  73. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/contract.py +0 -0
  74. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/erc20.py +0 -0
  75. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/external_state_executor.py +0 -0
  76. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/fee_manager.py +0 -0
  77. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/fuse.py +0 -0
  78. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/fuse_action.py +0 -0
  79. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/fuse_decoding.py +0 -0
  80. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/fusion_factory.py +0 -0
  81. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/logs.py +0 -0
  82. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/multicall.py +0 -0
  83. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/oracle.py +0 -0
  84. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/plasma_vault.py +0 -0
  85. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/rewards_manager.py +0 -0
  86. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/simulation.py +0 -0
  87. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/simulation_result.py +0 -0
  88. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  89. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/__init__.py +0 -0
  90. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  91. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  92. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  93. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  94. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  95. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  96. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  97. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  98. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  99. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/contracts.py +0 -0
  100. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/discovery.py +0 -0
  101. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/errors.py +0 -0
  102. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  103. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  104. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  105. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  106. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/lane.py +0 -0
  107. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/logs.py +0 -0
  108. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/messages.py +0 -0
  109. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/simulation.py +0 -0
  110. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  111. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  112. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  113. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  114. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  115. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/crosschain/transport.py +0 -0
  116. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/dependency_graph.py +0 -0
  117. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/entry_cli.py +0 -0
  118. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/entry_mcp.py +0 -0
  119. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/errors.py +0 -0
  120. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/explorer.py +0 -0
  121. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/field_docs.py +0 -0
  122. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/__init__.py +0 -0
  123. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  124. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/async_action.py +0 -0
  125. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/base.py +0 -0
  126. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  127. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/erc4626.py +0 -0
  128. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  129. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/events.py +0 -0
  130. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/external_state.py +0 -0
  131. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  132. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  133. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  134. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/hypercore.py +0 -0
  135. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  136. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/merkl.py +0 -0
  137. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/morpho.py +0 -0
  138. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  139. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  140. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fuses/universal.py +0 -0
  141. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/fusion_errors.py +0 -0
  142. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/guide/__init__.py +0 -0
  143. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/guide/architecture.md +0 -0
  144. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/guide/glossary.md +0 -0
  145. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/guide/invariants.md +0 -0
  146. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/guide/quickstart.md +0 -0
  147. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/hypercore/__init__.py +0 -0
  148. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/market_ids.py +0 -0
  149. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/market_info.py +0 -0
  150. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/mcp/__init__.py +0 -0
  151. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/mcp/guide.py +0 -0
  152. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/mcp/models.py +0 -0
  153. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/mcp/server.py +0 -0
  154. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/morpho_api.py +0 -0
  155. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/preflight.py +0 -0
  156. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/py.typed +0 -0
  157. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/__init__.py +0 -0
  158. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/aave_v3.py +0 -0
  159. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/aave_v4.py +0 -0
  160. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/compound_v3.py +0 -0
  161. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  162. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/morpho.py +0 -0
  163. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  164. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  165. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  166. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/position_manager.py +0 -0
  167. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  168. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  169. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/readers/unpriceable.py +0 -0
  170. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/registry.py +0 -0
  171. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/substrates.py +0 -0
  172. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/types.py +0 -0
  173. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/validation.py +0 -0
  174. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/vault_erc20.py +0 -0
  175. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/vault_info.py +0 -0
  176. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/vault_managers.py +0 -0
  177. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/vault_positions.py +0 -0
  178. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/src/ipor_fusion/vault_reads.py +0 -0
  179. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/UniswapV3UniversalRouter.py +0 -0
  180. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_crosschain.py +0 -0
  181. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_crosschain_lifecycle.py +0 -0
  182. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_crosschain_recovery.py +0 -0
  183. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_euler_v2.py +0 -0
  184. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_event_args.py +0 -0
  185. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_fake_rpc.py +0 -0
  186. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_foundry.py +0 -0
  187. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_golden.py +0 -0
  188. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_guide.py +0 -0
  189. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_hypercore_model.py +0 -0
  190. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_hypercore_shadow.py +0 -0
  191. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_live.py +0 -0
  192. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_multicall.py +0 -0
  193. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_oracle_mapping_fake.py +0 -0
  194. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_simulate.py +0 -0
  195. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/_validation.py +0 -0
  196. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/addresses.py +0 -0
  197. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/conftest.py +0 -0
  198. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/constants.py +0 -0
  199. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  200. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/hypercore_market55.json +0 -0
  201. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/hypercore_official.json +0 -0
  202. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  203. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  204. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  205. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  206. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  207. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  208. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  209. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  210. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  211. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  212. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  213. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  214. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  215. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  216. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  217. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  218. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/simulate_v1_request.json +0 -0
  219. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/clean.json +0 -0
  220. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  221. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  222. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  223. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  224. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  225. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  226. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  227. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  228. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/full.json +0 -0
  229. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  230. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/full.txt +0 -0
  231. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/rich.json +0 -0
  232. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  233. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  234. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_about.py +0 -0
  235. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_access_manager.py +0 -0
  236. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cache.py +0 -0
  237. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_chains.py +0 -0
  238. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_address.py +0 -0
  239. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_changelog.py +0 -0
  240. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_commands.py +0 -0
  241. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_config_store.py +0 -0
  242. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_hypercore.py +0 -0
  243. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_market_golden.py +0 -0
  244. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_market_morpho_blue.py +0 -0
  245. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_mcp_parity.py +0 -0
  246. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_oracle_mapping.py +0 -0
  247. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_oracle_mapping_golden.py +0 -0
  248. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_pending_requests_scan.py +0 -0
  249. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_preflight.py +0 -0
  250. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_role_accounts.py +0 -0
  251. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_vault_info_golden.py +0 -0
  252. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_vault_predates_getters.py +0 -0
  253. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_cli_vault_validate.py +0 -0
  254. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_contract_call.py +0 -0
  255. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_contract_encoder.py +0 -0
  256. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_ccip_events.py +0 -0
  257. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_ccip_indexer.py +0 -0
  258. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_ccip_quote.py +0 -0
  259. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_chainlink.py +0 -0
  260. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_contracts.py +0 -0
  261. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_encoding.py +0 -0
  262. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_errors.py +0 -0
  263. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_lane.py +0 -0
  264. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_layering.py +0 -0
  265. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_crosschain_transport.py +0 -0
  266. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_docs_typing.py +0 -0
  267. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_erc20.py +0 -0
  268. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_error_hierarchy.py +0 -0
  269. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_examples_crosschain.py +0 -0
  270. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  271. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_examples_hypercore_hyperevm.py +0 -0
  272. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_examples_vaults.py +0 -0
  273. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_explorer.py +0 -0
  274. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_external_state_events.py +0 -0
  275. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_external_state_executor.py +0 -0
  276. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_fee_manager.py +0 -0
  277. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_fuse_decoding.py +0 -0
  278. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_fuse_encoding.py +0 -0
  279. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_fusion_errors.py +0 -0
  280. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_fusion_instance.py +0 -0
  281. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_governance_wrappers.py +0 -0
  282. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_guide.py +0 -0
  283. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_hypercore_deployment.py +0 -0
  284. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_hypercore_events.py +0 -0
  285. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_hypercore_flow.py +0 -0
  286. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_hypercore_live.py +0 -0
  287. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_hypercore_outcome.py +0 -0
  288. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_hypercore_simulation.py +0 -0
  289. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_logs.py +0 -0
  290. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_mcp_address.py +0 -0
  291. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_mcp_hypercore.py +0 -0
  292. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_mcp_models.py +0 -0
  293. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_mcp_preflight.py +0 -0
  294. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_mcp_server.py +0 -0
  295. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_mcp_vault_validate.py +0 -0
  296. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_multicall.py +0 -0
  297. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_oracle.py +0 -0
  298. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_oracle_mapping.py +0 -0
  299. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_plasma_vault.py +0 -0
  300. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_preflight.py +0 -0
  301. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_public_api.py +0 -0
  302. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_public_type_reachability.py +0 -0
  303. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_reader_encoding.py +0 -0
  304. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_registry.py +0 -0
  305. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_revert_decoding.py +0 -0
  306. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_rewards_manager.py +0 -0
  307. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_roles.py +0 -0
  308. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_sdk_layering.py +0 -0
  309. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  310. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  311. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  312. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  313. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  314. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  315. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  316. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  317. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  318. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_examples_arbitrum.py +0 -0
  319. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_examples_base.py +0 -0
  320. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_examples_crosschain.py +0 -0
  321. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_examples_hyperevm.py +0 -0
  322. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_external_state_executor_base.py +0 -0
  323. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  324. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  325. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  326. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_morpho_blue.py +0 -0
  327. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_pilot_v3.py +0 -0
  328. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  329. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  330. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  331. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  332. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_preflight_base.py +0 -0
  333. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_readers_integration.py +0 -0
  334. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_release_funds.py +0 -0
  335. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_swap_on_base.py +0 -0
  336. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_timelock_base.py +0 -0
  337. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  338. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulation_errors.py +0 -0
  339. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_simulation_observation.py +0 -0
  340. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_solidity_compiler.py +0 -0
  341. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_solidity_mirrors.py +0 -0
  342. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_substrates.py +0 -0
  343. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_types_and_events.py +0 -0
  344. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_unpriceable_tokens.py +0 -0
  345. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_validation.py +0 -0
  346. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_vault_data_block.py +0 -0
  347. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_vault_dep_graph.py +0 -0
  348. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_vault_info_fetched_golden.py +0 -0
  349. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_vault_info_onchain_base.py +0 -0
  350. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  351. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  352. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_vault_simulator_blocks.py +0 -0
  353. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_web3_context.py +0 -0
  354. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/test_withdraw_manager.py +0 -0
  355. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/typed_client/consumer.py +0 -0
  356. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/tests/typed_client/rejected.py +0 -0
  357. {ipor_fusion-4.1.0.dev9 → ipor_fusion-4.1.0.dev14}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.1.0.dev9
3
+ Version: 4.1.0.dev14
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
@@ -560,7 +560,7 @@ Read-only aggregators over positions and health, no fuses involved:
560
560
  |--------|---------|
561
561
  | `MorphoReader`, `AaveV3Reader`, `CompoundV3Reader` | Market params, rates and vault positions per lending protocol |
562
562
  | `UniswapV3Reader`, `RamsesV2Reader` | LP position details |
563
- | `fetch_vault_lending_health` | Health factor per lending market a vault is in |
563
+ | `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2; any other market that can borrow is listed as unreadable) |
564
564
  | `build_oracle_mapping` | How the vault prices every configured asset |
565
565
 
566
566
  ### Supported networks
@@ -511,7 +511,7 @@ Read-only aggregators over positions and health, no fuses involved:
511
511
  |--------|---------|
512
512
  | `MorphoReader`, `AaveV3Reader`, `CompoundV3Reader` | Market params, rates and vault positions per lending protocol |
513
513
  | `UniswapV3Reader`, `RamsesV2Reader` | LP position details |
514
- | `fetch_vault_lending_health` | Health factor per lending market a vault is in |
514
+ | `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2; any other market that can borrow is listed as unreadable) |
515
515
  | `build_oracle_mapping` | How the vault prices every configured asset |
516
516
 
517
517
  ### Supported networks
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.1.0.dev9"
3
+ version = "4.1.0.dev14"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -94,6 +94,8 @@ def _print_lending_health(data: VaultData) -> None: # noqa: C901
94
94
  click.echo(f" {format_market_label(m.market_id)}:")
95
95
  if m.protocol == "aave_v4":
96
96
  click.echo(f" spoke {m.substrate_id}:")
97
+ elif m.protocol == "euler_v2":
98
+ click.echo(f" sub-account {m.substrate_id}:")
97
99
  _print_health_lines(m, indent=" ")
98
100
  _print_unreadable_health(lh)
99
101
 
@@ -115,7 +117,8 @@ def _index_lending_health(
115
117
 
116
118
  Morpho rows are keyed by morpho substrate id (one row per substrate).
117
119
  Aave V3 rows are keyed by IPOR market id (account-aggregated, one row per
118
- market); Aave V4 rows by Spoke address (account-aggregated per Spoke).
120
+ market); Aave V4 rows by Spoke address (account-aggregated per Spoke) and
121
+ Euler V2 rows by sub-account address, in the same account-level map.
119
122
  """
120
123
  morpho: dict[str, LendingMarketHealth] = {}
121
124
  aave: dict[MarketId | str, LendingMarketHealth] = {}
@@ -126,7 +129,7 @@ def _index_lending_health(
126
129
  morpho[str(m.substrate_id).lower().removeprefix("0x")] = m
127
130
  elif m.protocol == "aave_v3":
128
131
  aave[m.market_id] = m
129
- elif m.protocol == "aave_v4" and m.substrate_id:
132
+ elif m.protocol in ("aave_v4", "euler_v2") and m.substrate_id:
130
133
  aave[m.substrate_id] = m
131
134
  return morpho, aave
132
135
 
@@ -0,0 +1,111 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import NewType
5
+
6
+ from eth_typing import ChecksumAddress
7
+ from web3 import Web3
8
+
9
+ from ipor_fusion.core.contract import Call, ContractWrapper
10
+ from ipor_fusion.types import Amount
11
+
12
+ # A value in an Euler vault's unit of account, in that unit's decimals.
13
+ UnitOfAccountValue = NewType("UnitOfAccountValue", int)
14
+ # An Euler LTV in basis points (10_000 = 100%).
15
+ LtvBps = NewType("LtvBps", int)
16
+ # The byte XORed into the Plasma Vault address to name an EVC sub-account.
17
+ EulerSubAccountId = NewType("EulerSubAccountId", int)
18
+
19
+ # Euler's ISO 4217 code for USD (840) used as a unit-of-account address.
20
+ EULER_USD_UNIT_OF_ACCOUNT = Web3.to_checksum_address(
21
+ "0x0000000000000000000000000000000000000348"
22
+ )
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class EulerV2Substrate:
27
+ """A decoded Euler V2 market substrate (`EulerFuseLib.EulerSubstrate`)."""
28
+
29
+ euler_vault: ChecksumAddress
30
+ is_collateral: bool
31
+ can_borrow: bool
32
+ sub_account_id: EulerSubAccountId
33
+
34
+ @classmethod
35
+ def from_bytes(cls, substrate: bytes) -> EulerV2Substrate:
36
+ """Inverse of `EulerFuseLib.substrateToBytes32`:
37
+ `eulerVault<<96 | isCollateral<<88 | canBorrow<<80 | subAccounts<<72`."""
38
+ word = int.from_bytes(substrate, "big")
39
+ return cls(
40
+ euler_vault=Web3.to_checksum_address(f"0x{word >> 96:040x}"),
41
+ is_collateral=bool(word >> 88 & 0x01),
42
+ can_borrow=bool(word >> 80 & 0x01),
43
+ sub_account_id=EulerSubAccountId(word >> 72 & 0xFF),
44
+ )
45
+
46
+ def sub_account(self, plasma_vault: ChecksumAddress) -> ChecksumAddress:
47
+ """`EulerFuseLib.generateSubAccountAddress`: the vault address XOR the id."""
48
+ return Web3.to_checksum_address(
49
+ f"0x{int(plasma_vault, 16) ^ self.sub_account_id:040x}"
50
+ )
51
+
52
+
53
+ @dataclass(slots=True)
54
+ class EulerV2AccountLiquidity:
55
+ """`accountLiquidityFull(account, liquidation=true)` of the account's controller.
56
+
57
+ Each collateral value is already scaled by its liquidation LTV, so it is
58
+ the most debt that collateral backs, not its market value.
59
+ """
60
+
61
+ collaterals: list[ChecksumAddress]
62
+ risk_adjusted_collateral_values: list[UnitOfAccountValue]
63
+ liability_value: UnitOfAccountValue
64
+
65
+
66
+ def _account_liquidity_decoder(
67
+ value: tuple[list[str], list[int], int],
68
+ ) -> EulerV2AccountLiquidity:
69
+ collaterals, values, liability = value
70
+ return EulerV2AccountLiquidity(
71
+ collaterals=[Web3.to_checksum_address(c) for c in collaterals],
72
+ risk_adjusted_collateral_values=[UnitOfAccountValue(v) for v in values],
73
+ liability_value=UnitOfAccountValue(liability),
74
+ )
75
+
76
+
77
+ class EulerV2VaultReader(ContractWrapper):
78
+ """Reader for an Euler V2 (EVK) vault."""
79
+
80
+ def debt_of(self, account: ChecksumAddress) -> Call[Amount]:
81
+ return self._view(
82
+ "debtOf(address)", account, output_types=["uint256"], decoder=Amount
83
+ )
84
+
85
+ def account_liquidity_full(
86
+ self, account: ChecksumAddress
87
+ ) -> Call[EulerV2AccountLiquidity]:
88
+ """Reverts unless this vault is the account's controller."""
89
+ return self._view(
90
+ "accountLiquidityFull(address,bool)",
91
+ account,
92
+ True,
93
+ output_types=["address[]", "uint256[]", "uint256"],
94
+ decoder=_account_liquidity_decoder,
95
+ )
96
+
97
+ def ltv_liquidation(self, collateral: ChecksumAddress) -> Call[LtvBps]:
98
+ """Liquidation LTV of ``collateral``; 0 when not accepted."""
99
+ return self._view(
100
+ "LTVLiquidation(address)",
101
+ collateral,
102
+ output_types=["uint16"],
103
+ decoder=LtvBps,
104
+ )
105
+
106
+ def unit_of_account(self) -> Call[ChecksumAddress]:
107
+ return self._view(
108
+ "unitOfAccount()",
109
+ output_types=["address"],
110
+ decoder=Web3.to_checksum_address,
111
+ )
@@ -97,9 +97,10 @@ PERP_ASSET_INFO_PRECOMPILE = _precompile(0x80A)
97
97
  TOKEN_INFO_PRECOMPILE = _precompile(0x80C)
98
98
  ACCOUNT_MARGIN_SUMMARY_PRECOMPILE = _precompile(0x80F)
99
99
  CORE_USER_EXISTS_PRECOMPILE = _precompile(0x810)
100
- #: ``position2``: the position read that addresses HIP-3 read indexes above
101
- #: 65535; the legacy ``0x800`` cannot, so the fuses never use it.
102
- POSITION_PRECOMPILE = _precompile(0x813)
100
+ #: ``position2``: same layout as ``position`` (``0x800``), which reads indexes up
101
+ #: to 65535; ``position2`` also reads the HIP-3 ones above. The fuses read
102
+ #: ``0x800`` for native perp assets and this one for HIP-3.
103
+ POSITION2_PRECOMPILE = _precompile(0x813)
103
104
 
104
105
  #: The order ``HyperCoreValuationLib`` sums the enabled HIP-3 dexes in.
105
106
  HIP3_VALUATION_ORDER = (1, 6, 8, 9, 10)
@@ -293,7 +294,7 @@ class HyperCoreReader:
293
294
  ) -> Call[HyperCorePosition]:
294
295
  _validate_uint(read_index, 32, "read_index")
295
296
  return self._read(
296
- POSITION_PRECOMPILE,
297
+ POSITION2_PRECOMPILE,
297
298
  ["address", "uint32"],
298
299
  [user, read_index],
299
300
  ["int64", "uint64", "int64", "uint32", "bool"],
@@ -1,4 +1,4 @@
1
- """Lending health computation for Morpho Blue, Aave V3 and Aave V4 markets.
1
+ """Lending health computation for Morpho Blue, Aave V3, Aave V4 and Euler V2 markets.
2
2
 
3
3
  Provides a unified view of LTV, liquidation thresholds, and health factors
4
4
  for lending positions held by Plasma Vaults.
@@ -30,6 +30,11 @@ from ipor_fusion.errors import EmptyCallResultError
30
30
  from ipor_fusion.market_ids import IporFusionMarkets
31
31
  from ipor_fusion.readers.aave_v3 import AaveV3Reader
32
32
  from ipor_fusion.readers.aave_v4 import AaveV4OracleReader, AaveV4SpokeReader
33
+ from ipor_fusion.readers.euler_v2 import (
34
+ EULER_USD_UNIT_OF_ACCOUNT,
35
+ EulerV2Substrate,
36
+ EulerV2VaultReader,
37
+ )
33
38
  from ipor_fusion.readers.morpho import (
34
39
  MorphoReader,
35
40
  morpho_blue_address,
@@ -61,6 +66,7 @@ _READ_ERRORS = (
61
66
 
62
67
  WAD = 10**18
63
68
  RAY = 10**27
69
+ BPS = 10_000
64
70
  ORACLE_PRICE_SCALE = 10**36
65
71
 
66
72
  # Aave V3 Core Pool per chain — the Pool of market AAVE_V3 only. Aave V3 Prime
@@ -109,6 +115,27 @@ AAVE_V4_MARKET_IDS = frozenset(
109
115
  }
110
116
  )
111
117
 
118
+ # Market IDs served by the Euler V2 fuses. Health is account-level per
119
+ # sub-account, read from the controller: the one borrowable vault it owes.
120
+ EULER_V2_MARKET_IDS = frozenset(
121
+ {
122
+ IporFusionMarkets.EULER_V2,
123
+ }
124
+ )
125
+
126
+ # Markets whose fuses can borrow but whose health has no reader yet. Their
127
+ # positions are listed as unreadable rather than left out: an unknown
128
+ # liquidation risk is not a healthy one. Dolomite counts only when a
129
+ # substrate grants borrowing.
130
+ UNSUPPORTED_BORROW_MARKET_IDS = frozenset(
131
+ {
132
+ IporFusionMarkets.MOONWELL,
133
+ IporFusionMarkets.SILO_V2,
134
+ IporFusionMarkets.EBISU,
135
+ }
136
+ )
137
+ _UNSUPPORTED_REASON = "no lending health reader for this market"
138
+
112
139
 
113
140
  @dataclass(slots=True)
114
141
  class LendingMarketHealth:
@@ -118,6 +145,7 @@ class LendingMarketHealth:
118
145
  - Morpho: the morpho market id (`0x` + 64 lowercase hex) (one row per substrate).
119
146
  - Aave V3: None — Aave health is account-level, aggregated across reserves.
120
147
  - Aave V4: the Spoke address — account-level per Spoke, across its reserves.
148
+ - Euler V2: the sub-account address — account-level, across its collaterals.
121
149
  """
122
150
 
123
151
  protocol: str
@@ -409,6 +437,127 @@ def _compute_aave_v4_market_health(
409
437
  )
410
438
 
411
439
 
440
+ @dataclass(frozen=True, slots=True)
441
+ class _EulerV2Account:
442
+ """An EVC sub-account of the vault and the Euler vaults it may borrow from."""
443
+
444
+ sub_account: ChecksumAddress
445
+ borrow_vaults: tuple[ChecksumAddress, ...]
446
+
447
+
448
+ def _compute_euler_v2_account_health(
449
+ ctx: Web3Context,
450
+ account: _EulerV2Account,
451
+ ipor_market_id: MarketId,
452
+ market_name: str,
453
+ ) -> LendingMarketHealth | UnreadableLendingMarket | None:
454
+ """Compute LTV health for one Euler V2 sub-account; None when it owes nothing.
455
+
456
+ Euler values each collateral at its liquidation LTV, so the market value
457
+ is recovered by dividing that LTV back out.
458
+ """
459
+ sub_account = account.sub_account
460
+ multicall = Multicall3(ctx)
461
+ try:
462
+ debts = multicall.aggregate(
463
+ [EulerV2VaultReader(v).debt_of(sub_account) for v in account.borrow_vaults]
464
+ )
465
+ except _READ_ERRORS as exc:
466
+ return UnreadableLendingMarket(
467
+ protocol="euler_v2",
468
+ market_id=ipor_market_id,
469
+ market_name=market_name,
470
+ reason=_reason("debt", exc),
471
+ substrate_id=sub_account,
472
+ )
473
+ # A sub-account has at most one controller: the only vault it can owe.
474
+ controller = next(
475
+ (v for v, d in zip(account.borrow_vaults, debts, strict=True) if d > 0),
476
+ None,
477
+ )
478
+ if controller is None:
479
+ return None
480
+
481
+ reader = EulerV2VaultReader(controller)
482
+ try:
483
+ batch = multicall.batch()
484
+ liquidity_read = batch.add(reader.account_liquidity_full(sub_account))
485
+ unit_read = batch.add(reader.unit_of_account())
486
+ batch.run()
487
+ liquidity = liquidity_read.value
488
+ liquidation_ltvs = multicall.aggregate(
489
+ [reader.ltv_liquidation(c) for c in liquidity.collaterals]
490
+ )
491
+ except _READ_ERRORS as exc:
492
+ return UnreadableLendingMarket(
493
+ protocol="euler_v2",
494
+ market_id=ipor_market_id,
495
+ market_name=market_name,
496
+ reason=_reason("account liquidity", exc),
497
+ substrate_id=sub_account,
498
+ )
499
+
500
+ adjusted = sum(liquidity.risk_adjusted_collateral_values)
501
+ collateral = sum(
502
+ value * BPS // ltv
503
+ for value, ltv in zip(
504
+ liquidity.risk_adjusted_collateral_values, liquidation_ltvs, strict=True
505
+ )
506
+ if ltv > 0
507
+ )
508
+ liability = liquidity.liability_value
509
+ current_ltv = liability / collateral if collateral > 0 else None
510
+ max_ltv = adjusted / collateral if collateral > 0 else 0.0
511
+ health_factor = adjusted / liability if liability > 0 else None
512
+ ltv_usage = liability / adjusted * 100 if adjusted > 0 else None
513
+ # The USD unit of account prices in 18 decimals; other units are not USD.
514
+ in_usd = unit_read.value == EULER_USD_UNIT_OF_ACCOUNT
515
+
516
+ return LendingMarketHealth(
517
+ protocol="euler_v2",
518
+ market_id=ipor_market_id,
519
+ market_name=market_name,
520
+ current_ltv=round(current_ltv, 6) if current_ltv is not None else None,
521
+ max_ltv=round(max_ltv, 6),
522
+ health_factor=round(health_factor, 4) if health_factor is not None else None,
523
+ total_collateral_usd=round(collateral / WAD, 2) if in_usd else None,
524
+ total_debt_usd=round(liability / WAD, 2) if in_usd else None,
525
+ ltv_usage_percent=round(ltv_usage, 2) if ltv_usage is not None else None,
526
+ substrate_id=sub_account,
527
+ )
528
+
529
+
530
+ def _euler_v2_borrow_accounts(
531
+ substrates: list[bytes], vault: PlasmaVaultAddress
532
+ ) -> list[_EulerV2Account]:
533
+ """The vault's sub-accounts that may borrow, from its Euler V2 substrates."""
534
+ borrow_vaults: dict[ChecksumAddress, list[ChecksumAddress]] = {}
535
+ for raw in substrates:
536
+ substrate = EulerV2Substrate.from_bytes(raw)
537
+ if substrate.can_borrow:
538
+ borrow_vaults.setdefault(substrate.sub_account(vault), []).append(
539
+ substrate.euler_vault
540
+ )
541
+ return [
542
+ _EulerV2Account(sub_account, tuple(vaults))
543
+ for sub_account, vaults in borrow_vaults.items()
544
+ ]
545
+
546
+
547
+ def _dolomite_can_borrow(substrate: bytes) -> bool:
548
+ """DolomiteFuseLib: asset<<96 | subAccountId<<88 | canBorrow<<80."""
549
+ return bool(int.from_bytes(substrate, "big") >> 80 & 0x01)
550
+
551
+
552
+ def _can_borrow(market_id: MarketId, substrates: list[bytes]) -> bool:
553
+ """Whether a market without a health reader may hold a borrow position."""
554
+ if market_id in UNSUPPORTED_BORROW_MARKET_IDS:
555
+ return True
556
+ if market_id == IporFusionMarkets.DOLOMITE:
557
+ return any(_dolomite_can_borrow(sub) for sub in substrates)
558
+ return False
559
+
560
+
412
561
  def _aave_v4_spokes(substrates: list[bytes]) -> list[ChecksumAddress]:
413
562
  """Unique Spokes named by Aave V4 reserve substrates, in grant order."""
414
563
  spokes: dict[ChecksumAddress, None] = {}
@@ -424,25 +573,32 @@ class _LendingMarkets:
424
573
  morpho: list[tuple[MarketId, str, MorphoBlueMarketId]]
425
574
  aave_v3: list[tuple[MarketId, str, ChecksumAddress]]
426
575
  aave_v4: list[tuple[MarketId, str, ChecksumAddress]]
576
+ euler_v2: list[tuple[MarketId, str, _EulerV2Account]]
427
577
  unreadable: list[UnreadableLendingMarket]
428
578
 
429
579
 
430
580
  def _collect_lending_markets(
581
+ vault: PlasmaVaultAddress,
431
582
  balance_fuse_market_ids: list[MarketId],
432
583
  market_substrates: dict[MarketId, list[bytes]],
433
584
  aave_pools: dict[MarketId, ChecksumAddress],
434
585
  ) -> _LendingMarkets:
435
586
  """Pick the positions to read: Morpho substrates, Aave V3 markets with a Pool,
436
- and the Spokes of Aave V4 markets."""
437
- markets = _LendingMarkets(morpho=[], aave_v3=[], aave_v4=[], unreadable=[])
587
+ the Spokes of Aave V4 markets and the borrowing Euler V2 sub-accounts. A
588
+ market that may borrow but has no reader is listed as unreadable."""
589
+ markets = _LendingMarkets(
590
+ morpho=[], aave_v3=[], aave_v4=[], euler_v2=[], unreadable=[]
591
+ )
438
592
 
439
593
  for mid in balance_fuse_market_ids:
440
594
  name = market_name(mid)
595
+ substrates = market_substrates.get(mid, [])
441
596
  if mid in MORPHO_MARKET_IDS:
442
- substrates = market_substrates.get(mid, [])
443
- for sub in substrates:
444
- if len(sub) == 32:
445
- markets.morpho.append((mid, name, parse_morpho_market_id(sub)))
597
+ markets.morpho.extend(
598
+ (mid, name, parse_morpho_market_id(sub))
599
+ for sub in substrates
600
+ if len(sub) == 32
601
+ )
446
602
  elif mid in AAVE_V3_MARKET_IDS:
447
603
  if aave_pool := aave_pools.get(mid):
448
604
  markets.aave_v3.append((mid, name, aave_pool))
@@ -456,8 +612,23 @@ def _collect_lending_markets(
456
612
  )
457
613
  )
458
614
  elif mid in AAVE_V4_MARKET_IDS:
459
- for spoke in _aave_v4_spokes(market_substrates.get(mid, [])):
460
- markets.aave_v4.append((mid, name, spoke))
615
+ markets.aave_v4.extend(
616
+ (mid, name, spoke) for spoke in _aave_v4_spokes(substrates)
617
+ )
618
+ elif mid in EULER_V2_MARKET_IDS:
619
+ markets.euler_v2.extend(
620
+ (mid, name, account)
621
+ for account in _euler_v2_borrow_accounts(substrates, vault)
622
+ )
623
+ elif _can_borrow(mid, substrates):
624
+ markets.unreadable.append(
625
+ UnreadableLendingMarket(
626
+ protocol=name.lower(),
627
+ market_id=mid,
628
+ market_name=name,
629
+ reason=_UNSUPPORTED_REASON,
630
+ )
631
+ )
461
632
 
462
633
  return markets
463
634
 
@@ -477,7 +648,8 @@ def fetch_vault_lending_health(
477
648
  vault: The Plasma Vault address.
478
649
  balance_fuse_market_ids: List of market IDs from balance fuses.
479
650
  market_substrates: Map of market_id -> list of raw substrate bytes.
480
- Morpho market ids and Aave V4 Spokes are read from it.
651
+ Morpho market ids, Aave V4 Spokes and Euler V2 sub-accounts are
652
+ read from it.
481
653
  aave_pools: Map of Aave V3 market_id -> the Pool its balance fuse
482
654
  reads (`AaveV3FuseReader.pool`). An Aave V3 market without an
483
655
  entry is listed in ``unreadable``. Defaults to the chain's Core pool for AAVE_V3 only:
@@ -489,7 +661,7 @@ def fetch_vault_lending_health(
489
661
  aave_pools = {IporFusionMarkets.AAVE_V3: core_pool} if core_pool else {}
490
662
 
491
663
  markets = _collect_lending_markets(
492
- balance_fuse_market_ids, market_substrates, aave_pools
664
+ vault, balance_fuse_market_ids, market_substrates, aave_pools
493
665
  )
494
666
 
495
667
  results: list[LendingMarketHealth] = []
@@ -538,8 +710,17 @@ def fetch_vault_lending_health(
538
710
  )
539
711
  )
540
712
 
713
+ for ipor_mid, name, account in markets.euler_v2:
714
+ futures.append(
715
+ pool.submit(
716
+ _compute_euler_v2_account_health, ctx, account, ipor_mid, name
717
+ )
718
+ )
719
+
541
720
  for fut in futures:
542
721
  result = fut.result()
722
+ if result is None:
723
+ continue
543
724
  if isinstance(result, UnreadableLendingMarket):
544
725
  unreadable.append(result)
545
726
  else:
@@ -554,7 +554,7 @@ def _fetch_lending_reads(
554
554
  vault_addr: PlasmaVaultAddress,
555
555
  markets: _MarketReads,
556
556
  ) -> _LendingReads:
557
- """Lending health (Morpho, Aave V3) plus the per-position breakdowns.
557
+ """Lending health plus the Morpho and Aave V3 per-position breakdowns.
558
558
 
559
559
  Health runs as one pool task (it fans out on its own executor); the
560
560
  breakdowns fan out on `pool` from the calling thread, so no pool task ever
@@ -1494,6 +1494,21 @@ class TestPrintLendingHealth:
1494
1494
  assert "AAVE_V3 (1):" in out
1495
1495
  assert "Health Factor:" in out
1496
1496
 
1497
+ def test_euler_health_renders_under_its_sub_account(self, capsys):
1498
+ sub_account = "0x" + "3e" * 20
1499
+ m = _make_lending_market(
1500
+ protocol="euler_v2",
1501
+ market_id=MarketId(11),
1502
+ substrate_id=sub_account,
1503
+ health_factor=1.17,
1504
+ )
1505
+ data = _make_data(lending_health=VaultLendingHealth(markets=[m]))
1506
+ _print_lending_health(data)
1507
+ out = capsys.readouterr().out
1508
+ assert "EULER_V2 (11):" in out
1509
+ assert f"sub-account {sub_account}:" in out
1510
+ assert "Health Factor:" in out
1511
+
1497
1512
  def test_orphan_health_critical_status_colored(self, capsys):
1498
1513
  m = _make_lending_market(health_factor=1.03)
1499
1514
  m.protocol = "aave_v3"