ipor-fusion 4.2.0.dev20__tar.gz → 4.2.1.dev2__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 (375) hide show
  1. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.claude-plugin/plugin.json +1 -1
  2. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/AGENTS.md +4 -0
  3. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/CHANGELOG.md +56 -0
  4. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/PKG-INFO +1 -1
  5. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/pyproject.toml +1 -1
  6. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/config_cmd.py +1 -1
  7. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/config_store.py +9 -14
  8. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/main.py +19 -5
  9. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/logs.py +4 -4
  10. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/errors.py +4 -1
  11. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/server.py +19 -4
  12. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/lending_health.py +7 -1
  13. ipor_fusion-4.2.1.dev2/src/ipor_fusion/redaction.py +39 -0
  14. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_reads.py +6 -1
  15. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_config_store.py +1 -1
  16. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_logs.py +29 -5
  17. ipor_fusion-4.2.1.dev2/tests/test_provider_url_redaction.py +338 -0
  18. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/uv.lock +1 -1
  19. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.claude-plugin/marketplace.json +0 -0
  20. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.env.example +0 -0
  21. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.gemini/settings.json +0 -0
  22. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.git-blame-ignore-revs +0 -0
  23. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/cd.yml +0 -0
  24. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/ci.yml +0 -0
  25. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/commit-lint.yml +0 -0
  26. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/notify-slack.yml +0 -0
  27. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/python-build.yml +0 -0
  28. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/python-publish.yml +0 -0
  29. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/release.yml +0 -0
  30. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/report-slack.yml +0 -0
  31. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.gitignore +0 -0
  32. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.mcp.json +0 -0
  33. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.pre-commit-config.yaml +0 -0
  34. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.python-version +0 -0
  35. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/CLAUDE.md +0 -0
  36. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/CODEOWNERS +0 -0
  37. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/LICENSE +0 -0
  38. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/MIGRATION_4.md +0 -0
  39. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/README.md +0 -0
  40. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/build.sh +0 -0
  41. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/CONTRIBUTING.md +0 -0
  42. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/README.md +0 -0
  43. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/advanced_compound_v3_institutional_usdc_ethereum.py +0 -0
  44. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/advanced_euler_v2_credit_market_base.py +0 -0
  45. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
  46. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
  47. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/external_state_margin_leg_base.py +0 -0
  48. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
  49. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/simple_aave_v3_supply_base.py +0 -0
  50. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/pytest.ini +0 -0
  51. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/scripts/commit-check.sh +0 -0
  52. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/scripts/fusion_error_signatures.py +0 -0
  53. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/skills/ipor-deploy-vault/SKILL.md +0 -0
  54. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/__init__.py +0 -0
  55. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/about.py +0 -0
  56. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cache.py +0 -0
  57. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/chains.py +0 -0
  58. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/__init__.py +0 -0
  59. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/address_cmd.py +0 -0
  60. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
  61. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/fuse_cmd.py +0 -0
  62. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/market_cmd.py +0 -0
  63. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/options.py +0 -0
  64. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
  65. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
  66. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_cmd.py +0 -0
  67. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_health.py +0 -0
  68. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_info_view.py +0 -0
  69. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
  70. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_rendering.py +0 -0
  71. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/config/__init__.py +0 -0
  72. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/config/roles.py +0 -0
  73. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/__init__.py +0 -0
  74. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/access.py +0 -0
  75. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/address.py +0 -0
  76. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/context.py +0 -0
  77. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/context_manager.py +0 -0
  78. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/contract.py +0 -0
  79. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/erc20.py +0 -0
  80. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/external_state_executor.py +0 -0
  81. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fee_manager.py +0 -0
  82. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fuse.py +0 -0
  83. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fuse_action.py +0 -0
  84. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fuse_decoding.py +0 -0
  85. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fusion_factory.py +0 -0
  86. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/multicall.py +0 -0
  87. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/oracle.py +0 -0
  88. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/plasma_vault.py +0 -0
  89. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/rewards_manager.py +0 -0
  90. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/simulation.py +0 -0
  91. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/simulation_result.py +0 -0
  92. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/withdraw_manager.py +0 -0
  93. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/__init__.py +0 -0
  94. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
  95. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
  96. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
  97. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
  98. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
  99. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
  100. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
  101. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
  102. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
  103. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/contracts.py +0 -0
  104. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/discovery.py +0 -0
  105. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/errors.py +0 -0
  106. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
  107. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
  108. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
  109. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
  110. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/lane.py +0 -0
  111. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/logs.py +0 -0
  112. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/messages.py +0 -0
  113. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/simulation.py +0 -0
  114. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
  115. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
  116. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
  117. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
  118. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
  119. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/transport.py +0 -0
  120. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/dependency_graph.py +0 -0
  121. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/entry_cli.py +0 -0
  122. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/entry_mcp.py +0 -0
  123. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/explorer.py +0 -0
  124. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/field_docs.py +0 -0
  125. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuse_whitelist.py +0 -0
  126. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/__init__.py +0 -0
  127. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/aave_v3.py +0 -0
  128. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/async_action.py +0 -0
  129. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/base.py +0 -0
  130. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/compound_v3.py +0 -0
  131. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/erc4626.py +0 -0
  132. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/euler_v2.py +0 -0
  133. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/events.py +0 -0
  134. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/external_state.py +0 -0
  135. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
  136. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
  137. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
  138. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/hypercore.py +0 -0
  139. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
  140. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
  141. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/merkl.py +0 -0
  142. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/morpho.py +0 -0
  143. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
  144. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
  145. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/universal.py +0 -0
  146. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fusion_errors.py +0 -0
  147. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/__init__.py +0 -0
  148. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/architecture.md +0 -0
  149. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/glossary.md +0 -0
  150. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/invariants.md +0 -0
  151. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/quickstart.md +0 -0
  152. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/hypercore/__init__.py +0 -0
  153. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/market_ids.py +0 -0
  154. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/market_info.py +0 -0
  155. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/__init__.py +0 -0
  156. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/guide.py +0 -0
  157. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/models.py +0 -0
  158. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/morpho_api.py +0 -0
  159. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/onchain_text.py +0 -0
  160. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/preflight.py +0 -0
  161. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/py.typed +0 -0
  162. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/__init__.py +0 -0
  163. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/aave_v3.py +0 -0
  164. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/aave_v4.py +0 -0
  165. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/compound_v3.py +0 -0
  166. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/euler_v2.py +0 -0
  167. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/hypercore.py +0 -0
  168. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
  169. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/morpho.py +0 -0
  170. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
  171. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
  172. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
  173. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/position_manager.py +0 -0
  174. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/ramses_v2.py +0 -0
  175. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
  176. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/unpriceable.py +0 -0
  177. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/registry.py +0 -0
  178. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/substrates.py +0 -0
  179. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/types.py +0 -0
  180. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/validation.py +0 -0
  181. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_data.py +0 -0
  182. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_erc20.py +0 -0
  183. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_info.py +0 -0
  184. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_managers.py +0 -0
  185. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_positions.py +0 -0
  186. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/UniswapV3UniversalRouter.py +0 -0
  187. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_addresses.py +0 -0
  188. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_crosschain.py +0 -0
  189. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_crosschain_lifecycle.py +0 -0
  190. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_crosschain_recovery.py +0 -0
  191. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_euler_v2.py +0 -0
  192. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_event_args.py +0 -0
  193. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_fake_rpc.py +0 -0
  194. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_foundry.py +0 -0
  195. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_golden.py +0 -0
  196. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_guide.py +0 -0
  197. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_hypercore_model.py +0 -0
  198. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_hypercore_shadow.py +0 -0
  199. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_live.py +0 -0
  200. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_multicall.py +0 -0
  201. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_oracle_mapping_fake.py +0 -0
  202. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_simulate.py +0 -0
  203. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_validation.py +0 -0
  204. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/addresses.py +0 -0
  205. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/conftest.py +0 -0
  206. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/constants.py +0 -0
  207. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/hypercore_hip3_flow.json +0 -0
  208. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/hypercore_market55.json +0 -0
  209. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/hypercore_official.json +0 -0
  210. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
  211. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
  212. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
  213. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
  214. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
  215. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
  216. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
  217. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_api/vault_v2_not_found.json +0 -0
  218. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue.json +0 -0
  219. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
  220. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
  221. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
  222. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
  223. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
  224. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
  225. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
  226. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/simulate_v1_request.json +0 -0
  227. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/clean.json +0 -0
  228. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
  229. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/clean.txt +0 -0
  230. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
  231. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
  232. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
  233. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
  234. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/fetched.json +0 -0
  235. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
  236. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/full.json +0 -0
  237. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
  238. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/full.txt +0 -0
  239. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/rich.json +0 -0
  240. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
  241. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/rich.txt +0 -0
  242. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_about.py +0 -0
  243. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_access_manager.py +0 -0
  244. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_addresses_helper.py +0 -0
  245. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cache.py +0 -0
  246. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_ccip_read_disabled.py +0 -0
  247. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_chains.py +0 -0
  248. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_address.py +0 -0
  249. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_changelog.py +0 -0
  250. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_commands.py +0 -0
  251. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_helpers.py +0 -0
  252. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_hypercore.py +0 -0
  253. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_lending_health.py +0 -0
  254. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_market_golden.py +0 -0
  255. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_market_morpho_blue.py +0 -0
  256. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_mcp_fuse_whitelist.py +0 -0
  257. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_mcp_parity.py +0 -0
  258. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_oracle_mapping.py +0 -0
  259. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_oracle_mapping_golden.py +0 -0
  260. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_pending_requests_scan.py +0 -0
  261. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_preflight.py +0 -0
  262. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_role_accounts.py +0 -0
  263. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_vault_info_golden.py +0 -0
  264. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_vault_predates_getters.py +0 -0
  265. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_vault_validate.py +0 -0
  266. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_contract_call.py +0 -0
  267. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_contract_encoder.py +0 -0
  268. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_ccip_events.py +0 -0
  269. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_ccip_indexer.py +0 -0
  270. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_ccip_quote.py +0 -0
  271. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_chainlink.py +0 -0
  272. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_contracts.py +0 -0
  273. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_encoding.py +0 -0
  274. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_errors.py +0 -0
  275. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_lane.py +0 -0
  276. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_layering.py +0 -0
  277. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_transport.py +0 -0
  278. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_docs_typing.py +0 -0
  279. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_erc20.py +0 -0
  280. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_error_hierarchy.py +0 -0
  281. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_compound_v3_ethereum.py +0 -0
  282. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_crosschain.py +0 -0
  283. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_euler_looping_arbitrum.py +0 -0
  284. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_hypercore_hyperevm.py +0 -0
  285. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_vaults.py +0 -0
  286. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_explorer.py +0 -0
  287. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_external_state_events.py +0 -0
  288. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_external_state_executor.py +0 -0
  289. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fee_manager.py +0 -0
  290. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fuse_decoding.py +0 -0
  291. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fuse_encoding.py +0 -0
  292. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fuse_whitelist.py +0 -0
  293. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fusion_errors.py +0 -0
  294. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fusion_instance.py +0 -0
  295. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_governance_wrappers.py +0 -0
  296. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_guide.py +0 -0
  297. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_deployment.py +0 -0
  298. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_events.py +0 -0
  299. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_flow.py +0 -0
  300. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_live.py +0 -0
  301. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_outcome.py +0 -0
  302. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_pre_hooks.py +0 -0
  303. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_readers.py +0 -0
  304. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_simulation.py +0 -0
  305. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_lending_health_compound_v3_onchain.py +0 -0
  306. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_address.py +0 -0
  307. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_hypercore.py +0 -0
  308. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_models.py +0 -0
  309. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_preflight.py +0 -0
  310. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_server.py +0 -0
  311. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_vault_validate.py +0 -0
  312. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_multicall.py +0 -0
  313. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_onchain_text.py +0 -0
  314. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_oracle.py +0 -0
  315. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_oracle_mapping.py +0 -0
  316. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_plasma_vault.py +0 -0
  317. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_preflight.py +0 -0
  318. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_public_api.py +0 -0
  319. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_public_type_reachability.py +0 -0
  320. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_reader_encoding.py +0 -0
  321. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_registry.py +0 -0
  322. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_revert_decoding.py +0 -0
  323. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_rewards_manager.py +0 -0
  324. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_roles.py +0 -0
  325. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_sdk_layering.py +0 -0
  326. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
  327. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
  328. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_claim_morpho_rewards.py +0 -0
  329. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_compound_v3_institutional_usdc.py +0 -0
  330. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_crosschain_hyperevm.py +0 -0
  331. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_batch_base.py +0 -0
  332. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
  333. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
  334. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
  335. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
  336. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_arbitrum.py +0 -0
  337. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_base.py +0 -0
  338. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_crosschain.py +0 -0
  339. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_ethereum.py +0 -0
  340. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_hyperevm.py +0 -0
  341. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_external_state_executor_base.py +0 -0
  342. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
  343. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_looping_aave_v3_base.py +0 -0
  344. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
  345. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_morpho_blue.py +0 -0
  346. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v3.py +0 -0
  347. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
  348. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v5_ramses.py +0 -0
  349. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
  350. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
  351. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_preflight_base.py +0 -0
  352. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_readers_integration.py +0 -0
  353. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_release_funds.py +0 -0
  354. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_swap_on_base.py +0 -0
  355. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_timelock_base.py +0 -0
  356. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_vault_from_scratch_base.py +0 -0
  357. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulation_errors.py +0 -0
  358. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulation_observation.py +0 -0
  359. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_solidity_compiler.py +0 -0
  360. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_solidity_mirrors.py +0 -0
  361. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_substrates.py +0 -0
  362. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_types_and_events.py +0 -0
  363. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_unpriceable_tokens.py +0 -0
  364. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_validation.py +0 -0
  365. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_data_block.py +0 -0
  366. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_dep_graph.py +0 -0
  367. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_info_fetched_golden.py +0 -0
  368. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_info_onchain_base.py +0 -0
  369. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
  370. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_reconciliation_onchain_base.py +0 -0
  371. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_simulator_blocks.py +0 -0
  372. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_web3_context.py +0 -0
  373. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_withdraw_manager.py +0 -0
  374. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/typed_client/consumer.py +0 -0
  375. {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/typed_client/rejected.py +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ipor-fusion",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "Skills for building on IPOR Fusion vaults with the ipor-fusion Python SDK, plus the hosted read-only inspection server",
5
5
  "author": {
6
6
  "name": "IPOR Labs",
@@ -202,6 +202,10 @@ checkout's default profile), the pending reader included.
202
202
 
203
203
  ## Layout (`src/ipor_fusion/`)
204
204
 
205
+ - `redaction.py` — `redact_provider_urls`, the one redactor for text carrying
206
+ exception messages (the CLI group and `main`, every MCP tool through
207
+ `_RedactingFastMCP.call_tool`, `LogScanError` through `core/logs.py`), and
208
+ `mask_provider_url`; a new entry point goes through it
205
209
  - `chains.py` chain registry; `market_ids.py` `IporFusionMarkets`; `types.py`; `errors.py`
206
210
  defines every SDK exception class (`tests/test_error_hierarchy.py` pins it)
207
211
  - `fusion_errors.py` — `FUSION_ERROR_SIGNATURES`, every custom error of the
@@ -2,6 +2,62 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v4.2.0 (2026-11-05)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **sdk**: Harden the Fuse Whitelist check against unreadable and non-fuse entries
10
+ ([`dbd2e55`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/dbd2e5538368b2fdf0caa5e491cdea80f05c2bef))
11
+
12
+ - **sdk**: Keep a Comet position critical when its limit or an unused feed fails
13
+ ([`cb6661c`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/cb6661c066540aa699ec3e9f02928a63f0c34c09))
14
+
15
+ - **sdk**: Keep provider URLs and config values out of error text
16
+ ([`276ab7b`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/276ab7b95c3ea4a11b50b37c7b61d8fc11f015e9))
17
+
18
+ - **sdk**: Report Morpho and Euler debt that has no usable health as a risk
19
+ ([`88a3c55`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/88a3c55373ce857b7f79895332ff51fde25a89da))
20
+
21
+ - **sdk**: Sanitize text read from contracts
22
+ ([`7d692af`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/7d692af40030eba067657058efe1e58284099482))
23
+
24
+ - **sdk**: Tell agents to reuse whitelisted fuses instead of deploying copies
25
+ ([`614ab00`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/614ab003a24bf0b21c11dbd0f5000f8eaaadf1c4))
26
+
27
+ ### Documentation
28
+
29
+ - **sdk**: Add a Compound V3 Institutional USDC borrow example
30
+ ([`5616c46`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/5616c46592ef374134e40645dc1f12cb6038ce61))
31
+
32
+ - **sdk**: Label the Compound example's simulation-only constructs
33
+ ([`5fca364`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/5fca3643c2c77dec82354e768b031a1ef7a9762c))
34
+
35
+ - **sdk**: Name the fuse whitelist command in the guide
36
+ ([`b17b62f`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/b17b62f157aa94eecbab214d10b1274887a72fe9))
37
+
38
+ ### Features
39
+
40
+ - **sdk**: Check vault and batch fuses against the on-chain Fuse Whitelist
41
+ ([`d289d6d`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/d289d6db49d300be269967f3c58fbd501de53974))
42
+
43
+ - **sdk**: Report Compound V3 lending health
44
+ ([`5baa9c2`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/5baa9c243b9a8f8c9986f08bce701acda5ef98ad))
45
+
46
+ - **sdk**: Support Compound V3 Institutional USDC market fuses
47
+ ([`b01c5db`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/b01c5db8904e88ce734698f3037327e72234f676))
48
+
49
+ ### Testing
50
+
51
+ - **sdk**: Drop type ignores from the redaction tests' RPC server
52
+ ([`ffa57c1`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/ffa57c1c7ea9f1341e7f8bde8a19f84f9c056d88))
53
+
54
+ - **sdk**: Keep the live preflight checks independent of the Fuse Whitelist
55
+ ([`e9bc161`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/e9bc1618228234b8b0c6e993b003eaca58cdc6ba))
56
+
57
+ - **sdk**: Pass the live context arguments in the Fuse Whitelist preflight test
58
+ ([`b974e08`](https://github.com/IPOR-Labs/ipor-fusion.py/commit/b974e0864aaa9cf16786a74a4b39c268d4b7214a))
59
+
60
+
5
61
  ## v4.1.0 (2026-11-03)
6
62
 
7
63
  ### Bug Fixes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ipor-fusion
3
- Version: 4.2.0.dev20
3
+ Version: 4.2.1.dev2
4
4
  Summary: The IPOR Fusion SDK for Python
5
5
  Project-URL: homepage, https://ipor.io
6
6
  Project-URL: repository, https://github.com/IPOR-Labs/ipor-fusion.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ipor-fusion"
3
- version = "4.2.0.dev20"
3
+ version = "4.2.1.dev2"
4
4
  description = "The IPOR Fusion SDK for Python"
5
5
  authors = [
6
6
  { name = "Adam Mydlarz", email = "amydlarz@gmail.com" },
@@ -5,9 +5,9 @@ import click
5
5
  from ipor_fusion.cli.config_store import (
6
6
  FusionConfig,
7
7
  load_config,
8
- mask_provider_url,
9
8
  save_config,
10
9
  )
10
+ from ipor_fusion.redaction import mask_provider_url
11
11
  from ipor_fusion.types import ChainId
12
12
 
13
13
 
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import json
4
4
  import os
5
5
  from pathlib import Path
6
- from urllib.parse import urlsplit
7
6
 
8
7
  import click
9
8
  from eth_typing import ChecksumAddress
@@ -54,18 +53,14 @@ class FusionConfig(BaseModel):
54
53
  return data
55
54
 
56
55
 
57
- def mask_provider_url(url: str) -> str:
58
- """Render a provider URL as scheme and host only.
59
-
60
- RPC provider URLs carry API keys in the path, the query string or the
61
- userinfo, so nothing but the scheme and the host (with port) may be
62
- displayed. An unparsable value is rendered as ``***``.
63
- """
64
- parts = urlsplit(url)
65
- host = parts.netloc.rpartition("@")[2]
66
- if not parts.scheme or not host:
67
- return "***"
68
- return f"{parts.scheme}://{host}"
56
+ def _describe_invalid_fields(exc: ValidationError) -> str:
57
+ """Which fields failed and why, never their values: the file holds API keys."""
58
+ return "\n".join(
59
+ f" {'.'.join(str(part) for part in error['loc'])}: {error['msg']}"
60
+ for error in exc.errors(
61
+ include_input=False, include_url=False, include_context=False
62
+ )
63
+ )
69
64
 
70
65
 
71
66
  def load_config() -> FusionConfig:
@@ -75,7 +70,7 @@ def load_config() -> FusionConfig:
75
70
  return FusionConfig.model_validate_json(CONFIG_FILE.read_text(encoding="utf-8"))
76
71
  except ValidationError as exc:
77
72
  raise click.ClickException(
78
- f"Config {CONFIG_FILE} is invalid:\n{exc}\n"
73
+ f"Config {CONFIG_FILE} is invalid:\n{_describe_invalid_fields(exc)}\n"
79
74
  "Fix the file manually or delete it to start fresh."
80
75
  ) from exc
81
76
  except json.JSONDecodeError as exc:
@@ -4,6 +4,7 @@ import os
4
4
  import sys
5
5
 
6
6
  import click
7
+ import requests
7
8
 
8
9
  from ipor_fusion.cli.address_cmd import address
9
10
  from ipor_fusion.cli.changelog_cmd import changelog
@@ -12,9 +13,21 @@ from ipor_fusion.cli.fuse_cmd import fuse
12
13
  from ipor_fusion.cli.market_cmd import market
13
14
  from ipor_fusion.cli.preflight_cmd import preflight
14
15
  from ipor_fusion.cli.vault_cmd import vault
16
+ from ipor_fusion.redaction import redact_provider_urls
15
17
 
16
18
 
17
- @click.group()
19
+ class _RedactingGroup(click.Group):
20
+ """Click prints a `ClickException` itself, before `main` could see it."""
21
+
22
+ def invoke(self, ctx: click.Context) -> object:
23
+ try:
24
+ return super().invoke(ctx)
25
+ except click.ClickException as exc:
26
+ exc.message = redact_provider_urls(exc.message)
27
+ raise
28
+
29
+
30
+ @click.group(cls=_RedactingGroup)
18
31
  @click.version_option(package_name="ipor_fusion")
19
32
  @click.option("--verbose", "-v", is_flag=True, help="Print RPC call details.")
20
33
  @click.option("--quiet", "-q", is_flag=True, help="Suppress non-essential output.")
@@ -49,16 +62,17 @@ def main() -> None:
49
62
  sys.exit(130)
50
63
  except click.UsageError:
51
64
  raise
52
- except ConnectionError as exc:
53
- click.secho(f"Connection failed: {exc}. Check your provider URL.", fg="red")
65
+ except (ConnectionError, requests.ConnectionError) as exc:
66
+ msg = redact_provider_urls(str(exc))
67
+ click.secho(f"Connection failed: {msg}. Check your provider URL.", fg="red")
54
68
  sys.exit(1)
55
69
  except ValueError as exc:
56
- msg = str(exc)
70
+ msg = redact_provider_urls(str(exc))
57
71
  if "address" in msg.lower():
58
72
  click.secho(f"Invalid address: {msg}", fg="red")
59
73
  else:
60
74
  click.secho(f"Error: {msg}", fg="red")
61
75
  sys.exit(1)
62
76
  except Exception as exc:
63
- click.secho(f"Error: {exc}", fg="red")
77
+ click.secho(f"Error: {redact_provider_urls(str(exc))}", fg="red")
64
78
  sys.exit(1)
@@ -28,6 +28,7 @@ from web3.exceptions import Web3Exception
28
28
  from web3.types import BlockIdentifier, FilterParams, LogReceipt
29
29
 
30
30
  from ipor_fusion.errors import LogScanError
31
+ from ipor_fusion.redaction import redact_provider_urls
31
32
  from ipor_fusion.types import ChainId
32
33
 
33
34
  # The log shapes the SDK's event readers accept: a receipt log from web3.py
@@ -70,7 +71,6 @@ _TRANSIENT_TEXT = re.compile(
70
71
  r"rate.?limit|too many requests|compute units|temporar|try again later", re.I
71
72
  )
72
73
  _TRANSIENT_STATUS = frozenset({429, 502, 503})
73
- _URL = re.compile(r"\w+://\S+")
74
74
 
75
75
 
76
76
  class LogSource(Protocol):
@@ -370,13 +370,13 @@ def _status_code(exc: BaseException) -> int | None:
370
370
 
371
371
 
372
372
  def _describe(exc: BaseException) -> str:
373
- """Error text with the response body, provider URLs (they embed API keys)
374
- removed, truncated."""
373
+ """Error text with the response body, provider URLs reduced to their host,
374
+ truncated."""
375
375
  text = f"{type(exc).__name__}: {exc}"
376
376
  body = getattr(getattr(exc, "response", None), "text", None)
377
377
  if isinstance(body, str) and body:
378
378
  text = f"{text} {body}"
379
- return _URL.sub("<provider>", text)[:400]
379
+ return redact_provider_urls(text)[:400]
380
380
 
381
381
 
382
382
  def _is_transient(exc: BaseException) -> bool:
@@ -12,6 +12,7 @@ from web3.types import TxParams, TxReceipt
12
12
 
13
13
  from ipor_fusion.fusion_errors import FUSION_ERROR_SIGNATURES
14
14
  from ipor_fusion.onchain_text import sanitize_onchain_text
15
+ from ipor_fusion.redaction import redact_provider_urls
15
16
  from ipor_fusion.types import TxHash
16
17
 
17
18
  log = logging.getLogger(__name__)
@@ -148,7 +149,9 @@ def get_revert_reason(web3: Web3, tx_hash: TxHash, receipt: TxReceipt) -> str |
148
149
  or "execution reverted" in exc_message.lower()
149
150
  ):
150
151
  return exc_message
151
- log.debug("Could not decode revert reason: %s", exc)
152
+ log.debug(
153
+ "Could not decode revert reason: %s", redact_provider_urls(exc_message)
154
+ )
152
155
  return None
153
156
 
154
157
 
@@ -4,12 +4,14 @@ Calls the ipor_fusion SDK directly — no CLI subprocess.
4
4
  Configuration is loaded from the shared CLI config (~/.config/ipor-fusion/).
5
5
  """
6
6
 
7
- from collections.abc import Callable
7
+ from collections.abc import Callable, Sequence
8
8
  from functools import partial
9
- from typing import Annotated, Literal
9
+ from typing import Annotated, Any, Literal
10
10
 
11
11
  from eth_typing import ChecksumAddress
12
12
  from mcp.server.fastmcp import FastMCP
13
+ from mcp.server.fastmcp.exceptions import ToolError
14
+ from mcp.types import ContentBlock
13
15
  from pydantic import AfterValidator, BeforeValidator, Field
14
16
  from web3 import Web3
15
17
 
@@ -23,7 +25,6 @@ from ipor_fusion.cli.config_store import (
23
25
  FusionConfig,
24
26
  VaultEntry,
25
27
  load_config,
26
- mask_provider_url,
27
28
  resolve_chain_id,
28
29
  save_config,
29
30
  )
@@ -69,6 +70,7 @@ from ipor_fusion.preflight import (
69
70
  )
70
71
  from ipor_fusion.readers.morpho import MorphoReader, morpho_blue_address
71
72
  from ipor_fusion.readers.oracle_mapping import build_oracle_mapping
73
+ from ipor_fusion.redaction import mask_provider_url, redact_provider_urls
72
74
  from ipor_fusion.registry import (
73
75
  build_address_list_json,
74
76
  build_address_lookup_json,
@@ -92,7 +94,20 @@ from ipor_fusion.vault_info import build_vault_info_json, build_vault_validation
92
94
 
93
95
  # chain_id default: detect the chain from the saved vault or the single provider.
94
96
 
95
- mcp = FastMCP(
97
+
98
+ class _RedactingFastMCP(FastMCP):
99
+ """Every tool failure reaches the client through `call_tool`."""
100
+
101
+ async def call_tool(
102
+ self, name: str, arguments: dict[str, Any]
103
+ ) -> Sequence[ContentBlock] | dict[str, Any]:
104
+ try:
105
+ return await super().call_tool(name, arguments)
106
+ except Exception as exc:
107
+ raise ToolError(redact_provider_urls(str(exc))) from exc
108
+
109
+
110
+ mcp = _RedactingFastMCP(
96
111
  "ipor-fusion",
97
112
  instructions=(
98
113
  "Inspection and configuration tools for IPOR Fusion Plasma Vaults. "
@@ -44,6 +44,7 @@ from ipor_fusion.readers.morpho import (
44
44
  morpho_blue_address,
45
45
  to_assets_up,
46
46
  )
47
+ from ipor_fusion.redaction import redact_provider_urls
47
48
  from ipor_fusion.substrates import decode_substrate, market_name
48
49
  from ipor_fusion.types import (
49
50
  ChainId,
@@ -227,7 +228,12 @@ class VaultLendingHealth:
227
228
 
228
229
  def _reason(what: str, exc: Exception) -> str:
229
230
  """Why a read failed, for the unreadable entry and its finding."""
230
- _logger.debug("lending %s read failed: %s: %s", what, type(exc).__name__, exc)
231
+ _logger.debug(
232
+ "lending %s read failed: %s: %s",
233
+ what,
234
+ type(exc).__name__,
235
+ redact_provider_urls(str(exc)),
236
+ )
231
237
  return f"{what} read failed: {type(exc).__name__}"
232
238
 
233
239
 
@@ -0,0 +1,39 @@
1
+ """Removes provider URLs from text meant for a person or a log.
2
+
3
+ RPC provider URLs carry API keys in the path, the query string or the userinfo,
4
+ and requests and web3 quote them in their exception messages.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from urllib.parse import urlsplit
11
+
12
+ # A URL runs to the next whitespace, since quotes and brackets are legal inside
13
+ # one and a key may follow them; only punctuation closing it stays in the text.
14
+ # The lookbehind tries each run of scheme characters once, so long hex in an error
15
+ # stays linear; a run that is no valid scheme ("0xhttp") comes out masked whole.
16
+ _URL = re.compile(r"(?<![a-zA-Z0-9+.\-])[a-zA-Z0-9+.\-]+://\S*[^\s.,;:)'\">]")
17
+ # urllib3 names only the path and query: "Max retries exceeded with url: /v2/key"
18
+ _REQUEST_TARGET = re.compile(r"\burl: /\S*")
19
+ _HOST = re.compile(r"(?:[a-zA-Z0-9.\-]+|\[[0-9a-fA-F:.]+\])(?::\d+)?")
20
+
21
+
22
+ def mask_provider_url(url: str) -> str:
23
+ """Render a provider URL as scheme and host only. An unparsable value is
24
+ rendered as ``***``: what looks like its host may be part of a key."""
25
+ try:
26
+ parts = urlsplit(url)
27
+ except ValueError:
28
+ return "***"
29
+ host = parts.netloc.rpartition("@")[2]
30
+ if not parts.scheme or not _HOST.fullmatch(host):
31
+ return "***"
32
+ return f"{parts.scheme}://{host}"
33
+
34
+
35
+ def redact_provider_urls(text: str) -> str:
36
+ """``text`` with every URL reduced to scheme and host, and every request
37
+ target urllib3 quotes on its own replaced."""
38
+ text = _URL.sub(lambda match: mask_provider_url(match.group()), text)
39
+ return _REQUEST_TARGET.sub("url: <redacted>", text)
@@ -12,6 +12,7 @@ from web3.exceptions import ContractLogicError, TimeExhausted, Web3RPCError
12
12
  from ipor_fusion.core.context import Web3Context
13
13
  from ipor_fusion.core.contract import Call
14
14
  from ipor_fusion.errors import EmptyCallResultError, LogScanError
15
+ from ipor_fusion.redaction import redact_provider_urls
15
16
 
16
17
  T = TypeVar("T")
17
18
 
@@ -36,7 +37,11 @@ def _scan_or_none(func: Callable[[], T]) -> T | None:
36
37
  try:
37
38
  return func()
38
39
  except _ROLE_SCAN_ERRORS as exc:
39
- _logger.debug("scan unavailable: %s: %s", type(exc).__name__, exc)
40
+ _logger.debug(
41
+ "scan unavailable: %s: %s",
42
+ type(exc).__name__,
43
+ redact_provider_urls(str(exc)),
44
+ )
40
45
  return None
41
46
 
42
47
 
@@ -8,9 +8,9 @@ from ipor_fusion.cli.config_store import (
8
8
  FusionConfig,
9
9
  VaultEntry,
10
10
  load_config,
11
- mask_provider_url,
12
11
  save_config,
13
12
  )
13
+ from ipor_fusion.redaction import mask_provider_url
14
14
 
15
15
 
16
16
  @pytest.fixture(autouse=True)
@@ -1,5 +1,6 @@
1
1
  """Adaptive eth_getLogs paging (`core.logs`) against a fake provider."""
2
2
 
3
+ import socket
3
4
  import threading
4
5
  from collections.abc import Callable
5
6
  from typing import Any
@@ -366,17 +367,40 @@ class TestNoPartialResults:
366
367
  with pytest.raises(KeyError):
367
368
  get_logs_adaptive(_web3(eth), ADDRESS, TOPICS, chunk_hint=100)
368
369
 
369
- def test_error_names_no_provider_url(self):
370
- provider_url = "https://rpc.example/v2/SECRETKEY"
371
- eth = _FakeEth(
372
- reject=lambda _l, _h: Web3RPCError(f"bad for url: {provider_url}")
373
- )
370
+ @pytest.mark.parametrize(
371
+ "quoted",
372
+ ["https://rpc.example/v2/SECRETKEY", "/v2/SECRETKEY?apikey=SECRETKEY"],
373
+ )
374
+ def test_error_names_no_provider_url(self, quoted):
375
+ eth = _FakeEth(reject=lambda _l, _h: Web3RPCError(f"bad for url: {quoted}"))
374
376
 
375
377
  with pytest.raises(LogScanError) as info:
376
378
  get_logs_adaptive(_web3(eth), ADDRESS, TOPICS, 999, 1_000)
377
379
 
378
380
  assert "SECRETKEY" not in str(info.value)
379
381
 
382
+ def test_refused_connection_names_no_request_target(self, monkeypatch):
383
+ monkeypatch.setattr(logs_mod, "_TRANSIENT_RETRIES", 0)
384
+ with socket.socket() as probe:
385
+ probe.bind(("127.0.0.1", 0))
386
+ port = probe.getsockname()[1]
387
+
388
+ def reject(_low: int, _high: int) -> Exception | None:
389
+ try:
390
+ requests.post(
391
+ f"http://127.0.0.1:{port}/v2/SECRETKEY?apikey=SECRETKEY", timeout=1
392
+ )
393
+ except requests.ConnectionError as exc:
394
+ return exc
395
+ return None
396
+
397
+ with pytest.raises(LogScanError) as info:
398
+ get_logs_adaptive(
399
+ _web3(_FakeEth(reject=reject)), ADDRESS, TOPICS, 999, 1_000
400
+ )
401
+
402
+ assert "SECRETKEY" not in str(info.value)
403
+
380
404
  def test_is_an_ipor_fusion_error(self):
381
405
  assert issubclass(LogScanError, IporFusionError)
382
406