ipor-fusion 4.0.0.dev141__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.dev141 → ipor_fusion-4.0.0.dev146}/AGENTS.md +2 -1
  2. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/MIGRATION_4.md +45 -26
  3. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/PKG-INFO +1 -1
  4. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/composed_hypercore_flow_arbitrum_hyperevm.py +2 -2
  5. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +2 -1
  6. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/pyproject.toml +1 -1
  7. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_cmd.py +3 -1
  8. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fusion_factory.py +9 -8
  9. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/simulation.py +3 -3
  10. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/contracts.py +5 -2
  11. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/lane.py +2 -2
  12. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/contracts.py +8 -4
  13. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/lane.py +17 -8
  14. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/contracts.py +3 -2
  15. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/lane.py +2 -2
  16. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/server.py +1 -1
  17. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/lending_health.py +1 -1
  18. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/oracle_mapping.py +3 -2
  19. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/validation.py +16 -16
  20. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_data.py +14 -4
  21. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_commands.py +9 -0
  22. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_oracle_mapping.py +4 -4
  23. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_docs_typing.py +5 -1
  24. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_server.py +2 -2
  25. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_oracle_mapping.py +3 -3
  26. ipor_fusion-4.0.0.dev146/tests/test_vault_data_block.py +34 -0
  27. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.claude-plugin/marketplace.json +0 -0
  28. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.claude-plugin/plugin.json +0 -0
  29. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.env.example +0 -0
  30. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.gemini/settings.json +0 -0
  31. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.git-blame-ignore-revs +0 -0
  32. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/cd.yml +0 -0
  33. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/ci.yml +0 -0
  34. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/commit-lint.yml +0 -0
  35. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/notify-slack.yml +0 -0
  36. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/python-build.yml +0 -0
  37. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/python-publish.yml +0 -0
  38. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/release.yml +0 -0
  39. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.github/workflows/report-slack.yml +0 -0
  40. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.gitignore +0 -0
  41. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.mcp.json +0 -0
  42. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.pre-commit-config.yaml +0 -0
  43. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/.python-version +0 -0
  44. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/CHANGELOG.md +0 -0
  45. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/CLAUDE.md +0 -0
  46. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/CODEOWNERS +0 -0
  47. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/LICENSE +0 -0
  48. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/README.md +0 -0
  49. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/build.sh +0 -0
  50. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/CONTRIBUTING.md +0 -0
  51. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/README.md +0 -0
  52. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  53. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  54. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/external_state_margin_leg_base.py +0 -0
  55. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/examples/simple_aave_v3_supply_base.py +0 -0
  56. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/pytest.ini +0 -0
  57. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/scripts/commit-check.sh +0 -0
  58. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/scripts/crosschain_readiness.py +0 -0
  59. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/skills/ipor-deploy-vault/SKILL.md +0 -0
  60. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/__init__.py +0 -0
  61. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/about.py +0 -0
  62. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cache.py +0 -0
  63. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/chains.py +0 -0
  64. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/__init__.py +0 -0
  65. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  66. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/config_cmd.py +0 -0
  67. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/config_store.py +0 -0
  68. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/main.py +0 -0
  69. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/market_cmd.py +0 -0
  70. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/options.py +0 -0
  71. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  72. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_health.py +0 -0
  73. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  74. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  75. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  76. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/config/__init__.py +0 -0
  77. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/config/roles.py +0 -0
  78. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/__init__.py +0 -0
  79. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/access.py +0 -0
  80. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/address.py +0 -0
  81. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/context.py +0 -0
  82. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/contract.py +0 -0
  83. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/erc20.py +0 -0
  84. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/external_state_executor.py +0 -0
  85. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fee_manager.py +0 -0
  86. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fuse.py +0 -0
  87. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/fuse_action.py +0 -0
  88. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/logs.py +0 -0
  89. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/multicall.py +0 -0
  90. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/oracle.py +0 -0
  91. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/plasma_vault.py +0 -0
  92. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/rewards_manager.py +0 -0
  93. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  94. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/__init__.py +0 -0
  95. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  96. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  97. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  98. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  99. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  100. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  101. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  102. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/discovery.py +0 -0
  103. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/errors.py +0 -0
  104. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  105. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  106. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  107. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  108. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/logs.py +0 -0
  109. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/messages.py +0 -0
  110. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/simulation.py +0 -0
  111. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  112. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  113. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  114. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/crosschain/transport.py +0 -0
  115. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/dependency_graph.py +0 -0
  116. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/entry_cli.py +0 -0
  117. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/entry_mcp.py +0 -0
  118. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/errors.py +0 -0
  119. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/explorer.py +0 -0
  120. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/field_docs.py +0 -0
  121. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/__init__.py +0 -0
  122. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  123. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/async_action.py +0 -0
  124. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/base.py +0 -0
  125. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  126. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/erc4626.py +0 -0
  127. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  128. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/events.py +0 -0
  129. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/external_state.py +0 -0
  130. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/external_state_events.py +0 -0
  131. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  132. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  133. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/hypercore.py +0 -0
  134. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/hypercore_events.py +0 -0
  135. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/merkl.py +0 -0
  136. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/morpho.py +0 -0
  137. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  138. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  139. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/fuses/universal.py +0 -0
  140. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/__init__.py +0 -0
  141. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/architecture.md +0 -0
  142. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/glossary.md +0 -0
  143. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/invariants.md +0 -0
  144. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/guide/quickstart.md +0 -0
  145. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/market_ids.py +0 -0
  146. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/market_info.py +0 -0
  147. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/__init__.py +0 -0
  148. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/guide.py +0 -0
  149. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/mcp/models.py +0 -0
  150. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/morpho_api.py +0 -0
  151. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/py.typed +0 -0
  152. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/__init__.py +0 -0
  153. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/aave_v3.py +0 -0
  154. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/aave_v4.py +0 -0
  155. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/compound_v3.py +0 -0
  156. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/hypercore.py +0 -0
  157. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  158. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/morpho.py +0 -0
  159. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  160. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  161. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/position_manager.py +0 -0
  162. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  163. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  164. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/readers/unpriceable.py +0 -0
  165. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/substrates.py +0 -0
  166. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/types.py +0 -0
  167. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_erc20.py +0 -0
  168. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_info.py +0 -0
  169. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_managers.py +0 -0
  170. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_positions.py +0 -0
  171. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/src/ipor_fusion/vault_reads.py +0 -0
  172. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/UniswapV3UniversalRouter.py +0 -0
  173. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_crosschain.py +0 -0
  174. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_crosschain_lifecycle.py +0 -0
  175. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_crosschain_recovery.py +0 -0
  176. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_euler_v2.py +0 -0
  177. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_foundry.py +0 -0
  178. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_guide.py +0 -0
  179. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_hypercore_model.py +0 -0
  180. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_hypercore_shadow.py +0 -0
  181. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_multicall.py +0 -0
  182. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_simulate.py +0 -0
  183. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/_validation.py +0 -0
  184. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/addresses.py +0 -0
  185. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/conftest.py +0 -0
  186. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/constants.py +0 -0
  187. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  188. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/fixtures/hypercore_market55.json +0 -0
  189. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/clean.json +0 -0
  190. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  191. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/rich.json +0 -0
  192. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  193. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_about.py +0 -0
  194. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_access_manager.py +0 -0
  195. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cache.py +0 -0
  196. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_chains.py +0 -0
  197. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_changelog.py +0 -0
  198. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_config_store.py +0 -0
  199. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_helpers.py +0 -0
  200. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_hypercore.py +0 -0
  201. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_lending_health.py +0 -0
  202. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_market_morpho_blue.py +0 -0
  203. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_pending_requests_scan.py +0 -0
  204. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_role_accounts.py +0 -0
  205. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_vault_info_golden.py +0 -0
  206. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_vault_predates_getters.py +0 -0
  207. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_cli_vault_validate.py +0 -0
  208. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_contract_call.py +0 -0
  209. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_contract_encoder.py +0 -0
  210. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_ccip_events.py +0 -0
  211. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_ccip_indexer.py +0 -0
  212. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_ccip_quote.py +0 -0
  213. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_chainlink.py +0 -0
  214. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_contracts.py +0 -0
  215. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_encoding.py +0 -0
  216. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_errors.py +0 -0
  217. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_lane.py +0 -0
  218. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_layering.py +0 -0
  219. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_readiness.py +0 -0
  220. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_readiness_report.py +0 -0
  221. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_crosschain_transport.py +0 -0
  222. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_erc20.py +0 -0
  223. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_error_hierarchy.py +0 -0
  224. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_examples_composed_hypercore.py +0 -0
  225. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_examples_crosschain.py +0 -0
  226. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  227. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_examples_vaults.py +0 -0
  228. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_explorer.py +0 -0
  229. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_external_state_events.py +0 -0
  230. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_external_state_executor.py +0 -0
  231. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_fee_manager.py +0 -0
  232. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_fuse_encoding.py +0 -0
  233. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_fusion_instance.py +0 -0
  234. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_guide.py +0 -0
  235. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_deployment.py +0 -0
  236. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_events.py +0 -0
  237. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_flow.py +0 -0
  238. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_live.py +0 -0
  239. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_outcome.py +0 -0
  240. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_readers.py +0 -0
  241. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_hypercore_simulation.py +0 -0
  242. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_logs.py +0 -0
  243. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_hypercore.py +0 -0
  244. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_models.py +0 -0
  245. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_mcp_vault_validate.py +0 -0
  246. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_multicall.py +0 -0
  247. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_oracle.py +0 -0
  248. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_plasma_vault.py +0 -0
  249. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_public_api.py +0 -0
  250. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_public_type_reachability.py +0 -0
  251. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_reader_encoding.py +0 -0
  252. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_readiness_selection.py +0 -0
  253. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_revert_decoding.py +0 -0
  254. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_rewards_manager.py +0 -0
  255. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_roles.py +0 -0
  256. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_sdk_layering.py +0 -0
  257. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  258. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  259. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  260. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_canary.py +0 -0
  261. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_hypercore.py +0 -0
  262. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  263. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_crosschain_lifecycle.py +0 -0
  264. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  265. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  266. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  267. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  268. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  269. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_examples_arbitrum.py +0 -0
  270. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_examples_base.py +0 -0
  271. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_examples_crosschain.py +0 -0
  272. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_external_state_executor_base.py +0 -0
  273. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  274. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  275. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  276. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_morpho_blue.py +0 -0
  277. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v3.py +0 -0
  278. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  279. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  280. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  281. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  282. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_readers_integration.py +0 -0
  283. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_release_funds.py +0 -0
  284. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_swap_on_base.py +0 -0
  285. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  286. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_solidity_compiler.py +0 -0
  287. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_solidity_mirrors.py +0 -0
  288. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_substrates.py +0 -0
  289. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_types_and_events.py +0 -0
  290. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_unpriceable_tokens.py +0 -0
  291. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_validation.py +0 -0
  292. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_vault_dep_graph.py +0 -0
  293. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_vault_info_onchain_base.py +0 -0
  294. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  295. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_vault_simulator_blocks.py +0 -0
  296. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_web3_context.py +0 -0
  297. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/test_withdraw_manager.py +0 -0
  298. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/typed_client/consumer.py +0 -0
  299. {ipor_fusion-4.0.0.dev141 → ipor_fusion-4.0.0.dev146}/tests/typed_client/rejected.py +0 -0
  300. {ipor_fusion-4.0.0.dev141 → 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
@@ -6,10 +6,11 @@ the details, and each change is described once, in its section.
6
6
 
7
7
  ## Start here
8
8
 
9
- > **A 3.x program fails when a line runs, not when it is imported.**
10
- > `PlasmaVault(ctx, address)`, `ERC20(ctx, token)` or any other `Wrapper(ctx, ...)`
11
- > raises `TypeError` only when that line executes, and so does `call.call()` without
12
- > a context. A service that wraps its reads in a broad `except Exception` degrades
9
+ > **Most 3.x calls fail only when the line runs.** A removed import
10
+ > (`ipor_fusion.fuses.crosschain`, `NavMark`, the 3.x cache functions) fails at
11
+ > import, but `PlasmaVault(ctx, address)`, `ERC20(ctx, token)` or any other
12
+ > `Wrapper(ctx, ...)` raises `TypeError` only when that line executes, and so does
13
+ > `call.call()` without a context. A service that wraps its reads in a broad `except Exception` degrades
13
14
  > silently (empty values, skipped vaults) instead of failing. Before you deploy:
14
15
  >
15
16
  > - search for wrappers built with a context (`(ctx,`, `(self.ctx,`, `(self._ctx,`)
@@ -93,7 +94,7 @@ placeholder wrapper.
93
94
  `aggregate3(calls)` still returns the raw `Call`), `VaultSimulator`,
94
95
  `CrosschainSimulator` and `OracleMappingReader(ctx, oracle)`.
95
96
  - Resolvers that probe the chain stay eager and return context-free wrappers:
96
- `price_oracle_middleware(ctx, oracle)`, `resolve_access_manager(ctx, vault)`,
97
+ `price_oracle_middleware(ctx, address)`, `resolve_access_manager(ctx, vault)`,
97
98
  `ExternalStateExecutor.for_vault(ctx, vault)`. A resolved oracle carries the ABI
98
99
  found on that chain and block; resolve again for another one.
99
100
  - Crosschain lanes own both contexts: `CcipLane` / `StargateLane` take `hub_ctx=` and
@@ -112,9 +113,9 @@ placeholder wrapper.
112
113
  do not see a CCIP recall or tell a parked command from a pending one. `CrosschainTransport.executor(address)`
113
114
  and `.dispatcher(address)` no longer take a context.
114
115
  - Chain-bound decoding takes the chain explicitly:
115
- `ExternalStateExecutor.pending_proposals(account, *, chain_id)`,
116
+ `ExternalStateExecutor.pending_proposals(balance_account, *, chain_id)`,
116
117
  `parse_balance_proposed(log, *, chain_id)` and
117
- `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
118
119
  to the wrapper's context). The decoder captures it, so `.call(ctx)`, `Multicall3`
119
120
  and `VaultSimulator.observe` stamp the same hash for the same bytes, whatever
120
121
  context executes them. Pass `ctx.chain_id` for the chain you read from.
@@ -133,7 +134,7 @@ placeholder wrapper.
133
134
  | `call.build_transaction(block=b)`, `call.estimate_gas(block=b)` | `call.build_transaction(ctx, block=b)`, `call.estimate_gas(ctx, block=b)` |
134
135
  | `call.ctx` | gone: keep the context yourself |
135
136
  | `Multicall3(ctx, address)` | `Multicall3(ctx, address=address)` |
136
- | `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)` |
137
138
  | `executor.parse_balance_proposed(log)` / `find_balance_proposed(logs)` | `...(log, chain_id=...)` / `...(logs, chain_id=...)` |
138
139
  | `CcipLane(executor=, dispatcher=, spoke_chain_id=, ...)` | `CcipLane(hub_ctx=, spoke_ctx=, executor=, dispatcher=, ...)` (same for `StargateLane`) |
139
140
  | `transport.executor(ctx, address)` / `transport.dispatcher(ctx, address)` | `transport.executor(address)` / `transport.dispatcher(address)` |
@@ -173,7 +174,8 @@ The vault read is one pinned snapshot, and the chain is always the context's:
173
174
 
174
175
  - `fetch_vault_data(ctx, vault, *, block=None, lending=True, metadata=True)` takes
175
176
  the vault address (not a `PlasmaVault`) and no `chain_id`. It resolves one block
176
- (`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
177
179
  read, log scans included, on `ctx.at_block(block)`; `ctx` is not modified. In 3.x
178
180
  `block_number` only selected the block reported in `VaultData`, and the reads ran
179
181
  wherever `ctx` pointed. The old `chain_id=0` switch is `lending=False`;
@@ -189,8 +191,7 @@ The vault read is one pinned snapshot, and the chain is always the context's:
189
191
  no longer takes `resolve_symbol`. `metadata=True` names the ERC20 holdings in the
190
192
  result and its findings. `validate_vault_state(state)` is pure: a custom
191
193
  `VaultState` also provides `vault`, `chain_id`, `market_balances`, `erc20` and the
192
- queue's `shares_to_release_assets`. `read_erc20_holdings` is gone: the holdings are
193
- `VaultData.erc20`.
194
+ queue's `shares_to_release_assets`.
194
195
  - `build_vault_info_json(data, *, resolve_name=None)` renders from the snapshot and
195
196
  reads nothing; the chain and its label come from `data.chain_id`. Without
196
197
  `resolve_name` it names no contract (see
@@ -280,7 +281,8 @@ fails at run time:
280
281
  zero-address placeholder. `execute()` without `vault` and `alpha` raises
281
282
  `ValueError`, as does `execute_call` without `from_` or `alpha`.
282
283
  - `fetch_unpriceable_priced_tokens(ctx, *, price_oracle, underlying, balance_fuses,
283
- market_substrates, morpho_positions)` takes everything after `ctx` by keyword.
284
+ market_substrates, morpho_positions)` takes everything after `ctx` by keyword, and
285
+ its `oracle_address` parameter is renamed `price_oracle`.
284
286
  - `get_contract_name(chain_id, address, *, api_key=None, cache=None)` and
285
287
  `get_deployment_tx(chain_id, address, *, api_key=None)` take `api_key` by keyword.
286
288
  - `build_morpho_blue_market_json(*, market_id, chain_id, params, state, rates,
@@ -288,6 +290,8 @@ fails at run time:
288
290
  keyword.
289
291
  - `encode_oft_compose_msg(*, nonce=, src_eid=, amount_ld=, compose_msg=)` takes
290
292
  keyword arguments only.
293
+ - `build_oracle_mapping(ctx, vault, *, effective_block, max_depth=6)` takes
294
+ `effective_block` and `max_depth` by keyword.
291
295
  - The vault parameter is named `vault` throughout: `validate_vault`,
292
296
  `fetch_deployment_info`, `fetch_vault_lending_health`, `build_oracle_mapping`,
293
297
  `read_external_state`, `ExternalStateExecutor.for_vault`, `read_hypercore_nav`,
@@ -346,9 +350,11 @@ is the wrong one.
346
350
  - The Morpho API client raises `MorphoApiError` for every answer it cannot use and
347
351
  every transport failure, never `KeyError`, `TypeError` or `ValueError`:
348
352
  - a required market id or address (market and V1 allocation ids, V1 flow-cap ids,
349
- vault/asset/owner/token/oracle/IRM/allocator addresses) that is absent or
350
- invalid; an optional address (curator, guardian, fee recipients, ...) that is
351
- absent or `""` reads as `None`, and raises only when present and invalid;
353
+ vault/asset/owner/token/oracle/IRM addresses) that is absent or invalid; an
354
+ optional address (curator, guardian, fee recipients, ...) that is absent or `""`
355
+ reads as `None`, and raises only when present and invalid; an allocator or
356
+ sentinel entry without an address is skipped, and one with an invalid address
357
+ raises;
352
358
  - any other answer it cannot read (a missing key such as a `loanAsset` without
353
359
  `address`, a value of the wrong type, a body that is not a JSON object);
354
360
  - any `OSError` or `http.client.HTTPException` while fetching (it caught only
@@ -431,8 +437,8 @@ are cached in a `ContractCache`, injected instead of always written to `~/.cache
431
437
  explicitly (default: the file cache); pass `cache=ctx.cache` to share the
432
438
  context's policy. The CLI's text and JSON output name contracts through the
433
439
  context's cache.
434
- - `build_vault_info_json(data)` without `resolve_name` names no contract (every
435
- `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
436
442
  `resolve_name=partial(get_contract_name, chain_id, api_key=..., cache=ctx.cache)`
437
443
  for the CLI's document.
438
444
  - `ipor_fusion.cache` is a public module now and exports `ContractCache`,
@@ -533,13 +539,18 @@ converted amounts `Amount`. A custom `VaultState` annotates the same.
533
539
  `PlasmaVault.total_supply()`, `balance_of()` and `get_total_supply_cap()` return
534
540
  `Shares` (vault shares), and `transfer` / `approve` / `transfer_from` /
535
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`.
536
547
 
537
548
  **Vault data and helpers.** `ensure_supported_chain` takes `ChainId`.
538
549
  `fetch_unpriceable_priced_tokens` takes the oracle and underlying as
539
550
  `ChecksumAddress`. `resolve_token_symbol`, `resolve_token_decimals`,
540
551
  `get_contract_name` and `get_deployment_tx` take `HexAddress` (a checksum address
541
- fits), and the `resolve_name` / `resolve_symbol` callbacks receive one (a callback
542
- 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`:
543
554
  `VaultData.market_substrates`, `dependency_graph`, `morpho_positions`,
544
555
  `aave_positions`, `market_balances`; the `VaultState` protocol,
545
556
  `fetch_vault_lending_health`, `fetch_unpriceable_priced_tokens` and the
@@ -563,8 +574,8 @@ The Uniswap V3 / Ramses V2 position events' `version`, `sender` and `recipient`
563
574
  `MarketId`; `Erc20Holding.address` is `HexAddress`, and `SubstrateInfo.address` is
564
575
  `HexAddress | None`. `LegacyPriceOracleMiddleware` takes `base_currency_decimals:
565
576
  Decimals`, and `PriceOracleMiddleware.base_currency_decimals()` returns
566
- `Call[Decimals]`. `get_logs_adaptive(chain_id=)` and `LogSource.get_logs` take
567
- `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
568
579
  `OracleMappingReader.asset_source_events` take a `BlockIdentifier`.
569
580
 
570
581
  **Address roles.** `PlasmaVault(address)`,
@@ -599,7 +610,8 @@ by `ChainId` (`HYPEREVM_CHAIN_ID` is one) and `explorer.SUPPORTED_CHAINS` is a
599
610
  type-checks; `PUBLIC_ALLOCATOR_ADDRESSES` values are `ChecksumAddress`.
600
611
 
601
612
  **Markets, substrates and access.** Every `IporFusionMarkets` member is a `MarketId`.
602
- `decode_substrate`, `market_name`, `format_market_label` take `MarketId`.
613
+ `market_name` and `format_market_label` take `MarketId`, `decode_substrate` a
614
+ `MarketId | None`.
603
615
  `AccessManager.has_role`, `grant_role`, `revoke_role` and `read_accounts_with_role`
604
616
  take `RoleId | Roles`; `RoleAccount.role_id` is `RoleId`.
605
617
 
@@ -627,7 +639,12 @@ on CCIP, `active_return` → `OperationId`, `asset_id`, `tracked_position_set_ha
627
639
  `CcipObservation`, `OutboundMessage`, `CcipMessageSent`, `Any2EVMMessage`,
628
640
  `VerifierResult`, `Packet.guid`, `TaxiMessage`, `StargateChain`; and the helpers
629
641
  `fetch_verifier_result(message_id=)`, `lz_receive_calldata(guid=)` and
630
- `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
631
648
  (`ccip_events` → `CcipEvent.values`) stay a generic mapping: convert a value
632
649
  before passing it on.
633
650
 
@@ -654,20 +671,22 @@ HyperCore ids; `HyperCorePerpAssetInfo`, `HyperCorePerpMarket`, `HyperCoreSpotLe
654
671
 
655
672
  **External state.** `ExternalStateExecutor.proposal_hash` returns and `confirm_balance`
656
673
  takes a `ProposalHash`; `BalanceProposal.proposal_hash` is a `ProposalHash`;
657
- `ExternalStateEvent.transaction_hash` is a `TxHash`.
674
+ `ExternalStateEvent.transaction_hash` is a `TxHash | None`.
658
675
  `ExternalStateVaultState.last_checked_custodian_timestamp`,
659
676
  `BalanceFuseSnapshot.last_custodian_update_timestamp`, `BalanceProposal.proposed_at`,
660
677
  `last_updated()` and `get_oldest_update_timestamp()` are web3's `Timestamp`, and
661
678
  `ExternalStateExecutor.proposal_hash(proposed_at=)` takes one.
662
679
 
663
- **Errors and explorer.** `TransactionError(tx_hash=)` takes a `TxHash`,
680
+ **Errors and explorer.** `TransactionError(tx_hash=)` takes a `TxHash | None`,
664
681
  `LogScanError(address=)` a `ChecksumAddress`; `get_revert_reason` takes a `TxHash`;
665
682
  `get_deployment_tx` returns a `TxHash` (a malformed hash from Etherscan is reported
666
683
  as the error `malformed-tx-hash`).
667
684
 
668
685
  **MCP response models** (Python constructors only; the JSON schemas are unchanged
669
686
  except as listed under [Behavior changes](#behavior-changes)): `chain_id` fields are
670
- `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
671
690
  directly from Python takes `ChainId`.
672
691
 
673
692
  ## Behavior changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.0.0.dev141
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.dev141"
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,13 +428,14 @@ 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:
434
435
  """Build the full oracle mapping for ``vault``.
435
436
 
436
- ``ctx`` must already be pinned to ``effective_block`` (the caller sets
437
- ``ctx.default_block``); the block is echoed in the output. Raises
437
+ ``ctx`` must already be pinned to ``effective_block`` (pass
438
+ ``ctx.at_block(effective_block)``); the block is echoed in the output. Raises
438
439
  `UnsupportedVaultVersionError` for a vault that exposes no price-oracle
439
440
  getter.
440
441
  """