ipor-fusion 4.1.0.dev35__tar.gz → 4.1.0.dev40__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 (361) hide show
  1. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/PKG-INFO +1 -1
  2. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/hypercore_vault_official_deployment_hyperevm.py +10 -6
  3. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/pyproject.toml +1 -1
  4. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/hypercore.py +5 -2
  5. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/hypercore/__init__.py +8 -0
  6. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/hypercore.py +16 -0
  7. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/oracle_mapping.py +26 -9
  8. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/vault_data.py +21 -3
  9. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_commands.py +36 -9
  10. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_readers.py +12 -0
  11. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_oracle_mapping.py +20 -0
  12. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_public_api.py +4 -0
  13. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_vault_data_block.py +11 -1
  14. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.claude-plugin/marketplace.json +0 -0
  15. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.claude-plugin/plugin.json +0 -0
  16. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.env.example +0 -0
  17. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.gemini/settings.json +0 -0
  18. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.git-blame-ignore-revs +0 -0
  19. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/cd.yml +0 -0
  20. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/ci.yml +0 -0
  21. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/commit-lint.yml +0 -0
  22. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/notify-slack.yml +0 -0
  23. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/python-build.yml +0 -0
  24. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/python-publish.yml +0 -0
  25. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/release.yml +0 -0
  26. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.github/workflows/report-slack.yml +0 -0
  27. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.gitignore +0 -0
  28. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.mcp.json +0 -0
  29. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.pre-commit-config.yaml +0 -0
  30. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/.python-version +0 -0
  31. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/AGENTS.md +0 -0
  32. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/CHANGELOG.md +0 -0
  33. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/CLAUDE.md +0 -0
  34. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/CODEOWNERS +0 -0
  35. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/LICENSE +0 -0
  36. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/MIGRATION_4.md +0 -0
  37. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/README.md +0 -0
  38. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/build.sh +0 -0
  39. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/CONTRIBUTING.md +0 -0
  40. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/README.md +0 -0
  41. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  42. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  43. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  44. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/external_state_margin_leg_base.py +0 -0
  45. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/examples/simple_aave_v3_supply_base.py +0 -0
  46. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/pytest.ini +0 -0
  47. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/scripts/commit-check.sh +0 -0
  48. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/scripts/fusion_error_signatures.py +0 -0
  49. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/skills/ipor-deploy-vault/SKILL.md +0 -0
  50. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/__init__.py +0 -0
  51. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/about.py +0 -0
  52. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cache.py +0 -0
  53. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/chains.py +0 -0
  54. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/__init__.py +0 -0
  55. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/address_cmd.py +0 -0
  56. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  57. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/config_cmd.py +0 -0
  58. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/config_store.py +0 -0
  59. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/main.py +0 -0
  60. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/market_cmd.py +0 -0
  61. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/options.py +0 -0
  62. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  63. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
  64. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  65. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/vault_health.py +0 -0
  66. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  67. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  68. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  69. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/config/__init__.py +0 -0
  70. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/config/roles.py +0 -0
  71. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/__init__.py +0 -0
  72. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/access.py +0 -0
  73. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/address.py +0 -0
  74. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/context.py +0 -0
  75. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/context_manager.py +0 -0
  76. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/contract.py +0 -0
  77. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/erc20.py +0 -0
  78. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/external_state_executor.py +0 -0
  79. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/fee_manager.py +0 -0
  80. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/fuse.py +0 -0
  81. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/fuse_action.py +0 -0
  82. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/fuse_decoding.py +0 -0
  83. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/fusion_factory.py +0 -0
  84. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/logs.py +0 -0
  85. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/multicall.py +0 -0
  86. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/oracle.py +0 -0
  87. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/plasma_vault.py +0 -0
  88. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/rewards_manager.py +0 -0
  89. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/simulation.py +0 -0
  90. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/simulation_result.py +0 -0
  91. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  92. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/__init__.py +0 -0
  93. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  94. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  95. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  96. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  97. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  98. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  99. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  100. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  101. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  102. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/contracts.py +0 -0
  103. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/discovery.py +0 -0
  104. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/errors.py +0 -0
  105. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  106. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  107. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  108. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  109. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/lane.py +0 -0
  110. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/logs.py +0 -0
  111. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/messages.py +0 -0
  112. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/simulation.py +0 -0
  113. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  114. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  115. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  116. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  117. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  118. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/crosschain/transport.py +0 -0
  119. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/dependency_graph.py +0 -0
  120. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/entry_cli.py +0 -0
  121. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/entry_mcp.py +0 -0
  122. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/errors.py +0 -0
  123. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/explorer.py +0 -0
  124. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/field_docs.py +0 -0
  125. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/__init__.py +0 -0
  126. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  127. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/async_action.py +0 -0
  128. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/base.py +0 -0
  129. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  130. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/erc4626.py +0 -0
  131. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  132. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/events.py +0 -0
  133. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/external_state.py +0 -0
  134. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  135. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  136. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  137. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  138. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
  139. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/merkl.py +0 -0
  140. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/morpho.py +0 -0
  141. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  142. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  143. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fuses/universal.py +0 -0
  144. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/fusion_errors.py +0 -0
  145. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/guide/__init__.py +0 -0
  146. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/guide/architecture.md +0 -0
  147. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/guide/glossary.md +0 -0
  148. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/guide/invariants.md +0 -0
  149. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/guide/quickstart.md +0 -0
  150. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/market_ids.py +0 -0
  151. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/market_info.py +0 -0
  152. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/mcp/__init__.py +0 -0
  153. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/mcp/guide.py +0 -0
  154. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/mcp/models.py +0 -0
  155. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/mcp/server.py +0 -0
  156. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/morpho_api.py +0 -0
  157. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/preflight.py +0 -0
  158. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/py.typed +0 -0
  159. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/__init__.py +0 -0
  160. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/aave_v3.py +0 -0
  161. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/aave_v4.py +0 -0
  162. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/compound_v3.py +0 -0
  163. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/euler_v2.py +0 -0
  164. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  165. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/lending_health.py +0 -0
  166. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/morpho.py +0 -0
  167. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  168. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  169. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/position_manager.py +0 -0
  170. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  171. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  172. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/readers/unpriceable.py +0 -0
  173. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/registry.py +0 -0
  174. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/substrates.py +0 -0
  175. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/types.py +0 -0
  176. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/validation.py +0 -0
  177. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/vault_erc20.py +0 -0
  178. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/vault_info.py +0 -0
  179. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/vault_managers.py +0 -0
  180. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/vault_positions.py +0 -0
  181. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/src/ipor_fusion/vault_reads.py +0 -0
  182. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/UniswapV3UniversalRouter.py +0 -0
  183. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_addresses.py +0 -0
  184. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_crosschain.py +0 -0
  185. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_crosschain_lifecycle.py +0 -0
  186. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_crosschain_recovery.py +0 -0
  187. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_euler_v2.py +0 -0
  188. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_event_args.py +0 -0
  189. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_fake_rpc.py +0 -0
  190. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_foundry.py +0 -0
  191. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_golden.py +0 -0
  192. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_guide.py +0 -0
  193. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_hypercore_model.py +0 -0
  194. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_hypercore_shadow.py +0 -0
  195. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_live.py +0 -0
  196. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_multicall.py +0 -0
  197. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_oracle_mapping_fake.py +0 -0
  198. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_simulate.py +0 -0
  199. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/_validation.py +0 -0
  200. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/addresses.py +0 -0
  201. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/conftest.py +0 -0
  202. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/constants.py +0 -0
  203. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  204. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/hypercore_market55.json +0 -0
  205. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/hypercore_official.json +0 -0
  206. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  207. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  208. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  209. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  210. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  211. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  212. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  213. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  214. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  215. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  216. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  217. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  218. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  219. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  220. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  221. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  222. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/simulate_v1_request.json +0 -0
  223. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/clean.json +0 -0
  224. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  225. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  226. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  227. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  228. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  229. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  230. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  231. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  232. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/full.json +0 -0
  233. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  234. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/full.txt +0 -0
  235. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/rich.json +0 -0
  236. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  237. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  238. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_about.py +0 -0
  239. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_access_manager.py +0 -0
  240. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_addresses_helper.py +0 -0
  241. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cache.py +0 -0
  242. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_chains.py +0 -0
  243. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_address.py +0 -0
  244. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_changelog.py +0 -0
  245. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_config_store.py +0 -0
  246. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_helpers.py +0 -0
  247. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_hypercore.py +0 -0
  248. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_lending_health.py +0 -0
  249. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_market_golden.py +0 -0
  250. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_market_morpho_blue.py +0 -0
  251. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_mcp_parity.py +0 -0
  252. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_oracle_mapping.py +0 -0
  253. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_oracle_mapping_golden.py +0 -0
  254. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_pending_requests_scan.py +0 -0
  255. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_preflight.py +0 -0
  256. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_role_accounts.py +0 -0
  257. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_vault_info_golden.py +0 -0
  258. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_vault_predates_getters.py +0 -0
  259. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_cli_vault_validate.py +0 -0
  260. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_contract_call.py +0 -0
  261. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_contract_encoder.py +0 -0
  262. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_ccip_events.py +0 -0
  263. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_ccip_indexer.py +0 -0
  264. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_ccip_quote.py +0 -0
  265. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_chainlink.py +0 -0
  266. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_contracts.py +0 -0
  267. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_encoding.py +0 -0
  268. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_errors.py +0 -0
  269. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_lane.py +0 -0
  270. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_layering.py +0 -0
  271. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_crosschain_transport.py +0 -0
  272. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_docs_typing.py +0 -0
  273. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_erc20.py +0 -0
  274. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_error_hierarchy.py +0 -0
  275. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_examples_crosschain.py +0 -0
  276. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  277. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_examples_hypercore_hyperevm.py +0 -0
  278. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_examples_vaults.py +0 -0
  279. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_explorer.py +0 -0
  280. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_external_state_events.py +0 -0
  281. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_external_state_executor.py +0 -0
  282. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_fee_manager.py +0 -0
  283. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_fuse_decoding.py +0 -0
  284. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_fuse_encoding.py +0 -0
  285. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_fusion_errors.py +0 -0
  286. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_fusion_instance.py +0 -0
  287. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_governance_wrappers.py +0 -0
  288. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_guide.py +0 -0
  289. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_deployment.py +0 -0
  290. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_events.py +0 -0
  291. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_flow.py +0 -0
  292. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_live.py +0 -0
  293. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_outcome.py +0 -0
  294. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_pre_hooks.py +0 -0
  295. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_hypercore_simulation.py +0 -0
  296. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_logs.py +0 -0
  297. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_mcp_address.py +0 -0
  298. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_mcp_hypercore.py +0 -0
  299. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_mcp_models.py +0 -0
  300. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_mcp_preflight.py +0 -0
  301. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_mcp_server.py +0 -0
  302. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_mcp_vault_validate.py +0 -0
  303. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_multicall.py +0 -0
  304. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_oracle.py +0 -0
  305. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_plasma_vault.py +0 -0
  306. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_preflight.py +0 -0
  307. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_public_type_reachability.py +0 -0
  308. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_reader_encoding.py +0 -0
  309. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_registry.py +0 -0
  310. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_revert_decoding.py +0 -0
  311. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_rewards_manager.py +0 -0
  312. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_roles.py +0 -0
  313. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_sdk_layering.py +0 -0
  314. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  315. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  316. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  317. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  318. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  319. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  320. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  321. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  322. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  323. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_examples_arbitrum.py +0 -0
  324. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_examples_base.py +0 -0
  325. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_examples_crosschain.py +0 -0
  326. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_examples_hyperevm.py +0 -0
  327. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_external_state_executor_base.py +0 -0
  328. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  329. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  330. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  331. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_morpho_blue.py +0 -0
  332. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_pilot_v3.py +0 -0
  333. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  334. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  335. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  336. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  337. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_preflight_base.py +0 -0
  338. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_readers_integration.py +0 -0
  339. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_release_funds.py +0 -0
  340. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_swap_on_base.py +0 -0
  341. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_timelock_base.py +0 -0
  342. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  343. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulation_errors.py +0 -0
  344. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_simulation_observation.py +0 -0
  345. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_solidity_compiler.py +0 -0
  346. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_solidity_mirrors.py +0 -0
  347. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_substrates.py +0 -0
  348. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_types_and_events.py +0 -0
  349. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_unpriceable_tokens.py +0 -0
  350. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_validation.py +0 -0
  351. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_vault_dep_graph.py +0 -0
  352. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_vault_info_fetched_golden.py +0 -0
  353. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_vault_info_onchain_base.py +0 -0
  354. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  355. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  356. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_vault_simulator_blocks.py +0 -0
  357. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_web3_context.py +0 -0
  358. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/test_withdraw_manager.py +0 -0
  359. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/typed_client/consumer.py +0 -0
  360. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/tests/typed_client/rejected.py +0 -0
  361. {ipor_fusion-4.1.0.dev35 → ipor_fusion-4.1.0.dev40}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.1.0.dev35
3
+ Version: 4.1.0.dev40
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
@@ -95,12 +95,14 @@ from ipor_fusion import (
95
95
  Web3Context,
96
96
  decode_substrate,
97
97
  erc20_balance_slot,
98
+ format_amount,
98
99
  is_simulate_v1_supported,
99
100
  parse_checksum_address,
100
101
  parse_fuse_address,
101
102
  )
102
103
  from ipor_fusion.hypercore import (
103
104
  HYPERCORE_PENDING_READERS,
105
+ ORDER_DECIMALS,
104
106
  SPOT_DEX,
105
107
  USDC_SYSTEM_ADDRESS,
106
108
  USDC_TOKEN_INDEX,
@@ -119,6 +121,7 @@ from ipor_fusion.hypercore import (
119
121
  TimeInForce,
120
122
  decode_hypercore_event,
121
123
  hip3_action_asset,
124
+ perp_px_decimals,
122
125
  precompile_index_of,
123
126
  )
124
127
  from ipor_fusion.market_ids import IporFusionMarkets
@@ -224,9 +227,10 @@ BRIDGE_AMOUNT = Amount(15_000_000) # 15 USDC EVM -> Core
224
227
  # whose default is that same 1 USDC in Core wei.
225
228
  ACTIVATION_FEE_WEI = 100_000_000
226
229
  TO_DEX_WEI = 1_000_000_000 # 10 USDC spot -> xyz dex
227
- ORDER_SIZE = 5_000_000 # 0.05 xyz:NVDA (a multiple of the 10**(8 - 3) lot)
228
- LIMIT_BUY_PX = 230 * 10**8 # pays at most 230 USD
229
- LIMIT_SELL_PX = 220 * 10**8 # sells at no less than 220 USD
230
+ # 0.05 xyz:NVDA, a multiple of its 10**(ORDER_DECIMALS - 3) lot
231
+ ORDER_SIZE = 5 * 10 ** (ORDER_DECIMALS - 2)
232
+ LIMIT_BUY_PX = 230 * 10**ORDER_DECIMALS # pays at most 230 USD
233
+ LIMIT_SELL_PX = 220 * 10**ORDER_DECIMALS # sells at no less than 220 USD
230
234
  # Core -> EVM: a spot -> spot sendAsset to the USDC system address. The amount
231
235
  # must be a multiple of 100 Core wei (representable in 6 EVM decimals); Core
232
236
  # takes its transfer fee from what stays on spot.
@@ -374,7 +378,7 @@ def core_ledger() -> tuple[CoreAnswers, ...]:
374
378
  bridged = BRIDGE_AMOUNT * CORE_WEI_PER_EVM_UNIT - ACTIVATION_FEE_WEI
375
379
  mark = 225_000
376
380
  on_dex = TO_DEX_WEI // 100 # Core wei -> USD6
377
- filled = ORDER_SIZE // 10 ** (8 - XYZ_NVDA_SZ_DECIMALS)
381
+ filled = ORDER_SIZE // 10 ** (ORDER_DECIMALS - XYZ_NVDA_SZ_DECIMALS)
378
382
  return (
379
383
  CoreAnswers(False, 0, 0, 0, mark), # before the bridge: no Core account
380
384
  CoreAnswers(True, bridged, 0, 0, mark), # the bridge landed
@@ -481,7 +485,7 @@ def check_core_metadata(
481
485
  f"{XYZ_NVDA_COIN} szDecimals is {nvda_info.sz_decimals}",
482
486
  )
483
487
  _check(
484
- ORDER_SIZE % 10 ** (8 - nvda_info.sz_decimals) == 0,
488
+ ORDER_SIZE % 10 ** (ORDER_DECIMALS - nvda_info.sz_decimals) == 0,
485
489
  "the order size is not a whole number of lots",
486
490
  )
487
491
 
@@ -512,7 +516,7 @@ def read_core_metadata_at_head(web3: Web3) -> None:
512
516
  wallet_token,
513
517
  nvda_info.coin,
514
518
  nvda_info.sz_decimals,
515
- mark / 10 ** (6 - nvda_info.sz_decimals),
519
+ format_amount(Amount(mark), perp_px_decimals(nvda_info.sz_decimals)),
516
520
  )
517
521
 
518
522
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.1.0.dev35"
3
+ version = "4.1.0.dev40"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -17,7 +17,7 @@ native perp dex is 0. A HIP-3 builder-deployed market is addressed by its
17
17
  action asset ``100_000 + dex * 10_000 + index_in_dex`` in orders, cancels and
18
18
  the PerpMarket substrate; the read precompiles take ``dex * 10_000 +
19
19
  index_in_dex`` instead (:func:`precompile_index_of`). Amounts are Core integers:
20
- ``limit_px`` and ``sz`` carry 8 implied decimals, ``usd6`` 6, and
20
+ ``limit_px`` and ``sz`` carry :data:`ORDER_DECIMALS` implied decimals, ``usd6`` 6, and
21
21
  ``amount_wei`` is Core wei (8 decimals for USDC).
22
22
 
23
23
  ABI (one struct argument per method)::
@@ -64,6 +64,8 @@ from ipor_fusion.types import (
64
64
  SPOT_DEX = 0xFFFFFFFF
65
65
  #: ``HyperCoreLib.NATIVE_PERP_DEX``: Hyperliquid's own perp dex.
66
66
  NATIVE_PERP_DEX = 0
67
+ #: Decimals of ``limit_px`` and ``sz`` in :meth:`HyperCoreOrderFuse.enter`.
68
+ ORDER_DECIMALS = 8
67
69
  #: Core token index of USDC.
68
70
  USDC_TOKEN_INDEX = HyperCoreTokenIndex(0)
69
71
  #: Core -> EVM route for token 0: a spot -> spot ``sendAsset`` to this address
@@ -297,7 +299,8 @@ class HyperCoreOrderFuse(Fuse):
297
299
  cloid: HyperCoreClientOrderId,
298
300
  reduce_only: bool = False,
299
301
  ) -> FuseAction:
300
- """Place ``sz`` (8 decimals) of ``asset`` at ``limit_px`` (8 decimals).
302
+ """Place ``sz`` of ``asset`` at ``limit_px``, both with
303
+ :data:`ORDER_DECIMALS` decimals.
301
304
  The fuse enforces the market's notional cap and lot size and, for
302
305
  HIP-3 markets, that this is the vault's only PerpMarket grant."""
303
306
  _validate_uint(asset, 32, "asset")
@@ -8,6 +8,7 @@ with no backward compatibility, which is why they live here rather than in
8
8
  """
9
9
 
10
10
  from ipor_fusion.fuses.hypercore import (
11
+ ORDER_DECIMALS,
11
12
  RESERVED_HYPERCORE_CONFIG_KEYS,
12
13
  SPOT_DEX,
13
14
  USDC_SYSTEM_ADDRESS,
@@ -68,6 +69,8 @@ from ipor_fusion.fuses.hypercore_pre_hooks import (
68
69
  from ipor_fusion.readers.hypercore import (
69
70
  HYPERCORE_MARKET,
70
71
  HYPERCORE_PENDING_READERS,
72
+ PERP_MAX_DECIMALS,
73
+ USD6_DECIMALS,
71
74
  HyperCoreAccountMarginSummary,
72
75
  HyperCoreActionClass,
73
76
  HyperCoreEffectKind,
@@ -84,6 +87,7 @@ from ipor_fusion.readers.hypercore import (
84
87
  HyperCoreTokenInfo,
85
88
  HyperCoreVaultState,
86
89
  convert_to_wad_int,
90
+ perp_px_decimals,
87
91
  read_hypercore_nav,
88
92
  read_hypercore_vault_state,
89
93
  )
@@ -110,9 +114,12 @@ from ipor_fusion.types import (
110
114
 
111
115
  __all__ = [
112
116
  "MarginTableId",
117
+ "ORDER_DECIMALS",
118
+ "PERP_MAX_DECIMALS",
113
119
  "PayloadHash",
114
120
  "SPOT_DEX",
115
121
  "TransferDirection",
122
+ "USD6_DECIMALS",
116
123
  "USDC_SYSTEM_ADDRESS",
117
124
  "USDC_TOKEN_INDEX",
118
125
  "HYPERCORE_CAPITAL_FLOW_SELECTORS",
@@ -190,6 +197,7 @@ __all__ = [
190
197
  "is_hip3_asset",
191
198
  "is_native_perp_asset",
192
199
  "perp_dex_of",
200
+ "perp_px_decimals",
193
201
  "precompile_index_of",
194
202
  "read_hypercore_evm_exit_ceiling",
195
203
  "read_hypercore_nav",
@@ -81,6 +81,10 @@ HYPERCORE_PENDING_READERS: dict[ChainId, ChecksumAddress] = {
81
81
  WAD_DECIMALS = 18
82
82
  #: ``accountMarginSummary`` reports USD with 6 decimals.
83
83
  USD6_DECIMALS = 6
84
+ #: The most decimals a perp price can carry: Hyperliquid's ``MAX_DECIMALS``,
85
+ #: which is 6 for perps (8 for spot). A perp's price has
86
+ #: ``PERP_MAX_DECIMALS - szDecimals`` decimals (:func:`perp_px_decimals`).
87
+ PERP_MAX_DECIMALS = 6
84
88
  _INT256_MAX = (1 << 255) - 1
85
89
 
86
90
 
@@ -111,6 +115,16 @@ def _validate_uint(value: int, bits: int, name: str) -> None:
111
115
  raise ValueError(f"{name} must fit in uint{bits}, got {value}")
112
116
 
113
117
 
118
+ def perp_px_decimals(sz_decimals: Decimals) -> Decimals:
119
+ """Decimals of a perp's :meth:`HyperCoreReader.mark_px` and
120
+ :meth:`HyperCoreReader.oracle_px`, from its ``szDecimals``."""
121
+ if not 0 <= sz_decimals <= PERP_MAX_DECIMALS:
122
+ raise ValueError(
123
+ f"sz_decimals must be in 0..{PERP_MAX_DECIMALS}, got {sz_decimals}"
124
+ )
125
+ return Decimals(PERP_MAX_DECIMALS - sz_decimals)
126
+
127
+
114
128
  @dataclass(frozen=True, slots=True)
115
129
  class HyperCoreSpotBalance:
116
130
  """``spotBalance`` precompile result, all in Core wei of the token."""
@@ -306,10 +320,12 @@ class HyperCoreReader:
306
320
  return self._read(WITHDRAWABLE_PRECOMPILE, ["address"], [user], ["uint64"])
307
321
 
308
322
  def oracle_px(self, read_index: int) -> Call[int]:
323
+ """A perp's oracle price, with :func:`perp_px_decimals` decimals."""
309
324
  _validate_uint(read_index, 32, "read_index")
310
325
  return self._read(ORACLE_PX_PRECOMPILE, ["uint32"], [read_index], ["uint64"])
311
326
 
312
327
  def mark_px(self, read_index: int) -> Call[int]:
328
+ """A perp's mark price, with :func:`perp_px_decimals` decimals."""
313
329
  _validate_uint(read_index, 32, "read_index")
314
330
  return self._read(MARK_PX_PRECOMPILE, ["uint32"], [read_index], ["uint64"])
315
331
 
@@ -236,15 +236,32 @@ def _resolve_middleware_fallback(
236
236
  node.source_type = TYPE_MIDDLEWARE_FALLBACK
237
237
  node.status = "resolved"
238
238
  if underlying_middleware is not None:
239
- # Same asset, different oracle — must not be in `visited` yet; runaway
240
- # manager→manager chains are bounded by max_depth instead.
241
- dep = _resolve(
242
- reader.delegate(underlying_middleware),
243
- asset,
244
- depth + 1,
245
- visited,
246
- max_depth,
247
- )
239
+ # Same asset, different oracle: the asset key is not in `visited` yet,
240
+ # so the hop is keyed by (oracle, asset) — a manager chain that loops
241
+ # back ends as a cycle instead of running to max_depth.
242
+ hop = f"{underlying_middleware.lower()}/{asset.lower()}"
243
+ if hop in visited:
244
+ return _partial(
245
+ asset,
246
+ symbol,
247
+ decimals,
248
+ source,
249
+ TYPE_UNKNOWN,
250
+ "cycle_detected",
251
+ [label],
252
+ price,
253
+ )
254
+ visited.add(hop)
255
+ try:
256
+ dep = _resolve(
257
+ reader.delegate(underlying_middleware),
258
+ asset,
259
+ depth + 1,
260
+ visited,
261
+ max_depth,
262
+ )
263
+ finally:
264
+ visited.discard(hop)
248
265
  node.dependencies = [dep]
249
266
  # dep re-resolves the same asset, so drop its leading label from the path.
250
267
  node.path = [label, "middleware fallback", *dep.path[1:]]
@@ -312,7 +312,21 @@ def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> Decimals |
312
312
  return decimals
313
313
 
314
314
 
315
- def _block_timestamp(ctx: Web3Context, block: int) -> Timestamp:
315
+ def _latest_head(ctx: Web3Context) -> tuple[int, Timestamp]:
316
+ """The latest block's number and timestamp from one read.
317
+
318
+ Reading the head number first and the block by number second can reach a
319
+ node behind a load balancer that has not seen that block yet.
320
+ """
321
+ head = ctx.web3.eth.get_block("latest")
322
+ return head["number"], head["timestamp"]
323
+
324
+
325
+ def _block_timestamp(
326
+ ctx: Web3Context, block: int, known: Timestamp | None
327
+ ) -> Timestamp:
328
+ if known is not None:
329
+ return known
316
330
  return ctx.web3.eth.get_block(block)["timestamp"]
317
331
 
318
332
 
@@ -634,14 +648,18 @@ def fetch_vault_data(
634
648
  ensure_supported_chain(ctx.chain_id)
635
649
  if block is None:
636
650
  block = _pinned_default_block(ctx)
637
- resolved_block = ctx.web3.eth.block_number if block is None else block
638
651
  is_latest = block is None
652
+ head_timestamp: Timestamp | None = None
653
+ if block is None:
654
+ resolved_block, head_timestamp = _latest_head(ctx)
655
+ else:
656
+ resolved_block = block
639
657
  ctx = ctx.at_block(resolved_block)
640
658
  plasma_vault = PlasmaVault(vault)
641
659
  with ThreadPoolExecutor() as pool:
642
660
  # Independent pipelines, each a short chain of batched reads; none of
643
661
  # them waits on a pool future, so they cannot starve the pool.
644
- f_timestamp = pool.submit(_block_timestamp, ctx, resolved_block)
662
+ f_timestamp = pool.submit(_block_timestamp, ctx, resolved_block, head_timestamp)
645
663
  f_vault = pool.submit(_fetch_vault_reads, ctx, plasma_vault)
646
664
  f_fees = pool.submit(_fetch_fee_data, ctx, plasma_vault)
647
665
  f_withdraw = pool.submit(_fetch_withdraw_manager, ctx, plasma_vault)
@@ -443,7 +443,10 @@ class TestVaultInfo:
443
443
  mock_ctx.cache = NullCache()
444
444
  mock_ctx.web3.eth.block_number = 12345678
445
445
  mock_ctx.default_block = "latest"
446
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
446
+ mock_ctx.web3.eth.get_block.return_value = {
447
+ "number": 12345678,
448
+ "timestamp": 1700000000,
449
+ }
447
450
  mock_ctx.chain_id = 1
448
451
  mock_ctx.at_block.return_value = mock_ctx
449
452
  mock_ctx_cls.from_url.return_value = mock_ctx
@@ -573,7 +576,10 @@ class TestVaultInfo:
573
576
  mock_ctx.cache = NullCache()
574
577
  mock_ctx.web3.eth.block_number = 12345678
575
578
  mock_ctx.default_block = "latest"
576
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
579
+ mock_ctx.web3.eth.get_block.return_value = {
580
+ "number": 12345678,
581
+ "timestamp": 1700000000,
582
+ }
577
583
  mock_ctx.chain_id = 1
578
584
  mock_ctx.at_block.return_value = mock_ctx
579
585
  mock_ctx_cls.from_url.return_value = mock_ctx
@@ -654,7 +660,10 @@ class TestVaultInfo:
654
660
  mock_ctx.cache = NullCache()
655
661
  mock_ctx.web3.eth.block_number = 100
656
662
  mock_ctx.default_block = "latest"
657
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
663
+ mock_ctx.web3.eth.get_block.return_value = {
664
+ "number": 100,
665
+ "timestamp": 1700000000,
666
+ }
658
667
  mock_ctx.chain_id = 1
659
668
  mock_ctx.at_block.return_value = mock_ctx
660
669
  mock_ctx_cls.from_url.return_value = mock_ctx
@@ -723,7 +732,10 @@ class TestVaultInfo:
723
732
  mock_ctx.cache = NullCache()
724
733
  mock_ctx.web3.eth.block_number = 100
725
734
  mock_ctx.default_block = "latest"
726
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
735
+ mock_ctx.web3.eth.get_block.return_value = {
736
+ "number": 100,
737
+ "timestamp": 1700000000,
738
+ }
727
739
  mock_ctx.chain_id = 1
728
740
  mock_ctx.at_block.return_value = mock_ctx
729
741
  mock_ctx_cls.from_url.return_value = mock_ctx
@@ -804,7 +816,10 @@ class TestVaultInfo:
804
816
  mock_ctx.cache = NullCache()
805
817
  mock_ctx.web3.eth.block_number = 100
806
818
  mock_ctx.default_block = "latest"
807
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
819
+ mock_ctx.web3.eth.get_block.return_value = {
820
+ "number": 100,
821
+ "timestamp": 1700000000,
822
+ }
808
823
  mock_ctx.web3.eth.get_transaction.return_value = {"blockNumber": 18500000}
809
824
  mock_ctx.chain_id = 1
810
825
  mock_ctx.at_block.return_value = mock_ctx
@@ -875,7 +890,10 @@ class TestVaultInfo:
875
890
  mock_ctx.cache = NullCache()
876
891
  mock_ctx.web3.eth.block_number = 100
877
892
  mock_ctx.default_block = "latest"
878
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
893
+ mock_ctx.web3.eth.get_block.return_value = {
894
+ "number": 100,
895
+ "timestamp": 1700000000,
896
+ }
879
897
  mock_ctx.chain_id = 1
880
898
  mock_ctx.at_block.return_value = mock_ctx
881
899
  mock_ctx_cls.from_url.return_value = mock_ctx
@@ -986,7 +1004,10 @@ class TestVaultInfoJson:
986
1004
  mock_ctx.chain_id = 1
987
1005
  mock_ctx.web3.eth.block_number = 12345678
988
1006
  mock_ctx.default_block = "latest"
989
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
1007
+ mock_ctx.web3.eth.get_block.return_value = {
1008
+ "number": 12345678,
1009
+ "timestamp": 1700000000,
1010
+ }
990
1011
  mock_ctx.chain_id = 1
991
1012
  mock_ctx.at_block.return_value = mock_ctx
992
1013
  mock_ctx_cls.from_url.return_value = mock_ctx
@@ -1165,7 +1186,10 @@ class TestVaultInfoJson:
1165
1186
  mock_ctx.cache = NullCache()
1166
1187
  mock_ctx.web3.eth.block_number = 100
1167
1188
  mock_ctx.default_block = "latest"
1168
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
1189
+ mock_ctx.web3.eth.get_block.return_value = {
1190
+ "number": 100,
1191
+ "timestamp": 1700000000,
1192
+ }
1169
1193
  mock_ctx.web3.eth.get_transaction.return_value = {"blockNumber": 18500000}
1170
1194
  mock_ctx.chain_id = 1
1171
1195
  mock_ctx.at_block.return_value = mock_ctx
@@ -1246,7 +1270,10 @@ class TestVaultInfoJson:
1246
1270
  mock_ctx.cache = NullCache()
1247
1271
  mock_ctx.web3.eth.block_number = 100
1248
1272
  mock_ctx.default_block = "latest"
1249
- mock_ctx.web3.eth.get_block.return_value = {"timestamp": 1700000000}
1273
+ mock_ctx.web3.eth.get_block.return_value = {
1274
+ "number": 100,
1275
+ "timestamp": 1700000000,
1276
+ }
1250
1277
  mock_ctx.chain_id = 1
1251
1278
  mock_ctx.at_block.return_value = mock_ctx
1252
1279
  mock_ctx_cls.from_url.return_value = mock_ctx
@@ -29,6 +29,7 @@ from ipor_fusion.hypercore import (
29
29
  HyperCoreSpotBalance,
30
30
  HyperCoreTokenInfo,
31
31
  convert_to_wad_int,
32
+ perp_px_decimals,
32
33
  read_hypercore_nav,
33
34
  )
34
35
  from ipor_fusion.readers.hypercore import (
@@ -335,6 +336,17 @@ def test_convert_to_wad_int_rounds_like_ipor_math(value, decimals, expected):
335
336
  assert convert_to_wad_int(value, decimals) == expected
336
337
 
337
338
 
339
+ @pytest.mark.parametrize(("sz_decimals", "expected"), [(0, 6), (5, 1), (6, 0)])
340
+ def test_perp_px_decimals(sz_decimals, expected):
341
+ assert perp_px_decimals(sz_decimals) == expected
342
+
343
+
344
+ @pytest.mark.parametrize("sz_decimals", [-1, 7])
345
+ def test_perp_px_decimals_rejects_out_of_range(sz_decimals):
346
+ with pytest.raises(ValueError, match="sz_decimals"):
347
+ perp_px_decimals(sz_decimals)
348
+
349
+
338
350
  def _universal_read(data: bytes) -> bytes:
339
351
  """`PlasmaVault.read` double: the pending reader or the balance fuse."""
340
352
  target, inner = decode(["address", "bytes"], data[4:])
@@ -636,6 +636,26 @@ class TestResolve:
636
636
  "chainlink_feed_registry": None,
637
637
  }
638
638
 
639
+ def test_manager_delegating_to_itself_ends_as_a_cycle(self):
640
+ r = FakeReader()
641
+ weth = addr(1)
642
+ mw = addr(0x51)
643
+ zero = Web3.to_checksum_address(om.ZERO_ADDRESS)
644
+ r.symbols[weth] = "WETH"
645
+ r.decimals[weth] = 18
646
+ r.sources[weth] = zero
647
+ r.prices[weth] = Price(asset=weth, amount=2_000 * 10**18, decimals=18)
648
+ r.underlying_mw = mw
649
+ r.delegated = r # the manager names itself as its underlying middleware
650
+
651
+ node = om.resolve_asset(r, weth, max_depth=50)
652
+
653
+ # one hop back to the same manager, not 50 down to max_depth
654
+ (dep,) = node.dependencies
655
+ assert dep.reason == "cycle_detected"
656
+ assert not dep.dependencies
657
+ assert node.status == "partially_resolved"
658
+
639
659
  def test_zero_underlying_middleware_treated_as_absent(self):
640
660
  r = FakeReader()
641
661
  asset = addr(1)
@@ -516,10 +516,13 @@ _ALL_SNAPSHOT: dict[str, frozenset[str]] = {
516
516
  "HyperCoreTransferStatus",
517
517
  "HyperCoreVaultState",
518
518
  "MarginTableId",
519
+ "ORDER_DECIMALS",
520
+ "PERP_MAX_DECIMALS",
519
521
  "PayloadHash",
520
522
  "SPOT_DEX",
521
523
  "TimeInForce",
522
524
  "TransferDirection",
525
+ "USD6_DECIMALS",
523
526
  "USDC_SYSTEM_ADDRESS",
524
527
  "USDC_TOKEN_INDEX",
525
528
  "compare_hypercore_transfer",
@@ -532,6 +535,7 @@ _ALL_SNAPSHOT: dict[str, frozenset[str]] = {
532
535
  "is_hip3_asset",
533
536
  "is_native_perp_asset",
534
537
  "perp_dex_of",
538
+ "perp_px_decimals",
535
539
  "precompile_index_of",
536
540
  "read_hypercore_evm_exit_ceiling",
537
541
  "read_hypercore_nav",
@@ -5,7 +5,7 @@ import pytest
5
5
  from ipor_fusion import Web3Context
6
6
  from ipor_fusion.cache import NullCache
7
7
  from ipor_fusion.types import ChainId
8
- from ipor_fusion.vault_data import _pinned_default_block
8
+ from ipor_fusion.vault_data import _latest_head, _pinned_default_block
9
9
 
10
10
 
11
11
  def _ctx() -> tuple[Web3Context, MagicMock]:
@@ -32,3 +32,13 @@ def test_tag_or_hash_resolves_to_its_number_once(tag: str):
32
32
  ctx, web3 = _ctx()
33
33
  assert _pinned_default_block(ctx.at_block(tag)) == 4242 # type: ignore[arg-type]
34
34
  web3.eth.get_block.assert_called_once_with(tag)
35
+
36
+
37
+ def test_latest_head_reads_number_and_timestamp_in_one_call():
38
+ # The head number and then get_block(number) can reach a node behind a
39
+ # load balancer that has not seen that block yet.
40
+ web3 = MagicMock()
41
+ web3.eth.get_block.return_value = {"number": 4242, "timestamp": 1_700_000_000}
42
+ ctx = Web3Context(web3, ChainId(1), cache=NullCache())
43
+ assert _latest_head(ctx) == (4242, 1_700_000_000)
44
+ web3.eth.get_block.assert_called_once_with("latest")