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.
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.claude-plugin/plugin.json +1 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/AGENTS.md +4 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/CHANGELOG.md +56 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/PKG-INFO +1 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/pyproject.toml +1 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/config_cmd.py +1 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/config_store.py +9 -14
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/main.py +19 -5
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/logs.py +4 -4
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/errors.py +4 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/server.py +19 -4
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/lending_health.py +7 -1
- ipor_fusion-4.2.1.dev2/src/ipor_fusion/redaction.py +39 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_reads.py +6 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_config_store.py +1 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_logs.py +29 -5
- ipor_fusion-4.2.1.dev2/tests/test_provider_url_redaction.py +338 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/uv.lock +1 -1
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.claude-plugin/marketplace.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.env.example +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.gemini/settings.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.git-blame-ignore-revs +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/cd.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/ci.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/commit-lint.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/notify-slack.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/python-build.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/python-publish.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/release.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.github/workflows/report-slack.yml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.gitignore +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.pre-commit-config.yaml +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/.python-version +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/CLAUDE.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/CODEOWNERS +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/LICENSE +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/MIGRATION_4.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/README.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/build.sh +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/CONTRIBUTING.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/README.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/advanced_compound_v3_institutional_usdc_ethereum.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/advanced_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/advanced_euler_v2_looping_arbitrum.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/crosschain_ccip_usdc_arbitrum_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/external_state_margin_leg_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/hypercore_vault_official_deployment_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/examples/simple_aave_v3_supply_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/pytest.ini +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/scripts/commit-check.sh +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/scripts/fusion_error_signatures.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/skills/ipor-deploy-vault/SKILL.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/about.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cache.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/chains.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/address_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/changelog_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/fuse_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/market_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/options.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/oracle_mapping_view.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/preflight_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_cmd.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_health.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_info_view.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_markets_view.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/cli/vault_rendering.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/config/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/config/roles.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/access.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/address.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/context.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/context_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/contract.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/erc20.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/external_state_executor.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fee_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fuse.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fuse_action.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fuse_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/fusion_factory.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/multicall.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/oracle.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/plasma_vault.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/rewards_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/simulation.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/simulation_result.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/core/withdraw_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/chainlink.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/codec.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/contracts.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/events.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/indexer.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/lane.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/quote.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/ccip/transport.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/contracts.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/discovery.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/errors.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/ccip.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/fuses/stargate.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/lane.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/logs.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/messages.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/simulation.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/contracts.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/lane.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/layerzero.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/stargate/transport.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/crosschain/transport.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/dependency_graph.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/entry_cli.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/entry_mcp.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/explorer.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/field_docs.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuse_whitelist.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/aave_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/async_action.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/compound_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/erc4626.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/euler_v2.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/events.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/external_state.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/external_state_event_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/fluid_instadapp.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/gearbox_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/hypercore_event_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/hypercore_pre_hooks.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/merkl.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/morpho.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/ramses_v2.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/uniswap_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fuses/universal.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/fusion_errors.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/architecture.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/glossary.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/invariants.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/guide/quickstart.md +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/hypercore/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/market_ids.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/market_info.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/guide.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/mcp/models.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/morpho_api.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/onchain_text.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/preflight.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/py.typed +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/__init__.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/aave_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/aave_v4.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/compound_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/euler_v2.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/hypercore_outcome.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/morpho.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/oracle_mapping.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/oracle_mapping_nodes.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/oracle_mapping_reader.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/position_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/ramses_v2.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/uniswap_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/unpriceable.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/registry.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/substrates.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/types.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/validation.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_data.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_erc20.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_info.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_managers.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/vault_positions.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/UniswapV3UniversalRouter.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_addresses.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_crosschain.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_crosschain_lifecycle.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_crosschain_recovery.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_euler_v2.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_event_args.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_fake_rpc.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_foundry.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_golden.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_guide.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_hypercore_model.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_hypercore_shadow.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_live.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_multicall.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_oracle_mapping_fake.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_simulate.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/_validation.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/addresses.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/conftest.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/constants.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/hypercore_hip3_flow.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/hypercore_market55.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/hypercore_official.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v1.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v1.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v2.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/meta_morpho_v2.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_api/market.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_api/vault_v1.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_api/vault_v2.json +0 -0
- {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
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_api_error.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_api_error.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_no_api.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/market_golden/morpho_blue_no_api.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/oracle_mapping_golden/mapping.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/oracle_mapping_golden/mapping.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/simulate_v1_request.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/clean.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/clean.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/clean.txt +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/deployment_error.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/deployment_error.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/deployment_error.txt +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/fetched.cli.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/fetched.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/fetched.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/full.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/full.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/full.txt +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/rich.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/rich.mcp.json +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/fixtures/vault_info_golden/rich.txt +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_about.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_access_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_addresses_helper.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cache.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_ccip_read_disabled.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_chains.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_address.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_changelog.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_commands.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_helpers.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_lending_health.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_market_golden.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_market_morpho_blue.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_mcp_fuse_whitelist.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_mcp_parity.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_oracle_mapping.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_oracle_mapping_golden.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_pending_requests_scan.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_preflight.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_role_accounts.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_vault_info_golden.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_vault_predates_getters.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_cli_vault_validate.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_contract_call.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_contract_encoder.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_ccip_events.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_ccip_indexer.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_ccip_quote.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_chainlink.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_contracts.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_encoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_errors.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_lane.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_layering.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_crosschain_transport.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_docs_typing.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_erc20.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_error_hierarchy.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_compound_v3_ethereum.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_crosschain.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_euler_looping_arbitrum.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_hypercore_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_examples_vaults.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_explorer.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_external_state_events.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_external_state_executor.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fee_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fuse_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fuse_encoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fuse_whitelist.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fusion_errors.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_fusion_instance.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_governance_wrappers.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_guide.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_deployment.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_events.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_flow.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_live.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_outcome.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_pre_hooks.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_readers.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_hypercore_simulation.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_lending_health_compound_v3_onchain.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_address.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_hypercore.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_models.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_preflight.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_server.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_mcp_vault_validate.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_multicall.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_onchain_text.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_oracle.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_oracle_mapping.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_plasma_vault.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_preflight.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_public_api.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_public_type_reachability.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_reader_encoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_registry.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_revert_decoding.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_rewards_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_roles.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_sdk_layering.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_borrow_aave_v3_mainnet.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_claim_merkl_wrapper_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_claim_morpho_rewards.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_compound_v3_institutional_usdc.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_crosschain_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_batch_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_credit_market_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_swap_deploy_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_swap_reconfigure_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_euler_v2_swap_registry_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_arbitrum.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_crosschain.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_ethereum.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_examples_hyperevm.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_external_state_executor_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_fusion_factory_clone_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_looping_aave_v3_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_looping_morpho_blue_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_morpho_blue.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v3.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v4_uniswap.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v5_ramses.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_pilot_v5_scheduled.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_plasma_vault_setters_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_preflight_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_readers_integration.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_release_funds.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_swap_on_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_timelock_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulate_vault_from_scratch_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulation_errors.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_simulation_observation.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_solidity_compiler.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_solidity_mirrors.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_substrates.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_types_and_events.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_unpriceable_tokens.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_validation.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_data_block.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_dep_graph.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_info_fetched_golden.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_info_onchain_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_info_onchain_base_no_withdraw_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_reconciliation_onchain_base.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_vault_simulator_blocks.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_web3_context.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/test_withdraw_manager.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/typed_client/consumer.py +0 -0
- {ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/tests/typed_client/rejected.py +0 -0
|
@@ -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
|
|
@@ -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
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
374
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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. "
|
{ipor_fusion-4.2.0.dev20 → ipor_fusion-4.2.1.dev2}/src/ipor_fusion/readers/lending_health.py
RENAMED
|
@@ -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(
|
|
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(
|
|
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
|
|
|
@@ -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
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|