iwa 0.1.1__tar.gz → 0.1.3__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.1.1/src/iwa.egg-info → iwa-0.1.3}/PKG-INFO +1 -1
- {iwa-0.1.1 → iwa-0.1.3}/pyproject.toml +2 -2
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/chain/interface.py +3 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/transaction.py +77 -44
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/staking.py +135 -0
- iwa-0.1.3/src/iwa/plugins/olas/tests/test_staking_integration.py +773 -0
- {iwa-0.1.1 → iwa-0.1.3/src/iwa.egg-info}/PKG-INFO +1 -1
- iwa-0.1.1/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -273
- {iwa-0.1.1 → iwa-0.1.3}/LICENSE +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/README.md +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/setup.cfg +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/__main__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/chain/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/chain/errors.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/chain/manager.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/chain/models.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/chain/rate_limiter.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/chainlist.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/cli.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/constants.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/abis/erc20.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/abis/multisend.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/cache.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/contract.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/decoder.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/erc20.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/contracts/multisend.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/db.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/http.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/ipfs.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/keys.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/mnemonic.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/models.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/monitor.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/plugins.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/pricing.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/rpc_monitor.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/secrets.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/account.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/balance.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/plugin.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/safe.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/safe_executor.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/transfer/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/transfer/base.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/transfer/erc20.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/transfer/multisend.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/transfer/native.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/services/transfer/swap.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/tables.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/test.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/tests/test_gnosis_fee.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/tests/test_ipfs.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/tests/test_pricing.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/tests/test_regression_fixes.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/tests/test_wallet.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/types.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/ui.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/utils.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/core/wallet.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/cow/types.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/plugin.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/safe.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/gnosis/tests/test_safe.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/constants.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/mech_marketplace_v1.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/base.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/mech.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/mech_marketplace_v1.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/contracts/service.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/events.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/importer.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/mech_reference.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/models.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/plugin.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/service_manager/base.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/service_manager/lifecycle.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/service_manager/mech.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/service_manager/staking.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/conftest.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_olas_archiving.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_plugin.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_plugin_full.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_manager.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tui/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/check_profile.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/drain_accounts.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/list_contracts.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/release.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/reset_env.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/reset_tenderly.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/restore_backup.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/test_chainlist.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tools/wallet_check.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/app.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/modals/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/modals/base.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/rpc.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/screens/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/screens/wallets.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/tests/test_app.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/tests/test_rpc.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/tests/test_widgets.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/widgets/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/widgets/base.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/tui/workers.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/dependencies.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/models.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/accounts.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/olas/__init__.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/olas/admin.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/olas/funding.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/olas/general.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/olas/services.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/olas/staking.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/state.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/swap.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/routers/transactions.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/server.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/static/app.js +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/static/index.html +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/static/style.css +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/tests/test_web_endpoints.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/tests/test_web_olas.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/tests/test_web_swap.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa.egg-info/SOURCES.txt +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa.egg-info/dependency_links.txt +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa.egg-info/entry_points.txt +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa.egg-info/requires.txt +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/iwa.egg-info/top_level.txt +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/legacy_cow.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/legacy_safe.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/legacy_transaction_retry_logic.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/legacy_tui.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/legacy_wallets_screen.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/legacy_web.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_account_service.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_balance_service.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_chain.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_chain_interface.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_chain_interface_coverage.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_chainlist_enrichment.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_cli.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_contract.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_contract_cache.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_db.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_drain_coverage.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_erc20.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_gnosis_plugin.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_keys.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_legacy_wallet.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_main.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_migration.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_mnemonic.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_modals.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_models.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_monitor.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_multisend.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_plugin_service.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_rate_limiter.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_rate_limiter_retry.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_reset_tenderly.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_rpc_efficiency.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_rpc_rate_limit.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_rpc_rotation.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_rpc_view.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_safe_coverage.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_safe_executor.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_safe_integration.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_safe_service.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_service_manager_integration.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_service_manager_structure.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_service_transaction.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_staking_router.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_staking_simple.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_tables.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_transaction_service.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_transfer_multisend.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_transfer_native.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_transfer_security.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_transfer_structure.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_transfer_swap_unit.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_ui_coverage.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_utils.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tests/test_workers.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tools/create_and_stake_service.py +0 -0
- {iwa-0.1.1 → iwa-0.1.3}/src/tools/verify_drain.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "iwa"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
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.1.
|
|
75
|
+
target-version = "0.1.3"
|
|
76
76
|
fix = true
|
|
77
77
|
|
|
78
78
|
[tool.ruff.lint]
|
|
@@ -94,6 +94,9 @@ class ChainInterface:
|
|
|
94
94
|
"""Get the current active RPC URL."""
|
|
95
95
|
if not self.chain.rpcs:
|
|
96
96
|
return ""
|
|
97
|
+
# Ensure index is valid (could be stale after rpcs list changes or singleton reuse)
|
|
98
|
+
if self._current_rpc_index >= len(self.chain.rpcs):
|
|
99
|
+
self._current_rpc_index = 0
|
|
97
100
|
return self.chain.rpcs[self._current_rpc_index]
|
|
98
101
|
|
|
99
102
|
@property
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Transaction service module."""
|
|
2
2
|
|
|
3
|
+
import threading
|
|
3
4
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
|
4
5
|
|
|
5
6
|
from loguru import logger
|
|
@@ -220,12 +221,29 @@ class TransferLogger:
|
|
|
220
221
|
class TransactionService:
|
|
221
222
|
"""Manages transaction lifecycle: signing, sending, retrying."""
|
|
222
223
|
|
|
224
|
+
# Class-level lock for managing per-address locks
|
|
225
|
+
_locks_lock = threading.Lock()
|
|
226
|
+
_address_locks: Dict[str, threading.Lock] = {}
|
|
227
|
+
|
|
223
228
|
def __init__(self, key_storage: KeyStorage, account_service: AccountService, safe_service=None):
|
|
224
229
|
"""Initialize TransactionService."""
|
|
225
230
|
self.key_storage = key_storage
|
|
226
231
|
self.account_service = account_service
|
|
227
232
|
self.safe_service = safe_service
|
|
228
233
|
|
|
234
|
+
@classmethod
|
|
235
|
+
def _get_address_lock(cls, address: str) -> threading.Lock:
|
|
236
|
+
"""Get or create a lock for a specific address.
|
|
237
|
+
|
|
238
|
+
This prevents nonce collisions when sending multiple transactions
|
|
239
|
+
from the same address concurrently.
|
|
240
|
+
"""
|
|
241
|
+
address_lower = address.lower()
|
|
242
|
+
with cls._locks_lock:
|
|
243
|
+
if address_lower not in cls._address_locks:
|
|
244
|
+
cls._address_locks[address_lower] = threading.Lock()
|
|
245
|
+
return cls._address_locks[address_lower]
|
|
246
|
+
|
|
229
247
|
def _resolve_label(self, address: str, chain_name: str = "gnosis") -> str:
|
|
230
248
|
"""Resolve address to human-readable label."""
|
|
231
249
|
if not address:
|
|
@@ -255,69 +273,84 @@ class TransactionService:
|
|
|
255
273
|
|
|
256
274
|
Uses ChainInterface.with_retry() for consistent RPC rotation and retry logic.
|
|
257
275
|
Gas errors are handled by increasing gas and retrying within the same mechanism.
|
|
276
|
+
|
|
277
|
+
Note: Uses per-address locking to prevent nonce collisions when multiple
|
|
278
|
+
transactions are sent from the same address concurrently.
|
|
258
279
|
"""
|
|
259
280
|
chain_interface = ChainInterfaces().get(chain_name)
|
|
260
281
|
tx = dict(transaction)
|
|
261
282
|
|
|
262
|
-
|
|
283
|
+
# Resolve signer first to get the address for locking
|
|
284
|
+
signer_account = self.account_service.resolve_account(signer_address_or_tag)
|
|
285
|
+
if not signer_account:
|
|
286
|
+
logger.error(f"Signer {signer_address_or_tag} not found")
|
|
263
287
|
return False, {}
|
|
264
288
|
|
|
265
|
-
# CHECK FOR SAFE TRANSACTION
|
|
266
|
-
signer_account = self.account_service.resolve_account(signer_address_or_tag)
|
|
289
|
+
# CHECK FOR SAFE TRANSACTION (Safe has its own nonce management)
|
|
267
290
|
if isinstance(signer_account, StoredSafeAccount):
|
|
268
291
|
if not self.safe_service:
|
|
269
292
|
logger.error("Attempted Safe transaction but SafeService is not initialized.")
|
|
270
293
|
return False, {}
|
|
294
|
+
# Safe transactions don't need EOA nonce locking
|
|
295
|
+
if not self._prepare_transaction(tx, signer_address_or_tag, chain_interface):
|
|
296
|
+
return False, {}
|
|
271
297
|
return self._execute_via_safe(tx, signer_account, chain_interface, chain_name, tags)
|
|
272
298
|
|
|
273
|
-
#
|
|
274
|
-
|
|
299
|
+
# Acquire lock for this address to prevent nonce collisions
|
|
300
|
+
address_lock = self._get_address_lock(signer_account.address)
|
|
301
|
+
with address_lock:
|
|
302
|
+
if not self._prepare_transaction(tx, signer_address_or_tag, chain_interface):
|
|
303
|
+
return False, {}
|
|
275
304
|
|
|
276
|
-
|
|
277
|
-
""
|
|
278
|
-
try:
|
|
279
|
-
signed_txn = self.key_storage.sign_transaction(tx, signer_address_or_tag)
|
|
280
|
-
txn_hash = chain_interface.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
|
281
|
-
receipt = chain_interface.web3.eth.wait_for_transaction_receipt(txn_hash)
|
|
305
|
+
# Mutable state for retry attempts
|
|
306
|
+
state = {"gas_retries": 0, "max_gas_retries": 5}
|
|
282
307
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
308
|
+
def _do_sign_send_wait() -> Tuple[bool, Dict, bytes]:
|
|
309
|
+
"""Inner operation wrapped by with_retry."""
|
|
310
|
+
try:
|
|
311
|
+
signed_txn = self.key_storage.sign_transaction(tx, signer_address_or_tag)
|
|
312
|
+
txn_hash = chain_interface.web3.eth.send_raw_transaction(
|
|
313
|
+
signed_txn.raw_transaction
|
|
314
|
+
)
|
|
315
|
+
receipt = chain_interface.web3.eth.wait_for_transaction_receipt(txn_hash)
|
|
286
316
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
logger.error("Transaction failed (status 0).")
|
|
291
|
-
raise ValueError("Transaction reverted")
|
|
317
|
+
status = getattr(receipt, "status", None)
|
|
318
|
+
if status is None and isinstance(receipt, dict):
|
|
319
|
+
status = receipt.get("status")
|
|
292
320
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
321
|
+
if receipt and status == 1:
|
|
322
|
+
return True, receipt, txn_hash
|
|
323
|
+
# Transaction mined but reverted - don't retry
|
|
324
|
+
logger.error("Transaction failed (status 0).")
|
|
325
|
+
raise ValueError("Transaction reverted")
|
|
297
326
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
self._log_successful_transaction(
|
|
308
|
-
receipt, tx, signer_account, chain_name, txn_hash, tags, chain_interface
|
|
327
|
+
except web3_exceptions.Web3RPCError as e:
|
|
328
|
+
# Handle gas errors by increasing gas and re-raising
|
|
329
|
+
self._handle_gas_retry(e, tx, state)
|
|
330
|
+
raise # Re-raise to trigger with_retry's retry mechanism
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
success, receipt, txn_hash = chain_interface.with_retry(
|
|
334
|
+
_do_sign_send_wait,
|
|
335
|
+
operation_name=f"sign_and_send to {tx.get('to', 'unknown')[:10]}...",
|
|
309
336
|
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
337
|
+
if success:
|
|
338
|
+
chain_interface.wait_for_no_pending_tx(signer_account.address)
|
|
339
|
+
logger.info(f"Transaction sent successfully. Tx Hash: {txn_hash.hex()}")
|
|
340
|
+
self._log_successful_transaction(
|
|
341
|
+
receipt, tx, signer_account, chain_name, txn_hash, tags, chain_interface
|
|
342
|
+
)
|
|
343
|
+
return True, receipt
|
|
344
|
+
return False, {}
|
|
345
|
+
except ValueError as e:
|
|
346
|
+
# Transaction reverted - already logged
|
|
347
|
+
if "reverted" in str(e).lower():
|
|
348
|
+
return False, {}
|
|
349
|
+
logger.exception(f"Transaction failed: {e}")
|
|
350
|
+
return False, {}
|
|
351
|
+
except Exception as e:
|
|
352
|
+
logger.exception(f"Transaction failed after retries: {e}")
|
|
315
353
|
return False, {}
|
|
316
|
-
logger.exception(f"Transaction failed: {e}")
|
|
317
|
-
return False, {}
|
|
318
|
-
except Exception as e:
|
|
319
|
-
logger.exception(f"Transaction failed after retries: {e}")
|
|
320
|
-
return False, {}
|
|
321
354
|
|
|
322
355
|
def _prepare_transaction(self, tx: dict, signer_tag: str, chain_interface) -> bool:
|
|
323
356
|
"""Ensure nonce and chainId are set."""
|
|
@@ -495,3 +495,138 @@ class StakingContract(ContractInstance):
|
|
|
495
495
|
tx_params={"from": from_address},
|
|
496
496
|
)
|
|
497
497
|
return tx
|
|
498
|
+
|
|
499
|
+
def _fetch_events_chunked(
|
|
500
|
+
self,
|
|
501
|
+
event_type: str,
|
|
502
|
+
from_block: int,
|
|
503
|
+
to_block: int,
|
|
504
|
+
chunk_size: int = 500,
|
|
505
|
+
) -> List:
|
|
506
|
+
"""Fetch events in chunks to handle RPC block range limits.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
event_type: Name of the event (Checkpoint, ServiceInactivityWarning, etc.)
|
|
510
|
+
from_block: Starting block number.
|
|
511
|
+
to_block: Ending block number.
|
|
512
|
+
chunk_size: Max blocks per request (default 500).
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
List of event log entries.
|
|
516
|
+
|
|
517
|
+
"""
|
|
518
|
+
all_logs: List = []
|
|
519
|
+
current_from = from_block
|
|
520
|
+
|
|
521
|
+
event = getattr(self.contract.events, event_type, None)
|
|
522
|
+
if event is None:
|
|
523
|
+
logger.debug(f"Event {event_type} not found in contract ABI")
|
|
524
|
+
return []
|
|
525
|
+
|
|
526
|
+
while current_from <= to_block:
|
|
527
|
+
current_to = min(current_from + chunk_size - 1, to_block)
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
event_filter = event.create_filter(
|
|
531
|
+
from_block=current_from, to_block=current_to
|
|
532
|
+
)
|
|
533
|
+
logs = event_filter.get_all_entries()
|
|
534
|
+
all_logs.extend(logs)
|
|
535
|
+
except Exception as e:
|
|
536
|
+
error_msg = str(e).lower()
|
|
537
|
+
# If range too large, try smaller chunks
|
|
538
|
+
if "range" in error_msg or "limit" in error_msg or "10000" in error_msg:
|
|
539
|
+
if chunk_size > 100:
|
|
540
|
+
logger.debug(
|
|
541
|
+
"Block range too large, retrying with smaller chunks"
|
|
542
|
+
)
|
|
543
|
+
smaller_logs = self._fetch_events_chunked(
|
|
544
|
+
event_type, current_from, current_to, chunk_size // 2
|
|
545
|
+
)
|
|
546
|
+
all_logs.extend(smaller_logs)
|
|
547
|
+
else:
|
|
548
|
+
logger.warning(
|
|
549
|
+
f"Cannot fetch {event_type} events for blocks "
|
|
550
|
+
f"{current_from}-{current_to}: {e}"
|
|
551
|
+
)
|
|
552
|
+
else:
|
|
553
|
+
logger.debug(f"Error fetching {event_type} events: {e}")
|
|
554
|
+
|
|
555
|
+
current_from = current_to + 1
|
|
556
|
+
|
|
557
|
+
return all_logs
|
|
558
|
+
|
|
559
|
+
def get_checkpoint_events(
|
|
560
|
+
self, from_block: int, to_block: Optional[int] = None
|
|
561
|
+
) -> Dict:
|
|
562
|
+
"""Get checkpoint-related events from a block range.
|
|
563
|
+
|
|
564
|
+
Retrieves Checkpoint, ServiceInactivityWarning, and ServicesEvicted events
|
|
565
|
+
to determine which services received rewards and which got warnings.
|
|
566
|
+
|
|
567
|
+
Uses chunked fetching to handle RPCs that limit block ranges.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
from_block: Starting block number.
|
|
571
|
+
to_block: Ending block number (defaults to latest).
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Dict with:
|
|
575
|
+
- epoch: int, the new epoch number
|
|
576
|
+
- rewarded_services: Dict[int, int] mapping service_id -> reward (wei)
|
|
577
|
+
- inactivity_warnings: List[int] of service IDs with warnings
|
|
578
|
+
- evicted_services: List[int] of evicted service IDs
|
|
579
|
+
- checkpoint_block: int, block where checkpoint occurred
|
|
580
|
+
|
|
581
|
+
"""
|
|
582
|
+
if to_block is None:
|
|
583
|
+
to_block = self.chain_interface.web3.eth.block_number
|
|
584
|
+
|
|
585
|
+
result: Dict = {
|
|
586
|
+
"epoch": None,
|
|
587
|
+
"rewarded_services": {},
|
|
588
|
+
"inactivity_warnings": [],
|
|
589
|
+
"evicted_services": [],
|
|
590
|
+
"checkpoint_block": None,
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
# Get Checkpoint events
|
|
595
|
+
checkpoint_logs = self._fetch_events_chunked(
|
|
596
|
+
"Checkpoint", from_block, to_block
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
if checkpoint_logs:
|
|
600
|
+
# Take the most recent checkpoint
|
|
601
|
+
latest = checkpoint_logs[-1]
|
|
602
|
+
result["epoch"] = latest.args.get("epoch")
|
|
603
|
+
result["checkpoint_block"] = latest.blockNumber
|
|
604
|
+
|
|
605
|
+
# Parse serviceIds and rewards arrays
|
|
606
|
+
service_ids = latest.args.get("serviceIds", [])
|
|
607
|
+
rewards = latest.args.get("rewards", [])
|
|
608
|
+
|
|
609
|
+
for sid, reward in zip(service_ids, rewards, strict=True):
|
|
610
|
+
result["rewarded_services"][sid] = reward
|
|
611
|
+
|
|
612
|
+
# Get ServiceInactivityWarning events
|
|
613
|
+
warning_logs = self._fetch_events_chunked(
|
|
614
|
+
"ServiceInactivityWarning", from_block, to_block
|
|
615
|
+
)
|
|
616
|
+
for log in warning_logs:
|
|
617
|
+
service_id = log.args.get("serviceId")
|
|
618
|
+
if service_id is not None:
|
|
619
|
+
result["inactivity_warnings"].append(service_id)
|
|
620
|
+
|
|
621
|
+
# Get ServicesEvicted events
|
|
622
|
+
evicted_logs = self._fetch_events_chunked(
|
|
623
|
+
"ServicesEvicted", from_block, to_block
|
|
624
|
+
)
|
|
625
|
+
for log in evicted_logs:
|
|
626
|
+
evicted_ids = log.args.get("serviceIds", [])
|
|
627
|
+
result["evicted_services"].extend(evicted_ids)
|
|
628
|
+
|
|
629
|
+
except Exception as e:
|
|
630
|
+
logger.error(f"Error fetching checkpoint events: {e}")
|
|
631
|
+
|
|
632
|
+
return result
|