iwa 0.0.16__tar.gz → 0.0.18__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.16/src/iwa.egg-info → iwa-0.0.18}/PKG-INFO +1 -1
- {iwa-0.0.16 → iwa-0.0.18}/pyproject.toml +2 -2
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/chain/interface.py +1 -65
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/pricing.py +1 -4
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/transaction.py +72 -70
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/transfer/erc20.py +8 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/transfer/native.py +47 -13
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/service_manager/mech.py +43 -25
- {iwa-0.0.16 → iwa-0.0.18/src/iwa.egg-info}/PKG-INFO +1 -1
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_chain.py +2 -97
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_legacy_wallet.py +6 -6
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_service_transaction.py +23 -8
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_staking_router.py +14 -6
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_transaction_service.py +20 -6
- {iwa-0.0.16 → iwa-0.0.18}/LICENSE +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/README.md +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/setup.cfg +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/__main__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/chain/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/chain/errors.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/chain/manager.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/chain/models.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/chain/rate_limiter.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/cli.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/constants.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/contracts/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/contracts/abis/erc20.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/contracts/abis/multisend.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/contracts/contract.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/contracts/erc20.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/contracts/multisend.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/db.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/ipfs.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/keys.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/mnemonic.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/models.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/monitor.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/plugins.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/secrets.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/account.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/balance.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/plugin.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/safe.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/transfer/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/transfer/base.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/transfer/multisend.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/services/transfer/swap.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/tables.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/test.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/tests/test_wallet.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/types.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/ui.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/utils.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/core/wallet.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/cow/types.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/plugin.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/safe.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/gnosis/tests/test_safe.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/constants.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/base.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/mech.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/service.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/contracts/staking.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/importer.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/mech_reference.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/models.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/plugin.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/service_manager/base.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/service_manager/lifecycle.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/service_manager/staking.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/conftest.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_plugin.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_plugin_full.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_manager.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tui/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/check_profile.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/list_contracts.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/release.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/reset_env.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/reset_tenderly.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/restore_backup.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tools/wallet_check.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/app.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/modals/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/modals/base.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/rpc.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/screens/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/screens/wallets.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/tests/test_app.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/tests/test_rpc.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/tests/test_widgets.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/widgets/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/widgets/base.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/tui/workers.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/dependencies.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/models.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/accounts.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/olas/__init__.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/olas/admin.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/olas/funding.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/olas/general.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/olas/services.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/olas/staking.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/state.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/swap.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/routers/transactions.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/server.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/static/app.js +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/static/index.html +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/static/style.css +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/tests/test_web_endpoints.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/tests/test_web_olas.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/tests/test_web_swap.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa.egg-info/SOURCES.txt +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa.egg-info/dependency_links.txt +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa.egg-info/entry_points.txt +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa.egg-info/requires.txt +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/iwa.egg-info/top_level.txt +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/legacy_cow.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/legacy_safe.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/legacy_transaction_retry_logic.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/legacy_tui.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/legacy_wallets_screen.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/legacy_web.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_account_service.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_balance_service.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_chain_interface.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_chain_interface_coverage.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_cli.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_contract.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_db.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_drain_coverage.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_erc20.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_gnosis_plugin.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_keys.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_main.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_migration.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_mnemonic.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_modals.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_models.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_monitor.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_multisend.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_plugin_service.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_pricing.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_rate_limiter.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_reset_tenderly.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_rpc_rotation.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_rpc_view.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_safe_coverage.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_safe_service.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_service_manager_integration.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_service_manager_structure.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_staking_simple.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_tables.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_transfer_multisend.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_transfer_native.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_transfer_security.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_transfer_structure.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_transfer_swap_unit.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_ui_coverage.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_utils.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tests/test_workers.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/src/tools/create_and_stake_service.py +0 -0
- {iwa-0.0.16 → iwa-0.0.18}/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.18"
|
|
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"
|
|
@@ -71,7 +71,7 @@ where = ["src"]
|
|
|
71
71
|
|
|
72
72
|
[tool.ruff]
|
|
73
73
|
line-length = 100
|
|
74
|
-
target-version = "0.0.
|
|
74
|
+
target-version = "0.0.18"
|
|
75
75
|
fix = true
|
|
76
76
|
|
|
77
77
|
[tool.ruff.lint]
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import threading
|
|
4
4
|
import time
|
|
5
|
-
from typing import Callable, Dict, Optional,
|
|
5
|
+
from typing import Callable, Dict, Optional, TypeVar, Union
|
|
6
6
|
|
|
7
|
-
from eth_account.datastructures import SignedTransaction
|
|
8
7
|
from web3 import Web3
|
|
9
8
|
|
|
10
9
|
from iwa.core.chain.errors import TenderlyQuotaExceededError, sanitize_rpc_url
|
|
@@ -449,69 +448,6 @@ class ChainInterface:
|
|
|
449
448
|
|
|
450
449
|
return False
|
|
451
450
|
|
|
452
|
-
def send_native_transfer(
|
|
453
|
-
self,
|
|
454
|
-
from_address: EthereumAddress,
|
|
455
|
-
to_address: EthereumAddress,
|
|
456
|
-
value_wei: int,
|
|
457
|
-
sign_callback: Callable[[dict], SignedTransaction],
|
|
458
|
-
) -> Tuple[bool, Optional[str]]:
|
|
459
|
-
"""Send native currency transaction with retry logic."""
|
|
460
|
-
|
|
461
|
-
def _do_transfer() -> Tuple[bool, Optional[str]]:
|
|
462
|
-
tx = {
|
|
463
|
-
"from": from_address,
|
|
464
|
-
"to": to_address,
|
|
465
|
-
"value": value_wei,
|
|
466
|
-
"nonce": self.web3.eth.get_transaction_count(from_address),
|
|
467
|
-
"chainId": self.chain.chain_id,
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
balance_wei = self.get_native_balance_wei(from_address)
|
|
471
|
-
gas_price = self.web3.eth.gas_price
|
|
472
|
-
gas_estimate = self.web3.eth.estimate_gas(tx)
|
|
473
|
-
required_wei = value_wei + (gas_estimate * gas_price)
|
|
474
|
-
|
|
475
|
-
if balance_wei < required_wei:
|
|
476
|
-
logger.error(
|
|
477
|
-
f"Insufficient balance. "
|
|
478
|
-
f"Balance: {self.web3.from_wei(balance_wei, 'ether'):.4f} "
|
|
479
|
-
f"{self.chain.native_currency}, "
|
|
480
|
-
f"Required: {self.web3.from_wei(required_wei, 'ether'):.4f} "
|
|
481
|
-
f"{self.chain.native_currency}"
|
|
482
|
-
)
|
|
483
|
-
return False, None
|
|
484
|
-
|
|
485
|
-
tx["gas"] = gas_estimate
|
|
486
|
-
tx["gasPrice"] = gas_price
|
|
487
|
-
|
|
488
|
-
signed_tx = sign_callback(tx)
|
|
489
|
-
txn_hash = self.web3.eth.send_raw_transaction(signed_tx.raw_transaction)
|
|
490
|
-
receipt = self.web3.eth.wait_for_transaction_receipt(txn_hash)
|
|
491
|
-
|
|
492
|
-
status = getattr(receipt, "status", None)
|
|
493
|
-
if status is None and isinstance(receipt, dict):
|
|
494
|
-
status = receipt.get("status")
|
|
495
|
-
|
|
496
|
-
if receipt and status == 1:
|
|
497
|
-
self.wait_for_no_pending_tx(from_address)
|
|
498
|
-
logger.info(f"Transaction sent successfully. Tx Hash: {txn_hash.hex()}")
|
|
499
|
-
# Check Tenderly block limit after each successful transaction
|
|
500
|
-
self.check_block_limit()
|
|
501
|
-
return True, receipt["transactionHash"].hex()
|
|
502
|
-
|
|
503
|
-
logger.error("Transaction failed (status != 1)")
|
|
504
|
-
return False, None
|
|
505
|
-
|
|
506
|
-
try:
|
|
507
|
-
return self.with_retry(
|
|
508
|
-
_do_transfer,
|
|
509
|
-
operation_name=f"native_transfer to {str(to_address)[:10]}...",
|
|
510
|
-
)
|
|
511
|
-
except Exception as e:
|
|
512
|
-
logger.exception(f"Native transfer failed: {e}")
|
|
513
|
-
return False, None
|
|
514
|
-
|
|
515
451
|
def get_token_address(self, token_name: str) -> Optional[EthereumAddress]:
|
|
516
452
|
"""Get token address by name"""
|
|
517
453
|
return self.chain.get_token_address(token_name)
|
|
@@ -19,7 +19,6 @@ class PriceService:
|
|
|
19
19
|
"""Service to fetch token prices from CoinGecko."""
|
|
20
20
|
|
|
21
21
|
BASE_URL = "https://api.coingecko.com/api/v3"
|
|
22
|
-
DEMO_URL = "https://demo-api.coingecko.com/api/v3"
|
|
23
22
|
|
|
24
23
|
def __init__(self):
|
|
25
24
|
"""Initialize PriceService."""
|
|
@@ -60,9 +59,7 @@ class PriceService:
|
|
|
60
59
|
max_retries = 2
|
|
61
60
|
for attempt in range(max_retries + 1):
|
|
62
61
|
try:
|
|
63
|
-
|
|
64
|
-
# NOTE: Demo URL is significantly more reliable for demo keys
|
|
65
|
-
base_url = self.DEMO_URL if self.api_key else self.BASE_URL
|
|
62
|
+
base_url = self.BASE_URL
|
|
66
63
|
url = f"{base_url}/simple/price"
|
|
67
64
|
params = {"ids": token_id, "vs_currencies": vs_currency}
|
|
68
65
|
headers = {}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Transaction service module."""
|
|
2
2
|
|
|
3
|
-
import time
|
|
4
3
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
|
5
4
|
|
|
6
5
|
from loguru import logger
|
|
@@ -31,32 +30,37 @@ class TransferLogger:
|
|
|
31
30
|
self.account_service = account_service
|
|
32
31
|
self.chain_interface = chain_interface
|
|
33
32
|
|
|
34
|
-
def log_transfers(self, receipt: Dict
|
|
33
|
+
def log_transfers(self, receipt: Dict) -> None:
|
|
35
34
|
"""Log all transfers (ERC20 and native) from a transaction receipt.
|
|
36
35
|
|
|
37
36
|
Args:
|
|
38
37
|
receipt: Transaction receipt containing logs.
|
|
39
|
-
tx: Original transaction dict.
|
|
40
38
|
|
|
41
39
|
"""
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
if
|
|
45
|
-
|
|
40
|
+
# Get the original transaction to check for native value transfer
|
|
41
|
+
tx_hash = receipt.get("transactionHash") or getattr(receipt, "transactionHash", None)
|
|
42
|
+
if tx_hash:
|
|
43
|
+
try:
|
|
44
|
+
tx = self.chain_interface.web3.eth.get_transaction(tx_hash)
|
|
45
|
+
native_value = getattr(tx, "value", 0) or tx.get("value", 0) if isinstance(tx, dict) else getattr(tx, "value", 0)
|
|
46
|
+
if native_value and int(native_value) > 0:
|
|
47
|
+
from_addr = getattr(tx, "from", "") if hasattr(tx, "from") else tx.get("from", "")
|
|
48
|
+
# Handle AttributeDict's special 'from' attribute
|
|
49
|
+
if not from_addr and hasattr(tx, "__getitem__"):
|
|
50
|
+
from_addr = tx["from"]
|
|
51
|
+
to_addr = getattr(tx, "to", "") or (tx.get("to", "") if isinstance(tx, dict) else "")
|
|
52
|
+
self._log_native_transfer(from_addr, to_addr, native_value)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.debug(f"Could not get tx for native transfer logging: {e}")
|
|
46
55
|
|
|
47
56
|
# Log ERC20 transfers from event logs
|
|
48
|
-
logs = receipt.get("logs", [])
|
|
49
|
-
if hasattr(receipt, "logs"):
|
|
50
|
-
logs = receipt.logs
|
|
57
|
+
logs = receipt.get("logs", []) if isinstance(receipt, dict) else getattr(receipt, "logs", [])
|
|
51
58
|
|
|
52
59
|
for log in logs:
|
|
53
60
|
self._process_log(log)
|
|
54
61
|
|
|
55
|
-
def _log_native_transfer(self,
|
|
62
|
+
def _log_native_transfer(self, from_addr: str, to_addr: str, value_wei: int) -> None:
|
|
56
63
|
"""Log a native currency transfer."""
|
|
57
|
-
from_addr = tx.get("from", "")
|
|
58
|
-
to_addr = tx.get("to", "")
|
|
59
|
-
|
|
60
64
|
from_label = self._resolve_address_label(from_addr)
|
|
61
65
|
to_label = self._resolve_address_label(to_addr)
|
|
62
66
|
|
|
@@ -213,53 +217,67 @@ class TransactionService:
|
|
|
213
217
|
chain_name: str = "gnosis",
|
|
214
218
|
tags: Optional[List[str]] = None,
|
|
215
219
|
) -> Tuple[bool, Dict]:
|
|
216
|
-
"""Sign and send a transaction
|
|
220
|
+
"""Sign and send a transaction using unified retry mechanism.
|
|
221
|
+
|
|
222
|
+
Uses ChainInterface.with_retry() for consistent RPC rotation and retry logic.
|
|
223
|
+
Gas errors are handled by increasing gas and retrying within the same mechanism.
|
|
224
|
+
"""
|
|
217
225
|
chain_interface = ChainInterfaces().get(chain_name)
|
|
218
226
|
tx = dict(transaction)
|
|
219
|
-
max_retries = 10
|
|
220
227
|
|
|
221
228
|
if not self._prepare_transaction(tx, signer_address_or_tag, chain_interface):
|
|
222
229
|
return False, {}
|
|
223
230
|
|
|
224
|
-
|
|
231
|
+
# Mutable state for retry attempts
|
|
232
|
+
state = {"gas_retries": 0, "max_gas_retries": 5}
|
|
233
|
+
|
|
234
|
+
def _do_sign_send_wait() -> Tuple[bool, Dict, bytes]:
|
|
235
|
+
"""Inner operation wrapped by with_retry."""
|
|
225
236
|
try:
|
|
226
237
|
signed_txn = self.key_storage.sign_transaction(tx, signer_address_or_tag)
|
|
227
|
-
txn_hash = chain_interface.web3.eth.send_raw_transaction(
|
|
228
|
-
|
|
229
|
-
# Use chain_interface.with_retry for waiting for receipt to handle timeouts/RPC errors
|
|
230
|
-
def wait_for_receipt(tx_h=txn_hash):
|
|
231
|
-
return chain_interface.web3.eth.wait_for_transaction_receipt(tx_h)
|
|
232
|
-
|
|
233
|
-
receipt = chain_interface.with_retry(
|
|
234
|
-
wait_for_receipt, operation_name="wait_for_receipt"
|
|
238
|
+
txn_hash = chain_interface.web3.eth.send_raw_transaction(
|
|
239
|
+
signed_txn.raw_transaction
|
|
235
240
|
)
|
|
241
|
+
receipt = chain_interface.web3.eth.wait_for_transaction_receipt(txn_hash)
|
|
236
242
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
logger.info(f"Transaction sent successfully. Tx Hash: {txn_hash.hex()}")
|
|
241
|
-
|
|
242
|
-
self._log_successful_transaction(
|
|
243
|
-
receipt, tx, signer_account, chain_name, txn_hash, tags, chain_interface
|
|
244
|
-
)
|
|
245
|
-
return True, receipt
|
|
243
|
+
status = getattr(receipt, "status", None)
|
|
244
|
+
if status is None and isinstance(receipt, dict):
|
|
245
|
+
status = receipt.get("status")
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
if receipt and status == 1:
|
|
248
|
+
return True, receipt, txn_hash
|
|
249
|
+
# Transaction mined but reverted - don't retry
|
|
248
250
|
logger.error("Transaction failed (status 0).")
|
|
249
|
-
|
|
251
|
+
raise ValueError("Transaction reverted")
|
|
250
252
|
|
|
251
253
|
except web3_exceptions.Web3RPCError as e:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
# Handle gas errors by increasing gas and re-raising
|
|
255
|
+
self._handle_gas_retry(e, tx, state)
|
|
256
|
+
raise # Re-raise to trigger with_retry's retry mechanism
|
|
255
257
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
try:
|
|
259
|
+
success, receipt, txn_hash = chain_interface.with_retry(
|
|
260
|
+
_do_sign_send_wait,
|
|
261
|
+
operation_name=f"sign_and_send to {tx.get('to', 'unknown')[:10]}...",
|
|
262
|
+
)
|
|
263
|
+
if success:
|
|
264
|
+
signer_account = self.account_service.resolve_account(signer_address_or_tag)
|
|
265
|
+
chain_interface.wait_for_no_pending_tx(signer_account.address)
|
|
266
|
+
logger.info(f"Transaction sent successfully. Tx Hash: {txn_hash.hex()}")
|
|
267
|
+
self._log_successful_transaction(
|
|
268
|
+
receipt, tx, signer_account, chain_name, txn_hash, tags, chain_interface
|
|
269
|
+
)
|
|
270
|
+
return True, receipt
|
|
271
|
+
return False, {}
|
|
272
|
+
except ValueError as e:
|
|
273
|
+
# Transaction reverted - already logged
|
|
274
|
+
if "reverted" in str(e).lower():
|
|
260
275
|
return False, {}
|
|
261
|
-
|
|
262
|
-
|
|
276
|
+
logger.exception(f"Transaction failed: {e}")
|
|
277
|
+
return False, {}
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.exception(f"Transaction failed after retries: {e}")
|
|
280
|
+
return False, {}
|
|
263
281
|
|
|
264
282
|
def _prepare_transaction(self, tx: dict, signer_tag: str, chain_interface) -> bool:
|
|
265
283
|
"""Ensure nonce and chainId are set."""
|
|
@@ -274,32 +292,16 @@ class TransactionService:
|
|
|
274
292
|
tx["chainId"] = chain_interface.chain.chain_id
|
|
275
293
|
return True
|
|
276
294
|
|
|
277
|
-
def
|
|
278
|
-
|
|
279
|
-
if self._is_gas_too_low_error(
|
|
280
|
-
logger.warning(
|
|
281
|
-
f"Gas too low error detected. Retrying with increased gas (Attempt {attempt}/{max_retries})..."
|
|
282
|
-
)
|
|
295
|
+
def _handle_gas_retry(self, e: Exception, tx: dict, state: dict) -> None:
|
|
296
|
+
"""Increase gas if error is gas-related and retries remaining."""
|
|
297
|
+
if self._is_gas_too_low_error(str(e)) and state["gas_retries"] < state["max_gas_retries"]:
|
|
283
298
|
current_gas = int(tx.get("gas", 30_000))
|
|
284
299
|
tx["gas"] = int(current_gas * 1.5)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return False
|
|
291
|
-
|
|
292
|
-
def _handle_generic_error(self, e, chain_interface, attempt, max_retries) -> bool:
|
|
293
|
-
if attempt < max_retries:
|
|
294
|
-
logger.warning(f"Error encountered: {e}. Attempting to rotate RPC...")
|
|
295
|
-
|
|
296
|
-
if chain_interface.rotate_rpc():
|
|
297
|
-
logger.info("Retrying with new RPC...")
|
|
298
|
-
# Exponential backoff
|
|
299
|
-
time.sleep(min(2**attempt, 30))
|
|
300
|
-
return True
|
|
301
|
-
logger.exception(f"Unexpected error sending transaction: {e}")
|
|
302
|
-
return False
|
|
300
|
+
state["gas_retries"] += 1
|
|
301
|
+
logger.warning(
|
|
302
|
+
f"Gas too low, increasing to {tx['gas']} "
|
|
303
|
+
f"(attempt {state['gas_retries']}/{state['max_gas_retries']})"
|
|
304
|
+
)
|
|
303
305
|
|
|
304
306
|
def _log_successful_transaction(
|
|
305
307
|
self, receipt, tx, signer_account, chain_name, txn_hash, tags, chain_interface
|
|
@@ -323,7 +325,7 @@ class TransactionService:
|
|
|
323
325
|
|
|
324
326
|
# Log transfer events (ERC20 and native value)
|
|
325
327
|
transfer_logger = TransferLogger(self.account_service, chain_interface)
|
|
326
|
-
transfer_logger.log_transfers(receipt
|
|
328
|
+
transfer_logger.log_transfers(receipt)
|
|
327
329
|
|
|
328
330
|
except Exception as log_err:
|
|
329
331
|
logger.warning(f"Failed to log transaction: {log_err}")
|
|
@@ -64,6 +64,14 @@ class ERC20TransferMixin:
|
|
|
64
64
|
value_eur=v_eur,
|
|
65
65
|
tags=["erc20-transfer", "safe-transaction"],
|
|
66
66
|
)
|
|
67
|
+
|
|
68
|
+
# Log transfers extracted from receipt events
|
|
69
|
+
if receipt:
|
|
70
|
+
from iwa.core.services.transaction import TransferLogger
|
|
71
|
+
|
|
72
|
+
transfer_logger = TransferLogger(self.account_service, interface)
|
|
73
|
+
transfer_logger.log_transfers(receipt)
|
|
74
|
+
|
|
67
75
|
return tx_hash
|
|
68
76
|
|
|
69
77
|
def _send_erc20_via_eoa(
|
|
@@ -61,6 +61,15 @@ class NativeTransferMixin:
|
|
|
61
61
|
value_eur=v_eur,
|
|
62
62
|
tags=["native-transfer", "safe-transaction"],
|
|
63
63
|
)
|
|
64
|
+
|
|
65
|
+
# Log transfers extracted from receipt events
|
|
66
|
+
if receipt:
|
|
67
|
+
from iwa.core.services.transaction import TransferLogger
|
|
68
|
+
|
|
69
|
+
interface = ChainInterfaces().get(chain_name)
|
|
70
|
+
transfer_logger = TransferLogger(self.account_service, interface)
|
|
71
|
+
transfer_logger.log_transfers(receipt)
|
|
72
|
+
|
|
64
73
|
return tx_hash
|
|
65
74
|
|
|
66
75
|
def _send_native_via_eoa(
|
|
@@ -74,20 +83,38 @@ class NativeTransferMixin:
|
|
|
74
83
|
to_tag: Optional[str],
|
|
75
84
|
token_symbol: str,
|
|
76
85
|
) -> Optional[str]:
|
|
77
|
-
"""Send native currency via EOA
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
"""Send native currency via EOA using unified TransactionService."""
|
|
87
|
+
# Build transaction dict
|
|
88
|
+
try:
|
|
89
|
+
gas_price = chain_interface.web3.eth.gas_price
|
|
90
|
+
gas_estimate = chain_interface.web3.eth.estimate_gas({
|
|
91
|
+
"from": from_account.address,
|
|
92
|
+
"to": to_address,
|
|
93
|
+
"value": amount_wei,
|
|
94
|
+
})
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Failed to estimate gas for native transfer: {e}")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
tx = {
|
|
100
|
+
"from": from_account.address,
|
|
101
|
+
"to": to_address,
|
|
102
|
+
"value": amount_wei,
|
|
103
|
+
"gas": gas_estimate,
|
|
104
|
+
"gasPrice": gas_price,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Use unified TransactionService
|
|
108
|
+
success, receipt = self.transaction_service.sign_and_send(
|
|
109
|
+
tx, from_account.address, chain_name, tags=["native-transfer"]
|
|
83
110
|
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
|
|
112
|
+
if success and receipt:
|
|
113
|
+
tx_hash = receipt.get("transactionHash", b"")
|
|
114
|
+
if hasattr(tx_hash, "hex"):
|
|
115
|
+
tx_hash = tx_hash.hex()
|
|
116
|
+
elif isinstance(tx_hash, bytes):
|
|
117
|
+
tx_hash = tx_hash.hex()
|
|
91
118
|
|
|
92
119
|
gas_cost, gas_value_eur = self._calculate_gas_info(receipt, chain_name)
|
|
93
120
|
p_eur, v_eur = self._get_token_price_info(token_symbol, amount_wei, chain_name)
|
|
@@ -106,6 +133,13 @@ class NativeTransferMixin:
|
|
|
106
133
|
value_eur=v_eur,
|
|
107
134
|
tags=["native-transfer"],
|
|
108
135
|
)
|
|
136
|
+
|
|
137
|
+
# Log transfers extracted from receipt events
|
|
138
|
+
from iwa.core.services.transaction import TransferLogger
|
|
139
|
+
|
|
140
|
+
transfer_logger = TransferLogger(self.account_service, chain_interface)
|
|
141
|
+
transfer_logger.log_transfers(receipt)
|
|
142
|
+
|
|
109
143
|
return tx_hash
|
|
110
144
|
return None
|
|
111
145
|
|
|
@@ -140,11 +140,13 @@ class MechManagerMixin:
|
|
|
140
140
|
)
|
|
141
141
|
if use_marketplace:
|
|
142
142
|
priority_mech = priority_mech or detected_priority_mech
|
|
143
|
+
mech_address = mech_address or detected_marketplace
|
|
143
144
|
|
|
144
145
|
if use_marketplace:
|
|
145
146
|
return self._send_marketplace_mech_request(
|
|
146
147
|
data=data,
|
|
147
148
|
value=value,
|
|
149
|
+
marketplace_address=mech_address,
|
|
148
150
|
priority_mech=priority_mech,
|
|
149
151
|
max_delivery_rate=max_delivery_rate,
|
|
150
152
|
payment_type=payment_type,
|
|
@@ -260,10 +262,39 @@ class MechManagerMixin:
|
|
|
260
262
|
|
|
261
263
|
return True
|
|
262
264
|
|
|
265
|
+
def _resolve_marketplace_config(
|
|
266
|
+
self, marketplace_addr: Optional[str], priority_addr: Optional[str]
|
|
267
|
+
) -> tuple[str, str]:
|
|
268
|
+
"""Resolve marketplace and priority mech addresses. Returns (marketplace, priority)."""
|
|
269
|
+
chain_name = self.chain_name if self.service else getattr(self, "chain_name", "gnosis")
|
|
270
|
+
protocol_contracts = OLAS_CONTRACTS.get(chain_name, {})
|
|
271
|
+
|
|
272
|
+
resolved_mp = marketplace_addr or protocol_contracts.get("OLAS_MECH_MARKETPLACE")
|
|
273
|
+
if not resolved_mp:
|
|
274
|
+
raise ValueError(f"Mech Marketplace address not found for chain {chain_name}")
|
|
275
|
+
|
|
276
|
+
if not priority_addr:
|
|
277
|
+
raise ValueError("priority_mech is required for marketplace requests")
|
|
278
|
+
|
|
279
|
+
return str(resolved_mp), Web3.to_checksum_address(priority_addr)
|
|
280
|
+
|
|
281
|
+
def _prepare_marketplace_params(
|
|
282
|
+
self,
|
|
283
|
+
value: Optional[int],
|
|
284
|
+
max_delivery_rate: Optional[int],
|
|
285
|
+
payment_type: Optional[bytes],
|
|
286
|
+
) -> tuple[int, int, bytes]:
|
|
287
|
+
"""Prepare default values for marketplace parameters."""
|
|
288
|
+
p_type = payment_type or bytes.fromhex(PAYMENT_TYPE_NATIVE)
|
|
289
|
+
val = value if value is not None else 10_000_000_000_000_000
|
|
290
|
+
rate = max_delivery_rate if max_delivery_rate is not None else val
|
|
291
|
+
return val, rate, p_type
|
|
292
|
+
|
|
263
293
|
def _send_marketplace_mech_request(
|
|
264
294
|
self,
|
|
265
295
|
data: bytes,
|
|
266
296
|
value: Optional[int] = None,
|
|
297
|
+
marketplace_address: Optional[str] = None,
|
|
267
298
|
priority_mech: Optional[str] = None,
|
|
268
299
|
max_delivery_rate: Optional[int] = None,
|
|
269
300
|
payment_type: Optional[bytes] = None,
|
|
@@ -275,43 +306,30 @@ class MechManagerMixin:
|
|
|
275
306
|
logger.error("No active service")
|
|
276
307
|
return None
|
|
277
308
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
logger.error(f"Mech Marketplace address not found for chain {chain_name}")
|
|
285
|
-
return None
|
|
286
|
-
|
|
287
|
-
if not priority_mech:
|
|
288
|
-
logger.error("priority_mech is required for marketplace requests")
|
|
309
|
+
try:
|
|
310
|
+
marketplace_address, priority_mech = self._resolve_marketplace_config(
|
|
311
|
+
marketplace_address, priority_mech
|
|
312
|
+
)
|
|
313
|
+
except ValueError as e:
|
|
314
|
+
logger.error(e)
|
|
289
315
|
return None
|
|
290
316
|
|
|
291
|
-
|
|
292
|
-
marketplace = MechMarketplaceContract(str(marketplace_address), chain_name=chain_name)
|
|
317
|
+
marketplace = MechMarketplaceContract(marketplace_address, chain_name=self.chain_name)
|
|
293
318
|
|
|
294
319
|
if not self._validate_priority_mech(marketplace, priority_mech):
|
|
295
320
|
return None
|
|
296
321
|
|
|
297
|
-
# Set defaults for payment
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if value is None:
|
|
302
|
-
value = 10_000_000_000_000_000
|
|
303
|
-
logger.info(f"Using default value: {value} wei (0.01 xDAI)")
|
|
304
|
-
|
|
305
|
-
if max_delivery_rate is None:
|
|
306
|
-
max_delivery_rate = value
|
|
307
|
-
logger.info(f"Using value as max_delivery_rate: {max_delivery_rate}")
|
|
322
|
+
# Set defaults for payment and delivery
|
|
323
|
+
value, max_delivery_rate, payment_type = self._prepare_marketplace_params(
|
|
324
|
+
value, max_delivery_rate, payment_type
|
|
325
|
+
)
|
|
308
326
|
|
|
309
327
|
if not self._validate_marketplace_params(marketplace, response_timeout, payment_type):
|
|
310
328
|
return None
|
|
311
329
|
|
|
312
330
|
# Prepare transaction
|
|
313
331
|
tx_data = marketplace.prepare_request_tx(
|
|
314
|
-
from_address=multisig_address,
|
|
332
|
+
from_address=self.service.multisig_address,
|
|
315
333
|
request_data=data,
|
|
316
334
|
priority_mech=priority_mech,
|
|
317
335
|
response_timeout=response_timeout,
|
|
@@ -195,43 +195,8 @@ def test_wait_for_no_pending_tx(mock_web3):
|
|
|
195
195
|
assert ci.wait_for_no_pending_tx("0xSender") is False
|
|
196
196
|
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
chain.name = "TestChain"
|
|
201
|
-
type(chain).rpc = PropertyMock(return_value="https://rpc")
|
|
202
|
-
ci = ChainInterface(chain)
|
|
203
|
-
account = MagicMock(address="0xSender", key="key")
|
|
204
|
-
|
|
205
|
-
ci.web3.eth.get_transaction_count.return_value = 0
|
|
206
|
-
ci.web3.eth.gas_price = 10
|
|
207
|
-
ci.web3.eth.estimate_gas.return_value = 21000
|
|
208
|
-
|
|
209
|
-
# Sufficient balance
|
|
210
|
-
ci.web3.eth.get_balance.return_value = 10**18 # plenty
|
|
211
|
-
ci.web3.eth.get_balance.return_value = 10**18 # plenty
|
|
212
|
-
# Valid mock return for success: (True, dict_receipt)
|
|
213
|
-
# The actual method returns tx_hash.hex().
|
|
214
|
-
mock_signed_tx = MagicMock()
|
|
215
|
-
mock_signed_tx.raw_transaction = b"raw"
|
|
216
|
-
mock_receipt = {"transactionHash": b"hash", "status": 1}
|
|
217
|
-
|
|
218
|
-
with (
|
|
219
|
-
patch.object(ci.web3.eth, "send_raw_transaction", return_value=b"hash"),
|
|
220
|
-
patch.object(ci.web3.eth, "wait_for_transaction_receipt", return_value=mock_receipt),
|
|
221
|
-
patch.object(ci, "wait_for_no_pending_tx", return_value=True),
|
|
222
|
-
):
|
|
223
|
-
success, tx_hash = ci.send_native_transfer(
|
|
224
|
-
account.address, "0xReceiver", 1000, sign_callback=lambda tx: mock_signed_tx
|
|
225
|
-
)
|
|
226
|
-
assert success is True
|
|
227
|
-
assert tx_hash == "68617368"
|
|
228
|
-
|
|
229
|
-
# Insufficient balance
|
|
230
|
-
ci.web3.eth.get_balance.return_value = 0
|
|
231
|
-
ci.web3.from_wei.return_value = 0.0
|
|
232
|
-
assert ci.send_native_transfer(
|
|
233
|
-
account.address, "0xReceiver", 1000, sign_callback=lambda tx: mock_signed_tx
|
|
234
|
-
) == (False, None)
|
|
198
|
+
# NOTE: test_send_native_transfer was removed because the method was removed
|
|
199
|
+
# from ChainInterface. Native transfers now go through TransactionService.
|
|
235
200
|
|
|
236
201
|
|
|
237
202
|
def test_chain_interfaces_get():
|
|
@@ -352,66 +317,6 @@ def test_chain_interface_with_real_chains():
|
|
|
352
317
|
# --- Negative Tests ---
|
|
353
318
|
|
|
354
319
|
|
|
355
|
-
def test_send_native_transfer_insufficient_balance(mock_web3):
|
|
356
|
-
"""Test send_native_transfer fails with insufficient balance."""
|
|
357
|
-
chain = MagicMock(spec=SupportedChain)
|
|
358
|
-
chain.name = "TestChain"
|
|
359
|
-
chain.rpcs = ["https://rpc"]
|
|
360
|
-
chain.chain_id = 1
|
|
361
|
-
chain.native_currency = "ETH"
|
|
362
|
-
type(chain).rpc = PropertyMock(return_value="https://rpc")
|
|
363
|
-
|
|
364
|
-
ci = ChainInterface(chain)
|
|
365
|
-
ci.web3.eth.get_transaction_count.return_value = 0
|
|
366
|
-
ci.web3.eth.gas_price = 1000000000 # 1 gwei
|
|
367
|
-
ci.web3.eth.estimate_gas.return_value = 21000
|
|
368
|
-
ci.web3.eth.get_balance.return_value = 1000 # Very low balance
|
|
369
|
-
ci.web3.from_wei.return_value = 0.000001
|
|
370
|
-
|
|
371
|
-
sign_callback = MagicMock()
|
|
372
|
-
|
|
373
|
-
success, tx_hash = ci.send_native_transfer(
|
|
374
|
-
from_address="0x1111111111111111111111111111111111111111",
|
|
375
|
-
to_address="0x2222222222222222222222222222222222222222",
|
|
376
|
-
value_wei=10**18, # 1 ETH - more than available
|
|
377
|
-
sign_callback=sign_callback,
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
assert success is False
|
|
381
|
-
assert tx_hash is None
|
|
382
|
-
sign_callback.assert_not_called()
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
def test_send_native_transfer_rpc_error(mock_web3):
|
|
386
|
-
"""Test send_native_transfer handles RPC errors."""
|
|
387
|
-
chain = MagicMock(spec=SupportedChain)
|
|
388
|
-
chain.name = "TestChain"
|
|
389
|
-
chain.rpcs = ["https://rpc"]
|
|
390
|
-
chain.chain_id = 1
|
|
391
|
-
chain.native_currency = "ETH"
|
|
392
|
-
type(chain).rpc = PropertyMock(return_value="https://rpc")
|
|
393
|
-
|
|
394
|
-
ci = ChainInterface(chain)
|
|
395
|
-
ci.web3.eth.get_transaction_count.return_value = 0
|
|
396
|
-
ci.web3.eth.gas_price = 1000000000
|
|
397
|
-
ci.web3.eth.estimate_gas.return_value = 21000
|
|
398
|
-
ci.web3.eth.get_balance.return_value = 10**19 # Enough balance
|
|
399
|
-
ci.web3.from_wei.return_value = 10.0
|
|
400
|
-
ci.web3.eth.send_raw_transaction.side_effect = Exception("Connection refused")
|
|
401
|
-
|
|
402
|
-
sign_callback = MagicMock()
|
|
403
|
-
sign_callback.return_value = MagicMock(raw_transaction=b"signed")
|
|
404
|
-
|
|
405
|
-
success, tx_hash = ci.send_native_transfer(
|
|
406
|
-
from_address="0x1111111111111111111111111111111111111111",
|
|
407
|
-
to_address="0x2222222222222222222222222222222222222222",
|
|
408
|
-
value_wei=10**17,
|
|
409
|
-
sign_callback=sign_callback,
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
assert success is False
|
|
413
|
-
assert tx_hash is None
|
|
414
|
-
|
|
415
320
|
|
|
416
321
|
def test_get_token_symbol_fallback_on_error(mock_web3):
|
|
417
322
|
"""Test get_token_symbol returns truncated address on error."""
|