ipor-fusion 4.0.0.dev88__tar.gz → 4.0.0.dev93__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 (296) hide show
  1. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/AGENTS.md +19 -8
  2. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/MIGRATION_4.md +29 -9
  3. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/pyproject.toml +1 -1
  5. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/__init__.py +0 -2
  6. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/vault_cmd.py +4 -16
  7. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/vault_health.py +1 -15
  8. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/vault_info_view.py +16 -37
  9. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/vault_markets_view.py +44 -96
  10. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/context.py +18 -5
  11. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/contract.py +12 -3
  12. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/mcp/server.py +4 -17
  13. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/lending_health.py +2 -3
  14. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/unpriceable.py +5 -1
  15. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/validation.py +36 -273
  16. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/vault_data.py +204 -75
  17. ipor_fusion-4.0.0.dev93/src/ipor_fusion/vault_erc20.py +204 -0
  18. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/vault_info.py +59 -98
  19. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/vault_managers.py +27 -4
  20. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_validation.py +6 -7
  21. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/conftest.py +1 -0
  22. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_commands.py +30 -14
  23. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_helpers.py +155 -116
  24. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_hypercore.py +8 -7
  25. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_lending_health.py +9 -7
  26. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_pending_requests_scan.py +11 -4
  27. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_vault_info_golden.py +51 -16
  28. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_vault_predates_getters.py +7 -6
  29. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_vault_validate.py +5 -2
  30. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_mcp_vault_validate.py +3 -8
  31. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_public_api.py +0 -1
  32. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_validation.py +40 -43
  33. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_vault_info_onchain_base.py +3 -4
  34. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +1 -1
  35. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_web3_context.py +24 -0
  36. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_withdraw_manager.py +24 -2
  37. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/typed_client/consumer.py +7 -4
  38. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.claude-plugin/marketplace.json +0 -0
  39. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.claude-plugin/plugin.json +0 -0
  40. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.env.example +0 -0
  41. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.gemini/settings.json +0 -0
  42. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.git-blame-ignore-revs +0 -0
  43. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/cd.yml +0 -0
  44. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/ci.yml +0 -0
  45. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/commit-lint.yml +0 -0
  46. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/notify-slack.yml +0 -0
  47. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/python-build.yml +0 -0
  48. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/python-publish.yml +0 -0
  49. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/release.yml +0 -0
  50. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.github/workflows/report-slack.yml +0 -0
  51. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.gitignore +0 -0
  52. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.mcp.json +0 -0
  53. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.pre-commit-config.yaml +0 -0
  54. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/.python-version +0 -0
  55. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/CHANGELOG.md +0 -0
  56. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/CLAUDE.md +0 -0
  57. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/CODEOWNERS +0 -0
  58. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/LICENSE +0 -0
  59. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/README.md +0 -0
  60. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/build.sh +0 -0
  61. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/CONTRIBUTING.md +0 -0
  62. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/README.md +0 -0
  63. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  64. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  65. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  66. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  67. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/external_state_margin_leg_base.py +0 -0
  68. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/examples/simple_aave_v3_supply_base.py +0 -0
  69. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/pytest.ini +0 -0
  70. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/scripts/commit-check.sh +0 -0
  71. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/scripts/crosschain_readiness.py +0 -0
  72. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/skills/ipor-deploy-vault/SKILL.md +0 -0
  73. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/about.py +0 -0
  74. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cache.py +0 -0
  75. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/chains.py +0 -0
  76. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/__init__.py +0 -0
  77. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  78. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/config_cmd.py +0 -0
  79. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/config_store.py +0 -0
  80. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/main.py +0 -0
  81. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/market_cmd.py +0 -0
  82. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/options.py +0 -0
  83. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  84. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  85. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/config/__init__.py +0 -0
  86. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/config/roles.py +0 -0
  87. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/__init__.py +0 -0
  88. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/access.py +0 -0
  89. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/address.py +0 -0
  90. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/erc20.py +0 -0
  91. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/external_state_executor.py +0 -0
  92. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/fee_manager.py +0 -0
  93. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/fuse.py +0 -0
  94. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/fuse_action.py +0 -0
  95. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/fusion_factory.py +0 -0
  96. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/logs.py +0 -0
  97. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/multicall.py +0 -0
  98. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/oracle.py +0 -0
  99. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/plasma_vault.py +0 -0
  100. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/rewards_manager.py +0 -0
  101. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/simulation.py +0 -0
  102. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  103. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/__init__.py +0 -0
  104. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  105. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  106. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  107. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  108. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  109. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  110. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  111. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  112. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  113. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/contracts.py +0 -0
  114. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/discovery.py +0 -0
  115. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/errors.py +0 -0
  116. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/lane.py +0 -0
  117. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/logs.py +0 -0
  118. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/messages.py +0 -0
  119. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/simulation.py +0 -0
  120. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  121. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  122. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  123. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  124. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  125. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/crosschain/transport.py +0 -0
  126. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/dependency_graph.py +0 -0
  127. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/entry_cli.py +0 -0
  128. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/entry_mcp.py +0 -0
  129. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/errors.py +0 -0
  130. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/explorer.py +0 -0
  131. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/field_docs.py +0 -0
  132. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/__init__.py +0 -0
  133. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  134. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/async_action.py +0 -0
  135. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/base.py +0 -0
  136. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  137. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/crosschain/__init__.py +0 -0
  138. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/crosschain/base.py +0 -0
  139. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/crosschain/ccip.py +0 -0
  140. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/crosschain/stargate.py +0 -0
  141. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/erc4626.py +0 -0
  142. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  143. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/events.py +0 -0
  144. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/external_state.py +0 -0
  145. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  146. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  147. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  148. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/hypercore.py +0 -0
  149. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  150. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/merkl.py +0 -0
  151. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/morpho.py +0 -0
  152. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  153. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  154. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/fuses/universal.py +0 -0
  155. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/guide/__init__.py +0 -0
  156. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/guide/architecture.md +0 -0
  157. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/guide/glossary.md +0 -0
  158. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/guide/invariants.md +0 -0
  159. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/guide/quickstart.md +0 -0
  160. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/market_ids.py +0 -0
  161. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/market_info.py +0 -0
  162. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/mcp/__init__.py +0 -0
  163. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/mcp/guide.py +0 -0
  164. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/mcp/models.py +0 -0
  165. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/morpho_api.py +0 -0
  166. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/py.typed +0 -0
  167. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/__init__.py +0 -0
  168. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/aave_v3.py +0 -0
  169. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/aave_v4.py +0 -0
  170. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/compound_v3.py +0 -0
  171. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/hypercore.py +0 -0
  172. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  173. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/morpho.py +0 -0
  174. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  175. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  176. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  177. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/position_manager.py +0 -0
  178. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  179. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  180. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/substrates.py +0 -0
  181. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/types.py +0 -0
  182. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/vault_positions.py +0 -0
  183. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/src/ipor_fusion/vault_reads.py +0 -0
  184. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/UniswapV3UniversalRouter.py +0 -0
  185. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_crosschain.py +0 -0
  186. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_crosschain_lifecycle.py +0 -0
  187. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_crosschain_recovery.py +0 -0
  188. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_euler_v2.py +0 -0
  189. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_foundry.py +0 -0
  190. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_guide.py +0 -0
  191. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_hypercore_model.py +0 -0
  192. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_hypercore_shadow.py +0 -0
  193. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_multicall.py +0 -0
  194. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/_simulate.py +0 -0
  195. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/addresses.py +0 -0
  196. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/constants.py +0 -0
  197. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  198. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/fixtures/hypercore_market55.json +0 -0
  199. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/fixtures/vault_info_golden/clean.json +0 -0
  200. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  201. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/fixtures/vault_info_golden/rich.json +0 -0
  202. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  203. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_about.py +0 -0
  204. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_access_manager.py +0 -0
  205. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cache.py +0 -0
  206. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_chains.py +0 -0
  207. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_changelog.py +0 -0
  208. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_config_store.py +0 -0
  209. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_market_morpho_blue.py +0 -0
  210. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_oracle_mapping.py +0 -0
  211. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_cli_role_accounts.py +0 -0
  212. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_contract_call.py +0 -0
  213. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_contract_encoder.py +0 -0
  214. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_ccip_events.py +0 -0
  215. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_ccip_indexer.py +0 -0
  216. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_ccip_quote.py +0 -0
  217. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_chainlink.py +0 -0
  218. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_contracts.py +0 -0
  219. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_encoding.py +0 -0
  220. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_errors.py +0 -0
  221. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_lane.py +0 -0
  222. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_layering.py +0 -0
  223. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_readiness.py +0 -0
  224. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_readiness_report.py +0 -0
  225. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_crosschain_transport.py +0 -0
  226. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_erc20.py +0 -0
  227. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_error_hierarchy.py +0 -0
  228. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_examples_composed_hypercore.py +0 -0
  229. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_examples_crosschain.py +0 -0
  230. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  231. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_examples_vaults.py +0 -0
  232. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_explorer.py +0 -0
  233. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_external_state_events.py +0 -0
  234. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_external_state_executor.py +0 -0
  235. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_fee_manager.py +0 -0
  236. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_fuse_encoding.py +0 -0
  237. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_fusion_instance.py +0 -0
  238. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_guide.py +0 -0
  239. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_hypercore_deployment.py +0 -0
  240. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_hypercore_events.py +0 -0
  241. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_hypercore_flow.py +0 -0
  242. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_hypercore_live.py +0 -0
  243. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_hypercore_outcome.py +0 -0
  244. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_hypercore_readers.py +0 -0
  245. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_hypercore_simulation.py +0 -0
  246. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_logs.py +0 -0
  247. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_mcp_hypercore.py +0 -0
  248. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_mcp_models.py +0 -0
  249. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_mcp_server.py +0 -0
  250. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_multicall.py +0 -0
  251. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_oracle.py +0 -0
  252. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_oracle_mapping.py +0 -0
  253. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_plasma_vault.py +0 -0
  254. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_reader_encoding.py +0 -0
  255. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_readiness_selection.py +0 -0
  256. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_revert_decoding.py +0 -0
  257. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_rewards_manager.py +0 -0
  258. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_roles.py +0 -0
  259. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_sdk_layering.py +0 -0
  260. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  261. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  262. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  263. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_crosschain_canary.py +0 -0
  264. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_crosschain_hypercore.py +0 -0
  265. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  266. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  267. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  270. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_examples_arbitrum.py +0 -0
  273. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_examples_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_examples_crosschain.py +0 -0
  275. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_external_state_executor_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  277. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  278. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  279. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_morpho_blue.py +0 -0
  280. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_pilot_v3.py +0 -0
  281. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  282. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  283. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  284. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  285. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_readers_integration.py +0 -0
  286. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_release_funds.py +0 -0
  287. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_swap_on_base.py +0 -0
  288. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  289. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_solidity_compiler.py +0 -0
  290. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_solidity_mirrors.py +0 -0
  291. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_substrates.py +0 -0
  292. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_types_and_events.py +0 -0
  293. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_unpriceable_tokens.py +0 -0
  294. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_vault_dep_graph.py +0 -0
  295. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/tests/test_vault_simulator_blocks.py +0 -0
  296. {ipor_fusion-4.0.0.dev88 → ipor_fusion-4.0.0.dev93}/uv.lock +0 -0
@@ -188,8 +188,9 @@ the lifecycle matrix follows that.
188
188
  - `substrates.py` — public per-market bytes32 substrate decoding (`decode_substrate`,
189
189
  `SubstrateInfo`, `format_market_label`, `market_name`; re-exported from `ipor_fusion`),
190
190
  used by the CLI, MCP and `readers/lending_health`
191
- - `validation.py` — public vault validation (`validate_vault`; `read_erc20_holdings` +
192
- `validate_vault_state` for an already-fetched state typed by the `VaultState` protocol):
191
+ - `validation.py` — public vault validation (`validate_vault` fetches and validates;
192
+ `validate_vault_state` is a pure function of a snapshot typed by the `VaultState`
193
+ protocol):
193
194
  `VaultValidation` with ordered `Finding`s, an ok/warning/critical verdict, the
194
195
  `Reconciliation`, `Erc20Holdings` and lending health; `dependency_graph.py` holds the
195
196
  balance-graph checks
@@ -197,9 +198,11 @@ the lifecycle matrix follows that.
197
198
  `WithdrawManagerData`), `fetch_deployment_info`, cached `resolve_token_symbol` /
198
199
  `resolve_token_decimals`; it composes `vault_reads.py` (the shared Multicall3 batch
199
200
  and degrade-to-``None`` helpers), `vault_managers.py` (fee and WithdrawManager
200
- snapshots) and `vault_positions.py` (Morpho/Aave position breakdowns and their token
201
- prices); `vault_info.py` — `build_vault_info_json`, the
202
- `fusion vault info --json` document (contract names via an injected `resolve_name`),
201
+ snapshots), `vault_positions.py` (Morpho/Aave position breakdowns and their token
202
+ prices) and `vault_erc20.py` (the ERC20_VAULT_BALANCE holdings);
203
+ `vault_info.py` — `build_vault_info_json(data)`, the `fusion vault info --json`
204
+ document rendered from the snapshot alone (contract names via an injected
205
+ `resolve_name`),
203
206
  and `health_check_lists`; `explorer.py` Etherscan V2 (single endpoint, needs API key);
204
207
  `cache.py` the on-disk contract and deployment caches (`~/.cache/ipor-fusion/`).
205
208
  `morpho_api.py` the Morpho public GraphQL API client (`fetch_market`, `fetch_vault`,
@@ -295,6 +298,9 @@ or examples.
295
298
  via `self._action_raw(solidity_signature, values)`; the shared `_validate_*`
296
299
  helpers raise before encoding. `PlasmaVault.execute([actions])` runs the
297
300
  batch atomically.
301
+ - Pin reads, never mutate a shared context: `ctx.at_block(n)` is a copy whose
302
+ reads (and `get_logs` upper bound) default to block ``n``; `Call.call(block=n)`
303
+ pins one read.
298
304
  - Simulate before sending: `VaultSimulator(web3, vault=, alpha=)` batches
299
305
  `execute` and `observe` calls into one `eth_simulateV1` round trip, across
300
306
  blocks via `next_block`. Integration tests and examples use it instead of a
@@ -383,9 +389,14 @@ are not validated.
383
389
 
384
390
  ## Domain rules (verified against the contracts)
385
391
 
386
- - `fetch_vault_data(..., chain_id=0)` (the default) skips lending_health,
387
- Morpho/Aave position breakdowns and token prices, all guarded by `if chain_id:`.
388
- Pass a real chain_id or those come back null/empty, silently.
392
+ - `fetch_vault_data(ctx, vault, *, block=, lending=, metadata=)` is the snapshot:
393
+ it resolves one block (``block``, else a numeric `ctx.default_block`, else latest),
394
+ runs every read on `ctx.at_block(block)` (log scans end there too) and returns
395
+ everything validation and the ``vault info`` document need, so neither reads the
396
+ chain. The chain is `ctx.chain_id`; ``lending=False`` skips lending health, the
397
+ position breakdowns, their prices and the unpriceable check; ``metadata=False``
398
+ skips token symbols/decimals. Add a new chain-state input to the snapshot, never a
399
+ read inside a renderer or validator.
389
400
  - PlasmaVault serves governance/view functions via `fallback()` delegatecall to
390
401
  PlasmaVaultBase, so Etherscan's ABI for the vault address omits callable
391
402
  view functions such as `getDependencyBalanceGraph`. An empty dependency graph
@@ -57,20 +57,40 @@ names earlier reachable only by module path (`FusionFactory`, `FusionInstance`,
57
57
  `ipor_fusion`: a module path outside the README's list (*Public API and stability*)
58
58
  still works but is internal.
59
59
 
60
- **Vault reads and validation.** `fetch_vault_data(chain_id=)`, `fetch_deployment_info`,
61
- `build_vault_info_json`, `ensure_supported_chain` and `fetch_vault_lending_health`
62
- take `ChainId`. The vault is a `PlasmaVaultAddress` (see *Address roles*) in
63
- `validate_vault` and `fetch_deployment_info`; `build_vault_info_json(ctx, plasma_vault,
64
- data, *, chain_id, chain_label)` takes no address, takes the chain by keyword, and
65
- labels the document with `plasma_vault.address` (a test that mocks `PlasmaVault`
66
- sets `.address`). `fetch_unpriceable_priced_tokens` takes the oracle and
60
+ **Vault reads and validation.** The vault read is one pinned snapshot, and the
61
+ chain is always the context's:
62
+
63
+ - `fetch_vault_data(ctx, vault, *, block=None, lending=True, metadata=True)` takes
64
+ the vault address (not a `PlasmaVault`) and no `chain_id`. It resolves one block
65
+ (``block``, else a numeric `ctx.default_block`, else the latest) and runs every
66
+ read, log scans included, on `ctx.at_block(block)`; `ctx` is not modified. The old
67
+ ``chain_id=0`` switch is ``lending=False``; ``metadata=False`` skips token symbols
68
+ and decimals. `VaultData` gains `vault`, `chain_id`, `market_balances`, `erc20`,
69
+ `token_symbols` and `token_decimals`; `WithdrawManagerData` gains
70
+ `shares_to_release_assets` and `pending_request_assets`.
71
+ - `validate_vault(ctx, vault, *, block=None)` fetches with ``metadata=False`` and
72
+ validates; it no longer takes ``resolve_symbol``. `validate_vault_state(state)` is
73
+ pure: a custom `VaultState` also provides `vault`, `chain_id`, `market_balances`,
74
+ `erc20` and the queue's `shares_to_release_assets`. `read_erc20_holdings` is gone:
75
+ the holdings are `VaultData.erc20`.
76
+ - `build_vault_info_json(data, *, resolve_name=None)` renders from the snapshot and
77
+ reads nothing; the chain and its label come from `data.chain_id`.
78
+ - `fetch_deployment_info(ctx, vault, *, api_key=None)` and
79
+ `fetch_vault_lending_health(ctx, vault, *, balance_fuse_market_ids,
80
+ market_substrates, aave_pools=None)` take the chain from `ctx`.
81
+ - `Web3Context(web3, chain_id, *, ...)` and `Web3Context.from_url(url, *, ...)` take
82
+ their options by keyword. New: `Web3Context.at_block(block)` (a pinned copy) and
83
+ `Call.call(ctx=None, *, block=None)`. `Web3Context.get_logs(to_block=None)` ends at
84
+ the context's default block and `Web3Context.get_block(block=None)` returns it,
85
+ both instead of ``"latest"``.
86
+
87
+ `ensure_supported_chain` takes `ChainId`. `fetch_unpriceable_priced_tokens` takes the oracle and
67
88
  underlying as `ChecksumAddress`. `resolve_token_symbol`, `resolve_token_decimals`,
68
89
  `get_contract_name` and `get_deployment_tx` take `HexAddress` (a checksum address
69
90
  fits), and the `resolve_name` / `resolve_symbol` callbacks receive one (a callback
70
91
  typed for `str` still fits). Market-keyed data is keyed by `MarketId`:
71
92
  `VaultData.market_substrates`, `dependency_graph`, `morpho_positions`,
72
- `aave_positions`; the `VaultState` protocol, `validate_vault_state`
73
- (`market_balances`), `fetch_vault_lending_health`, `fetch_unpriceable_priced_tokens`
93
+ `aave_positions`, `market_balances`; the `VaultState` protocol, `fetch_vault_lending_health`, `fetch_unpriceable_priced_tokens`
74
94
  and the `dependency_graph` helpers (`compute_update_reach`, `compute_update_groups`,
75
95
  `find_orphan_fuse_markets`, `find_markets_missing_erc20_dependency`).
76
96
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev88
3
+ Version: 4.0.0.dev93
4
4
  Summary: The IPOR Fusion SDK for Python
5
5
  Project-URL: homepage, https://ipor.io
6
6
  Project-URL: repository, https://github.com/IPOR-Labs/ipor-fusion.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev88"
3
+ version = "4.0.0.dev93"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -445,7 +445,6 @@ from ipor_fusion.validation import (
445
445
  VaultState,
446
446
  VaultValidation,
447
447
  WithdrawQueue,
448
- read_erc20_holdings,
449
448
  validate_vault,
450
449
  validate_vault_state,
451
450
  )
@@ -861,7 +860,6 @@ __all__ = [
861
860
  "VaultState",
862
861
  "VaultValidation",
863
862
  "WithdrawQueue",
864
- "read_erc20_holdings",
865
863
  "validate_vault",
866
864
  "validate_vault_state",
867
865
  "compute_update_groups",
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import sys
5
- from functools import partial
6
5
  from typing import Any
7
6
 
8
7
  import click
@@ -50,7 +49,6 @@ from ipor_fusion.validation import (
50
49
  from ipor_fusion.vault_data import (
51
50
  _ROLE_SCAN_ERRORS,
52
51
  fetch_vault_data,
53
- resolve_token_symbol,
54
52
  )
55
53
  from ipor_fusion.vault_info import (
56
54
  _erc20_balances_json,
@@ -207,7 +205,6 @@ def info(
207
205
  """
208
206
  cfg = load_config()
209
207
  chain_id, ctx = _build_ctx(cfg, vault_address, chain_id, block_number)
210
- checksum_address = vault_address
211
208
 
212
209
  # Cheap single-call probe: friendly errors for "nothing deployed here" and
213
210
  # "not a Plasma Vault" (revert and empty-return flavors alike) before the
@@ -217,17 +214,13 @@ def info(
217
214
  except (ContractNotFoundError, NotPlasmaVaultError) as exc:
218
215
  raise click.UsageError(str(exc)) from exc
219
216
 
220
- plasma_vault = PlasmaVault(ctx, checksum_address)
221
-
222
- _auto_save_vault(cfg, vault_address, chain_id, plasma_vault)
217
+ _auto_save_vault(cfg, vault_address, chain_id, PlasmaVault(ctx, vault_address))
223
218
 
224
219
  try:
225
- data = fetch_vault_data(ctx, plasma_vault, block_number, chain_id=chain_id)
220
+ data = fetch_vault_data(ctx, vault_address, block=block_number)
226
221
  except UnsupportedVaultVersionError as exc:
227
222
  raise click.ClickException(str(exc)) from exc
228
- _print_vault_info(
229
- ctx, plasma_vault, cfg, data, vault_address, chain_id, json_output
230
- )
223
+ _print_vault_info(ctx, cfg, data, json_output)
231
224
 
232
225
 
233
226
  @vault.command("validate")
@@ -264,12 +257,7 @@ def validate(
264
257
  raise click.UsageError(str(exc)) from exc
265
258
 
266
259
  try:
267
- validation = validate_vault(
268
- ctx,
269
- vault_address,
270
- block_number,
271
- resolve_symbol=partial(resolve_token_symbol, ctx),
272
- )
260
+ validation = validate_vault(ctx, vault_address, block=block_number)
273
261
  except UnsupportedVaultVersionError as exc:
274
262
  raise click.ClickException(str(exc)) from exc
275
263
 
@@ -3,32 +3,18 @@
3
3
 
4
4
  from __future__ import annotations
5
5
 
6
- from functools import partial
7
-
8
6
  import click
9
7
 
10
8
  from ipor_fusion.cli.vault_rendering import _format_amount, _format_usd, _print_table
11
- from ipor_fusion.core.context import Web3Context
12
- from ipor_fusion.core.plasma_vault import PlasmaVault
13
9
  from ipor_fusion.substrates import market_name
14
10
  from ipor_fusion.validation import (
15
11
  Erc20Holdings,
16
12
  VaultValidation,
17
- read_erc20_holdings,
18
13
  )
19
- from ipor_fusion.vault_data import VaultData, resolve_token_symbol
14
+ from ipor_fusion.vault_data import VaultData
20
15
  from ipor_fusion.vault_info import health_check_lists
21
16
 
22
17
 
23
- def _read_erc20_holdings(
24
- ctx: Web3Context, plasma_vault: PlasmaVault, data: VaultData
25
- ) -> Erc20Holdings:
26
- """ERC20 holdings with token symbols from the CLI's contract cache."""
27
- return read_erc20_holdings(
28
- ctx, plasma_vault, data, partial(resolve_token_symbol, ctx)
29
- )
30
-
31
-
32
18
  def _print_erc20_balances(data: VaultData, erc20: Erc20Holdings) -> None:
33
19
  if not erc20.tokens:
34
20
  has_erc20_market = any(
@@ -14,7 +14,6 @@ from ipor_fusion.cli.vault_health import (
14
14
  _print_erc20_balances,
15
15
  _print_health_check,
16
16
  _print_reconciliation,
17
- _read_erc20_holdings,
18
17
  )
19
18
  from ipor_fusion.cli.vault_markets_view import _print_lending_health, _print_markets
20
19
  from ipor_fusion.cli.vault_rendering import (
@@ -26,7 +25,6 @@ from ipor_fusion.cli.vault_rendering import (
26
25
  )
27
26
  from ipor_fusion.core.context import Web3Context
28
27
  from ipor_fusion.core.fee_manager import RecipientFee
29
- from ipor_fusion.core.plasma_vault import PlasmaVault
30
28
  from ipor_fusion.explorer import get_contract_name
31
29
  from ipor_fusion.readers.hypercore import HyperCorePendingState
32
30
  from ipor_fusion.substrates import (
@@ -48,7 +46,6 @@ from ipor_fusion.vault_info import (
48
46
  _wad_to_usd,
49
47
  build_vault_info_json,
50
48
  )
51
- from ipor_fusion.vault_reads import _safe_call
52
49
 
53
50
 
54
51
  def _print_role_accounts_table(role_accounts: list[dict[str, Any]]) -> None:
@@ -76,38 +73,28 @@ def _print_role_accounts(role_accounts: list[dict[str, Any]] | None) -> None:
76
73
 
77
74
 
78
75
  def _print_vault_info(
79
- ctx: Web3Context,
80
- plasma_vault: PlasmaVault,
81
- cfg: FusionConfig,
82
- data: VaultData,
83
- vault_address: PlasmaVaultAddress,
84
- chain_id: ChainId,
85
- json_output: bool = False,
76
+ ctx: Web3Context, cfg: FusionConfig, data: VaultData, json_output: bool = False
86
77
  ) -> None:
87
78
  api_key = cfg.etherscan_api_key
79
+ chain_id = data.chain_id
88
80
  chain_label = CHAIN_NAMES.get(chain_id, str(chain_id))
89
81
 
90
82
  (
91
83
  data.deployment_block,
92
84
  data.deployment_timestamp,
93
85
  data.deployment_error,
94
- ) = fetch_deployment_info(ctx, chain_id, vault_address, api_key)
86
+ ) = fetch_deployment_info(ctx, data.vault, api_key=api_key)
95
87
 
96
88
  if json_output:
97
89
  result = build_vault_info_json(
98
- ctx,
99
- plasma_vault,
100
- data,
101
- chain_id=chain_id,
102
- chain_label=chain_label,
103
- resolve_name=partial(get_contract_name, chain_id, api_key=api_key),
90
+ data, resolve_name=partial(get_contract_name, chain_id, api_key=api_key)
104
91
  )
105
92
  click.echo(json.dumps(result, indent=2))
106
93
  return
107
94
 
108
- _print_identity(data, vault_address, chain_id, chain_label)
95
+ _print_identity(data, data.vault, chain_id, chain_label)
109
96
  _print_supply(data)
110
- _print_managers(data, plasma_vault)
97
+ _print_managers(data)
111
98
  click.echo()
112
99
 
113
100
  _print_fees(data)
@@ -115,15 +102,9 @@ def _print_vault_info(
115
102
 
116
103
  _print_role_accounts(_role_accounts_json(data))
117
104
 
118
- market_balances = _print_markets(ctx, plasma_vault, data, chain_id, api_key)
105
+ _print_markets(data, api_key)
119
106
 
120
- validation = validate_vault_state(
121
- ctx,
122
- plasma_vault,
123
- data,
124
- market_balances,
125
- _read_erc20_holdings(ctx, plasma_vault, data),
126
- )
107
+ validation = validate_vault_state(data)
127
108
  click.echo("ERC20 Balances (vault holdings):")
128
109
  _print_erc20_balances(data, validation.erc20)
129
110
  click.echo()
@@ -131,7 +112,7 @@ def _print_vault_info(
131
112
  _print_reconciliation(data, validation)
132
113
  click.echo()
133
114
 
134
- _print_lending_health(ctx, data)
115
+ _print_lending_health(data)
135
116
  click.echo()
136
117
  _print_hypercore(data)
137
118
 
@@ -207,7 +188,7 @@ def _print_supply(data: VaultData) -> None:
207
188
  )
208
189
 
209
190
 
210
- def _print_managers(data: VaultData, plasma_vault: PlasmaVault) -> None:
191
+ def _print_managers(data: VaultData) -> None:
211
192
  click.echo(f"Access Manager: {data.access_manager}")
212
193
  click.echo(f"Price Oracle: {data.price_oracle_addr}")
213
194
  click.echo(f"Rewards Manager: {data.rewards_manager or 'N/A'}")
@@ -222,7 +203,7 @@ def _print_managers(data: VaultData, plasma_vault: PlasmaVault) -> None:
222
203
  if wmd.last_release_funds_timestamp > 0:
223
204
  last_iso = _unix_to_iso(wmd.last_release_funds_timestamp)
224
205
  click.echo(f" Last release: {last_iso}")
225
- _print_pending_requests(data, plasma_vault)
206
+ _print_pending_requests(data)
226
207
 
227
208
 
228
209
  def _format_fee_percent(value: float | None) -> str:
@@ -375,7 +356,7 @@ def _print_hypercore_pending(pending: HyperCorePendingState | None) -> None:
375
356
  )
376
357
 
377
358
 
378
- def _print_pending_requests(data: VaultData, plasma_vault: PlasmaVault) -> None:
359
+ def _print_pending_requests(data: VaultData) -> None:
379
360
  if (wmd := data.withdraw_manager_data) is None:
380
361
  return
381
362
  if wmd.pending_requests is None:
@@ -389,16 +370,14 @@ def _print_pending_requests(data: VaultData, plasma_vault: PlasmaVault) -> None:
389
370
  return
390
371
 
391
372
  total_shares = Shares(sum(r.shares for r in requests))
392
- total_assets: int | None = _safe_call(
393
- lambda ts=total_shares: plasma_vault.convert_to_assets(ts).call()
373
+ request_assets = [wmd.pending_request_assets.get(r.account) for r in requests]
374
+ total_assets = (
375
+ None if None in request_assets else sum(a for a in request_assets if a)
394
376
  )
395
377
 
396
378
  click.echo(f" Pending requests ({len(requests)}):")
397
379
  rows: list[tuple[str, ...]] = []
398
- for req in requests:
399
- assets: int | None = _safe_call(
400
- lambda s=req.shares: plasma_vault.convert_to_assets(s).call()
401
- )
380
+ for req, assets in zip(requests, request_assets, strict=True):
402
381
  assets_str = (
403
382
  f"{_format_amount(assets, data.asset_decimals)} {data.asset_symbol}"
404
383
  if assets is not None
@@ -13,8 +13,6 @@ from ipor_fusion.cli.vault_rendering import (
13
13
  _print_table,
14
14
  _substrate_details,
15
15
  )
16
- from ipor_fusion.core.context import Web3Context
17
- from ipor_fusion.core.plasma_vault import BalanceFuse, PlasmaVault
18
16
  from ipor_fusion.explorer import get_contract_name
19
17
  from ipor_fusion.substrates import (
20
18
  decode_substrate,
@@ -24,17 +22,13 @@ from ipor_fusion.types import ChainId, FuseAddress, MarketId, parse_hex_address
24
22
  from ipor_fusion.vault_data import (
25
23
  GET_MARKET_SUBSTRATES,
26
24
  VaultData,
27
- resolve_token_decimals,
28
- resolve_token_symbol,
29
25
  )
30
26
  from ipor_fusion.vault_info import (
31
27
  _CAPABILITY_BALANCE_FUSES,
32
28
  )
33
29
 
34
30
 
35
- def _print_lending_health( # noqa: C901
36
- ctx: Web3Context, data: VaultData
37
- ) -> None:
31
+ def _print_lending_health(data: VaultData) -> None: # noqa: C901
38
32
  lh = data.lending_health
39
33
  has_health = lh is not None and lh.has_lending_positions
40
34
  has_breakdown = bool(data.morpho_positions) or bool(data.aave_positions)
@@ -46,22 +40,21 @@ def _print_lending_health( # noqa: C901
46
40
  morpho_health, aave_health = _index_lending_health(lh)
47
41
  consumed_morpho_subs: set[str] = set()
48
42
  consumed_aave_mids: set[int | str] = set()
49
- prices = data.token_prices_usd or {}
50
43
 
51
44
  for ipor_mid, positions in (data.morpho_positions or {}).items():
52
45
  click.echo(f" {format_market_label(ipor_mid)}:")
53
46
  for pb in positions:
54
- coll_sym = resolve_token_symbol(ctx, pb.collateral_token) or "?"
55
- loan_sym = resolve_token_symbol(ctx, pb.loan_token) or "?"
47
+ coll_sym = _token_symbol(data, pb.collateral_token)
48
+ loan_sym = _token_symbol(data, pb.loan_token)
56
49
  click.echo(f" morpho market {pb.market_id} ({coll_sym}/{loan_sym}):")
57
50
  click.echo(
58
- f" Collateral: {_format_token_amount(ctx, int(pb.collateral), pb.collateral_token, prices)}"
51
+ f" Collateral: {_format_token_amount(data, int(pb.collateral), pb.collateral_token)}"
59
52
  )
60
53
  click.echo(
61
- f" Borrow: {_format_token_amount(ctx, int(pb.borrow_assets), pb.loan_token, prices)}"
54
+ f" Borrow: {_format_token_amount(data, int(pb.borrow_assets), pb.loan_token)}"
62
55
  )
63
56
  click.echo(
64
- f" Supply: {_format_token_amount(ctx, int(pb.supply_assets), pb.loan_token, prices)}"
57
+ f" Supply: {_format_token_amount(data, int(pb.supply_assets), pb.loan_token)}"
65
58
  )
66
59
  sid = str(pb.market_id).lower().removeprefix("0x")
67
60
  consumed_morpho_subs.add(sid)
@@ -71,17 +64,17 @@ def _print_lending_health( # noqa: C901
71
64
  for ipor_mid, aave_positions in (data.aave_positions or {}).items():
72
65
  click.echo(f" {format_market_label(ipor_mid)}:")
73
66
  for ab in aave_positions:
74
- asset_symbol = resolve_token_symbol(ctx, ab.asset) or "?"
67
+ asset_symbol = _token_symbol(data, ab.asset)
75
68
  click.echo(f" asset {ab.asset} ({asset_symbol}):")
76
69
  click.echo(
77
- f" Supply: {_format_token_amount(ctx, int(ab.supply), ab.asset, prices)}"
70
+ f" Supply: {_format_token_amount(data, int(ab.supply), ab.asset)}"
78
71
  )
79
72
  click.echo(
80
- f" Variable Debt: {_format_token_amount(ctx, int(ab.variable_debt), ab.asset, prices)}"
73
+ f" Variable Debt: {_format_token_amount(data, int(ab.variable_debt), ab.asset)}"
81
74
  )
82
75
  if ab.stable_debt > 0:
83
76
  click.echo(
84
- f" Stable Debt: {_format_token_amount(ctx, int(ab.stable_debt), ab.asset, prices)}"
77
+ f" Stable Debt: {_format_token_amount(data, int(ab.stable_debt), ab.asset)}"
85
78
  )
86
79
  consumed_aave_mids.add(ipor_mid)
87
80
  if m := aave_health.get(ipor_mid):
@@ -161,15 +154,9 @@ def _print_health_lines(market: Any, indent: str) -> None:
161
154
  click.secho(f"{indent}Status: {status}", fg=color, bold=bold)
162
155
 
163
156
 
164
- def _print_markets(
165
- ctx: Web3Context,
166
- plasma_vault: PlasmaVault,
167
- data: VaultData,
168
- chain_id: ChainId,
169
- api_key: str | None,
170
- ) -> dict[MarketId, int]:
171
- """Fuses, balance fuses, dependency graph and substrates; returns the
172
- per-market balances read for the balance-fuse table."""
157
+ def _print_markets(data: VaultData, api_key: str | None) -> None:
158
+ """Fuses, balance fuses, dependency graph and substrates."""
159
+ chain_id = data.chain_id
173
160
  _print_fuse_section(
174
161
  "Fuses",
175
162
  data.fuses,
@@ -190,46 +177,31 @@ def _print_markets(
190
177
  )
191
178
  click.echo()
192
179
 
193
- market_balances = _print_balance_fuses_table(
194
- plasma_vault,
195
- data.balance_fuses,
196
- data.asset_decimals,
197
- data.asset_symbol,
198
- data.asset_price_usd,
199
- chain_id,
200
- api_key,
201
- )
180
+ _print_balance_fuses_table(data, api_key)
202
181
  click.echo()
203
182
 
204
183
  _print_dependency_graph(data)
205
184
 
206
185
  click.echo("Substrates per Market:")
207
- _print_market_substrates(ctx, data, chain_id, api_key)
186
+ _print_market_substrates(data, api_key)
208
187
  click.echo()
209
- return market_balances
210
188
 
211
189
 
212
- def _format_token_amount(
213
- ctx: Web3Context,
214
- raw: int,
215
- token_address: HexAddress,
216
- prices_usd: dict[HexAddress, float] | None = None,
217
- ) -> str:
218
- """Format a raw on-chain amount using cached ERC-20 symbol + decimals.
190
+ def _token_symbol(data: VaultData, token_address: HexAddress) -> str:
191
+ return data.token_symbols.get(parse_hex_address(token_address)) or "?"
219
192
 
220
- When `prices_usd` (lowercase address → USD price) contains the token, the
221
- output is suffixed with ` ($X.XX)` via `_format_usd`.
222
- """
223
- symbol = resolve_token_symbol(ctx, token_address) or "?"
224
- decimals = resolve_token_decimals(ctx, token_address)
193
+
194
+ def _format_token_amount(data: VaultData, raw: int, token_address: HexAddress) -> str:
195
+ """Format a raw amount with the snapshot's symbol and decimals, suffixed
196
+ with ` ($X.XX)` when the snapshot has the token's USD price."""
197
+ key = parse_hex_address(token_address)
198
+ symbol = data.token_symbols.get(key) or "?"
199
+ decimals = data.token_decimals.get(key)
225
200
  if decimals is None:
226
201
  return f"{raw} raw ({symbol})"
227
202
  base = f"{_format_amount(raw, decimals)} {symbol}"
228
- if prices_usd:
229
- usd_suffix = _format_usd(
230
- raw, decimals, prices_usd.get(parse_hex_address(token_address))
231
- )
232
- return f"{base}{usd_suffix}"
203
+ if prices_usd := data.token_prices_usd:
204
+ return f"{base}{_format_usd(raw, decimals, prices_usd.get(key))}"
233
205
  return base
234
206
 
235
207
 
@@ -313,37 +285,24 @@ def _print_fuse_section(
313
285
  _print_table(("#", "Address", "Contract", "Market", "Substrates"), rows)
314
286
 
315
287
 
316
- def _print_balance_fuses_table(
317
- plasma_vault: PlasmaVault,
318
- balance_fuses: Sequence[BalanceFuse],
319
- decimals: int,
320
- asset_symbol: str,
321
- asset_price_usd: float | None,
322
- chain_id: ChainId,
323
- api_key: str | None,
324
- ) -> dict[MarketId, int]:
325
- """Print the table; return each market's cached balance, once per market
326
- (several balance fuses can share a market's single storage slot)."""
327
- market_balances: dict[MarketId, int] = {}
288
+ def _print_balance_fuses_table(data: VaultData, api_key: str | None) -> None:
289
+ balance_fuses = data.balance_fuses
290
+ decimals = data.asset_decimals
291
+ asset_symbol = data.asset_symbol
292
+ asset_price_usd = data.asset_price_usd
328
293
  with ThreadPoolExecutor() as pool:
329
- futures: list[tuple[int, MarketId, str, Future, Future]] = []
294
+ futures: list[tuple[int, MarketId, str, Future]] = []
330
295
  for idx, balance_fuse in enumerate(balance_fuses, 1):
331
296
  market_id_str = format_market_label(balance_fuse.market_id)
332
- f_balance = pool.submit(
333
- plasma_vault.total_assets_in_market(balance_fuse.market_id).call
334
- )
335
297
  f_contract = pool.submit(
336
- get_contract_name, chain_id, balance_fuse.fuse, api_key
337
- )
338
- futures.append(
339
- (idx, balance_fuse.market_id, market_id_str, f_balance, f_contract)
298
+ get_contract_name, data.chain_id, balance_fuse.fuse, api_key
340
299
  )
300
+ futures.append((idx, balance_fuse.market_id, market_id_str, f_contract))
341
301
 
342
302
  venue_rows: list[tuple[str, ...]] = []
343
303
  zero_rows: list[tuple[str, ...]] = []
344
- for idx, market_id, market_id_str, f_balance, f_contract in futures:
345
- assets_in_market = f_balance.result()
346
- market_balances.setdefault(market_id, assets_in_market)
304
+ for idx, market_id, market_id_str, f_contract in futures:
305
+ assets_in_market = data.market_balances[market_id]
347
306
  contract_name = f_contract.result() or "?"
348
307
  balance_str = (
349
308
  f"{_format_amount(assets_in_market, decimals)} {asset_symbol}"
@@ -367,30 +326,23 @@ def _print_balance_fuses_table(
367
326
  click.echo()
368
327
  click.echo(f"Zero-Balance Fuses ({len(zero_rows)}):")
369
328
  _print_table(header, [(str(i), *row) for i, row in enumerate(zero_rows, 1)])
370
- return market_balances
371
329
 
372
330
 
373
- def _print_market_substrates(
374
- ctx: Web3Context, data: VaultData, chain_id: ChainId, api_key: str | None
375
- ) -> set[str]:
331
+ def _print_market_substrates(data: VaultData, api_key: str | None) -> set[str]:
376
332
  if GET_MARKET_SUBSTRATES in data.unimplemented_getters:
377
333
  click.secho(
378
334
  f" (unavailable: the vault predates {GET_MARKET_SUBSTRATES})",
379
335
  fg="yellow",
380
336
  )
381
337
  return set()
382
- return _print_substrates(
383
- ctx, data.market_substrates or {}, data.balance_fuses, chain_id, api_key
384
- )
338
+ return _print_substrates(data, api_key)
385
339
 
386
340
 
387
341
  def _print_substrates( # noqa: C901
388
- ctx: Web3Context,
389
- substrates_by_market: dict[MarketId, list[bytes]],
390
- balance_fuses: Sequence[BalanceFuse],
391
- chain_id: ChainId,
392
- api_key: str | None,
342
+ data: VaultData, api_key: str | None
393
343
  ) -> set[str]:
344
+ substrates_by_market = data.market_substrates or {}
345
+ balance_fuses = data.balance_fuses
394
346
  # Phase 1: collect each balance fuse's substrates and the addresses to resolve
395
347
  market_substrates: list[tuple[str, MarketId, list[bytes]]] = []
396
348
  all_addresses: set[HexAddress] = set()
@@ -408,17 +360,13 @@ def _print_substrates( # noqa: C901
408
360
  click.echo(" (none)")
409
361
  return {a.lower() for a in all_addresses}
410
362
 
411
- # Phase 2: resolve all symbols + contract names in parallel
363
+ # Phase 2: resolve contract names in parallel; symbols are in the snapshot
364
+ resolved_symbols = data.token_symbols
412
365
  with ThreadPoolExecutor() as pool:
413
- symbol_futs = {
414
- addr: pool.submit(resolve_token_symbol, ctx, addr) for addr in all_addresses
415
- }
416
366
  contract_futs = {
417
- addr: pool.submit(get_contract_name, chain_id, addr, api_key)
367
+ addr: pool.submit(get_contract_name, data.chain_id, addr, api_key)
418
368
  for addr in all_addresses
419
369
  }
420
-
421
- resolved_symbols = {addr: fut.result() for addr, fut in symbol_futs.items()}
422
370
  resolved_contracts = {addr: fut.result() for addr, fut in contract_futs.items()}
423
371
 
424
372
  # Phase 3: print