iwa 0.0.61__tar.gz → 0.0.62__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.
- {iwa-0.0.61/src/iwa.egg-info → iwa-0.0.62}/PKG-INFO +1 -1
- {iwa-0.0.61 → iwa-0.0.62}/pyproject.toml +2 -2
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/interface.py +26 -12
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/safe_executor.py +110 -26
- {iwa-0.0.61 → iwa-0.0.62/src/iwa.egg-info}/PKG-INFO +1 -1
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chainlist_enrichment.py +2 -2
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_executor.py +70 -0
- {iwa-0.0.61 → iwa-0.0.62}/LICENSE +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/README.md +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/setup.cfg +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/__main__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/errors.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/manager.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/models.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chain/rate_limiter.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/chainlist.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/cli.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/constants.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/abis/erc20.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/abis/multisend.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/cache.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/contract.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/decoder.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/erc20.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/contracts/multisend.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/db.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/http.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/ipfs.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/keys.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/mnemonic.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/models.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/monitor.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/plugins.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/pricing.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/rpc_monitor.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/secrets.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/account.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/balance.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/plugin.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/safe.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transaction.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/base.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/erc20.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/multisend.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/native.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/services/transfer/swap.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tables.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/test.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_gnosis_fee.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_ipfs.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_pricing.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_regression_fixes.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/tests/test_wallet.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/types.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/ui.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/utils.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/core/wallet.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow/types.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/plugin.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/safe.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/gnosis/tests/test_safe.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/constants.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech_marketplace_v1.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/base.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/mech.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/mech_marketplace_v1.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/service.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/staking.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/events.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/importer.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/mech_reference.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/models.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/plugin.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/base.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/lifecycle.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/mech.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/service_manager/staking.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/conftest.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_archiving.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_plugin.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_plugin_full.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tui/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/check_profile.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/drain_accounts.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/list_contracts.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/release.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/reset_env.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/reset_tenderly.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/restore_backup.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/test_chainlist.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tools/wallet_check.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/app.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/modals/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/modals/base.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/rpc.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/screens/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/screens/wallets.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_app.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_rpc.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/tests/test_widgets.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/widgets/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/widgets/base.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/tui/workers.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/dependencies.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/models.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/accounts.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/__init__.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/admin.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/funding.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/general.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/services.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/olas/staking.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/state.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/swap.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/routers/transactions.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/server.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/static/app.js +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/static/index.html +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/static/style.css +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_endpoints.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_olas.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_swap.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/SOURCES.txt +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/dependency_links.txt +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/entry_points.txt +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/requires.txt +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/iwa.egg-info/top_level.txt +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_cow.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_safe.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_transaction_retry_logic.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_tui.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_wallets_screen.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/legacy_web.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_account_service.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_balance_service.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chain.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chain_interface.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_chain_interface_coverage.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_cli.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_contract.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_db.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_drain_coverage.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_erc20.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_gnosis_plugin.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_keys.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_legacy_wallet.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_main.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_migration.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_mnemonic.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_modals.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_models.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_monitor.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_multisend.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_plugin_service.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rate_limiter.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rate_limiter_retry.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_reset_tenderly.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_efficiency.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_rate_limit.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_rotation.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_rpc_view.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_coverage.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_integration.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_safe_service.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_service_manager_integration.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_service_manager_structure.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_service_transaction.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_staking_router.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_staking_simple.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_tables.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transaction_service.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_multisend.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_native.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_security.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_structure.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_transfer_swap_unit.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_ui_coverage.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_utils.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tests/test_workers.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tools/create_and_stake_service.py +0 -0
- {iwa-0.0.61 → iwa-0.0.62}/src/tools/verify_drain.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "iwa"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.62"
|
|
4
4
|
description = "A secure, modular, and plugin-based framework for crypto agents and ops"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12,<4.0"
|
|
@@ -72,7 +72,7 @@ where = ["src"]
|
|
|
72
72
|
|
|
73
73
|
[tool.ruff]
|
|
74
74
|
line-length = 100
|
|
75
|
-
target-version = "0.0.
|
|
75
|
+
target-version = "0.0.62"
|
|
76
76
|
fix = true
|
|
77
77
|
|
|
78
78
|
[tool.ruff.lint]
|
|
@@ -285,11 +285,15 @@ class ChainInterface:
|
|
|
285
285
|
|
|
286
286
|
# -- ChainList enrichment ----------------------------------------------
|
|
287
287
|
|
|
288
|
-
MAX_RPCS =
|
|
288
|
+
MAX_RPCS = 20 # Cap total RPCs per chain
|
|
289
289
|
|
|
290
290
|
def _enrich_rpcs_from_chainlist(self) -> None:
|
|
291
291
|
"""Add validated public RPCs from ChainList to the rotation pool."""
|
|
292
292
|
if len(self.chain.rpcs) >= self.MAX_RPCS:
|
|
293
|
+
logger.debug(
|
|
294
|
+
f"{self.chain.name}: skipping ChainList enrichment "
|
|
295
|
+
f"(already have {len(self.chain.rpcs)} RPCs)"
|
|
296
|
+
)
|
|
293
297
|
return
|
|
294
298
|
|
|
295
299
|
try:
|
|
@@ -354,35 +358,45 @@ class ChainInterface:
|
|
|
354
358
|
|
|
355
359
|
if should_rotate:
|
|
356
360
|
failed_index = self._current_rpc_index
|
|
361
|
+
failed_rpc = sanitize_rpc_url(self.chain.rpcs[failed_index]) if self.chain.rpcs else "?"
|
|
357
362
|
|
|
358
363
|
# Apply per-RPC backoff so smart rotation skips this RPC.
|
|
359
364
|
if result["is_quota_exceeded"]:
|
|
360
|
-
error_type = "
|
|
361
|
-
|
|
365
|
+
error_type = "QUOTA"
|
|
366
|
+
backoff = self.QUOTA_EXCEEDED_BACKOFF
|
|
367
|
+
self._mark_rpc_backoff(failed_index, backoff)
|
|
362
368
|
elif result["is_rate_limit"]:
|
|
363
|
-
error_type = "
|
|
364
|
-
|
|
369
|
+
error_type = "RATE_LIMIT"
|
|
370
|
+
backoff = self.RATE_LIMIT_BACKOFF
|
|
371
|
+
self._mark_rpc_backoff(failed_index, backoff)
|
|
365
372
|
# Brief global backoff so other threads don't immediately flood
|
|
366
373
|
# the same (now backed-off) RPC before rotation takes effect.
|
|
367
374
|
self._rate_limiter.trigger_backoff(seconds=2.0)
|
|
368
375
|
else:
|
|
369
|
-
error_type = "
|
|
370
|
-
|
|
376
|
+
error_type = "CONNECTION"
|
|
377
|
+
backoff = self.CONNECTION_ERROR_BACKOFF
|
|
378
|
+
self._mark_rpc_backoff(failed_index, backoff)
|
|
379
|
+
|
|
380
|
+
# Count healthy RPCs for visibility
|
|
381
|
+
healthy_count = sum(1 for i in range(len(self.chain.rpcs)) if self._is_rpc_healthy(i))
|
|
382
|
+
total_rpcs = len(self.chain.rpcs) if self.chain.rpcs else 0
|
|
371
383
|
|
|
372
384
|
logger.warning(
|
|
373
|
-
f"
|
|
374
|
-
f"(
|
|
385
|
+
f"[{self.chain.name}] RPC #{failed_index} {error_type} → "
|
|
386
|
+
f"backoff {int(backoff)}s ({healthy_count}/{total_rpcs} healthy) | "
|
|
387
|
+
f"{failed_rpc}: {str(error)[:100]}"
|
|
375
388
|
)
|
|
376
389
|
|
|
377
390
|
if self.rotate_rpc():
|
|
378
391
|
result["rotated"] = True
|
|
379
392
|
result["should_retry"] = True
|
|
380
|
-
|
|
393
|
+
new_rpc = sanitize_rpc_url(self.chain.rpcs[self._current_rpc_index])
|
|
394
|
+
logger.info(f"[{self.chain.name}] Rotated to RPC #{self._current_rpc_index}: {new_rpc}")
|
|
381
395
|
else:
|
|
382
396
|
# Rotation skipped (cooldown or single RPC) - still allow retry
|
|
383
397
|
result["should_retry"] = True
|
|
384
|
-
logger.
|
|
385
|
-
f"
|
|
398
|
+
logger.debug(
|
|
399
|
+
f"[{self.chain.name}] Rotation skipped (cooldown), retrying RPC #{self._current_rpc_index}"
|
|
386
400
|
)
|
|
387
401
|
|
|
388
402
|
elif result["is_server_error"]:
|
|
@@ -39,6 +39,10 @@ class SafeTransactionExecutor:
|
|
|
39
39
|
MAX_GAS_MULTIPLIER = 10 # Hard cap: never exceed 10x original estimate
|
|
40
40
|
DEFAULT_FALLBACK_GAS = 500_000 # Fallback when estimation fails
|
|
41
41
|
|
|
42
|
+
# Fee bumping for "max fee per gas less than block base fee" errors
|
|
43
|
+
FEE_BUMP_PERCENTAGE = 1.30 # 30% bump per retry on fee errors
|
|
44
|
+
MAX_FEE_BUMP_FACTOR = 3.0 # Cap: never bump more than 3x original
|
|
45
|
+
|
|
42
46
|
def __init__(
|
|
43
47
|
self,
|
|
44
48
|
chain_interface: "ChainInterface",
|
|
@@ -76,6 +80,7 @@ class SafeTransactionExecutor:
|
|
|
76
80
|
last_error = None
|
|
77
81
|
current_gas = safe_tx.safe_tx_gas
|
|
78
82
|
base_estimate = current_gas if current_gas > 0 else 0
|
|
83
|
+
fee_bump_factor = 1.0 # Multiplier for EIP-1559 fees, increases on fee errors
|
|
79
84
|
|
|
80
85
|
for attempt in range(self.max_retries + 1):
|
|
81
86
|
SAFE_TX_STATS["total_attempts"] += 1
|
|
@@ -89,6 +94,7 @@ class SafeTransactionExecutor:
|
|
|
89
94
|
attempt,
|
|
90
95
|
current_gas,
|
|
91
96
|
base_estimate,
|
|
97
|
+
fee_bump_factor,
|
|
92
98
|
)
|
|
93
99
|
|
|
94
100
|
# Check receipt
|
|
@@ -106,7 +112,7 @@ class SafeTransactionExecutor:
|
|
|
106
112
|
raise ValueError("Transaction reverted on-chain")
|
|
107
113
|
|
|
108
114
|
except Exception as e:
|
|
109
|
-
updated_tx, should_retry = self._handle_execution_failure(
|
|
115
|
+
updated_tx, should_retry, is_fee_error = self._handle_execution_failure(
|
|
110
116
|
e, safe_address, safe_tx, attempt, operation_name
|
|
111
117
|
)
|
|
112
118
|
last_error = e
|
|
@@ -115,7 +121,12 @@ class SafeTransactionExecutor:
|
|
|
115
121
|
|
|
116
122
|
# Update gas/nonce for next loop if needed
|
|
117
123
|
safe_tx = updated_tx
|
|
118
|
-
|
|
124
|
+
|
|
125
|
+
# Bump fee multiplier on fee-related errors (base fee > max fee)
|
|
126
|
+
if is_fee_error and fee_bump_factor < self.MAX_FEE_BUMP_FACTOR:
|
|
127
|
+
fee_bump_factor *= self.FEE_BUMP_PERCENTAGE
|
|
128
|
+
fee_bump_factor = min(fee_bump_factor, self.MAX_FEE_BUMP_FACTOR)
|
|
129
|
+
logger.info(f"[{operation_name}] Fee bump factor increased to {fee_bump_factor:.2f}x")
|
|
119
130
|
|
|
120
131
|
delay = self.DEFAULT_RETRY_DELAY * (2**attempt)
|
|
121
132
|
time.sleep(delay)
|
|
@@ -131,6 +142,7 @@ class SafeTransactionExecutor:
|
|
|
131
142
|
attempt,
|
|
132
143
|
current_gas,
|
|
133
144
|
base_estimate,
|
|
145
|
+
fee_bump_factor: float = 1.0,
|
|
134
146
|
) -> str:
|
|
135
147
|
"""Prepare client, estimate gas, simulate, and execute."""
|
|
136
148
|
# 1. (Re)Create Safe client
|
|
@@ -171,24 +183,11 @@ class SafeTransactionExecutor:
|
|
|
171
183
|
signatures_backup = safe_tx.signatures
|
|
172
184
|
|
|
173
185
|
try:
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
# Handle both tuple return (tx_hash, tx) and bytes return
|
|
180
|
-
if isinstance(result, tuple):
|
|
181
|
-
tx_hash_bytes = result[0]
|
|
182
|
-
else:
|
|
183
|
-
tx_hash_bytes = result
|
|
184
|
-
|
|
185
|
-
# Handle both bytes and hex string returns
|
|
186
|
-
if isinstance(tx_hash_bytes, bytes):
|
|
187
|
-
return f"0x{tx_hash_bytes.hex()}"
|
|
188
|
-
elif isinstance(tx_hash_bytes, str):
|
|
189
|
-
return tx_hash_bytes if tx_hash_bytes.startswith("0x") else f"0x{tx_hash_bytes}"
|
|
190
|
-
else:
|
|
191
|
-
return str(tx_hash_bytes)
|
|
186
|
+
# Execute with appropriate gas pricing
|
|
187
|
+
result = self._execute_with_gas_pricing(
|
|
188
|
+
safe_tx, signer_keys[0], fee_bump_factor, operation_name
|
|
189
|
+
)
|
|
190
|
+
return self._extract_tx_hash(result)
|
|
192
191
|
|
|
193
192
|
finally:
|
|
194
193
|
# Restore signatures for next attempt if needed
|
|
@@ -196,6 +195,39 @@ class SafeTransactionExecutor:
|
|
|
196
195
|
if safe_tx.signatures != signatures_backup:
|
|
197
196
|
safe_tx.signatures = signatures_backup
|
|
198
197
|
|
|
198
|
+
def _execute_with_gas_pricing(
|
|
199
|
+
self, safe_tx: SafeTx, signer_key: str, fee_bump_factor: float, operation_name: str
|
|
200
|
+
):
|
|
201
|
+
"""Execute transaction with appropriate gas pricing strategy.
|
|
202
|
+
|
|
203
|
+
If fee_bump_factor > 1.0, calculates a bumped gas price to overcome
|
|
204
|
+
base fee volatility. Otherwise uses EIP-1559 FAST speed.
|
|
205
|
+
"""
|
|
206
|
+
if fee_bump_factor > 1.0:
|
|
207
|
+
bumped_gas_price = self._calculate_bumped_gas_price(fee_bump_factor)
|
|
208
|
+
if bumped_gas_price:
|
|
209
|
+
logger.debug(
|
|
210
|
+
f"[{operation_name}] Using bumped gas price: {bumped_gas_price} wei "
|
|
211
|
+
f"(factor: {fee_bump_factor:.2f}x)"
|
|
212
|
+
)
|
|
213
|
+
return safe_tx.execute(signer_key, tx_gas_price=bumped_gas_price)
|
|
214
|
+
# Fallback to FAST if calculation fails
|
|
215
|
+
return safe_tx.execute(signer_key, eip1559_speed=TxSpeed.FAST)
|
|
216
|
+
# Default: use EIP-1559 'FAST' speed
|
|
217
|
+
return safe_tx.execute(signer_key, eip1559_speed=TxSpeed.FAST)
|
|
218
|
+
|
|
219
|
+
def _extract_tx_hash(self, result) -> str:
|
|
220
|
+
"""Extract transaction hash from execute() result."""
|
|
221
|
+
# Handle both tuple return (tx_hash, tx) and bytes return
|
|
222
|
+
tx_hash_bytes = result[0] if isinstance(result, tuple) else result
|
|
223
|
+
|
|
224
|
+
# Handle both bytes and hex string returns
|
|
225
|
+
if isinstance(tx_hash_bytes, bytes):
|
|
226
|
+
return f"0x{tx_hash_bytes.hex()}"
|
|
227
|
+
if isinstance(tx_hash_bytes, str):
|
|
228
|
+
return tx_hash_bytes if tx_hash_bytes.startswith("0x") else f"0x{tx_hash_bytes}"
|
|
229
|
+
return str(tx_hash_bytes)
|
|
230
|
+
|
|
199
231
|
def _check_receipt_status(self, receipt) -> bool:
|
|
200
232
|
"""Check if receipt has successful status."""
|
|
201
233
|
status = getattr(receipt, "status", None)
|
|
@@ -210,14 +242,20 @@ class SafeTransactionExecutor:
|
|
|
210
242
|
safe_tx: SafeTx,
|
|
211
243
|
attempt: int,
|
|
212
244
|
operation_name: str,
|
|
213
|
-
) -> Tuple[SafeTx, bool]:
|
|
214
|
-
"""Handle execution failure and determine next steps.
|
|
245
|
+
) -> Tuple[SafeTx, bool, bool]:
|
|
246
|
+
"""Handle execution failure and determine next steps.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Tuple of (updated_safe_tx, should_retry, is_fee_error)
|
|
250
|
+
|
|
251
|
+
"""
|
|
215
252
|
classification = self._classify_error(error)
|
|
253
|
+
is_fee_error = classification["is_fee_error"]
|
|
216
254
|
|
|
217
255
|
if attempt >= self.max_retries:
|
|
218
256
|
SAFE_TX_STATS["final_failures"] += 1
|
|
219
257
|
logger.error(f"[{operation_name}] Failed after {attempt + 1} attempts: {error}")
|
|
220
|
-
return safe_tx, False
|
|
258
|
+
return safe_tx, False, is_fee_error
|
|
221
259
|
|
|
222
260
|
strategy = "retry"
|
|
223
261
|
safe = self._recreate_safe_client(safe_address)
|
|
@@ -231,13 +269,16 @@ class SafeTransactionExecutor:
|
|
|
231
269
|
SAFE_TX_STATS["rpc_rotations"] += 1
|
|
232
270
|
result = self.chain_interface._handle_rpc_error(error)
|
|
233
271
|
if not result["should_retry"]:
|
|
234
|
-
return safe_tx, False
|
|
272
|
+
return safe_tx, False, is_fee_error
|
|
273
|
+
elif is_fee_error:
|
|
274
|
+
strategy = "fee bump"
|
|
275
|
+
SAFE_TX_STATS["gas_retries"] += 1
|
|
235
276
|
elif classification["is_gas_error"]:
|
|
236
277
|
strategy = "gas increase"
|
|
237
|
-
|
|
278
|
+
SAFE_TX_STATS["gas_retries"] += 1
|
|
238
279
|
|
|
239
280
|
self._log_retry(attempt + 1, error, strategy)
|
|
240
|
-
return safe_tx, True
|
|
281
|
+
return safe_tx, True, is_fee_error
|
|
241
282
|
|
|
242
283
|
def _estimate_safe_tx_gas(self, safe: Safe, safe_tx: SafeTx, base_estimate: int = 0) -> int:
|
|
243
284
|
"""Estimate gas for a Safe transaction with buffer and hard cap."""
|
|
@@ -320,14 +361,57 @@ class SafeTransactionExecutor:
|
|
|
320
361
|
error
|
|
321
362
|
) or self.chain_interface._is_connection_error(error)
|
|
322
363
|
|
|
364
|
+
# Fee-specific errors: base fee jumped above our max fee
|
|
365
|
+
fee_error_signals = [
|
|
366
|
+
"max fee per gas less than block base fee",
|
|
367
|
+
"maxfeepergas",
|
|
368
|
+
"fee too low",
|
|
369
|
+
"underpriced",
|
|
370
|
+
]
|
|
371
|
+
is_fee_error = any(signal in err_text for signal in fee_error_signals)
|
|
372
|
+
|
|
323
373
|
return {
|
|
324
374
|
"is_gas_error": any(x in err_text for x in ["gas", "out of gas", "intrinsic"]),
|
|
375
|
+
"is_fee_error": is_fee_error,
|
|
325
376
|
"is_nonce_error": self._is_nonce_error(error),
|
|
326
377
|
"is_rpc_error": is_rpc,
|
|
327
378
|
"is_revert": "revert" in err_text or "execution reverted" in err_text,
|
|
328
379
|
"is_signature_error": self._is_signature_error(error),
|
|
329
380
|
}
|
|
330
381
|
|
|
382
|
+
def _calculate_bumped_gas_price(self, bump_factor: float) -> Optional[int]:
|
|
383
|
+
"""Calculate a bumped gas price based on current base fee.
|
|
384
|
+
|
|
385
|
+
Uses legacy gas price (not EIP-1559) for compatibility with safe-eth-py's
|
|
386
|
+
tx_gas_price parameter. The bumped price ensures we're above the current
|
|
387
|
+
base fee even if it's volatile.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
bump_factor: Multiplier to apply to the base fee (e.g., 1.3 = 30% bump)
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Gas price in wei, or None if calculation fails
|
|
394
|
+
|
|
395
|
+
"""
|
|
396
|
+
try:
|
|
397
|
+
web3 = self.chain_interface.web3
|
|
398
|
+
latest_block = web3.eth.get_block("latest")
|
|
399
|
+
base_fee = latest_block.get("baseFeePerGas")
|
|
400
|
+
|
|
401
|
+
if base_fee is not None:
|
|
402
|
+
# EIP-1559 chain: calculate bumped max fee
|
|
403
|
+
# base_fee * bump_factor * 1.5 (extra buffer) + priority fee
|
|
404
|
+
priority_fee = max(int(web3.eth.max_priority_fee), 1)
|
|
405
|
+
bumped_fee = int(base_fee * bump_factor * 1.5) + priority_fee
|
|
406
|
+
return bumped_fee
|
|
407
|
+
else:
|
|
408
|
+
# Legacy chain: bump the gas price directly
|
|
409
|
+
gas_price = web3.eth.gas_price
|
|
410
|
+
return int(gas_price * bump_factor)
|
|
411
|
+
except Exception as e:
|
|
412
|
+
logger.debug(f"Failed to calculate bumped gas price: {e}")
|
|
413
|
+
return None
|
|
414
|
+
|
|
331
415
|
def _decode_revert_reason(self, error: Exception) -> Optional[str]:
|
|
332
416
|
"""Attempt to decode the revert reason."""
|
|
333
417
|
import re
|
|
@@ -343,12 +343,12 @@ class TestEnrichFromChainlist:
|
|
|
343
343
|
|
|
344
344
|
chain = MagicMock(spec=SupportedChain)
|
|
345
345
|
chain.name = "TestChain"
|
|
346
|
-
chain.rpcs = [f"https://rpc{i}.example.com" for i in range(
|
|
346
|
+
chain.rpcs = [f"https://rpc{i}.example.com" for i in range(20)]
|
|
347
347
|
chain.rpc = "https://rpc0.example.com"
|
|
348
348
|
chain.chain_id = 100
|
|
349
349
|
|
|
350
350
|
with patch("iwa.core.chainlist.ChainlistRPC") as mock_cl_cls:
|
|
351
351
|
ChainInterface(chain)
|
|
352
352
|
|
|
353
|
-
# Already at MAX_RPCS=
|
|
353
|
+
# Already at MAX_RPCS=20, ChainlistRPC should not be called
|
|
354
354
|
mock_cl_cls.assert_not_called()
|
|
@@ -567,3 +567,73 @@ def test_rpc_rotation_stops_when_should_not_retry(
|
|
|
567
567
|
assert success is False
|
|
568
568
|
# Only 1 attempt because should_retry=False
|
|
569
569
|
assert mock_safe_tx.execute.call_count == 1
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
# =============================================================================
|
|
573
|
+
# Test: Fee bumping on base fee errors
|
|
574
|
+
# =============================================================================
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def test_fee_error_triggers_bump(executor, mock_chain_interface, mock_safe_tx, mock_safe):
|
|
578
|
+
"""Test that fee errors trigger gas price bump on retry."""
|
|
579
|
+
with patch.object(executor, "_recreate_safe_client", return_value=mock_safe):
|
|
580
|
+
# First attempt fails with fee error, second succeeds
|
|
581
|
+
mock_safe_tx.execute.side_effect = [
|
|
582
|
+
ValueError("max fee per gas less than block base fee: maxFeePerGas: 596, baseFee: 681"),
|
|
583
|
+
b"tx_hash",
|
|
584
|
+
]
|
|
585
|
+
mock_chain_interface.web3.eth.wait_for_transaction_receipt.return_value = MagicMock(
|
|
586
|
+
status=1
|
|
587
|
+
)
|
|
588
|
+
# Mock fee calculation
|
|
589
|
+
mock_chain_interface.web3.eth.get_block.return_value = {"baseFeePerGas": 700}
|
|
590
|
+
mock_chain_interface.web3.eth.max_priority_fee = 1
|
|
591
|
+
|
|
592
|
+
with patch("time.sleep"):
|
|
593
|
+
success, tx_hash, receipt = executor.execute_with_retry(
|
|
594
|
+
"0xSafe", mock_safe_tx, ["key1"]
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
assert success is True
|
|
598
|
+
assert mock_safe_tx.execute.call_count == 2
|
|
599
|
+
# Second call should have tx_gas_price (bumped), not eip1559_speed
|
|
600
|
+
second_call_kwargs = mock_safe_tx.execute.call_args_list[1][1]
|
|
601
|
+
assert "tx_gas_price" in second_call_kwargs
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def test_fee_error_classification(executor):
|
|
605
|
+
"""Test classification of fee-related errors."""
|
|
606
|
+
fee_errors = [
|
|
607
|
+
"max fee per gas less than block base fee",
|
|
608
|
+
"transaction underpriced",
|
|
609
|
+
"maxFeePerGas too low",
|
|
610
|
+
"fee too low for mempool",
|
|
611
|
+
]
|
|
612
|
+
for error_msg in fee_errors:
|
|
613
|
+
error = ValueError(error_msg)
|
|
614
|
+
result = executor._classify_error(error)
|
|
615
|
+
assert result["is_fee_error"] is True, f"Should detect fee error: {error_msg}"
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def test_calculate_bumped_gas_price_eip1559(executor, mock_chain_interface):
|
|
619
|
+
"""Test bumped gas price calculation for EIP-1559 chains."""
|
|
620
|
+
mock_chain_interface.web3.eth.get_block.return_value = {"baseFeePerGas": 1000}
|
|
621
|
+
mock_chain_interface.web3.eth.max_priority_fee = 10
|
|
622
|
+
|
|
623
|
+
# With 1.3x bump factor: base_fee * 1.3 * 1.5 + priority = 1000 * 1.3 * 1.5 + 10 = 1960
|
|
624
|
+
result = executor._calculate_bumped_gas_price(1.3)
|
|
625
|
+
|
|
626
|
+
assert result is not None
|
|
627
|
+
assert result == int(1000 * 1.3 * 1.5) + 10
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def test_calculate_bumped_gas_price_legacy(executor, mock_chain_interface):
|
|
631
|
+
"""Test bumped gas price calculation for legacy chains."""
|
|
632
|
+
mock_chain_interface.web3.eth.get_block.return_value = {} # No baseFeePerGas
|
|
633
|
+
mock_chain_interface.web3.eth.gas_price = 2000
|
|
634
|
+
|
|
635
|
+
# Legacy: gas_price * bump_factor = 2000 * 1.3 = 2600
|
|
636
|
+
result = executor._calculate_bumped_gas_price(1.3)
|
|
637
|
+
|
|
638
|
+
assert result is not None
|
|
639
|
+
assert result == int(2000 * 1.3)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iwa-0.0.61 → iwa-0.0.62}/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|