ipor-fusion 4.0.0.dev144__tar.gz → 4.0.0.dev146__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 (300) hide show
  1. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/AGENTS.md +2 -1
  2. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/MIGRATION_4.md +32 -16
  3. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +2 -2
  5. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +2 -1
  6. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/pyproject.toml +1 -1
  7. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_cmd.py +3 -1
  8. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fusion_factory.py +9 -8
  9. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/simulation.py +3 -3
  10. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/contracts.py +5 -2
  11. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/lane.py +2 -2
  12. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/contracts.py +8 -4
  13. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/lane.py +17 -8
  14. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/contracts.py +3 -2
  15. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/lane.py +2 -2
  16. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/server.py +1 -1
  17. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/lending_health.py +1 -1
  18. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/oracle_mapping.py +1 -0
  19. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/validation.py +16 -16
  20. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_data.py +14 -4
  21. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_commands.py +9 -0
  22. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_oracle_mapping.py +4 -4
  23. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_server.py +2 -2
  24. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_oracle_mapping.py +3 -3
  25. ipor_fusion-4.0.0.dev146/tests/test_vault_data_block.py +34 -0
  26. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.claude-plugin/marketplace.json +0 -0
  27. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.claude-plugin/plugin.json +0 -0
  28. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.env.example +0 -0
  29. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.gemini/settings.json +0 -0
  30. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.git-blame-ignore-revs +0 -0
  31. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/cd.yml +0 -0
  32. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/ci.yml +0 -0
  33. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/commit-lint.yml +0 -0
  34. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/notify-slack.yml +0 -0
  35. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/python-build.yml +0 -0
  36. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/python-publish.yml +0 -0
  37. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/release.yml +0 -0
  38. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.github/workflows/report-slack.yml +0 -0
  39. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.gitignore +0 -0
  40. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.mcp.json +0 -0
  41. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.pre-commit-config.yaml +0 -0
  42. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/.python-version +0 -0
  43. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/CHANGELOG.md +0 -0
  44. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/CLAUDE.md +0 -0
  45. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/CODEOWNERS +0 -0
  46. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/LICENSE +0 -0
  47. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/README.md +0 -0
  48. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/build.sh +0 -0
  49. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/CONTRIBUTING.md +0 -0
  50. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/README.md +0 -0
  51. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  52. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  53. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/external_state_margin_leg_base.py +0 -0
  54. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/examples/simple_aave_v3_supply_base.py +0 -0
  55. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/pytest.ini +0 -0
  56. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/scripts/commit-check.sh +0 -0
  57. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/scripts/crosschain_readiness.py +0 -0
  58. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/skills/ipor-deploy-vault/SKILL.md +0 -0
  59. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/__init__.py +0 -0
  60. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/about.py +0 -0
  61. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cache.py +0 -0
  62. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/chains.py +0 -0
  63. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/__init__.py +0 -0
  64. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  65. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/config_cmd.py +0 -0
  66. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/config_store.py +0 -0
  67. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/main.py +0 -0
  68. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/market_cmd.py +0 -0
  69. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/options.py +0 -0
  70. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  71. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_health.py +0 -0
  72. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  73. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  74. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  75. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/config/__init__.py +0 -0
  76. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/config/roles.py +0 -0
  77. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/__init__.py +0 -0
  78. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/access.py +0 -0
  79. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/address.py +0 -0
  80. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/context.py +0 -0
  81. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/contract.py +0 -0
  82. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/erc20.py +0 -0
  83. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/external_state_executor.py +0 -0
  84. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fee_manager.py +0 -0
  85. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fuse.py +0 -0
  86. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fuse_action.py +0 -0
  87. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/logs.py +0 -0
  88. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/multicall.py +0 -0
  89. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/oracle.py +0 -0
  90. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/plasma_vault.py +0 -0
  91. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/rewards_manager.py +0 -0
  92. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  93. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/__init__.py +0 -0
  94. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  95. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  96. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  97. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  98. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  99. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  100. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  101. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/discovery.py +0 -0
  102. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/errors.py +0 -0
  103. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  104. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  105. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  106. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  107. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/logs.py +0 -0
  108. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/messages.py +0 -0
  109. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/simulation.py +0 -0
  110. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  111. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  112. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  113. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/transport.py +0 -0
  114. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/dependency_graph.py +0 -0
  115. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/entry_cli.py +0 -0
  116. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/entry_mcp.py +0 -0
  117. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/errors.py +0 -0
  118. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/explorer.py +0 -0
  119. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/field_docs.py +0 -0
  120. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/__init__.py +0 -0
  121. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  122. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/async_action.py +0 -0
  123. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/base.py +0 -0
  124. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  125. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/erc4626.py +0 -0
  126. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  127. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/events.py +0 -0
  128. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/external_state.py +0 -0
  129. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  130. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  131. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  132. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/hypercore.py +0 -0
  133. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  134. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/merkl.py +0 -0
  135. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/morpho.py +0 -0
  136. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  137. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  138. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/universal.py +0 -0
  139. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/__init__.py +0 -0
  140. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/architecture.md +0 -0
  141. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/glossary.md +0 -0
  142. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/invariants.md +0 -0
  143. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/quickstart.md +0 -0
  144. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/market_ids.py +0 -0
  145. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/market_info.py +0 -0
  146. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/__init__.py +0 -0
  147. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/guide.py +0 -0
  148. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/models.py +0 -0
  149. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/morpho_api.py +0 -0
  150. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/py.typed +0 -0
  151. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/__init__.py +0 -0
  152. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/aave_v3.py +0 -0
  153. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/aave_v4.py +0 -0
  154. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/compound_v3.py +0 -0
  155. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/hypercore.py +0 -0
  156. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  157. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/morpho.py +0 -0
  158. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  159. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  160. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/position_manager.py +0 -0
  161. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  162. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  163. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/unpriceable.py +0 -0
  164. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/substrates.py +0 -0
  165. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/types.py +0 -0
  166. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_erc20.py +0 -0
  167. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_info.py +0 -0
  168. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_managers.py +0 -0
  169. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_positions.py +0 -0
  170. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_reads.py +0 -0
  171. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/UniswapV3UniversalRouter.py +0 -0
  172. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_crosschain.py +0 -0
  173. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_crosschain_lifecycle.py +0 -0
  174. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_crosschain_recovery.py +0 -0
  175. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_euler_v2.py +0 -0
  176. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_foundry.py +0 -0
  177. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_guide.py +0 -0
  178. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_hypercore_model.py +0 -0
  179. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_hypercore_shadow.py +0 -0
  180. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_multicall.py +0 -0
  181. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_simulate.py +0 -0
  182. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/_validation.py +0 -0
  183. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/addresses.py +0 -0
  184. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/conftest.py +0 -0
  185. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/constants.py +0 -0
  186. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  187. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/fixtures/hypercore_market55.json +0 -0
  188. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/clean.json +0 -0
  189. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  190. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/rich.json +0 -0
  191. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  192. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_about.py +0 -0
  193. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_access_manager.py +0 -0
  194. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cache.py +0 -0
  195. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_chains.py +0 -0
  196. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_changelog.py +0 -0
  197. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_config_store.py +0 -0
  198. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_helpers.py +0 -0
  199. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_hypercore.py +0 -0
  200. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_lending_health.py +0 -0
  201. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_market_morpho_blue.py +0 -0
  202. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_pending_requests_scan.py +0 -0
  203. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_role_accounts.py +0 -0
  204. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_vault_info_golden.py +0 -0
  205. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_vault_predates_getters.py +0 -0
  206. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_cli_vault_validate.py +0 -0
  207. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_contract_call.py +0 -0
  208. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_contract_encoder.py +0 -0
  209. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_ccip_events.py +0 -0
  210. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_ccip_indexer.py +0 -0
  211. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_ccip_quote.py +0 -0
  212. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_chainlink.py +0 -0
  213. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_contracts.py +0 -0
  214. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_encoding.py +0 -0
  215. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_errors.py +0 -0
  216. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_lane.py +0 -0
  217. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_layering.py +0 -0
  218. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_readiness.py +0 -0
  219. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_readiness_report.py +0 -0
  220. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_transport.py +0 -0
  221. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_docs_typing.py +0 -0
  222. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_erc20.py +0 -0
  223. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_error_hierarchy.py +0 -0
  224. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_examples_composed_hypercore.py +0 -0
  225. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_examples_crosschain.py +0 -0
  226. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  227. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_examples_vaults.py +0 -0
  228. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_explorer.py +0 -0
  229. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_external_state_events.py +0 -0
  230. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_external_state_executor.py +0 -0
  231. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_fee_manager.py +0 -0
  232. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_fuse_encoding.py +0 -0
  233. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_fusion_instance.py +0 -0
  234. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_guide.py +0 -0
  235. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_deployment.py +0 -0
  236. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_events.py +0 -0
  237. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_flow.py +0 -0
  238. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_live.py +0 -0
  239. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_outcome.py +0 -0
  240. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_readers.py +0 -0
  241. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_simulation.py +0 -0
  242. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_logs.py +0 -0
  243. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_hypercore.py +0 -0
  244. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_models.py +0 -0
  245. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_vault_validate.py +0 -0
  246. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_multicall.py +0 -0
  247. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_oracle.py +0 -0
  248. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_plasma_vault.py +0 -0
  249. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_public_api.py +0 -0
  250. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_public_type_reachability.py +0 -0
  251. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_reader_encoding.py +0 -0
  252. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_readiness_selection.py +0 -0
  253. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_revert_decoding.py +0 -0
  254. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_rewards_manager.py +0 -0
  255. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_roles.py +0 -0
  256. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_sdk_layering.py +0 -0
  257. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  258. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  259. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  260. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_canary.py +0 -0
  261. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_hypercore.py +0 -0
  262. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  263. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  264. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_examples_arbitrum.py +0 -0
  270. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_examples_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_examples_crosschain.py +0 -0
  272. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_external_state_executor_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  275. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_morpho_blue.py +0 -0
  277. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v3.py +0 -0
  278. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  279. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  280. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  281. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  282. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_readers_integration.py +0 -0
  283. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_release_funds.py +0 -0
  284. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_swap_on_base.py +0 -0
  285. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  286. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_solidity_compiler.py +0 -0
  287. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_solidity_mirrors.py +0 -0
  288. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_substrates.py +0 -0
  289. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_types_and_events.py +0 -0
  290. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_unpriceable_tokens.py +0 -0
  291. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_validation.py +0 -0
  292. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_vault_dep_graph.py +0 -0
  293. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_vault_info_onchain_base.py +0 -0
  294. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  295. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_vault_simulator_blocks.py +0 -0
  296. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_web3_context.py +0 -0
  297. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/test_withdraw_manager.py +0 -0
  298. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/typed_client/consumer.py +0 -0
  299. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/tests/typed_client/rejected.py +0 -0
  300. {ipor_fusion-4.0.0.dev144 → ipor_fusion-4.0.0.dev146}/uv.lock +0 -0
@@ -441,7 +441,8 @@ are not validated.
441
441
  ## Domain rules (verified against the contracts)
442
442
 
443
443
  - `fetch_vault_data(ctx, vault, *, block=, lending=, metadata=)` is the snapshot:
444
- it resolves one block (``block``, else a numeric `ctx.default_block`, else latest),
444
+ it resolves one block (``block``, else `ctx.default_block` with a tag or hash resolved to its
445
+ number, else latest),
445
446
  runs every read on `ctx.at_block(block)` (log scans end there too) and returns
446
447
  everything validation and the ``vault info`` document need, so neither reads the
447
448
  chain. The chain is `ctx.chain_id`; ``lending=False`` skips lending health, the
@@ -94,7 +94,7 @@ placeholder wrapper.
94
94
  `aggregate3(calls)` still returns the raw `Call`), `VaultSimulator`,
95
95
  `CrosschainSimulator` and `OracleMappingReader(ctx, oracle)`.
96
96
  - Resolvers that probe the chain stay eager and return context-free wrappers:
97
- `price_oracle_middleware(ctx, oracle)`, `resolve_access_manager(ctx, vault)`,
97
+ `price_oracle_middleware(ctx, address)`, `resolve_access_manager(ctx, vault)`,
98
98
  `ExternalStateExecutor.for_vault(ctx, vault)`. A resolved oracle carries the ABI
99
99
  found on that chain and block; resolve again for another one.
100
100
  - Crosschain lanes own both contexts: `CcipLane` / `StargateLane` take `hub_ctx=` and
@@ -113,9 +113,9 @@ placeholder wrapper.
113
113
  do not see a CCIP recall or tell a parked command from a pending one. `CrosschainTransport.executor(address)`
114
114
  and `.dispatcher(address)` no longer take a context.
115
115
  - Chain-bound decoding takes the chain explicitly:
116
- `ExternalStateExecutor.pending_proposals(account, *, chain_id)`,
116
+ `ExternalStateExecutor.pending_proposals(balance_account, *, chain_id)`,
117
117
  `parse_balance_proposed(log, *, chain_id)` and
118
- `find_balance_proposed(logs, account=None, *, chain_id)` require it (3.x fell back
118
+ `find_balance_proposed(logs, balance_account=None, *, chain_id)` require it (3.x fell back
119
119
  to the wrapper's context). The decoder captures it, so `.call(ctx)`, `Multicall3`
120
120
  and `VaultSimulator.observe` stamp the same hash for the same bytes, whatever
121
121
  context executes them. Pass `ctx.chain_id` for the chain you read from.
@@ -134,7 +134,7 @@ placeholder wrapper.
134
134
  | `call.build_transaction(block=b)`, `call.estimate_gas(block=b)` | `call.build_transaction(ctx, block=b)`, `call.estimate_gas(ctx, block=b)` |
135
135
  | `call.ctx` | gone: keep the context yourself |
136
136
  | `Multicall3(ctx, address)` | `Multicall3(ctx, address=address)` |
137
- | `executor.pending_proposals(account)` | `executor.pending_proposals(account, chain_id=ctx.chain_id)` |
137
+ | `executor.pending_proposals(balance_account)` | `executor.pending_proposals(balance_account, chain_id=ctx.chain_id)` |
138
138
  | `executor.parse_balance_proposed(log)` / `find_balance_proposed(logs)` | `...(log, chain_id=...)` / `...(logs, chain_id=...)` |
139
139
  | `CcipLane(executor=, dispatcher=, spoke_chain_id=, ...)` | `CcipLane(hub_ctx=, spoke_ctx=, executor=, dispatcher=, ...)` (same for `StargateLane`) |
140
140
  | `transport.executor(ctx, address)` / `transport.dispatcher(ctx, address)` | `transport.executor(address)` / `transport.dispatcher(address)` |
@@ -174,7 +174,8 @@ The vault read is one pinned snapshot, and the chain is always the context's:
174
174
 
175
175
  - `fetch_vault_data(ctx, vault, *, block=None, lending=True, metadata=True)` takes
176
176
  the vault address (not a `PlasmaVault`) and no `chain_id`. It resolves one block
177
- (`block`, else a numeric `ctx.default_block`, else the latest) and runs every
177
+ (`block`, else `ctx.default_block`, with a `safe` / `finalized` tag or a block hash
178
+ resolved to its number, else the latest) and runs every
178
179
  read, log scans included, on `ctx.at_block(block)`; `ctx` is not modified. In 3.x
179
180
  `block_number` only selected the block reported in `VaultData`, and the reads ran
180
181
  wherever `ctx` pointed. The old `chain_id=0` switch is `lending=False`;
@@ -289,6 +290,8 @@ fails at run time:
289
290
  keyword.
290
291
  - `encode_oft_compose_msg(*, nonce=, src_eid=, amount_ld=, compose_msg=)` takes
291
292
  keyword arguments only.
293
+ - `build_oracle_mapping(ctx, vault, *, effective_block, max_depth=6)` takes
294
+ `effective_block` and `max_depth` by keyword.
292
295
  - The vault parameter is named `vault` throughout: `validate_vault`,
293
296
  `fetch_deployment_info`, `fetch_vault_lending_health`, `build_oracle_mapping`,
294
297
  `read_external_state`, `ExternalStateExecutor.for_vault`, `read_hypercore_nav`,
@@ -434,8 +437,8 @@ are cached in a `ContractCache`, injected instead of always written to `~/.cache
434
437
  explicitly (default: the file cache); pass `cache=ctx.cache` to share the
435
438
  context's policy. The CLI's text and JSON output name contracts through the
436
439
  context's cache.
437
- - `build_vault_info_json(data)` without `resolve_name` names no contract (every
438
- `contract` is `"?"`) instead of reading names from the on-disk cache; pass
440
+ - `build_vault_info_json(data)` without `resolve_name` names no contract (fuse and
441
+ balance-fuse entries show `contract` as `"?"`, substrate entries omit it) instead of reading names from the on-disk cache; pass
439
442
  `resolve_name=partial(get_contract_name, chain_id, api_key=..., cache=ctx.cache)`
440
443
  for the CLI's document.
441
444
  - `ipor_fusion.cache` is a public module now and exports `ContractCache`,
@@ -536,13 +539,18 @@ converted amounts `Amount`. A custom `VaultState` annotates the same.
536
539
  `PlasmaVault.total_supply()`, `balance_of()` and `get_total_supply_cap()` return
537
540
  `Shares` (vault shares), and `transfer` / `approve` / `transfer_from` /
538
541
  `set_total_supply_cap` take `Shares`; `ERC20` keeps `Amount`.
542
+ `Reconciliation` amounts (`balance_fuses_total`, `underlying_on_vault`,
543
+ `erc20_direct_total`, `sum`, `on_chain_total_assets`, `delta`,
544
+ `pending_withdrawals`, `implied_market_total`, `market_storage_divergence`) are
545
+ `Amount`. `FusionInstance.asset_decimals` / `underlying_token_decimals` are
546
+ `Decimals`. `VaultSimulator.current_time` is a `Timestamp`.
539
547
 
540
548
  **Vault data and helpers.** `ensure_supported_chain` takes `ChainId`.
541
549
  `fetch_unpriceable_priced_tokens` takes the oracle and underlying as
542
550
  `ChecksumAddress`. `resolve_token_symbol`, `resolve_token_decimals`,
543
551
  `get_contract_name` and `get_deployment_tx` take `HexAddress` (a checksum address
544
- fits), and the `resolve_name` / `resolve_symbol` callbacks receive one (a callback
545
- typed for `str` still fits). Market-keyed data is keyed by `MarketId`:
552
+ fits), and the `resolve_name` callback receives one (a callback typed for `str`
553
+ still fits). Market-keyed data is keyed by `MarketId`:
546
554
  `VaultData.market_substrates`, `dependency_graph`, `morpho_positions`,
547
555
  `aave_positions`, `market_balances`; the `VaultState` protocol,
548
556
  `fetch_vault_lending_health`, `fetch_unpriceable_priced_tokens` and the
@@ -566,8 +574,8 @@ The Uniswap V3 / Ramses V2 position events' `version`, `sender` and `recipient`
566
574
  `MarketId`; `Erc20Holding.address` is `HexAddress`, and `SubstrateInfo.address` is
567
575
  `HexAddress | None`. `LegacyPriceOracleMiddleware` takes `base_currency_decimals:
568
576
  Decimals`, and `PriceOracleMiddleware.base_currency_decimals()` returns
569
- `Call[Decimals]`. `get_logs_adaptive(chain_id=)` and `LogSource.get_logs` take
570
- `ChainId`; `quote_ccip_native_fee(block=)` and
577
+ `Call[Decimals]`. `get_logs_adaptive(chain_id=)` takes `ChainId | None` and
578
+ `LogSource.get_logs` a `ChainId`; `quote_ccip_native_fee(block=)` and
571
579
  `OracleMappingReader.asset_source_events` take a `BlockIdentifier`.
572
580
 
573
581
  **Address roles.** `PlasmaVault(address)`,
@@ -602,7 +610,8 @@ by `ChainId` (`HYPEREVM_CHAIN_ID` is one) and `explorer.SUPPORTED_CHAINS` is a
602
610
  type-checks; `PUBLIC_ALLOCATOR_ADDRESSES` values are `ChecksumAddress`.
603
611
 
604
612
  **Markets, substrates and access.** Every `IporFusionMarkets` member is a `MarketId`.
605
- `decode_substrate`, `market_name`, `format_market_label` take `MarketId`.
613
+ `market_name` and `format_market_label` take `MarketId`, `decode_substrate` a
614
+ `MarketId | None`.
606
615
  `AccessManager.has_role`, `grant_role`, `revoke_role` and `read_accounts_with_role`
607
616
  take `RoleId | Roles`; `RoleAccount.role_id` is `RoleId`.
608
617
 
@@ -630,7 +639,12 @@ on CCIP, `active_return` → `OperationId`, `asset_id`, `tracked_position_set_ha
630
639
  `CcipObservation`, `OutboundMessage`, `CcipMessageSent`, `Any2EVMMessage`,
631
640
  `VerifierResult`, `Packet.guid`, `TaxiMessage`, `StargateChain`; and the helpers
632
641
  `fetch_verifier_result(message_id=)`, `lz_receive_calldata(guid=)` and
633
- `lz_compose_calldata(guid=)` take a `MessageId`. Decoded event values
642
+ `lz_compose_calldata(guid=)` take a `MessageId`. Lane times are typed:
643
+ `last_approved_observed_at()` returns a `Timestamp` and `staleness_max()` (with
644
+ `STALENESS_MAX` / `BALANCE_STALENESS_MAX` on the executors) a `Period`, and so do
645
+ the `LaneHubState` fields; `attestation(remote_timestamp=, expiry=)` and
646
+ `BalanceObservation` take `Timestamp`, `needs_attestation(now=, margin=)` a
647
+ `Timestamp` and a `Period`. Decoded event values
634
648
  (`ccip_events` → `CcipEvent.values`) stay a generic mapping: convert a value
635
649
  before passing it on.
636
650
 
@@ -657,20 +671,22 @@ HyperCore ids; `HyperCorePerpAssetInfo`, `HyperCorePerpMarket`, `HyperCoreSpotLe
657
671
 
658
672
  **External state.** `ExternalStateExecutor.proposal_hash` returns and `confirm_balance`
659
673
  takes a `ProposalHash`; `BalanceProposal.proposal_hash` is a `ProposalHash`;
660
- `ExternalStateEvent.transaction_hash` is a `TxHash`.
674
+ `ExternalStateEvent.transaction_hash` is a `TxHash | None`.
661
675
  `ExternalStateVaultState.last_checked_custodian_timestamp`,
662
676
  `BalanceFuseSnapshot.last_custodian_update_timestamp`, `BalanceProposal.proposed_at`,
663
677
  `last_updated()` and `get_oldest_update_timestamp()` are web3's `Timestamp`, and
664
678
  `ExternalStateExecutor.proposal_hash(proposed_at=)` takes one.
665
679
 
666
- **Errors and explorer.** `TransactionError(tx_hash=)` takes a `TxHash`,
680
+ **Errors and explorer.** `TransactionError(tx_hash=)` takes a `TxHash | None`,
667
681
  `LogScanError(address=)` a `ChecksumAddress`; `get_revert_reason` takes a `TxHash`;
668
682
  `get_deployment_tx` returns a `TxHash` (a malformed hash from Etherscan is reported
669
683
  as the error `malformed-tx-hash`).
670
684
 
671
685
  **MCP response models** (Python constructors only; the JSON schemas are unchanged
672
686
  except as listed under [Behavior changes](#behavior-changes)): `chain_id` fields are
673
- `ChainId`, `role_id` `RoleId`, `market_id` `MarketId`. Calling an MCP tool function
687
+ `ChainId`, `role_id` `RoleId`, `market_id` `MarketId` (except
688
+ `LendingMarketHealth.market_id`, which stays `str | int`: a Morpho market id is a
689
+ string). Calling an MCP tool function
674
690
  directly from Python takes `ChainId`.
675
691
 
676
692
  ## Behavior changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev144
3
+ Version: 4.0.0.dev146
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
@@ -68,7 +68,7 @@ from typing import Any, NamedTuple
68
68
  from eth_abi.abi import encode as abi_encode
69
69
  from eth_utils import keccak
70
70
  from web3 import Web3
71
- from web3.types import BlockIdentifier, RPCEndpoint
71
+ from web3.types import BlockIdentifier, RPCEndpoint, Timestamp
72
72
 
73
73
  from ipor_fusion import (
74
74
  ERC20,
@@ -791,7 +791,7 @@ def _attest(flow: _Tail, tag: str, observation_label: str, *, hub_idle: int = 0)
791
791
  observation,
792
792
  remote_block=flow.spoke.current_block_number,
793
793
  remote_timestamp=flow.spoke.current_time,
794
- expiry=flow.hub.current_time + PROPOSAL_TTL,
794
+ expiry=Timestamp(flow.hub.current_time + PROPOSAL_TTL),
795
795
  )
796
796
  # SIMULATION ONLY: two keyless addresses driven from one process; live
797
797
  # they are two signers, which is the point of the two-key design.
@@ -66,6 +66,7 @@ import sys
66
66
  from typing import NamedTuple
67
67
 
68
68
  from web3 import Web3
69
+ from web3.types import Timestamp
69
70
 
70
71
  from ipor_fusion import (
71
72
  ERC20,
@@ -431,7 +432,7 @@ def _attest(flow: _Flow, tag: str, observation_label: str, *, hub_idle: int = 0)
431
432
  observation,
432
433
  remote_block=flow.spoke.current_block_number,
433
434
  remote_timestamp=flow.spoke.current_time,
434
- expiry=flow.hub.current_time + PROPOSAL_TTL,
435
+ expiry=Timestamp(flow.hub.current_time + PROPOSAL_TTL),
435
436
  )
436
437
  # SIMULATION ONLY: the proposer and the approver are two addresses with no
437
438
  # keys here, driven from one process. Live they are two separate signers,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.0.0.dev144"
3
+ version = "4.0.0.dev146"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -449,7 +449,9 @@ def oracle_mapping(
449
449
  raise click.UsageError(str(exc)) from exc
450
450
 
451
451
  try:
452
- mapping = build_oracle_mapping(ctx, vault_address, effective_block, max_depth)
452
+ mapping = build_oracle_mapping(
453
+ ctx, vault_address, effective_block=effective_block, max_depth=max_depth
454
+ )
453
455
  except UnsupportedVaultVersionError as exc:
454
456
  raise click.ClickException(str(exc)) from exc
455
457
 
@@ -35,6 +35,7 @@ from web3.types import TxReceipt
35
35
  from ipor_fusion.core.contract import Call, ContractWrapper
36
36
  from ipor_fusion.types import (
37
37
  AccessManagerAddress,
38
+ Decimals,
38
39
  Period,
39
40
  PlasmaVaultAddress,
40
41
  parse_access_manager_address,
@@ -64,10 +65,10 @@ class FusionInstance:
64
65
  version: int
65
66
  asset_name: str
66
67
  asset_symbol: str
67
- asset_decimals: int
68
+ asset_decimals: Decimals
68
69
  underlying_token: ChecksumAddress
69
70
  underlying_token_symbol: str
70
- underlying_token_decimals: int
71
+ underlying_token_decimals: Decimals
71
72
  initial_owner: ChecksumAddress
72
73
  plasma_vault: PlasmaVaultAddress
73
74
  plasma_vault_base: ChecksumAddress
@@ -118,10 +119,10 @@ _INSTANCE_FIELDS: dict[str, Callable[[Any], Any]] = {
118
119
  "version": _integer,
119
120
  "asset_name": _text,
120
121
  "asset_symbol": _text,
121
- "asset_decimals": _integer,
122
+ "asset_decimals": lambda value: Decimals(_integer(value)),
122
123
  "underlying_token": _address,
123
124
  "underlying_token_symbol": _text,
124
- "underlying_token_decimals": _integer,
125
+ "underlying_token_decimals": lambda value: Decimals(_integer(value)),
125
126
  "initial_owner": _address,
126
127
  "plasma_vault": lambda value: parse_plasma_vault_address(_text(value)),
127
128
  "plasma_vault_base": _address,
@@ -185,10 +186,10 @@ def _fusion_instance_decoder(values: tuple) -> FusionInstance:
185
186
  version=version,
186
187
  asset_name=asset_name,
187
188
  asset_symbol=asset_symbol,
188
- asset_decimals=asset_decimals,
189
+ asset_decimals=Decimals(asset_decimals),
189
190
  underlying_token=addr(underlying_token),
190
191
  underlying_token_symbol=underlying_token_symbol,
191
- underlying_token_decimals=underlying_token_decimals,
192
+ underlying_token_decimals=Decimals(underlying_token_decimals),
192
193
  initial_owner=addr(initial_owner),
193
194
  plasma_vault=PlasmaVaultAddress(addr(plasma_vault)),
194
195
  plasma_vault_base=addr(plasma_vault_base),
@@ -347,10 +348,10 @@ class FusionFactory(ContractWrapper):
347
348
  version=int(values[1]),
348
349
  asset_name=values[2],
349
350
  asset_symbol=values[3],
350
- asset_decimals=int(values[4]),
351
+ asset_decimals=Decimals(int(values[4])),
351
352
  underlying_token=Web3.to_checksum_address(values[5]),
352
353
  underlying_token_symbol=values[6],
353
- underlying_token_decimals=int(values[7]),
354
+ underlying_token_decimals=Decimals(int(values[7])),
354
355
  initial_owner=Web3.to_checksum_address(values[8]),
355
356
  plasma_vault=PlasmaVaultAddress(Web3.to_checksum_address(values[9])),
356
357
  plasma_vault_base=Web3.to_checksum_address(values[10]),
@@ -10,7 +10,7 @@ from eth_typing import ChecksumAddress
10
10
  from eth_utils import keccak
11
11
  from hexbytes import HexBytes
12
12
  from web3 import Web3
13
- from web3.types import BlockIdentifier, Nonce, RPCEndpoint
13
+ from web3.types import BlockIdentifier, Nonce, RPCEndpoint, Timestamp
14
14
  from web3.utils.address import get_create_address
15
15
 
16
16
  from ipor_fusion.core.contract import Call, _encode_calldata
@@ -221,13 +221,13 @@ class VaultSimulator:
221
221
  return any(block.calls for block in self._blocks)
222
222
 
223
223
  @property
224
- def current_time(self) -> int:
224
+ def current_time(self) -> Timestamp:
225
225
  """Timestamp of the latest sent block or an explicit pending time.
226
226
 
227
227
  A pending block without an explicit time will run 12 seconds later
228
228
  once it contains a call.
229
229
  """
230
- return self._current_time()
230
+ return Timestamp(self._current_time())
231
231
 
232
232
  @property
233
233
  def current_block_number(self) -> int:
@@ -26,6 +26,7 @@ from ipor_fusion.types import (
26
26
  CommandId,
27
27
  MessageId,
28
28
  OperationId,
29
+ Period,
29
30
  PlasmaVaultAddress,
30
31
  PositionSetHash,
31
32
  ProposalId,
@@ -143,8 +144,10 @@ class CcipCrosschainExecutor(CrosschainExecutor):
143
144
  def decimal_conversion_rate(self) -> Call[int]:
144
145
  return self._view("DECIMAL_CONVERSION_RATE()", output_types=["uint256"])
145
146
 
146
- def balance_staleness_max(self) -> Call[int]:
147
- return self._view("BALANCE_STALENESS_MAX()", output_types=["uint256"])
147
+ def balance_staleness_max(self) -> Call[Period]:
148
+ return self._view(
149
+ "BALANCE_STALENESS_MAX()", output_types=["uint256"], decoder=Period
150
+ )
148
151
 
149
152
  def ccip_route(self, chain_id: ChainId) -> Call[CcipRouteConfig]:
150
153
  return self._view(
@@ -22,7 +22,7 @@ from ipor_fusion.crosschain.fuses.ccip import (
22
22
  )
23
23
  from ipor_fusion.crosschain.lane import CrosschainLane, LaneFuses, LaneObservation
24
24
  from ipor_fusion.crosschain.messages import CommandStatus, CrosschainTransportKind
25
- from ipor_fusion.types import Amount, ProposalId
25
+ from ipor_fusion.types import Amount, Period, ProposalId
26
26
 
27
27
 
28
28
  class CcipLane(CrosschainLane):
@@ -89,7 +89,7 @@ class CcipLane(CrosschainLane):
89
89
  ),
90
90
  )
91
91
 
92
- def staleness_max(self) -> Call[int]:
92
+ def staleness_max(self) -> Call[Period]:
93
93
  return self.executor.balance_staleness_max()
94
94
 
95
95
  def default_send(self, *, token: bool) -> CcipSendParams:
@@ -16,6 +16,7 @@ from dataclasses import dataclass
16
16
 
17
17
  from eth_typing import ChecksumAddress
18
18
  from web3 import Web3
19
+ from web3.types import Timestamp
19
20
 
20
21
  from ipor_fusion.core.contract import Call, ContractWrapper
21
22
  from ipor_fusion.crosschain.messages import CommandStatus
@@ -42,8 +43,8 @@ class BalanceObservation:
42
43
  settled_balance: Amount
43
44
  state_version: int
44
45
  remote_block: int
45
- remote_timestamp: int
46
- expiry: int
46
+ remote_timestamp: Timestamp
47
+ expiry: Timestamp
47
48
  tracked_position_set_hash: PositionSetHash
48
49
 
49
50
  def as_tuple(self) -> tuple:
@@ -160,9 +161,12 @@ class CrosschainExecutor(ContractWrapper):
160
161
  def chain_blocked(self, chain_id: ChainId) -> Call[bool]:
161
162
  return self._view("chainBlocked(uint256)", chain_id, output_types=["bool"])
162
163
 
163
- def last_approved_observed_at(self, chain_id: ChainId) -> Call[int]:
164
+ def last_approved_observed_at(self, chain_id: ChainId) -> Call[Timestamp]:
164
165
  return self._view(
165
- "lastApprovedObservedAt(uint256)", chain_id, output_types=["uint64"]
166
+ "lastApprovedObservedAt(uint256)",
167
+ chain_id,
168
+ output_types=["uint64"],
169
+ decoder=Timestamp,
166
170
  )
167
171
 
168
172
  def approve_balance(self, proposal_id: ProposalId) -> Call[None]:
@@ -34,6 +34,7 @@ from typing import Any, ClassVar, cast
34
34
 
35
35
  from eth_abi import decode
36
36
  from eth_typing import ChecksumAddress
37
+ from web3.types import Timestamp
37
38
 
38
39
  from ipor_fusion.core.context import Web3Context
39
40
  from ipor_fusion.core.contract import Call
@@ -52,7 +53,14 @@ from ipor_fusion.crosschain.fuses.base import (
52
53
  )
53
54
  from ipor_fusion.crosschain.logs import log_address, log_bytes, log_topic0
54
55
  from ipor_fusion.crosschain.messages import Command, CrosschainTransportKind
55
- from ipor_fusion.types import Amount, ChainId, FuseAddress, PositionSetHash, ProposalId
56
+ from ipor_fusion.types import (
57
+ Amount,
58
+ ChainId,
59
+ FuseAddress,
60
+ Period,
61
+ PositionSetHash,
62
+ ProposalId,
63
+ )
56
64
 
57
65
 
58
66
  @dataclass(frozen=True, slots=True)
@@ -73,6 +81,7 @@ class LaneObservation:
73
81
  # set pinned to the number the tag resolves to once keeps the tag's meaning.
74
82
  # "pending" names no mined block and is left as is.
75
83
  _MOVING_BLOCK_TAGS = frozenset({"latest", "safe", "finalized"})
84
+ _NO_MARGIN = Period(0)
76
85
 
77
86
 
78
87
  @dataclass(frozen=True, slots=True)
@@ -86,8 +95,8 @@ class LaneHubState:
86
95
  idle_ledger: Amount
87
96
  dispatcher_ready: bool
88
97
  chain_blocked: bool
89
- last_approved_observed_at: int
90
- staleness_max: int
98
+ last_approved_observed_at: Timestamp
99
+ staleness_max: Period
91
100
  remote_state_version: int
92
101
  has_active_command: bool
93
102
  pending_transfer_count: int
@@ -233,7 +242,7 @@ class CrosschainLane(ABC):
233
242
  def chain_blocked(self) -> Call[bool]:
234
243
  return self.executor.chain_blocked(self.spoke_chain_id)
235
244
 
236
- def last_approved_observed_at(self) -> Call[int]:
245
+ def last_approved_observed_at(self) -> Call[Timestamp]:
237
246
  """Remote timestamp of the approved observation NAV freshness is
238
247
  measured from; 0 before the first attestation."""
239
248
  return self.executor.last_approved_observed_at(self.spoke_chain_id)
@@ -279,8 +288,8 @@ class CrosschainLane(ABC):
279
288
  observation: LaneObservation,
280
289
  *,
281
290
  remote_block: int,
282
- remote_timestamp: int,
283
- expiry: int,
291
+ remote_timestamp: Timestamp,
292
+ expiry: Timestamp,
284
293
  ) -> BalanceObservation:
285
294
  """The proposal for ``observation``, read on the spoke at
286
295
  ``remote_block`` / ``remote_timestamp`` and valid on the hub until
@@ -297,7 +306,7 @@ class CrosschainLane(ABC):
297
306
  tracked_position_set_hash=observation.tracked_position_set_hash,
298
307
  )
299
308
 
300
- def needs_attestation(self, *, now: int, margin: int = 0) -> bool:
309
+ def needs_attestation(self, *, now: Timestamp, margin: Period = _NO_MARGIN) -> bool:
301
310
  """Whether a keeper should propose now (up to seven reads on the hub):
302
311
  the settled bucket is non-zero, no transfer or command receipt is in
303
312
  flight, and the approved observation is missing or older than
@@ -362,7 +371,7 @@ class CrosschainLane(ABC):
362
371
  """Bind ``min_received`` into the transport's send parameters."""
363
372
 
364
373
  @abstractmethod
365
- def staleness_max(self) -> Call[int]:
374
+ def staleness_max(self) -> Call[Period]:
366
375
  """Seconds an approved observation keeps the settled bucket in NAV
367
376
  (``STALENESS_MAX`` on Stargate, ``BALANCE_STALENESS_MAX`` on CCIP)."""
368
377
 
@@ -24,6 +24,7 @@ from ipor_fusion.types import (
24
24
  CommandId,
25
25
  DeploymentRequestId,
26
26
  LayerZeroEid,
27
+ Period,
27
28
  PlasmaVaultAddress,
28
29
  PositionSetHash,
29
30
  ProposalId,
@@ -111,8 +112,8 @@ class StargateCrosschainExecutor(CrosschainExecutor):
111
112
  def local_chain_id(self) -> Call[ChainId]:
112
113
  return self._view("LOCAL_CHAIN_ID()", output_types=["uint256"], decoder=ChainId)
113
114
 
114
- def staleness_max(self) -> Call[int]:
115
- return self._view("STALENESS_MAX()", output_types=["uint256"])
115
+ def staleness_max(self) -> Call[Period]:
116
+ return self._view("STALENESS_MAX()", output_types=["uint256"], decoder=Period)
116
117
 
117
118
  def deployment_ttl(self) -> Call[int]:
118
119
  return self._view("DEPLOYMENT_TTL()", output_types=["uint256"])
@@ -25,7 +25,7 @@ from ipor_fusion.crosschain.stargate.contracts import (
25
25
  StargateCrosschainExecutor,
26
26
  )
27
27
  from ipor_fusion.crosschain.stargate.layerzero import OptionsBuilder
28
- from ipor_fusion.types import Amount
28
+ from ipor_fusion.types import Amount, Period
29
29
 
30
30
 
31
31
  class StargateLane(CrosschainLane):
@@ -73,7 +73,7 @@ class StargateLane(CrosschainLane):
73
73
  fuses=fuses,
74
74
  )
75
75
 
76
- def staleness_max(self) -> Call[int]:
76
+ def staleness_max(self) -> Call[Period]:
77
77
  return self.executor.staleness_max()
78
78
 
79
79
  def default_send(self, *, token: bool) -> StargateSendParams:
@@ -398,7 +398,7 @@ def vault_oracle_mapping(
398
398
 
399
399
  resolve_access_manager(ctx, checksum)
400
400
 
401
- mapping = build_oracle_mapping(ctx, checksum, effective_block)
401
+ mapping = build_oracle_mapping(ctx, checksum, effective_block=effective_block)
402
402
  return OracleMappingResponse.from_mapping(mapping)
403
403
 
404
404
 
@@ -94,7 +94,7 @@ class LendingMarketHealth:
94
94
  """Health status of a single lending market position.
95
95
 
96
96
  `substrate_id` identifies the underlying market within the IPOR market_id:
97
- - Morpho: the 64-char hex morpho market id (one row per substrate).
97
+ - Morpho: the morpho market id (`0x` + 64 lowercase hex) (one row per substrate).
98
98
  - Aave V3: None — Aave health is account-level, aggregated across reserves.
99
99
  - Aave V4: the Spoke address — account-level per Spoke, across its reserves.
100
100
  """
@@ -428,6 +428,7 @@ def _enumerate_assets(
428
428
  def build_oracle_mapping(
429
429
  ctx: Web3Context,
430
430
  vault: PlasmaVaultAddress,
431
+ *,
431
432
  effective_block: int,
432
433
  max_depth: int = 6,
433
434
  ) -> OracleMapping:
@@ -165,29 +165,29 @@ class Reconciliation:
165
165
  only; ``erc20_direct_total`` is informational and never added to it.
166
166
  """
167
167
 
168
- balance_fuses_total: int
168
+ balance_fuses_total: Amount
169
169
  balance_fuses_total_usd: float
170
- underlying_on_vault: int
170
+ underlying_on_vault: Amount
171
171
  underlying_on_vault_usd: float
172
- erc20_direct_total: int
172
+ erc20_direct_total: Amount
173
173
  erc20_direct_total_usd: float
174
- sum: int
174
+ sum: Amount
175
175
  sum_usd: float
176
- on_chain_total_assets: int
176
+ on_chain_total_assets: Amount
177
177
  on_chain_total_assets_usd: float | None
178
- delta: int
178
+ delta: Amount
179
179
  delta_usd: float
180
180
  delta_percent: float
181
181
  # sharesToRelease of the WithdrawManager converted to assets.
182
- pending_withdrawals: int
182
+ pending_withdrawals: Amount
183
183
  pending_withdrawals_usd: float
184
184
  # totalAssets minus idle underlying: the global market total the vault
185
185
  # stores, which per-market caches drift from after partial refreshes.
186
- implied_market_total: int
186
+ implied_market_total: Amount
187
187
 
188
188
  @property
189
- def market_storage_divergence(self) -> int:
190
- return self.balance_fuses_total - self.implied_market_total
189
+ def market_storage_divergence(self) -> Amount:
190
+ return Amount(self.balance_fuses_total - self.implied_market_total)
191
191
 
192
192
 
193
193
  @dataclass(frozen=True)
@@ -288,10 +288,10 @@ def _reconcile(state: VaultState) -> Reconciliation:
288
288
  decimals = state.asset_decimals
289
289
  price = state.asset_price_usd
290
290
 
291
- balance_fuses_total = 0
291
+ balance_fuses_total = Amount(0)
292
292
  balance_fuses_usd = 0.0
293
293
  for balance in market_balances.values():
294
- balance_fuses_total += balance
294
+ balance_fuses_total = Amount(balance_fuses_total + balance)
295
295
  if price is not None:
296
296
  balance_fuses_usd += (balance / 10**decimals) * price
297
297
 
@@ -300,17 +300,17 @@ def _reconcile(state: VaultState) -> Reconciliation:
300
300
  if underlying > 0 and price:
301
301
  underlying_usd = (underlying / 10**decimals) * price
302
302
 
303
- total = balance_fuses_total + underlying
303
+ total = Amount(balance_fuses_total + underlying)
304
304
  total_usd = balance_fuses_usd + underlying_usd
305
305
  on_chain = state.total_assets
306
- delta = total - on_chain
306
+ delta = Amount(total - on_chain)
307
307
  delta_usd = abs(total_usd - (on_chain / 10**decimals * price)) if price else 0
308
308
  delta_percent = abs(delta / on_chain * 100) if on_chain else 0.0
309
309
 
310
310
  # totalAssets reads the global market total while balance fuses report
311
311
  # per-market caches; they diverge after withdrawals with sharesToRelease,
312
312
  # which refresh only the markets the instant-withdrawal fuses touch.
313
- pending = 0
313
+ pending = Amount(0)
314
314
  pending_usd = 0.0
315
315
  queue = state.withdraw_manager_data
316
316
  if queue and queue.shares_to_release > 0:
@@ -336,7 +336,7 @@ def _reconcile(state: VaultState) -> Reconciliation:
336
336
  delta_percent=delta_percent,
337
337
  pending_withdrawals=pending,
338
338
  pending_withdrawals_usd=pending_usd,
339
- implied_market_total=max(0, on_chain - underlying),
339
+ implied_market_total=Amount(max(0, on_chain - underlying)),
340
340
  )
341
341
 
342
342
 
@@ -532,6 +532,16 @@ def _fetch_lending_reads(
532
532
  )
533
533
 
534
534
 
535
+ def _pinned_default_block(ctx: Web3Context) -> int | None:
536
+ """The number ``ctx.default_block`` names, or None for the latest block."""
537
+ default = ctx.default_block
538
+ if isinstance(default, int):
539
+ return default
540
+ if default in ("latest", "pending"):
541
+ return None
542
+ return ctx.web3.eth.get_block(default)["number"]
543
+
544
+
535
545
  def fetch_vault_data(
536
546
  ctx: Web3Context,
537
547
  vault: PlasmaVaultAddress,
@@ -541,8 +551,8 @@ def fetch_vault_data(
541
551
  metadata: bool = True,
542
552
  ) -> VaultData:
543
553
  """Read the vault's state at one block: ``block``, else the context's
544
- default block when that is a number, else the latest block, resolved once
545
- up front. Every read, log scans included, runs on a copy of ``ctx`` pinned
554
+ default block (a ``safe`` / ``finalized`` tag or a block hash is resolved
555
+ to its number), else the latest block, resolved once up front. Every read, log scans included, runs on a copy of ``ctx`` pinned
546
556
  to that block (`Web3Context.at_block`); ``ctx`` itself is not modified.
547
557
  The chain is ``ctx.chain_id``.
548
558
 
@@ -561,8 +571,8 @@ def fetch_vault_data(
561
571
  # Fail fast with a client-mappable typed error instead of letting the
562
572
  # fetch die deep in the stack on chains the tooling is not validated on.
563
573
  ensure_supported_chain(ctx.chain_id)
564
- if block is None and isinstance(ctx.default_block, int):
565
- block = ctx.default_block
574
+ if block is None:
575
+ block = _pinned_default_block(ctx)
566
576
  resolved_block = ctx.web3.eth.block_number if block is None else block
567
577
  is_latest = block is None
568
578
  ctx = ctx.at_block(resolved_block)