ipor-fusion 4.2.0.dev9__tar.gz → 4.2.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 (372) hide show
  1. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/PKG-INFO +3 -3
  2. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/README.md +2 -2
  3. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/pyproject.toml +1 -1
  4. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/__init__.py +10 -0
  5. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_markets_view.py +3 -3
  6. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/__init__.py +7 -1
  7. ipor_fusion-4.2.0.dev14/src/ipor_fusion/fuses/compound_v3.py +62 -0
  8. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/market_ids.py +4 -0
  9. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/__init__.py +9 -1
  10. ipor_fusion-4.2.0.dev14/src/ipor_fusion/readers/compound_v3.py +164 -0
  11. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/lending_health.py +132 -54
  12. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/substrates.py +1 -0
  13. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/validation.py +8 -2
  14. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_data.py +8 -4
  15. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_positions.py +28 -1
  16. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/constants.py +18 -0
  17. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_lending_health.py +202 -0
  18. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_vault_predates_getters.py +1 -1
  19. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_fuse_encoding.py +37 -1
  20. ipor_fusion-4.2.0.dev14/tests/test_lending_health_compound_v3_onchain.py +87 -0
  21. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_public_api.py +10 -0
  22. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_reader_encoding.py +15 -1
  23. ipor_fusion-4.2.0.dev14/tests/test_simulate_compound_v3_institutional_usdc.py +453 -0
  24. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_solidity_mirrors.py +2 -3
  25. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_validation.py +8 -0
  26. ipor_fusion-4.2.0.dev9/src/ipor_fusion/fuses/compound_v3.py +0 -23
  27. ipor_fusion-4.2.0.dev9/src/ipor_fusion/readers/compound_v3.py +0 -21
  28. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.claude-plugin/marketplace.json +0 -0
  29. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.claude-plugin/plugin.json +0 -0
  30. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.env.example +0 -0
  31. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.gemini/settings.json +0 -0
  32. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.git-blame-ignore-revs +0 -0
  33. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/cd.yml +0 -0
  34. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/ci.yml +0 -0
  35. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/commit-lint.yml +0 -0
  36. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/notify-slack.yml +0 -0
  37. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/python-build.yml +0 -0
  38. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/python-publish.yml +0 -0
  39. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/release.yml +0 -0
  40. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.github/workflows/report-slack.yml +0 -0
  41. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.gitignore +0 -0
  42. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.mcp.json +0 -0
  43. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.pre-commit-config.yaml +0 -0
  44. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/.python-version +0 -0
  45. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/AGENTS.md +0 -0
  46. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/CHANGELOG.md +0 -0
  47. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/CLAUDE.md +0 -0
  48. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/CODEOWNERS +0 -0
  49. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/LICENSE +0 -0
  50. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/MIGRATION_4.md +0 -0
  51. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/build.sh +0 -0
  52. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/CONTRIBUTING.md +0 -0
  53. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/README.md +0 -0
  54. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  55. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  56. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  57. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/external_state_margin_leg_base.py +0 -0
  58. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
  59. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/examples/simple_aave_v3_supply_base.py +0 -0
  60. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/pytest.ini +0 -0
  61. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/scripts/commit-check.sh +0 -0
  62. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/scripts/fusion_error_signatures.py +0 -0
  63. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/skills/ipor-deploy-vault/SKILL.md +0 -0
  64. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/about.py +0 -0
  65. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cache.py +0 -0
  66. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/chains.py +0 -0
  67. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/__init__.py +0 -0
  68. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/address_cmd.py +0 -0
  69. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  70. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/config_cmd.py +0 -0
  71. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/config_store.py +0 -0
  72. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/fuse_cmd.py +0 -0
  73. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/main.py +0 -0
  74. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/market_cmd.py +0 -0
  75. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/options.py +0 -0
  76. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  77. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
  78. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  79. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_health.py +0 -0
  80. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  81. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  82. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/config/__init__.py +0 -0
  83. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/config/roles.py +0 -0
  84. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/__init__.py +0 -0
  85. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/access.py +0 -0
  86. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/address.py +0 -0
  87. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/context.py +0 -0
  88. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/context_manager.py +0 -0
  89. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/contract.py +0 -0
  90. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/erc20.py +0 -0
  91. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/external_state_executor.py +0 -0
  92. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fee_manager.py +0 -0
  93. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fuse.py +0 -0
  94. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fuse_action.py +0 -0
  95. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fuse_decoding.py +0 -0
  96. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/fusion_factory.py +0 -0
  97. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/logs.py +0 -0
  98. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/multicall.py +0 -0
  99. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/oracle.py +0 -0
  100. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/plasma_vault.py +0 -0
  101. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/rewards_manager.py +0 -0
  102. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/simulation.py +0 -0
  103. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/simulation_result.py +0 -0
  104. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  105. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/__init__.py +0 -0
  106. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  107. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  108. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  109. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  110. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  111. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  112. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  113. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  114. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  115. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/contracts.py +0 -0
  116. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/discovery.py +0 -0
  117. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/errors.py +0 -0
  118. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  119. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  120. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  121. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  122. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/lane.py +0 -0
  123. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/logs.py +0 -0
  124. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/messages.py +0 -0
  125. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/simulation.py +0 -0
  126. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  127. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  128. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  129. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  130. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  131. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/crosschain/transport.py +0 -0
  132. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/dependency_graph.py +0 -0
  133. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/entry_cli.py +0 -0
  134. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/entry_mcp.py +0 -0
  135. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/errors.py +0 -0
  136. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/explorer.py +0 -0
  137. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/field_docs.py +0 -0
  138. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuse_whitelist.py +0 -0
  139. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  140. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/async_action.py +0 -0
  141. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/base.py +0 -0
  142. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/erc4626.py +0 -0
  143. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  144. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/events.py +0 -0
  145. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/external_state.py +0 -0
  146. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  147. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  148. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  149. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/hypercore.py +0 -0
  150. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  151. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
  152. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/merkl.py +0 -0
  153. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/morpho.py +0 -0
  154. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  155. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  156. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fuses/universal.py +0 -0
  157. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/fusion_errors.py +0 -0
  158. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/__init__.py +0 -0
  159. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/architecture.md +0 -0
  160. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/glossary.md +0 -0
  161. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/invariants.md +0 -0
  162. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/guide/quickstart.md +0 -0
  163. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/hypercore/__init__.py +0 -0
  164. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/market_info.py +0 -0
  165. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/__init__.py +0 -0
  166. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/guide.py +0 -0
  167. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/models.py +0 -0
  168. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/mcp/server.py +0 -0
  169. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/morpho_api.py +0 -0
  170. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/onchain_text.py +0 -0
  171. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/preflight.py +0 -0
  172. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/py.typed +0 -0
  173. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/aave_v3.py +0 -0
  174. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/aave_v4.py +0 -0
  175. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/euler_v2.py +0 -0
  176. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/hypercore.py +0 -0
  177. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  178. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/morpho.py +0 -0
  179. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  180. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  181. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  182. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/position_manager.py +0 -0
  183. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  184. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  185. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/readers/unpriceable.py +0 -0
  186. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/registry.py +0 -0
  187. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/types.py +0 -0
  188. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_erc20.py +0 -0
  189. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_info.py +0 -0
  190. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_managers.py +0 -0
  191. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/src/ipor_fusion/vault_reads.py +0 -0
  192. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/UniswapV3UniversalRouter.py +0 -0
  193. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_addresses.py +0 -0
  194. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_crosschain.py +0 -0
  195. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_crosschain_lifecycle.py +0 -0
  196. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_crosschain_recovery.py +0 -0
  197. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_euler_v2.py +0 -0
  198. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_event_args.py +0 -0
  199. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_fake_rpc.py +0 -0
  200. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_foundry.py +0 -0
  201. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_golden.py +0 -0
  202. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_guide.py +0 -0
  203. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_hypercore_model.py +0 -0
  204. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_hypercore_shadow.py +0 -0
  205. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_live.py +0 -0
  206. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_multicall.py +0 -0
  207. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_oracle_mapping_fake.py +0 -0
  208. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_simulate.py +0 -0
  209. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/_validation.py +0 -0
  210. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/addresses.py +0 -0
  211. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/conftest.py +0 -0
  212. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  213. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/hypercore_market55.json +0 -0
  214. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/hypercore_official.json +0 -0
  215. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  216. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  217. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  218. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  219. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  220. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  221. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  222. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  223. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  224. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  225. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  226. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  227. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  228. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  229. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  230. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  231. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/simulate_v1_request.json +0 -0
  232. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/clean.json +0 -0
  233. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  234. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  235. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  236. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  237. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  238. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  239. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  240. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  241. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/full.json +0 -0
  242. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  243. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/full.txt +0 -0
  244. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/rich.json +0 -0
  245. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  246. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  247. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_about.py +0 -0
  248. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_access_manager.py +0 -0
  249. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_addresses_helper.py +0 -0
  250. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cache.py +0 -0
  251. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_ccip_read_disabled.py +0 -0
  252. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_chains.py +0 -0
  253. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_address.py +0 -0
  254. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_changelog.py +0 -0
  255. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_commands.py +0 -0
  256. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_config_store.py +0 -0
  257. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_helpers.py +0 -0
  258. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_hypercore.py +0 -0
  259. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_market_golden.py +0 -0
  260. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_market_morpho_blue.py +0 -0
  261. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_mcp_fuse_whitelist.py +0 -0
  262. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_mcp_parity.py +0 -0
  263. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_oracle_mapping.py +0 -0
  264. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_oracle_mapping_golden.py +0 -0
  265. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_pending_requests_scan.py +0 -0
  266. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_preflight.py +0 -0
  267. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_role_accounts.py +0 -0
  268. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_vault_info_golden.py +0 -0
  269. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_cli_vault_validate.py +0 -0
  270. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_contract_call.py +0 -0
  271. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_contract_encoder.py +0 -0
  272. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_ccip_events.py +0 -0
  273. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_ccip_indexer.py +0 -0
  274. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_ccip_quote.py +0 -0
  275. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_chainlink.py +0 -0
  276. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_contracts.py +0 -0
  277. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_encoding.py +0 -0
  278. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_errors.py +0 -0
  279. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_lane.py +0 -0
  280. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_layering.py +0 -0
  281. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_crosschain_transport.py +0 -0
  282. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_docs_typing.py +0 -0
  283. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_erc20.py +0 -0
  284. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_error_hierarchy.py +0 -0
  285. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_examples_crosschain.py +0 -0
  286. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  287. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_examples_hypercore_hyperevm.py +0 -0
  288. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_examples_vaults.py +0 -0
  289. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_explorer.py +0 -0
  290. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_external_state_events.py +0 -0
  291. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_external_state_executor.py +0 -0
  292. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_fee_manager.py +0 -0
  293. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_fuse_decoding.py +0 -0
  294. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_fuse_whitelist.py +0 -0
  295. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_fusion_errors.py +0 -0
  296. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_fusion_instance.py +0 -0
  297. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_governance_wrappers.py +0 -0
  298. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_guide.py +0 -0
  299. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_deployment.py +0 -0
  300. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_events.py +0 -0
  301. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_flow.py +0 -0
  302. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_live.py +0 -0
  303. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_outcome.py +0 -0
  304. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_pre_hooks.py +0 -0
  305. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_readers.py +0 -0
  306. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_hypercore_simulation.py +0 -0
  307. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_logs.py +0 -0
  308. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_address.py +0 -0
  309. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_hypercore.py +0 -0
  310. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_models.py +0 -0
  311. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_preflight.py +0 -0
  312. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_server.py +0 -0
  313. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_mcp_vault_validate.py +0 -0
  314. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_multicall.py +0 -0
  315. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_onchain_text.py +0 -0
  316. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_oracle.py +0 -0
  317. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_oracle_mapping.py +0 -0
  318. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_plasma_vault.py +0 -0
  319. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_preflight.py +0 -0
  320. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_public_type_reachability.py +0 -0
  321. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_registry.py +0 -0
  322. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_revert_decoding.py +0 -0
  323. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_rewards_manager.py +0 -0
  324. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_roles.py +0 -0
  325. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_sdk_layering.py +0 -0
  326. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  327. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  328. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  329. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  330. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  331. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  332. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  333. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  334. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  335. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_arbitrum.py +0 -0
  336. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_base.py +0 -0
  337. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_crosschain.py +0 -0
  338. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_examples_hyperevm.py +0 -0
  339. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_external_state_executor_base.py +0 -0
  340. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  341. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  342. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  343. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_morpho_blue.py +0 -0
  344. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v3.py +0 -0
  345. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  346. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  347. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  348. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  349. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_preflight_base.py +0 -0
  350. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_readers_integration.py +0 -0
  351. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_release_funds.py +0 -0
  352. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_swap_on_base.py +0 -0
  353. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_timelock_base.py +0 -0
  354. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  355. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulation_errors.py +0 -0
  356. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_simulation_observation.py +0 -0
  357. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_solidity_compiler.py +0 -0
  358. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_substrates.py +0 -0
  359. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_types_and_events.py +0 -0
  360. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_unpriceable_tokens.py +0 -0
  361. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_vault_data_block.py +0 -0
  362. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_vault_dep_graph.py +0 -0
  363. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_vault_info_fetched_golden.py +0 -0
  364. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_vault_info_onchain_base.py +0 -0
  365. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  366. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  367. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_vault_simulator_blocks.py +0 -0
  368. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_web3_context.py +0 -0
  369. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/test_withdraw_manager.py +0 -0
  370. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/typed_client/consumer.py +0 -0
  371. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/tests/typed_client/rejected.py +0 -0
  372. {ipor_fusion-4.2.0.dev9 → ipor_fusion-4.2.0.dev14}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.2.0.dev9
3
+ Version: 4.2.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
@@ -520,7 +520,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
520
520
  | Euler V2 | `EulerV2SupplyFuse`, `EulerV2CollateralFuse`, `EulerV2ControllerFuse`, `EulerV2BorrowFuse`, `EulerV2BatchFuse`, `EulerV2SwapDeployFuse`, `EulerV2SwapReconfigureFuse`, `EulerV2SwapRegistryFuse` |
521
521
  | Uniswap V3 | `UniswapV3SwapFuse`, `UniswapV3NewPositionFuse`, `UniswapV3ModifyPositionFuse`, `UniswapV3CollectFuse` |
522
522
  | Ramses V2 | `RamsesV2NewPositionFuse`, `RamsesV2ModifyPositionFuse`, `RamsesV2CollectFuse`, `RamsesClaimFuse` |
523
- | Compound V3 | `CompoundV3SupplyFuse` |
523
+ | Compound V3 | `CompoundV3SupplyFuse`, `CompoundV3BorrowFuse`, `CompoundV3ClaimFuse` |
524
524
  | Gearbox V3 | `GearboxSupplyFuse`, `GearboxStakeFuse` |
525
525
  | ERC-4626 | `ERC4626SupplyFuse` |
526
526
  | Fluid Instadapp | `FluidInstadappSupplyFuse`, `FluidInstadappStakingFuse` |
@@ -564,7 +564,7 @@ Read-only aggregators over positions and health, no fuses involved:
564
564
  |--------|---------|
565
565
  | `MorphoReader`, `AaveV3Reader`, `CompoundV3Reader` | Market params, rates and vault positions per lending protocol |
566
566
  | `UniswapV3Reader`, `RamsesV2Reader` | LP position details |
567
- | `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) |
567
+ | `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2, Compound V3; any other market that can borrow is listed as unreadable) |
568
568
  | `build_oracle_mapping` | How the vault prices every configured asset |
569
569
 
570
570
  ### Supported networks
@@ -471,7 +471,7 @@ Fuse.method() --> FuseAction --> PlasmaVault.execute([actions]) --> Call
471
471
  | Euler V2 | `EulerV2SupplyFuse`, `EulerV2CollateralFuse`, `EulerV2ControllerFuse`, `EulerV2BorrowFuse`, `EulerV2BatchFuse`, `EulerV2SwapDeployFuse`, `EulerV2SwapReconfigureFuse`, `EulerV2SwapRegistryFuse` |
472
472
  | Uniswap V3 | `UniswapV3SwapFuse`, `UniswapV3NewPositionFuse`, `UniswapV3ModifyPositionFuse`, `UniswapV3CollectFuse` |
473
473
  | Ramses V2 | `RamsesV2NewPositionFuse`, `RamsesV2ModifyPositionFuse`, `RamsesV2CollectFuse`, `RamsesClaimFuse` |
474
- | Compound V3 | `CompoundV3SupplyFuse` |
474
+ | Compound V3 | `CompoundV3SupplyFuse`, `CompoundV3BorrowFuse`, `CompoundV3ClaimFuse` |
475
475
  | Gearbox V3 | `GearboxSupplyFuse`, `GearboxStakeFuse` |
476
476
  | ERC-4626 | `ERC4626SupplyFuse` |
477
477
  | Fluid Instadapp | `FluidInstadappSupplyFuse`, `FluidInstadappStakingFuse` |
@@ -515,7 +515,7 @@ Read-only aggregators over positions and health, no fuses involved:
515
515
  |--------|---------|
516
516
  | `MorphoReader`, `AaveV3Reader`, `CompoundV3Reader` | Market params, rates and vault positions per lending protocol |
517
517
  | `UniswapV3Reader`, `RamsesV2Reader` | LP position details |
518
- | `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) |
518
+ | `fetch_vault_lending_health` | Health factor per lending market a vault is in (Morpho, Aave V3/V4, Euler V2, Compound V3; any other market that can borrow is listed as unreadable) |
519
519
  | `build_oracle_mapping` | How the vault prices every configured asset |
520
520
 
521
521
  ### Supported networks
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.2.0.dev9"
3
+ version = "4.2.0.dev14"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -171,6 +171,8 @@ from ipor_fusion.fuses import (
171
171
  AaveV3SupplyFuse,
172
172
  AsyncActionFuse,
173
173
  AsyncActionSubstrates,
174
+ CompoundV3BorrowFuse,
175
+ CompoundV3ClaimFuse,
174
176
  CompoundV3SupplyFuse,
175
177
  ERC4626SupplyFuse,
176
178
  EulerSwapDynamicParams,
@@ -310,6 +312,9 @@ from ipor_fusion.readers import (
310
312
  AaveV4OracleReader,
311
313
  AaveV4SpokeReader,
312
314
  AaveV4UserAccountData,
315
+ CometAccountValue,
316
+ CometAssetInfo,
317
+ CompoundV3FuseReader,
313
318
  CompoundV3Reader,
314
319
  LendingMarketHealth,
315
320
  MiddlewarePricedToken,
@@ -656,6 +661,8 @@ __all__ = [
656
661
  "UniswapV3NewPositionEvent",
657
662
  "UniswapV3ClosePositionEvent",
658
663
  "UniswapV3Events",
664
+ "CompoundV3BorrowFuse",
665
+ "CompoundV3ClaimFuse",
659
666
  "CompoundV3SupplyFuse",
660
667
  "RamsesV2NewPositionFuse",
661
668
  "RamsesV2ModifyPositionFuse",
@@ -784,6 +791,9 @@ __all__ = [
784
791
  "AaveV4OracleReader",
785
792
  "AaveV4SpokeReader",
786
793
  "AaveV4UserAccountData",
794
+ "CometAccountValue",
795
+ "CometAssetInfo",
796
+ "CompoundV3FuseReader",
787
797
  "CompoundV3Reader",
788
798
  "HYPEREVM_CHAIN_ID",
789
799
  "LendingMarketHealth",
@@ -116,8 +116,8 @@ def _index_lending_health(
116
116
  """Split lending health rows by protocol for fast lookup during rendering.
117
117
 
118
118
  Morpho rows are keyed by morpho substrate id (one row per substrate).
119
- Aave V3 rows are keyed by IPOR market id (account-aggregated, one row per
120
- market); Aave V4 rows by Spoke address (account-aggregated per Spoke) and
119
+ Aave V3 and Compound V3 rows are keyed by IPOR market id (account-aggregated,
120
+ one row per market); Aave V4 rows by Spoke address (account-aggregated per Spoke) and
121
121
  Euler V2 rows by sub-account address, in the same account-level map.
122
122
  """
123
123
  morpho: dict[str, LendingMarketHealth] = {}
@@ -127,7 +127,7 @@ def _index_lending_health(
127
127
  for m in lh.markets:
128
128
  if m.protocol == "morpho" and m.substrate_id:
129
129
  morpho[str(m.substrate_id).lower().removeprefix("0x")] = m
130
- elif m.protocol == "aave_v3":
130
+ elif m.protocol in ("aave_v3", "compound_v3"):
131
131
  aave[m.market_id] = m
132
132
  elif m.protocol in ("aave_v4", "euler_v2") and m.substrate_id:
133
133
  aave[m.substrate_id] = m
@@ -1,7 +1,11 @@
1
1
  from ipor_fusion.fuses.aave_v3 import AaveV3BorrowFuse, AaveV3SupplyFuse
2
2
  from ipor_fusion.fuses.async_action import AsyncActionFuse, AsyncActionSubstrates
3
3
  from ipor_fusion.fuses.base import Fuse, FuseAction, StakeFuse
4
- from ipor_fusion.fuses.compound_v3 import CompoundV3SupplyFuse
4
+ from ipor_fusion.fuses.compound_v3 import (
5
+ CompoundV3BorrowFuse,
6
+ CompoundV3ClaimFuse,
7
+ CompoundV3SupplyFuse,
8
+ )
5
9
  from ipor_fusion.fuses.erc4626 import ERC4626SupplyFuse
6
10
  from ipor_fusion.fuses.euler_v2 import (
7
11
  EulerSwapDynamicParams,
@@ -112,6 +116,8 @@ __all__ = [
112
116
  "UniswapV3NewPositionEvent",
113
117
  "UniswapV3ClosePositionEvent",
114
118
  "UniswapV3Events",
119
+ "CompoundV3BorrowFuse",
120
+ "CompoundV3ClaimFuse",
115
121
  "CompoundV3SupplyFuse",
116
122
  "RamsesV2NewPositionFuse",
117
123
  "RamsesV2ModifyPositionFuse",
@@ -0,0 +1,62 @@
1
+ from eth_typing import ChecksumAddress
2
+
3
+ from ipor_fusion.fuses.base import Fuse, FuseAction
4
+ from ipor_fusion.types import Amount
5
+
6
+
7
+ class CompoundV3SupplyFuse(Fuse):
8
+ """Fuse for supplying and withdrawing assets on Compound V3."""
9
+
10
+ SIGNATURES = (
11
+ "enter((address,uint256))",
12
+ "exit((address,uint256))",
13
+ )
14
+
15
+ def supply(self, *, asset: ChecksumAddress, amount: Amount) -> FuseAction:
16
+ self._validate_address(asset, "asset")
17
+ self._validate_amount(amount, "amount")
18
+ return self._action_raw("enter((address,uint256))", [[asset, amount]])
19
+
20
+ def withdraw(self, *, asset: ChecksumAddress, amount: Amount) -> FuseAction:
21
+ self._validate_address(asset, "asset")
22
+ self._validate_amount(amount, "amount")
23
+ return self._action_raw("exit((address,uint256))", [[asset, amount]])
24
+
25
+
26
+ class CompoundV3BorrowFuse(Fuse):
27
+ """Fuse for borrowing and repaying the base token of a Comet.
28
+
29
+ The fuse is bound to one Comet, whose base token is the only asset it can
30
+ borrow, so neither action names an asset; the base token must be granted as
31
+ a substrate of the fuse's market. A repay is capped at the debt, so
32
+ ``MAX_UINT256`` repays in full.
33
+ """
34
+
35
+ SIGNATURES = (
36
+ "enter((uint256))",
37
+ "exit((uint256))",
38
+ )
39
+
40
+ def borrow(self, *, amount: Amount) -> FuseAction:
41
+ self._validate_amount(amount, "amount")
42
+ return self._action_raw("enter((uint256))", [[amount]])
43
+
44
+ def repay(self, *, amount: Amount) -> FuseAction:
45
+ self._validate_amount(amount, "amount")
46
+ return self._action_raw("exit((uint256))", [[amount]])
47
+
48
+
49
+ class CompoundV3ClaimFuse(Fuse):
50
+ """Fuse for claiming a Comet's rewards to the vault's rewards claim manager.
51
+
52
+ The fuse is bound to one ``CometRewards``. Route the action through
53
+ ``RewardsManager.claim_rewards(...)``, not ``PlasmaVault.execute(...)``, and
54
+ register the fuse with ``RewardsManager.add_reward_fuses(...)`` first. A
55
+ Comet with no reward token configured reverts ``NotSupported(address)``.
56
+ """
57
+
58
+ SIGNATURES = ("claim(address)",)
59
+
60
+ def claim(self, *, comet: ChecksumAddress) -> FuseAction:
61
+ self._validate_address(comet, "comet")
62
+ return self._action_raw("claim(address)", [comet])
@@ -106,6 +106,10 @@ class IporFusionMarkets:
106
106
  # Substrate type: HyperCoreSubstrateLib (1 SpotToken, 2 PerpMarket,
107
107
  # 3 Destination, 4 Builder, 5 Config, 6 SendCap, 7 PerpPositionCap).
108
108
  HYPERCORE = MarketId(55)
109
+ # Compound V3 Institutional USDC (ciUSDCv3) on Ethereum: its own Comet, so
110
+ # the Compound V3 fuses are deployed again under this id; substrates: asset
111
+ # addresses, as for COMPOUND_V3_USDC
112
+ COMPOUND_V3_INSTITUTIONAL_USDC = MarketId(57)
109
113
  # POL unstaking via sPOLController. The out-of-band id is baked into the
110
114
  # deployed fuses as an immutable (424243 = 0x67933), so it must match the
111
115
  # deployments. Substrates per IporFusionMarkets.sol: the sPOLController
@@ -11,7 +11,12 @@ from ipor_fusion.readers.aave_v4 import (
11
11
  AaveV4SpokeReader,
12
12
  AaveV4UserAccountData,
13
13
  )
14
- from ipor_fusion.readers.compound_v3 import CompoundV3Reader
14
+ from ipor_fusion.readers.compound_v3 import (
15
+ CometAccountValue,
16
+ CometAssetInfo,
17
+ CompoundV3FuseReader,
18
+ CompoundV3Reader,
19
+ )
15
20
  from ipor_fusion.readers.hypercore import (
16
21
  HYPEREVM_CHAIN_ID,
17
22
  )
@@ -79,6 +84,9 @@ __all__ = [
79
84
  "AaveV4OracleReader",
80
85
  "AaveV4SpokeReader",
81
86
  "AaveV4UserAccountData",
87
+ "CometAccountValue",
88
+ "CometAssetInfo",
89
+ "CompoundV3FuseReader",
82
90
  "CompoundV3Reader",
83
91
  "HYPEREVM_CHAIN_ID",
84
92
  "LendingMarketHealth",
@@ -0,0 +1,164 @@
1
+ from dataclasses import dataclass
2
+
3
+ from eth_typing import ChecksumAddress
4
+ from web3 import Web3
5
+
6
+ from ipor_fusion.core.context import Web3Context
7
+ from ipor_fusion.core.contract import Call, ContractWrapper
8
+ from ipor_fusion.core.multicall import Multicall3
9
+ from ipor_fusion.types import Amount
10
+
11
+ COMET_FACTOR_SCALE = 10**18
12
+ COMET_PRICE_SCALE = 10**8
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class CometAssetInfo:
17
+ """A collateral listing of a Comet; the factors are scaled by 1e18."""
18
+
19
+ asset: ChecksumAddress
20
+ price_feed: ChecksumAddress
21
+ scale: int
22
+ borrow_collateral_factor: int
23
+ liquidate_collateral_factor: int
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class CometAccountValue:
28
+ """An account's position in the Comet's own prices, scaled by 1e8.
29
+
30
+ The unit is the Comet's price unit: USD on USD-priced Comets, ETH on a WETH
31
+ Comet. ``liquidation_limit`` is the collateral at its liquidation factors;
32
+ the account is liquidatable when ``debt`` exceeds it.
33
+ """
34
+
35
+ collateral: int
36
+ liquidation_limit: int
37
+ debt: int
38
+
39
+
40
+ def _asset_info_decoder(
41
+ value: tuple[int, str, str, int, int, int, int, int],
42
+ ) -> CometAssetInfo:
43
+ _, asset, price_feed, scale, borrow_factor, liquidate_factor, _, _ = value
44
+ return CometAssetInfo(
45
+ asset=Web3.to_checksum_address(asset),
46
+ price_feed=Web3.to_checksum_address(price_feed),
47
+ scale=scale,
48
+ borrow_collateral_factor=borrow_factor,
49
+ liquidate_collateral_factor=liquidate_factor,
50
+ )
51
+
52
+
53
+ class CompoundV3FuseReader(ContractWrapper):
54
+ """Reader for the immutables of an IPOR Fusion Compound V3 fuse.
55
+
56
+ A Fusion market is bound to one Comet per chain through its fuses, so the
57
+ Comet behind a market is a property of its fuse.
58
+ """
59
+
60
+ def comet(self) -> Call[ChecksumAddress]:
61
+ return self._view(
62
+ "COMET()", output_types=["address"], decoder=Web3.to_checksum_address
63
+ )
64
+
65
+
66
+ class CompoundV3Reader(ContractWrapper):
67
+ """Reader for Compound V3 (Comet) on-chain state."""
68
+
69
+ def balance_of(self, account: ChecksumAddress) -> Call[Amount]:
70
+ return self._view(
71
+ "balanceOf(address)", account, output_types=["uint256"], decoder=Amount
72
+ )
73
+
74
+ def borrow_balance_of(self, account: ChecksumAddress) -> Call[Amount]:
75
+ return self._view(
76
+ "borrowBalanceOf(address)",
77
+ account,
78
+ output_types=["uint256"],
79
+ decoder=Amount,
80
+ )
81
+
82
+ def collateral_balance_of(
83
+ self, account: ChecksumAddress, asset: ChecksumAddress
84
+ ) -> Call[Amount]:
85
+ return self._view(
86
+ "collateralBalanceOf(address,address)",
87
+ account,
88
+ asset,
89
+ output_types=["uint128"],
90
+ decoder=Amount,
91
+ )
92
+
93
+ def base_scale(self) -> Call[int]:
94
+ return self._view("baseScale()", output_types=["uint256"])
95
+
96
+ def base_token_price_feed(self) -> Call[ChecksumAddress]:
97
+ return self._view(
98
+ "baseTokenPriceFeed()",
99
+ output_types=["address"],
100
+ decoder=Web3.to_checksum_address,
101
+ )
102
+
103
+ def get_price(self, price_feed: ChecksumAddress) -> Call[int]:
104
+ return self._view("getPrice(address)", price_feed, output_types=["uint256"])
105
+
106
+ def num_assets(self) -> Call[int]:
107
+ return self._view("numAssets()", output_types=["uint8"])
108
+
109
+ def get_asset_info(self, index: int) -> Call[CometAssetInfo]:
110
+ return self._view(
111
+ "getAssetInfo(uint8)",
112
+ index,
113
+ output_types=[
114
+ "(uint8,address,address,uint64,uint64,uint64,uint64,uint128)"
115
+ ],
116
+ decoder=_asset_info_decoder,
117
+ )
118
+
119
+ def read_account_value(
120
+ self, ctx: Web3Context, account: ChecksumAddress
121
+ ) -> CometAccountValue:
122
+ """Value ``account``'s collateral and debt as the Comet does, over every
123
+ collateral it lists rather than the vault's substrates: a revoked substrate
124
+ does not take collateral out of the account. Only the feeds of held assets
125
+ are read, as `isLiquidatable` does, so a broken feed of an asset the account
126
+ does not hold cannot hide its position (four batches)."""
127
+ multicall = Multicall3(ctx)
128
+ batch = multicall.batch()
129
+ count = batch.add(self.num_assets())
130
+ base_scale = batch.add(self.base_scale())
131
+ base_feed = batch.add(self.base_token_price_feed())
132
+ debt = batch.add(self.borrow_balance_of(account))
133
+ batch.run()
134
+
135
+ infos = multicall.aggregate(
136
+ [self.get_asset_info(i) for i in range(count.value)]
137
+ )
138
+ balances = multicall.aggregate(
139
+ [self.collateral_balance_of(account, info.asset) for info in infos]
140
+ )
141
+ held = [
142
+ (info, balance)
143
+ for info, balance in zip(infos, balances, strict=True)
144
+ if balance > 0
145
+ ]
146
+
147
+ batch = multicall.batch()
148
+ base_price = batch.add(self.get_price(base_feed.value))
149
+ prices = [batch.add(self.get_price(info.price_feed)) for info, _ in held]
150
+ batch.run()
151
+
152
+ collateral = 0
153
+ liquidation_limit = 0
154
+ for (info, balance), price in zip(held, prices, strict=True):
155
+ value = balance * price.value // info.scale
156
+ collateral += value
157
+ liquidation_limit += (
158
+ value * info.liquidate_collateral_factor // COMET_FACTOR_SCALE
159
+ )
160
+ return CometAccountValue(
161
+ collateral=collateral,
162
+ liquidation_limit=liquidation_limit,
163
+ debt=debt.value * base_price.value // base_scale.value,
164
+ )
@@ -1,4 +1,5 @@
1
- """Lending health computation for Morpho Blue, Aave V3, Aave V4 and Euler V2 markets.
1
+ """Lending health computation for Morpho Blue, Aave V3, Aave V4, Euler V2 and
2
+ Compound V3 markets.
2
3
 
3
4
  Provides a unified view of LTV, liquidation thresholds, and health factors
4
5
  for lending positions held by Plasma Vaults.
@@ -7,8 +8,10 @@ for lending positions held by Plasma Vaults.
7
8
  from __future__ import annotations
8
9
 
9
10
  import logging
11
+ from collections.abc import Callable
10
12
  from concurrent.futures import ThreadPoolExecutor
11
13
  from dataclasses import dataclass, field
14
+ from functools import partial
12
15
 
13
16
  import requests
14
17
  from eth_abi import decode
@@ -30,6 +33,7 @@ from ipor_fusion.errors import EmptyCallResultError
30
33
  from ipor_fusion.market_ids import IporFusionMarkets
31
34
  from ipor_fusion.readers.aave_v3 import AaveV3Reader
32
35
  from ipor_fusion.readers.aave_v4 import AaveV4OracleReader, AaveV4SpokeReader
36
+ from ipor_fusion.readers.compound_v3 import COMET_PRICE_SCALE, CompoundV3Reader
33
37
  from ipor_fusion.readers.euler_v2 import (
34
38
  EULER_USD_UNIT_OF_ACCOUNT,
35
39
  EulerV2Substrate,
@@ -123,6 +127,18 @@ EULER_V2_MARKET_IDS = frozenset(
123
127
  }
124
128
  )
125
129
 
130
+ # Market IDs served by the Compound V3 fuses. One Comet per market, read from its
131
+ # balance fuse. Health is account-level; a Comet account owing nothing has no
132
+ # liquidation risk and no row. The WETH Comet prices in ETH, not USD.
133
+ COMPOUND_V3_MARKET_IDS = frozenset(
134
+ {
135
+ IporFusionMarkets.COMPOUND_V3_USDC,
136
+ IporFusionMarkets.COMPOUND_V3_USDT,
137
+ IporFusionMarkets.COMPOUND_V3_WETH,
138
+ IporFusionMarkets.COMPOUND_V3_INSTITUTIONAL_USDC,
139
+ }
140
+ )
141
+
126
142
  # Markets whose fuses can borrow but whose health has no reader yet. Their
127
143
  # positions are listed as unreadable rather than left out: an unknown
128
144
  # liquidation risk is not a healthy one. Dolomite counts only when a
@@ -146,6 +162,7 @@ class LendingMarketHealth:
146
162
  - Aave V3: None — Aave health is account-level, aggregated across reserves.
147
163
  - Aave V4: the Spoke address — account-level per Spoke, across its reserves.
148
164
  - Euler V2: the sub-account address — account-level, across its collaterals.
165
+ - Compound V3: None — account-level on the market's Comet, across its collaterals.
149
166
  """
150
167
 
151
168
  protocol: str
@@ -437,6 +454,57 @@ def _compute_aave_v4_market_health(
437
454
  )
438
455
 
439
456
 
457
+ def _compute_compound_v3_market_health(
458
+ ctx: Web3Context,
459
+ comet: ChecksumAddress,
460
+ vault_address: PlasmaVaultAddress,
461
+ ipor_market_id: MarketId,
462
+ market_name: str,
463
+ ) -> LendingMarketHealth | UnreadableLendingMarket | None:
464
+ """Compute LTV health for the vault's account on one Comet; None when it owes
465
+ nothing. The health factor is the liquidation limit over the debt, so it falls
466
+ to 1 where `Comet.isLiquidatable` turns true."""
467
+ try:
468
+ reader = CompoundV3Reader(comet)
469
+ if reader.borrow_balance_of(vault_address).call(ctx) == 0:
470
+ return None
471
+ account = reader.read_account_value(ctx, vault_address)
472
+ except _READ_ERRORS as exc:
473
+ return UnreadableLendingMarket(
474
+ protocol="compound_v3",
475
+ market_id=ipor_market_id,
476
+ market_name=market_name,
477
+ reason=_reason("account value", exc),
478
+ )
479
+ # Dust that prices to nothing: the Comet rounds it the same way and cannot
480
+ # liquidate it.
481
+ if account.debt == 0:
482
+ return None
483
+
484
+ has_collateral = account.collateral > 0
485
+ has_limit = account.liquidation_limit > 0
486
+ current_ltv = account.debt / account.collateral if has_collateral else None
487
+ max_ltv = account.liquidation_limit / account.collateral if has_collateral else 0.0
488
+ # With no limit the health factor is 0, a known liquidation risk; only the usage
489
+ # has no value.
490
+ ltv_usage = account.debt / account.liquidation_limit * 100 if has_limit else None
491
+ in_usd = ipor_market_id != IporFusionMarkets.COMPOUND_V3_WETH
492
+
493
+ return LendingMarketHealth(
494
+ protocol="compound_v3",
495
+ market_id=ipor_market_id,
496
+ market_name=market_name,
497
+ current_ltv=round(current_ltv, 6) if current_ltv is not None else None,
498
+ max_ltv=round(max_ltv, 6),
499
+ health_factor=round(account.liquidation_limit / account.debt, 4),
500
+ total_collateral_usd=(
501
+ round(account.collateral / COMET_PRICE_SCALE, 2) if in_usd else None
502
+ ),
503
+ total_debt_usd=round(account.debt / COMET_PRICE_SCALE, 2) if in_usd else None,
504
+ ltv_usage_percent=round(ltv_usage, 2) if ltv_usage is not None else None,
505
+ )
506
+
507
+
440
508
  @dataclass(frozen=True, slots=True)
441
509
  class _EulerV2Account:
442
510
  """An EVC sub-account of the vault and the Euler vaults it may borrow from."""
@@ -574,6 +642,7 @@ class _LendingMarkets:
574
642
  aave_v3: list[tuple[MarketId, str, ChecksumAddress]]
575
643
  aave_v4: list[tuple[MarketId, str, ChecksumAddress]]
576
644
  euler_v2: list[tuple[MarketId, str, _EulerV2Account]]
645
+ compound_v3: list[tuple[MarketId, str, ChecksumAddress]]
577
646
  unreadable: list[UnreadableLendingMarket]
578
647
 
579
648
 
@@ -582,12 +651,14 @@ def _collect_lending_markets(
582
651
  balance_fuse_market_ids: list[MarketId],
583
652
  market_substrates: dict[MarketId, list[bytes]],
584
653
  aave_pools: dict[MarketId, ChecksumAddress],
654
+ comets: dict[MarketId, ChecksumAddress],
585
655
  ) -> _LendingMarkets:
586
656
  """Pick the positions to read: Morpho substrates, Aave V3 markets with a Pool,
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."""
657
+ the Spokes of Aave V4 markets, the borrowing Euler V2 sub-accounts and Compound
658
+ V3 markets with a Comet. A market that may borrow but has no reader is listed
659
+ as unreadable."""
589
660
  markets = _LendingMarkets(
590
- morpho=[], aave_v3=[], aave_v4=[], euler_v2=[], unreadable=[]
661
+ morpho=[], aave_v3=[], aave_v4=[], euler_v2=[], compound_v3=[], unreadable=[]
591
662
  )
592
663
 
593
664
  for mid in balance_fuse_market_ids:
@@ -620,6 +691,18 @@ def _collect_lending_markets(
620
691
  (mid, name, account)
621
692
  for account in _euler_v2_borrow_accounts(substrates, vault)
622
693
  )
694
+ elif mid in COMPOUND_V3_MARKET_IDS:
695
+ if comet := comets.get(mid):
696
+ markets.compound_v3.append((mid, name, comet))
697
+ else:
698
+ markets.unreadable.append(
699
+ UnreadableLendingMarket(
700
+ protocol="compound_v3",
701
+ market_id=mid,
702
+ market_name=name,
703
+ reason="the balance fuse names no Comet",
704
+ )
705
+ )
623
706
  elif _can_borrow(mid, substrates):
624
707
  markets.unreadable.append(
625
708
  UnreadableLendingMarket(
@@ -633,6 +716,45 @@ def _collect_lending_markets(
633
716
  return markets
634
717
 
635
718
 
719
+ def _health_reads(
720
+ ctx: Web3Context, vault: PlasmaVaultAddress, markets: _LendingMarkets
721
+ ) -> list[Callable[[], LendingMarketHealth | UnreadableLendingMarket | None]]:
722
+ """One read per position; Aave V3 health is account-level per Pool, so each
723
+ market reads its own."""
724
+ reads: list[Callable[[], LendingMarketHealth | UnreadableLendingMarket | None]] = []
725
+ if markets.morpho:
726
+ morpho_reader = MorphoReader(morpho_blue_address(ctx.chain_id))
727
+ reads += [
728
+ partial(
729
+ _compute_morpho_market_health,
730
+ ctx,
731
+ morpho_reader,
732
+ morpho_mid,
733
+ vault,
734
+ mid,
735
+ name,
736
+ )
737
+ for mid, name, morpho_mid in markets.morpho
738
+ ]
739
+ reads += [
740
+ partial(_compute_aave_market_health, ctx, AaveV3Reader(pool), vault, mid, name)
741
+ for mid, name, pool in markets.aave_v3
742
+ ]
743
+ reads += [
744
+ partial(_compute_aave_v4_market_health, ctx, spoke, vault, mid, name)
745
+ for mid, name, spoke in markets.aave_v4
746
+ ]
747
+ reads += [
748
+ partial(_compute_euler_v2_account_health, ctx, account, mid, name)
749
+ for mid, name, account in markets.euler_v2
750
+ ]
751
+ reads += [
752
+ partial(_compute_compound_v3_market_health, ctx, comet, vault, mid, name)
753
+ for mid, name, comet in markets.compound_v3
754
+ ]
755
+ return reads
756
+
757
+
636
758
  def fetch_vault_lending_health(
637
759
  ctx: Web3Context,
638
760
  vault: PlasmaVaultAddress,
@@ -640,6 +762,7 @@ def fetch_vault_lending_health(
640
762
  balance_fuse_market_ids: list[MarketId],
641
763
  market_substrates: dict[MarketId, list[bytes]],
642
764
  aave_pools: dict[MarketId, ChecksumAddress] | None = None,
765
+ comets: dict[MarketId, ChecksumAddress] | None = None,
643
766
  ) -> VaultLendingHealth:
644
767
  """Fetch lending health for all lending markets in a vault.
645
768
 
@@ -655,68 +778,23 @@ def fetch_vault_lending_health(
655
778
  entry is listed in ``unreadable``. Defaults to the chain's Core pool for AAVE_V3 only:
656
779
  the Pools of Aave V3 Prime and SparkLend are known only to their
657
780
  fuses.
781
+ comets: Map of Compound V3 market_id -> the Comet its balance fuse reads
782
+ (`CompoundV3FuseReader.comet`). A Compound V3 market without an entry
783
+ is listed in ``unreadable``.
658
784
  """
659
785
  if aave_pools is None:
660
786
  core_pool = AAVE_V3_POOL.get(ctx.chain_id)
661
787
  aave_pools = {IporFusionMarkets.AAVE_V3: core_pool} if core_pool else {}
662
788
 
663
789
  markets = _collect_lending_markets(
664
- vault, balance_fuse_market_ids, market_substrates, aave_pools
790
+ vault, balance_fuse_market_ids, market_substrates, aave_pools, comets or {}
665
791
  )
666
792
 
667
793
  results: list[LendingMarketHealth] = []
668
794
  unreadable = list(markets.unreadable)
669
795
 
670
796
  with ThreadPoolExecutor() as pool:
671
- futures = []
672
-
673
- if markets.morpho:
674
- morpho_reader = MorphoReader(morpho_blue_address(ctx.chain_id))
675
- for ipor_mid, name, morpho_mid in markets.morpho:
676
- futures.append(
677
- pool.submit(
678
- _compute_morpho_market_health,
679
- ctx,
680
- morpho_reader,
681
- morpho_mid,
682
- vault,
683
- ipor_mid,
684
- name,
685
- )
686
- )
687
-
688
- # Aave V3 health is account-level per Pool, so each market reads its own.
689
- for ipor_mid, name, aave_pool in markets.aave_v3:
690
- futures.append(
691
- pool.submit(
692
- _compute_aave_market_health,
693
- ctx,
694
- AaveV3Reader(aave_pool),
695
- vault,
696
- ipor_mid,
697
- name,
698
- )
699
- )
700
-
701
- for ipor_mid, name, spoke in markets.aave_v4:
702
- futures.append(
703
- pool.submit(
704
- _compute_aave_v4_market_health,
705
- ctx,
706
- spoke,
707
- vault,
708
- ipor_mid,
709
- name,
710
- )
711
- )
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
-
797
+ futures = [pool.submit(read) for read in _health_reads(ctx, vault, markets)]
720
798
  for fut in futures:
721
799
  result = fut.result()
722
800
  if result is None: