ipor-fusion 4.0.0.dev110__tar.gz → 4.0.0.dev116__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 (297) hide show
  1. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/AGENTS.md +3 -1
  2. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/MIGRATION_4.md +20 -0
  3. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/pyproject.toml +1 -1
  5. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/vault_info_view.py +1 -1
  6. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/context.py +43 -3
  7. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/plasma_vault.py +11 -10
  8. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/validation.py +17 -8
  9. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/vault_data.py +19 -16
  10. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/vault_erc20.py +28 -32
  11. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/vault_info.py +1 -1
  12. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/vault_managers.py +18 -18
  13. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/conftest.py +1 -0
  14. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/fixtures/vault_info_golden/clean.txt +1 -1
  15. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_helpers.py +2 -0
  16. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_web3_context.py +43 -1
  17. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.claude-plugin/marketplace.json +0 -0
  18. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.claude-plugin/plugin.json +0 -0
  19. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.env.example +0 -0
  20. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.gemini/settings.json +0 -0
  21. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.git-blame-ignore-revs +0 -0
  22. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/cd.yml +0 -0
  23. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/ci.yml +0 -0
  24. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/commit-lint.yml +0 -0
  25. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/notify-slack.yml +0 -0
  26. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/python-build.yml +0 -0
  27. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/python-publish.yml +0 -0
  28. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/release.yml +0 -0
  29. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.github/workflows/report-slack.yml +0 -0
  30. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.gitignore +0 -0
  31. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.mcp.json +0 -0
  32. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.pre-commit-config.yaml +0 -0
  33. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/.python-version +0 -0
  34. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/CHANGELOG.md +0 -0
  35. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/CLAUDE.md +0 -0
  36. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/CODEOWNERS +0 -0
  37. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/LICENSE +0 -0
  38. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/README.md +0 -0
  39. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/build.sh +0 -0
  40. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/CONTRIBUTING.md +0 -0
  41. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/README.md +0 -0
  42. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  43. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  44. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +0 -0
  45. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  46. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/external_state_margin_leg_base.py +0 -0
  47. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/examples/simple_aave_v3_supply_base.py +0 -0
  48. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/pytest.ini +0 -0
  49. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/scripts/commit-check.sh +0 -0
  50. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/scripts/crosschain_readiness.py +0 -0
  51. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/skills/ipor-deploy-vault/SKILL.md +0 -0
  52. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/__init__.py +0 -0
  53. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/about.py +0 -0
  54. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cache.py +0 -0
  55. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/chains.py +0 -0
  56. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/__init__.py +0 -0
  57. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  58. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/config_cmd.py +0 -0
  59. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/config_store.py +0 -0
  60. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/main.py +0 -0
  61. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/market_cmd.py +0 -0
  62. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/options.py +0 -0
  63. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  64. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  65. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/vault_health.py +0 -0
  66. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  67. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  68. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/config/__init__.py +0 -0
  69. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/config/roles.py +0 -0
  70. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/__init__.py +0 -0
  71. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/access.py +0 -0
  72. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/address.py +0 -0
  73. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/contract.py +0 -0
  74. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/erc20.py +0 -0
  75. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/external_state_executor.py +0 -0
  76. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/fee_manager.py +0 -0
  77. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/fuse.py +0 -0
  78. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/fuse_action.py +0 -0
  79. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/fusion_factory.py +0 -0
  80. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/logs.py +0 -0
  81. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/multicall.py +0 -0
  82. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/oracle.py +0 -0
  83. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/rewards_manager.py +0 -0
  84. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/simulation.py +0 -0
  85. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  86. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/__init__.py +0 -0
  87. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  88. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  89. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  90. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  91. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  92. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  93. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  94. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  95. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  96. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/contracts.py +0 -0
  97. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/discovery.py +0 -0
  98. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/errors.py +0 -0
  99. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  100. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  101. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  102. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  103. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/lane.py +0 -0
  104. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/logs.py +0 -0
  105. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/messages.py +0 -0
  106. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/simulation.py +0 -0
  107. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  108. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  109. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  110. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  111. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  112. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/crosschain/transport.py +0 -0
  113. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/dependency_graph.py +0 -0
  114. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/entry_cli.py +0 -0
  115. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/entry_mcp.py +0 -0
  116. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/errors.py +0 -0
  117. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/explorer.py +0 -0
  118. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/field_docs.py +0 -0
  119. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/__init__.py +0 -0
  120. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  121. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/async_action.py +0 -0
  122. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/base.py +0 -0
  123. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  124. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/erc4626.py +0 -0
  125. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  126. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/events.py +0 -0
  127. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/external_state.py +0 -0
  128. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  129. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  130. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  131. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/hypercore.py +0 -0
  132. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  133. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/merkl.py +0 -0
  134. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/morpho.py +0 -0
  135. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  136. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  137. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/fuses/universal.py +0 -0
  138. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/guide/__init__.py +0 -0
  139. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/guide/architecture.md +0 -0
  140. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/guide/glossary.md +0 -0
  141. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/guide/invariants.md +0 -0
  142. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/guide/quickstart.md +0 -0
  143. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/market_ids.py +0 -0
  144. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/market_info.py +0 -0
  145. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/mcp/__init__.py +0 -0
  146. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/mcp/guide.py +0 -0
  147. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/mcp/models.py +0 -0
  148. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/mcp/server.py +0 -0
  149. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/morpho_api.py +0 -0
  150. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/py.typed +0 -0
  151. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/__init__.py +0 -0
  152. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/aave_v3.py +0 -0
  153. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/aave_v4.py +0 -0
  154. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/compound_v3.py +0 -0
  155. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/hypercore.py +0 -0
  156. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  157. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/lending_health.py +0 -0
  158. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/morpho.py +0 -0
  159. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  160. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  161. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  162. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/position_manager.py +0 -0
  163. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  164. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  165. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/readers/unpriceable.py +0 -0
  166. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/substrates.py +0 -0
  167. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/types.py +0 -0
  168. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/vault_positions.py +0 -0
  169. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/src/ipor_fusion/vault_reads.py +0 -0
  170. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/UniswapV3UniversalRouter.py +0 -0
  171. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_crosschain.py +0 -0
  172. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_crosschain_lifecycle.py +0 -0
  173. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_crosschain_recovery.py +0 -0
  174. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_euler_v2.py +0 -0
  175. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_foundry.py +0 -0
  176. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_guide.py +0 -0
  177. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_hypercore_model.py +0 -0
  178. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_hypercore_shadow.py +0 -0
  179. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_multicall.py +0 -0
  180. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_simulate.py +0 -0
  181. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/_validation.py +0 -0
  182. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/addresses.py +0 -0
  183. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/constants.py +0 -0
  184. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  185. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/fixtures/hypercore_market55.json +0 -0
  186. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/fixtures/vault_info_golden/clean.json +0 -0
  187. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/fixtures/vault_info_golden/rich.json +0 -0
  188. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  189. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_about.py +0 -0
  190. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_access_manager.py +0 -0
  191. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cache.py +0 -0
  192. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_chains.py +0 -0
  193. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_changelog.py +0 -0
  194. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_commands.py +0 -0
  195. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_config_store.py +0 -0
  196. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_hypercore.py +0 -0
  197. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_lending_health.py +0 -0
  198. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_market_morpho_blue.py +0 -0
  199. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_oracle_mapping.py +0 -0
  200. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_pending_requests_scan.py +0 -0
  201. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_role_accounts.py +0 -0
  202. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_vault_info_golden.py +0 -0
  203. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_vault_predates_getters.py +0 -0
  204. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_cli_vault_validate.py +0 -0
  205. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_contract_call.py +0 -0
  206. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_contract_encoder.py +0 -0
  207. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_ccip_events.py +0 -0
  208. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_ccip_indexer.py +0 -0
  209. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_ccip_quote.py +0 -0
  210. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_chainlink.py +0 -0
  211. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_contracts.py +0 -0
  212. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_encoding.py +0 -0
  213. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_errors.py +0 -0
  214. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_lane.py +0 -0
  215. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_layering.py +0 -0
  216. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_readiness.py +0 -0
  217. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_readiness_report.py +0 -0
  218. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_crosschain_transport.py +0 -0
  219. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_erc20.py +0 -0
  220. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_error_hierarchy.py +0 -0
  221. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_examples_composed_hypercore.py +0 -0
  222. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_examples_crosschain.py +0 -0
  223. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  224. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_examples_vaults.py +0 -0
  225. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_explorer.py +0 -0
  226. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_external_state_events.py +0 -0
  227. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_external_state_executor.py +0 -0
  228. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_fee_manager.py +0 -0
  229. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_fuse_encoding.py +0 -0
  230. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_fusion_instance.py +0 -0
  231. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_guide.py +0 -0
  232. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_hypercore_deployment.py +0 -0
  233. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_hypercore_events.py +0 -0
  234. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_hypercore_flow.py +0 -0
  235. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_hypercore_live.py +0 -0
  236. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_hypercore_outcome.py +0 -0
  237. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_hypercore_readers.py +0 -0
  238. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_hypercore_simulation.py +0 -0
  239. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_logs.py +0 -0
  240. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_mcp_hypercore.py +0 -0
  241. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_mcp_models.py +0 -0
  242. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_mcp_server.py +0 -0
  243. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_mcp_vault_validate.py +0 -0
  244. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_multicall.py +0 -0
  245. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_oracle.py +0 -0
  246. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_oracle_mapping.py +0 -0
  247. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_plasma_vault.py +0 -0
  248. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_public_api.py +0 -0
  249. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_reader_encoding.py +0 -0
  250. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_readiness_selection.py +0 -0
  251. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_revert_decoding.py +0 -0
  252. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_rewards_manager.py +0 -0
  253. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_roles.py +0 -0
  254. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_sdk_layering.py +0 -0
  255. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  256. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  257. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  258. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_crosschain_canary.py +0 -0
  259. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_crosschain_hypercore.py +0 -0
  260. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  261. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  262. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  263. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  264. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_examples_arbitrum.py +0 -0
  268. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_examples_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_examples_crosschain.py +0 -0
  270. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_external_state_executor_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  272. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_morpho_blue.py +0 -0
  275. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_pilot_v3.py +0 -0
  276. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  277. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  278. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  279. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  280. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_readers_integration.py +0 -0
  281. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_release_funds.py +0 -0
  282. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_swap_on_base.py +0 -0
  283. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  284. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_solidity_compiler.py +0 -0
  285. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_solidity_mirrors.py +0 -0
  286. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_substrates.py +0 -0
  287. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_types_and_events.py +0 -0
  288. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_unpriceable_tokens.py +0 -0
  289. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_validation.py +0 -0
  290. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_vault_dep_graph.py +0 -0
  291. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_vault_info_onchain_base.py +0 -0
  292. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  293. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_vault_simulator_blocks.py +0 -0
  294. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/test_withdraw_manager.py +0 -0
  295. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/typed_client/consumer.py +0 -0
  296. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/tests/typed_client/rejected.py +0 -0
  297. {ipor_fusion-4.0.0.dev110 → ipor_fusion-4.0.0.dev116}/uv.lock +0 -0
@@ -394,7 +394,9 @@ byte, e.g. Ebisu ZAPPER/REGISTRY).
394
394
  role-accounts, oracle-mapping). `vault info` runs independent fetch pipelines
395
395
  (vault/asset, fees, WithdrawManager, markets, roles) concurrently on a
396
396
  `ThreadPoolExecutor`, each a short chain of `Multicall3` batches; keep new
397
- reads inside a batch rather than adding per-call round trips. Contract names,
397
+ reads inside a batch rather than adding per-call round trips. A `from_url` context
398
+ bounds requests in flight (`max_concurrent_requests`, default 8) whatever the pool
399
+ nesting, so a new fan-out does not need its own limit. Contract names,
398
400
  token symbols and decimals and deployment blocks are cached per chain in
399
401
  `$XDG_CACHE_HOME/ipor-fusion/contract_cache.json` (`<chain>:<address>:<field>`
400
402
  keys); tests redirect it with `monkeypatch.setenv("XDG_CACHE_HOME", ...)` or
@@ -84,6 +84,26 @@ chain is always the context's:
84
84
  the context's default block and `Web3Context.get_block(block=None)` returns it,
85
85
  both instead of ``"latest"``.
86
86
 
87
+ Snapshot fields carry their units: `VaultData.total_assets`,
88
+ `underlying_balance_on_vault` and `market_balances` values are `Amount`;
89
+ `total_supply` and `supply_cap` are `Shares`; `share_decimals` / `asset_decimals` are `Decimals`;
90
+ `block_timestamp` and `deployment_timestamp` are `Timestamp`. `FeeData` fee fields
91
+ are `Fee`, `management_fee_last_update` a `Timestamp`, `unrealized_management_fee` an
92
+ `Amount`; `WithdrawManagerData.withdraw_window` is a `Period`, its fees `Fee`,
93
+ `shares_to_release` `Shares`, `last_release_funds_timestamp` a `Timestamp` and the
94
+ converted amounts `Amount`. A custom `VaultState` annotates the same.
95
+ `PlasmaVault.total_supply()`, `balance_of()` and `get_total_supply_cap()` return
96
+ `Shares` (vault shares), and `transfer` / `approve` / `transfer_from` /
97
+ `set_total_supply_cap` take `Shares`; `ERC20` keeps `Amount`. The supply cap is
98
+ now shown in share decimals labelled `shares` (``fusion vault info``) and its JSON
99
+ ``formatted`` uses `share_decimals`; it was formatted with the asset's decimals.
100
+
101
+ `Web3Context.from_url(..., max_concurrent_requests=8)` bounds the RPC requests in
102
+ flight across every thread using the context (``None`` lifts the bound); the vault
103
+ reads fan out on nested thread pools, and the bound is what a rate-limited provider
104
+ sees. A context built from your own `Web3` object is not bounded unless you add the
105
+ same middleware.
106
+
87
107
  `ensure_supported_chain` takes `ChainId`. `fetch_unpriceable_priced_tokens` takes the oracle and
88
108
  underlying as `ChecksumAddress`. `resolve_token_symbol`, `resolve_token_decimals`,
89
109
  `get_contract_name` and `get_deployment_tx` take `HexAddress` (a checksum address
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev110
3
+ Version: 4.0.0.dev116
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.dev110"
3
+ version = "4.0.0.dev116"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -185,7 +185,7 @@ def _print_supply(data: VaultData) -> None:
185
185
  else:
186
186
  click.echo(
187
187
  f"Supply Cap: "
188
- f"{_format_amount(data.supply_cap, data.asset_decimals)} {data.asset_symbol}"
188
+ f"{_format_amount(data.supply_cap, data.share_decimals)} shares"
189
189
  )
190
190
 
191
191
 
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import copy
4
4
  import logging
5
+ import threading
6
+ from typing import Any
5
7
 
6
8
  from eth_account import Account
7
9
  from eth_typing import ChecksumAddress, HexStr
@@ -11,12 +13,21 @@ from requests import ConnectionError as RequestsConnectionError
11
13
  from requests import HTTPError, Timeout
12
14
  from web3 import Web3
13
15
  from web3.exceptions import ContractLogicError, Web3RPCError
14
- from web3.middleware import ExtraDataToPOAMiddleware
16
+ from web3.middleware import ExtraDataToPOAMiddleware, Web3Middleware
15
17
  from web3.providers.rpc.utils import (
16
18
  REQUEST_RETRY_ALLOWLIST,
17
19
  ExceptionRetryConfiguration,
18
20
  )
19
- from web3.types import BlockIdentifier, LogReceipt, TxParams, TxReceipt, Wei
21
+ from web3.types import (
22
+ BlockIdentifier,
23
+ LogReceipt,
24
+ MakeRequestFn,
25
+ RPCEndpoint,
26
+ RPCResponse,
27
+ TxParams,
28
+ TxReceipt,
29
+ Wei,
30
+ )
20
31
 
21
32
  from ipor_fusion.cache import ContractCache, default_contract_cache
22
33
  from ipor_fusion.chains import GET_LOGS_RANGE_HINTS
@@ -70,6 +81,11 @@ class Web3Context:
70
81
  # multi-call read (vault fetch, health check) fans that out into minutes of
71
82
  # wall clock. Long-running services should pass something tighter.
72
83
  DEFAULT_RPC_TIMEOUT_S = 30.0
84
+ # RPC requests in flight at once through a `from_url` context. The vault
85
+ # reads fan out on nested thread pools; this bounds what reaches the
86
+ # provider, whatever the nesting, so a rate-limited endpoint sees a steady
87
+ # stream instead of bursts.
88
+ DEFAULT_MAX_CONCURRENT_REQUESTS = 8
73
89
 
74
90
  def __init__(
75
91
  self,
@@ -142,7 +158,7 @@ class Web3Context:
142
158
  return self._signer
143
159
 
144
160
  @classmethod
145
- def from_url(
161
+ def from_url( # proctor: ignore[primitives-too-many-params]
146
162
  cls,
147
163
  url: str,
148
164
  *,
@@ -153,7 +169,11 @@ class Web3Context:
153
169
  log_scan_max_requests: int | None = DEFAULT_LOG_SCAN_MAX_REQUESTS,
154
170
  log_source: LogSource | None = None,
155
171
  cache: ContractCache | None = None,
172
+ max_concurrent_requests: int | None = DEFAULT_MAX_CONCURRENT_REQUESTS,
156
173
  ) -> Web3Context:
174
+ """A context on an HTTP provider at ``url``. ``max_concurrent_requests``
175
+ bounds the RPC requests in flight across every thread using it
176
+ (``None``: unbounded)."""
157
177
  web3 = Web3(
158
178
  Web3.HTTPProvider(
159
179
  url,
@@ -171,6 +191,8 @@ class Web3Context:
171
191
  # with a ValueError. The middleware moves it to proofOfAuthorityData
172
192
  # and leaves every other block untouched.
173
193
  web3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
194
+ if max_concurrent_requests is not None:
195
+ web3.middleware_onion.add(_in_flight_limit(max_concurrent_requests))
174
196
  chain_id = ChainId(web3.eth.chain_id)
175
197
 
176
198
  return cls(
@@ -385,3 +407,21 @@ class Web3Context:
385
407
  @staticmethod
386
408
  def _percent_of(value: int, percentage: int) -> int:
387
409
  return value * percentage // 100
410
+
411
+
412
+ def _in_flight_limit(limit: int) -> type[Web3Middleware]:
413
+ """A middleware class (web3 instantiates it per Web3) holding a semaphore
414
+ slot for the duration of each provider request."""
415
+ if limit < 1:
416
+ raise ValueError(f"max_concurrent_requests must be at least 1, got {limit}")
417
+ slots = threading.BoundedSemaphore(limit)
418
+
419
+ class InFlightLimit(Web3Middleware):
420
+ def wrap_make_request(self, make_request: MakeRequestFn) -> MakeRequestFn:
421
+ def limited(method: RPCEndpoint, params: Any) -> RPCResponse:
422
+ with slots:
423
+ return make_request(method, params)
424
+
425
+ return limited
426
+
427
+ return InFlightLimit
@@ -191,7 +191,7 @@ class PlasmaVault(ContractWrapper):
191
191
  `addFuses` steps must be reverted before the vault is usable."""
192
192
  return self._write("removeFuses(address[])", fuses)
193
193
 
194
- def set_total_supply_cap(self, cap: Amount) -> Call[None]:
194
+ def set_total_supply_cap(self, cap: Shares) -> Call[None]:
195
195
  return self._write("setTotalSupplyCap(uint256)", cap)
196
196
 
197
197
  def convert_to_public_vault(self) -> Call[None]:
@@ -362,25 +362,26 @@ class PlasmaVault(ContractWrapper):
362
362
  "configureInstantWithdrawalFuses((address,bytes32[])[])", normalized
363
363
  )
364
364
 
365
- def transfer(self, to: ChecksumAddress, value: Amount) -> Call[None]:
365
+ def transfer(self, to: ChecksumAddress, value: Shares) -> Call[None]:
366
366
  return self._write("transfer(address,uint256)", to, value)
367
367
 
368
- def approve(self, account: ChecksumAddress, amount: Amount) -> Call[None]:
368
+ def approve(self, account: ChecksumAddress, amount: Shares) -> Call[None]:
369
369
  return self._write("approve(address,uint256)", account, amount)
370
370
 
371
371
  def transfer_from(
372
- self, _from: ChecksumAddress, to: ChecksumAddress, amount: Amount
372
+ self, _from: ChecksumAddress, to: ChecksumAddress, amount: Shares
373
373
  ) -> Call[None]:
374
374
  return self._write("transferFrom(address,address,uint256)", _from, to, amount)
375
375
 
376
- def balance_of(self, account: ChecksumAddress) -> Call[Amount]:
376
+ def balance_of(self, account: ChecksumAddress) -> Call[Shares]:
377
377
  return self._view(
378
- "balanceOf(address)", account, output_types=["uint256"], decoder=Amount
378
+ "balanceOf(address)", account, output_types=["uint256"], decoder=Shares
379
379
  )
380
380
 
381
- def get_total_supply_cap(self) -> Call[Amount]:
381
+ def get_total_supply_cap(self) -> Call[Shares]:
382
+ """The maximum total supply, in shares (``2**256 - 1``: uncapped)."""
382
383
  return self._view(
383
- "getTotalSupplyCap()", output_types=["uint256"], decoder=Amount
384
+ "getTotalSupplyCap()", output_types=["uint256"], decoder=Shares
384
385
  )
385
386
 
386
387
  def max_withdraw(self, account: ChecksumAddress) -> Call[Amount]:
@@ -418,8 +419,8 @@ class PlasmaVault(ContractWrapper):
418
419
  def total_assets(self) -> Call[Amount]:
419
420
  return self._view("totalAssets()", output_types=["uint256"], decoder=Amount)
420
421
 
421
- def total_supply(self) -> Call[Amount]:
422
- return self._view("totalSupply()", output_types=["uint256"], decoder=Amount)
422
+ def total_supply(self) -> Call[Shares]:
423
+ return self._view("totalSupply()", output_types=["uint256"], decoder=Shares)
423
424
 
424
425
  def name(self) -> Call[str]:
425
426
  return self._view("name()", output_types=["string"])
@@ -13,6 +13,7 @@ from dataclasses import dataclass, field
13
13
  from typing import Protocol, TypeVar
14
14
 
15
15
  from eth_typing import ChecksumAddress
16
+ from web3.types import Timestamp
16
17
 
17
18
  from ipor_fusion.core.context import Web3Context
18
19
  from ipor_fusion.core.plasma_vault import BalanceFuse
@@ -24,7 +25,15 @@ from ipor_fusion.dependency_graph import (
24
25
  from ipor_fusion.market_ids import IporFusionMarkets
25
26
  from ipor_fusion.readers.lending_health import VaultLendingHealth
26
27
  from ipor_fusion.substrates import decode_substrate, format_market_label
27
- from ipor_fusion.types import ChainId, FuseAddress, MarketId, PlasmaVaultAddress
28
+ from ipor_fusion.types import (
29
+ Amount,
30
+ ChainId,
31
+ Decimals,
32
+ FuseAddress,
33
+ MarketId,
34
+ PlasmaVaultAddress,
35
+ Shares,
36
+ )
28
37
  from ipor_fusion.types import Check as Check
29
38
  from ipor_fusion.types import Severity as Severity
30
39
  from ipor_fusion.types import Verdict as Verdict
@@ -57,9 +66,9 @@ class WithdrawQueue(Protocol):
57
66
  """The WithdrawManager state the reconciliation needs."""
58
67
 
59
68
  @property
60
- def shares_to_release(self) -> int: ...
69
+ def shares_to_release(self) -> Shares: ...
61
70
  @property
62
- def shares_to_release_assets(self) -> int | None: ...
71
+ def shares_to_release_assets(self) -> Amount | None: ...
63
72
 
64
73
 
65
74
  class UnpriceableToken(Protocol):
@@ -97,19 +106,19 @@ class VaultState(Protocol):
97
106
  @property
98
107
  def block_number(self) -> int: ...
99
108
  @property
100
- def block_timestamp(self) -> int: ...
109
+ def block_timestamp(self) -> Timestamp: ...
101
110
  @property
102
111
  def asset(self) -> ChecksumAddress: ...
103
112
  @property
104
113
  def asset_symbol(self) -> str: ...
105
114
  @property
106
- def asset_decimals(self) -> int: ...
115
+ def asset_decimals(self) -> Decimals: ...
107
116
  @property
108
117
  def asset_price_usd(self) -> float | None: ...
109
118
  @property
110
- def total_assets(self) -> int: ...
119
+ def total_assets(self) -> Amount: ...
111
120
  @property
112
- def underlying_balance_on_vault(self) -> int: ...
121
+ def underlying_balance_on_vault(self) -> Amount: ...
113
122
  @property
114
123
  def price_oracle_addr(self) -> ChecksumAddress: ...
115
124
  @property
@@ -129,7 +138,7 @@ class VaultState(Protocol):
129
138
  @property
130
139
  def unimplemented_getters(self) -> Sequence[str]: ...
131
140
  @property
132
- def market_balances(self) -> Mapping[MarketId, int]: ...
141
+ def market_balances(self) -> Mapping[MarketId, Amount]: ...
133
142
  @property
134
143
  def erc20(self) -> Erc20Holdings: ...
135
144
 
@@ -18,7 +18,7 @@ from typing import Any, TypeVar
18
18
  from eth_typing import HexAddress
19
19
  from eth_utils import function_signature_to_4byte_selector
20
20
  from web3 import Web3
21
- from web3.types import ChecksumAddress
21
+ from web3.types import ChecksumAddress, Timestamp
22
22
 
23
23
  from ipor_fusion.cache import CacheKey, cache_get, cache_key, cache_put
24
24
  from ipor_fusion.chains import ensure_supported_chain
@@ -57,10 +57,13 @@ from ipor_fusion.readers.unpriceable import (
57
57
  from ipor_fusion.substrates import decode_substrate
58
58
  from ipor_fusion.types import (
59
59
  AccessManagerAddress,
60
+ Amount,
60
61
  ChainId,
62
+ Decimals,
61
63
  FuseAddress,
62
64
  MarketId,
63
65
  PlasmaVaultAddress,
66
+ Shares,
64
67
  parse_hex_address,
65
68
  )
66
69
  from ipor_fusion.vault_erc20 import Erc20Holdings, _Asset, _read_erc20_holdings
@@ -115,12 +118,12 @@ class VaultData:
115
118
  chain_id: ChainId
116
119
  block_number: int
117
120
  is_latest: bool
118
- block_timestamp: int
119
- share_decimals: int
120
- asset_decimals: int
121
- total_assets: int
122
- total_supply: int
123
- supply_cap: int
121
+ block_timestamp: Timestamp
122
+ share_decimals: Decimals
123
+ asset_decimals: Decimals
124
+ total_assets: Amount
125
+ total_supply: Shares
126
+ supply_cap: Shares
124
127
  asset: ChecksumAddress
125
128
  asset_symbol: str
126
129
  access_manager: AccessManagerAddress
@@ -135,10 +138,10 @@ class VaultData:
135
138
  # totalAssets via ERC4626 base accounting; by design NOT tracked as an
136
139
  # ERC20_VAULT_BALANCE substrate, so it is read directly in
137
140
  # fetch_vault_data rather than derived from substrates.
138
- underlying_balance_on_vault: int = 0
141
+ underlying_balance_on_vault: Amount = Amount(0)
139
142
  vault_name: str = ""
140
143
  deployment_block: int | None = None
141
- deployment_timestamp: int | None = None
144
+ deployment_timestamp: Timestamp | None = None
142
145
  # Short error code from `fetch_deployment_info` when block/timestamp could
143
146
  # not be resolved. Surfaced in CLI output and the JSON `deployment` field
144
147
  # so users can distinguish "lookup not attempted/failed" from
@@ -186,7 +189,7 @@ class VaultData:
186
189
  unimplemented_getters: list[str] = field(default_factory=list)
187
190
  # totalAssetsInMarket of every balance-fuse market, once per market, in
188
191
  # balance-fuse order.
189
- market_balances: dict[MarketId, int] = field(default_factory=dict)
192
+ market_balances: dict[MarketId, Amount] = field(default_factory=dict)
190
193
  # The ERC20_VAULT_BALANCE tokens read directly (empty without that market).
191
194
  erc20: Erc20Holdings = field(default_factory=Erc20Holdings)
192
195
  # Symbols (lowercase address keys) of the substrate and breakdown tokens,
@@ -215,7 +218,7 @@ def _fuse_market_id_call(fuse: ChecksumAddress) -> Call[int]:
215
218
  )
216
219
 
217
220
 
218
- _UNCAPPED = 2**256 - 1
221
+ _UNCAPPED = Shares(2**256 - 1)
219
222
 
220
223
 
221
224
  def _missing_required_getter(
@@ -285,7 +288,7 @@ def resolve_token_decimals(ctx: Web3Context, address: HexAddress) -> int | None:
285
288
  return decimals
286
289
 
287
290
 
288
- def _block_timestamp(ctx: Web3Context, block: int) -> int:
291
+ def _block_timestamp(ctx: Web3Context, block: int) -> Timestamp:
289
292
  return ctx.web3.eth.get_block(block)["timestamp"]
290
293
 
291
294
 
@@ -435,7 +438,7 @@ class _MarketReads:
435
438
  # Only markets with at least one substrate.
436
439
  market_substrates: dict[MarketId, list[bytes]]
437
440
  aave_pools: dict[MarketId, ChecksumAddress]
438
- market_balances: dict[MarketId, int]
441
+ market_balances: dict[MarketId, Amount]
439
442
  # False when the vault predates getMarketSubstrates (substrates unknown).
440
443
  substrates_available: bool = True
441
444
 
@@ -763,7 +766,7 @@ def fetch_deployment_info(
763
766
  vault_address: PlasmaVaultAddress,
764
767
  *,
765
768
  api_key: str | None = None,
766
- ) -> tuple[int | None, int | None, str | None]:
769
+ ) -> tuple[int | None, Timestamp | None, str | None]:
767
770
  """Return ``(block, timestamp, error)`` for the vault deployment, cached in
768
771
  ``ctx.cache``.
769
772
 
@@ -781,7 +784,7 @@ def fetch_deployment_info(
781
784
  block = _cached_int(cached, block_key)
782
785
  stamp = _cached_int(cached, time_key)
783
786
  if block is not None and stamp is not None:
784
- return block, stamp, None
787
+ return block, Timestamp(stamp), None
785
788
 
786
789
  tx_hash, error = get_deployment_tx(chain_id, vault_address, api_key)
787
790
  if not tx_hash:
@@ -790,7 +793,7 @@ def fetch_deployment_info(
790
793
  try:
791
794
  tx = ctx.web3.eth.get_transaction(tx_hash)
792
795
  block_number: int = tx["blockNumber"]
793
- timestamp: int = ctx.web3.eth.get_block(block_number)["timestamp"]
796
+ timestamp: Timestamp = ctx.web3.eth.get_block(block_number)["timestamp"]
794
797
  except Exception:
795
798
  return None, None, "rpc-fetch-failed"
796
799
  cache_put(ctx.cache, {block_key: str(block_number), time_key: str(timestamp)})
@@ -5,21 +5,21 @@ middleware price per token, valued in the underlying asset. Part of the
5
5
  from __future__ import annotations
6
6
 
7
7
  from collections.abc import Callable, Mapping, Sequence
8
- from concurrent.futures import Future, ThreadPoolExecutor
9
8
  from dataclasses import dataclass
10
- from functools import partial
9
+ from typing import Any
11
10
 
12
11
  from eth_typing import ChecksumAddress, HexAddress
13
12
  from web3 import Web3
14
13
 
15
14
  from ipor_fusion.core.context import Web3Context
15
+ from ipor_fusion.core.contract import Call
16
16
  from ipor_fusion.core.erc20 import ERC20
17
+ from ipor_fusion.core.multicall import Multicall3
17
18
  from ipor_fusion.core.oracle import PriceOracleMiddleware
18
19
  from ipor_fusion.core.plasma_vault import BalanceFuse
19
20
  from ipor_fusion.market_ids import IporFusionMarkets
20
21
  from ipor_fusion.substrates import decode_substrate
21
22
  from ipor_fusion.types import MarketId, PlasmaVaultAddress
22
- from ipor_fusion.vault_reads import _safe_call
23
23
 
24
24
 
25
25
  @dataclass(frozen=True)
@@ -113,36 +113,32 @@ def _read_tokens(
113
113
  addresses: list[HexAddress],
114
114
  resolve_symbol: Callable[[HexAddress], str],
115
115
  ) -> list[_TokenRead]:
116
- with ThreadPoolExecutor() as pool:
117
- futures: list[tuple[HexAddress, Future, Future, Future, Future]] = []
118
- for address in addresses:
119
- checksum = Web3.to_checksum_address(address)
120
- token = ERC20(checksum)
121
- futures.append(
122
- (
123
- address,
124
- pool.submit(resolve_symbol, address),
125
- pool.submit(_safe_call, partial(token.decimals().call, ctx)),
126
- pool.submit(_safe_call, partial(token.balance_of(vault).call, ctx)),
127
- pool.submit(
128
- _safe_call,
129
- partial(oracle.get_asset_price(checksum).call, ctx),
130
- ),
131
- )
116
+ """Every token's decimals, the vault's balance and the middleware price in
117
+ one Multicall3 batch (a read that fails is ``None``); symbols through
118
+ ``resolve_symbol``."""
119
+ calls: list[Call[Any]] = []
120
+ for address in addresses:
121
+ checksum = Web3.to_checksum_address(address)
122
+ token = ERC20(checksum)
123
+ calls += [
124
+ token.decimals(),
125
+ token.balance_of(vault),
126
+ oracle.get_asset_price(checksum),
127
+ ]
128
+ results = Multicall3(ctx).try_aggregate(calls)
129
+ reads: list[_TokenRead] = []
130
+ for index, address in enumerate(addresses):
131
+ decimals, balance, price = results[3 * index : 3 * index + 3]
132
+ reads.append(
133
+ _TokenRead(
134
+ address=address,
135
+ symbol=resolve_symbol(address) or "?",
136
+ decimals=decimals,
137
+ balance=balance,
138
+ price_usd=price.readable() if price else None,
132
139
  )
133
- reads: list[_TokenRead] = []
134
- for address, symbol, decimals, balance, price in futures:
135
- price_value = price.result()
136
- reads.append(
137
- _TokenRead(
138
- address=address,
139
- symbol=symbol.result() or "?",
140
- decimals=decimals.result(),
141
- balance=balance.result(),
142
- price_usd=price_value.readable() if price_value else None,
143
- )
144
- )
145
- return reads
140
+ )
141
+ return reads
146
142
 
147
143
 
148
144
  def _value_tokens(
@@ -645,7 +645,7 @@ def _supply_cap_json(data: VaultData) -> dict[str, Any]:
645
645
  "formatted": (
646
646
  "unlimited"
647
647
  if data.supply_cap == UINT256_MAX
648
- else _format_amount(data.supply_cap, data.asset_decimals)
648
+ else _format_amount(data.supply_cap, data.share_decimals)
649
649
  ),
650
650
  }
651
651
 
@@ -7,7 +7,7 @@ from dataclasses import dataclass, field
7
7
  from functools import partial
8
8
 
9
9
  from eth_abi.exceptions import DecodingError
10
- from web3.types import ChecksumAddress
10
+ from web3.types import ChecksumAddress, Timestamp
11
11
 
12
12
  from ipor_fusion.core.context import Web3Context
13
13
  from ipor_fusion.core.fee_manager import (
@@ -19,7 +19,7 @@ from ipor_fusion.core.fee_manager import (
19
19
  from ipor_fusion.core.plasma_vault import PlasmaVault
20
20
  from ipor_fusion.core.withdraw_manager import AccountRequest, WithdrawManager
21
21
  from ipor_fusion.errors import EmptyCallResultError
22
- from ipor_fusion.types import ZERO_ADDRESS, Shares
22
+ from ipor_fusion.types import ZERO_ADDRESS, Amount, Fee, Period, Shares
23
23
  from ipor_fusion.vault_reads import _read_batch, _safe_call, _scan_or_none
24
24
 
25
25
 
@@ -41,15 +41,15 @@ class FeeData:
41
41
  configurations have drifted.
42
42
  """
43
43
 
44
- performance_fee_vault_bps: int | None = None
45
- management_fee_vault_bps: int | None = None
46
- management_fee_last_update: int | None = None
47
- unrealized_management_fee: int | None = None
44
+ performance_fee_vault_bps: Fee | None = None
45
+ management_fee_vault_bps: Fee | None = None
46
+ management_fee_last_update: Timestamp | None = None
47
+ unrealized_management_fee: Amount | None = None
48
48
  fee_manager: ChecksumAddress | None = None
49
49
  ipor_dao_fee_recipient: ChecksumAddress | None = None
50
- deposit_fee_wad: int | None = None
51
- performance_fee_manager_bps: int | None = None
52
- management_fee_manager_bps: int | None = None
50
+ deposit_fee_wad: Fee | None = None
51
+ performance_fee_manager_bps: Fee | None = None
52
+ management_fee_manager_bps: Fee | None = None
53
53
  performance_fee_recipients: list[RecipientFee] | None = None
54
54
  management_fee_recipients: list[RecipientFee] | None = None
55
55
  high_water_mark: HighWaterMarkPerformanceFee | None = None
@@ -59,21 +59,21 @@ class FeeData:
59
59
  class WithdrawManagerData:
60
60
  """On-chain state snapshot from the WithdrawManager contract."""
61
61
 
62
- withdraw_window: int
62
+ withdraw_window: Period
63
63
  # None (not 0) when the getter failed: a zero fee and an unreadable fee
64
64
  # are different facts, and the fees section reports both.
65
- request_fee: int | None
66
- withdraw_fee: int | None
67
- shares_to_release: int
68
- last_release_funds_timestamp: int
65
+ request_fee: Fee | None
66
+ withdraw_fee: Fee | None
67
+ shares_to_release: Shares
68
+ last_release_funds_timestamp: Timestamp
69
69
  # None when the WithdrawRequestUpdated scan or its reads failed.
70
70
  pending_requests: list[AccountRequest] | None
71
71
  # convertToAssets of shares_to_release at the snapshot block; None when
72
72
  # the read failed (or there is nothing to release: 0).
73
- shares_to_release_assets: int | None = None
73
+ shares_to_release_assets: Amount | None = None
74
74
  # convertToAssets of each pending request's shares, by account; a failed
75
75
  # read is None.
76
- pending_request_assets: dict[ChecksumAddress, int | None] = field(
76
+ pending_request_assets: dict[ChecksumAddress, Amount | None] = field(
77
77
  default_factory=dict
78
78
  )
79
79
 
@@ -108,11 +108,11 @@ def _fetch_withdraw_manager(
108
108
  ],
109
109
  )
110
110
  return address, WithdrawManagerData(
111
- withdraw_window=window or 0,
111
+ withdraw_window=window or Period(0),
112
112
  request_fee=request_fee,
113
113
  withdraw_fee=withdraw_fee,
114
114
  shares_to_release=to_release,
115
- last_release_funds_timestamp=last_ts or 0,
115
+ last_release_funds_timestamp=last_ts or Timestamp(0),
116
116
  pending_requests=pending,
117
117
  shares_to_release_assets=release_assets,
118
118
  pending_request_assets={
@@ -49,6 +49,7 @@ def sequential_multicall():
49
49
  patch("ipor_fusion.vault_reads.Multicall3", SequentialMulticall),
50
50
  patch("ipor_fusion.vault_positions.Multicall3", SequentialMulticall),
51
51
  patch("ipor_fusion.vault_data.Multicall3", SequentialMulticall),
52
+ patch("ipor_fusion.vault_erc20.Multicall3", SequentialMulticall),
52
53
  ):
53
54
  yield
54
55
 
@@ -11,7 +11,7 @@ Share decimals: 6
11
11
  Asset price: N/A
12
12
  Total Assets: 0.0 USDC
13
13
  Total Supply: 0.0 shares
14
- Supply Cap: 1,000,000.0 USDC
14
+ Supply Cap: 1,000,000.0 shares
15
15
  Access Manager: 0x00000000000000000000000000000000000000AC
16
16
  Price Oracle: 0x000000000000000000000000000000000000000C
17
17
  Rewards Manager: N/A
@@ -752,6 +752,7 @@ class FakeBalanceFuse:
752
752
  fuse: str
753
753
 
754
754
 
755
+ @pytest.mark.usefixtures("sequential_multicall")
755
756
  class TestPrintErc20Balances:
756
757
  @pytest.fixture(autouse=True)
757
758
  def _tmp_config(self, tmp_path, monkeypatch):
@@ -1227,6 +1228,7 @@ class TestPrintReconciliation:
1227
1228
  assert "MISMATCH" in captured.out
1228
1229
 
1229
1230
 
1231
+ @pytest.mark.usefixtures("sequential_multicall")
1230
1232
  class TestErc20BalancesNotes:
1231
1233
  @pytest.fixture(autouse=True)
1232
1234
  def _tmp_config(self, tmp_path, monkeypatch):