iwa 0.0.2__tar.gz → 0.0.10__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.2/src/iwa.egg-info → iwa-0.0.10}/PKG-INFO +1 -1
- {iwa-0.0.2 → iwa-0.0.10}/pyproject.toml +2 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/interface.py +30 -23
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/models.py +9 -15
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/contract.py +8 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/pricing.py +10 -8
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/safe.py +13 -8
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transaction.py +15 -4
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/utils.py +22 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/safe.py +4 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/tests/test_safe.py +9 -7
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/service.py +4 -4
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/staking.py +2 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/plugin.py +14 -7
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/lifecycle.py +109 -48
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/mech.py +1 -1
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/staking.py +92 -34
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_plugin.py +6 -1
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_plugin_full.py +12 -7
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +16 -15
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/list_contracts.py +2 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/dependencies.py +1 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/accounts.py +1 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/admin.py +1 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/funding.py +1 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/general.py +1 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/services.py +1 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/staking.py +19 -22
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/swap.py +1 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/transactions.py +0 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/server.py +8 -6
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/static/app.js +22 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_endpoints.py +1 -1
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_olas.py +1 -1
- {iwa-0.0.2 → iwa-0.0.10/src/iwa.egg-info}/PKG-INFO +1 -1
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/SOURCES.txt +1 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_chain_interface_coverage.py +3 -2
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_contract.py +165 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_keys.py +2 -1
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_legacy_wallet.py +11 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_pricing.py +32 -15
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_safe_coverage.py +3 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_safe_service.py +3 -6
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_service_transaction.py +8 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_staking_router.py +6 -3
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transaction_service.py +4 -0
- iwa-0.0.10/src/tools/create_and_stake_service.py +103 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tools/verify_drain.py +1 -4
- {iwa-0.0.2 → iwa-0.0.10}/LICENSE +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/README.md +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/setup.cfg +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/__main__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/errors.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/manager.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/chain/rate_limiter.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/cli.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/constants.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/abis/erc20.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/abis/multisend.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/erc20.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/contracts/multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/db.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/ipfs.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/keys.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/mnemonic.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/monitor.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/plugins.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/secrets.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/account.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/balance.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/plugin.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/erc20.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/native.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/services/transfer/swap.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/tables.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/test.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/tests/test_wallet.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/types.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/ui.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/core/wallet.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow/types.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/plugin.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/constants.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/mech.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/importer.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/mech_reference.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/conftest.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_contracts.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tui/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/check_profile.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/release.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/reset_env.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/reset_tenderly.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/restore_backup.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tools/wallet_check.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/app.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/modals/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/modals/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/rpc.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/screens/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/screens/wallets.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_app.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_rpc.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/tests/test_widgets.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/widgets/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/widgets/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/tui/workers.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/olas/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/routers/state.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/static/index.html +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/static/style.css +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_swap.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/dependency_links.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/entry_points.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/requires.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/iwa.egg-info/top_level.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_cow.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_safe.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_transaction_retry_logic.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_tui.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_wallets_screen.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/legacy_web.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_account_service.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_balance_service.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_chain.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_chain_interface.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_cli.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_db.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_drain_coverage.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_erc20.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_gnosis_plugin.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_main.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_migration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_mnemonic.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_modals.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_monitor.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_plugin_service.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_rate_limiter.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_reset_tenderly.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_rpc_view.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_service_manager_integration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_service_manager_structure.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_staking_simple.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_tables.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_native.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_security.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_structure.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_transfer_swap_unit.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_ui_coverage.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_utils.py +0 -0
- {iwa-0.0.2 → iwa-0.0.10}/src/tests/test_workers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "iwa"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.10"
|
|
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.10"
|
|
75
75
|
fix = true
|
|
76
76
|
|
|
77
77
|
[tool.ruff.lint]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""ChainInterface class for blockchain interactions."""
|
|
2
2
|
|
|
3
|
+
import threading
|
|
3
4
|
import time
|
|
4
5
|
from typing import Callable, Dict, Optional, Tuple, TypeVar, Union
|
|
5
6
|
|
|
@@ -45,6 +46,7 @@ class ChainInterface:
|
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
self._initial_block = 0
|
|
49
|
+
self._rotation_lock = threading.Lock()
|
|
48
50
|
self._init_web3()
|
|
49
51
|
|
|
50
52
|
@property
|
|
@@ -154,18 +156,6 @@ class ChainInterface:
|
|
|
154
156
|
print("╚══════════════════════════════════════════════════╝")
|
|
155
157
|
print("")
|
|
156
158
|
|
|
157
|
-
def _init_web3(self):
|
|
158
|
-
"""Initialize Web3 with current RPC."""
|
|
159
|
-
rpc_url = self.chain.rpcs[self._current_rpc_index] if self.chain.rpcs else ""
|
|
160
|
-
raw_web3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={"timeout": DEFAULT_RPC_TIMEOUT}))
|
|
161
|
-
|
|
162
|
-
# Use duck typing to check if current web3 is a RateLimitedWeb3 wrapper
|
|
163
|
-
# (isinstance check fails when RateLimitedWeb3 is mocked in tests)
|
|
164
|
-
if hasattr(self, "web3") and hasattr(self.web3, "set_backend"):
|
|
165
|
-
self.web3.set_backend(raw_web3)
|
|
166
|
-
else:
|
|
167
|
-
self.web3 = RateLimitedWeb3(raw_web3, self._rate_limiter, self)
|
|
168
|
-
|
|
169
159
|
def _is_rate_limit_error(self, error: Exception) -> bool:
|
|
170
160
|
"""Check if error is a rate limit (429) error."""
|
|
171
161
|
err_text = str(error).lower()
|
|
@@ -269,19 +259,36 @@ class ChainInterface:
|
|
|
269
259
|
|
|
270
260
|
def rotate_rpc(self) -> bool:
|
|
271
261
|
"""Rotate to the next available RPC."""
|
|
272
|
-
|
|
273
|
-
|
|
262
|
+
with self._rotation_lock:
|
|
263
|
+
if not self.chain.rpcs or len(self.chain.rpcs) <= 1:
|
|
264
|
+
return False
|
|
265
|
+
|
|
266
|
+
# Simple Round Robin rotation
|
|
267
|
+
self._current_rpc_index = (self._current_rpc_index + 1) % len(self.chain.rpcs)
|
|
268
|
+
# Internal call to _init_web3 already expects to be under lock if called from here,
|
|
269
|
+
# but _init_web3 itself doesn't have a lock. Let's make it consistent.
|
|
270
|
+
self._init_web3_under_lock()
|
|
271
|
+
|
|
272
|
+
logger.info(
|
|
273
|
+
f"Rotated RPC for {self.chain.name} to index {self._current_rpc_index}: {self.chain.rpcs[self._current_rpc_index]}"
|
|
274
|
+
)
|
|
275
|
+
return True
|
|
274
276
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
self._init_web3()
|
|
277
|
+
def _init_web3(self):
|
|
278
|
+
"""Initialize Web3 with current RPC (thread-safe)."""
|
|
279
|
+
with self._rotation_lock:
|
|
280
|
+
self._init_web3_under_lock()
|
|
280
281
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
282
|
+
def _init_web3_under_lock(self):
|
|
283
|
+
"""Internal non-thread-safe web3 initialization."""
|
|
284
|
+
rpc_url = self.chain.rpcs[self._current_rpc_index] if self.chain.rpcs else ""
|
|
285
|
+
raw_web3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={"timeout": DEFAULT_RPC_TIMEOUT}))
|
|
286
|
+
|
|
287
|
+
# Use duck typing to check if current web3 is a RateLimitedWeb3 wrapper
|
|
288
|
+
if hasattr(self, "web3") and hasattr(self.web3, "set_backend"):
|
|
289
|
+
self.web3.set_backend(raw_web3)
|
|
290
|
+
else:
|
|
291
|
+
self.web3 = RateLimitedWeb3(raw_web3, self._rate_limiter, self)
|
|
285
292
|
|
|
286
293
|
def check_rpc_health(self) -> bool:
|
|
287
294
|
"""Check if the current RPC is healthy."""
|
|
@@ -78,13 +78,11 @@ class Gnosis(SupportedChain):
|
|
|
78
78
|
if not self.rpcs and secrets.gnosis_rpc:
|
|
79
79
|
self.rpcs = secrets.gnosis_rpc.get_secret_value().split(",")
|
|
80
80
|
|
|
81
|
-
# Defensive: ensure no comma-separated strings in list
|
|
81
|
+
# Defensive: ensure no comma-separated strings and NO quotes in list
|
|
82
82
|
new_rpcs = []
|
|
83
83
|
for rpc in self.rpcs:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
else:
|
|
87
|
-
new_rpcs.append(rpc)
|
|
84
|
+
parts = [r.strip().strip("'\"") for r in rpc.split(",") if r.strip()]
|
|
85
|
+
new_rpcs.extend(parts)
|
|
88
86
|
self.rpcs = new_rpcs
|
|
89
87
|
|
|
90
88
|
|
|
@@ -107,13 +105,11 @@ class Ethereum(SupportedChain):
|
|
|
107
105
|
if not self.rpcs and secrets.ethereum_rpc:
|
|
108
106
|
self.rpcs = secrets.ethereum_rpc.get_secret_value().split(",")
|
|
109
107
|
|
|
110
|
-
# Defensive: ensure no comma-separated strings in list
|
|
108
|
+
# Defensive: ensure no comma-separated strings and NO quotes in list
|
|
111
109
|
new_rpcs = []
|
|
112
110
|
for rpc in self.rpcs:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
else:
|
|
116
|
-
new_rpcs.append(rpc)
|
|
111
|
+
parts = [r.strip().strip("'\"") for r in rpc.split(",") if r.strip()]
|
|
112
|
+
new_rpcs.extend(parts)
|
|
117
113
|
self.rpcs = new_rpcs
|
|
118
114
|
|
|
119
115
|
|
|
@@ -136,13 +132,11 @@ class Base(SupportedChain):
|
|
|
136
132
|
if not self.rpcs and secrets.base_rpc:
|
|
137
133
|
self.rpcs = secrets.base_rpc.get_secret_value().split(",")
|
|
138
134
|
|
|
139
|
-
# Defensive: ensure no comma-separated strings in list
|
|
135
|
+
# Defensive: ensure no comma-separated strings and NO quotes in list
|
|
140
136
|
new_rpcs = []
|
|
141
137
|
for rpc in self.rpcs:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
else:
|
|
145
|
-
new_rpcs.append(rpc)
|
|
138
|
+
parts = [r.strip().strip("'\"") for r in rpc.split(",") if r.strip()]
|
|
139
|
+
new_rpcs.extend(parts)
|
|
146
140
|
self.rpcs = new_rpcs
|
|
147
141
|
|
|
148
142
|
|
|
@@ -190,10 +190,16 @@ class ContractInstance:
|
|
|
190
190
|
Exception: If the call fails, with decoded error information.
|
|
191
191
|
|
|
192
192
|
"""
|
|
193
|
-
method = getattr(self.contract.functions, method_name)
|
|
194
193
|
try:
|
|
194
|
+
|
|
195
|
+
def do_call():
|
|
196
|
+
# Re-evaluate self.contract on each retry to get current provider
|
|
197
|
+
# This is critical for RPC rotation to work correctly
|
|
198
|
+
method = getattr(self.contract.functions, method_name)
|
|
199
|
+
return method(*args).call()
|
|
200
|
+
|
|
195
201
|
return self.chain_interface.with_retry(
|
|
196
|
-
|
|
202
|
+
do_call,
|
|
197
203
|
operation_name=f"call {method_name} on {self.name}",
|
|
198
204
|
)
|
|
199
205
|
except Exception as e:
|
|
@@ -9,17 +9,19 @@ from loguru import logger
|
|
|
9
9
|
|
|
10
10
|
from iwa.core.secrets import secrets
|
|
11
11
|
|
|
12
|
+
# Global cache shared across all PriceService instances
|
|
13
|
+
_PRICE_CACHE: Dict[str, Dict] = {}
|
|
14
|
+
_CACHE_TTL = timedelta(minutes=30)
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
class PriceService:
|
|
14
18
|
"""Service to fetch token prices from CoinGecko."""
|
|
15
19
|
|
|
16
20
|
BASE_URL = "https://api.coingecko.com/api/v3"
|
|
17
21
|
|
|
18
|
-
def __init__(self
|
|
22
|
+
def __init__(self):
|
|
19
23
|
"""Initialize PriceService."""
|
|
20
24
|
self.secrets = secrets
|
|
21
|
-
self.cache: Dict[str, Dict] = {} # {id_currency: {"price": float, "timestamp": datetime}}
|
|
22
|
-
self.cache_ttl = timedelta(minutes=cache_ttl_minutes)
|
|
23
25
|
self.api_key = (
|
|
24
26
|
self.secrets.coingecko_api_key.get_secret_value()
|
|
25
27
|
if self.secrets.coingecko_api_key
|
|
@@ -39,15 +41,15 @@ class PriceService:
|
|
|
39
41
|
"""
|
|
40
42
|
cache_key = f"{token_id}_{vs_currency}"
|
|
41
43
|
|
|
42
|
-
# Check cache
|
|
43
|
-
if cache_key in
|
|
44
|
-
entry =
|
|
45
|
-
if datetime.now() - entry["timestamp"] <
|
|
44
|
+
# Check global cache
|
|
45
|
+
if cache_key in _PRICE_CACHE:
|
|
46
|
+
entry = _PRICE_CACHE[cache_key]
|
|
47
|
+
if datetime.now() - entry["timestamp"] < _CACHE_TTL:
|
|
46
48
|
return entry["price"]
|
|
47
49
|
|
|
48
50
|
price = self._fetch_price_from_api(token_id, vs_currency)
|
|
49
51
|
if price is not None:
|
|
50
|
-
|
|
52
|
+
_PRICE_CACHE[cache_key] = {"price": price, "timestamp": datetime.now()}
|
|
51
53
|
return price
|
|
52
54
|
|
|
53
55
|
def _fetch_price_from_api(self, token_id: str, vs_currency: str) -> Optional[float]:
|
|
@@ -11,7 +11,6 @@ from safe_eth.safe.safe_tx import SafeTx
|
|
|
11
11
|
from iwa.core.constants import ZERO_ADDRESS
|
|
12
12
|
from iwa.core.db import log_transaction
|
|
13
13
|
from iwa.core.models import StoredSafeAccount
|
|
14
|
-
from iwa.core.secrets import secrets
|
|
15
14
|
from iwa.core.utils import (
|
|
16
15
|
get_safe_master_copy_address,
|
|
17
16
|
get_safe_proxy_factory_address,
|
|
@@ -99,8 +98,11 @@ class SafeService:
|
|
|
99
98
|
return owner_addresses
|
|
100
99
|
|
|
101
100
|
def _get_ethereum_client(self, chain_name: str) -> EthereumClient:
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
from iwa.core.chain import ChainInterfaces
|
|
102
|
+
|
|
103
|
+
# Use ChainInterface which has proper RPC rotation and parsing
|
|
104
|
+
chain_interface = ChainInterfaces().get(chain_name)
|
|
105
|
+
return EthereumClient(chain_interface.chain.rpc)
|
|
104
106
|
|
|
105
107
|
def _deploy_safe_contract(
|
|
106
108
|
self,
|
|
@@ -141,7 +143,7 @@ class SafeService:
|
|
|
141
143
|
gas=5_000_000,
|
|
142
144
|
gas_price=gas_price,
|
|
143
145
|
)
|
|
144
|
-
return tx_sent.contract_address, tx_sent.tx_hash.hex()
|
|
146
|
+
return tx_sent.contract_address, f"0x{tx_sent.tx_hash.hex()}"
|
|
145
147
|
|
|
146
148
|
else:
|
|
147
149
|
# Standard random salt via Safe.create
|
|
@@ -153,7 +155,7 @@ class SafeService:
|
|
|
153
155
|
threshold=threshold,
|
|
154
156
|
proxy_factory_address=proxy_factory_address,
|
|
155
157
|
)
|
|
156
|
-
return create_tx.contract_address, create_tx.tx_hash.hex()
|
|
158
|
+
return create_tx.contract_address, f"0x{create_tx.tx_hash.hex()}"
|
|
157
159
|
|
|
158
160
|
def _log_safe_deployment(
|
|
159
161
|
self,
|
|
@@ -248,8 +250,11 @@ class SafeService:
|
|
|
248
250
|
continue
|
|
249
251
|
|
|
250
252
|
for chain in account.chains:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
+
from iwa.core.chain import ChainInterfaces
|
|
254
|
+
|
|
255
|
+
# Use ChainInterface which has proper RPC rotation and parsing
|
|
256
|
+
chain_interface = ChainInterfaces().get(chain)
|
|
257
|
+
ethereum_client = EthereumClient(chain_interface.chain.rpc)
|
|
253
258
|
|
|
254
259
|
code = ethereum_client.w3.eth.get_code(account.address)
|
|
255
260
|
|
|
@@ -304,7 +309,7 @@ class SafeService:
|
|
|
304
309
|
# Execute using the first signer
|
|
305
310
|
safe_tx.execute(signer_keys[0])
|
|
306
311
|
|
|
307
|
-
return safe_tx.tx_hash.hex()
|
|
312
|
+
return f"0x{safe_tx.tx_hash.hex()}"
|
|
308
313
|
finally:
|
|
309
314
|
# SECURITY: Overwrite keys with zeros before clearing (best effort)
|
|
310
315
|
for i in range(len(signer_keys)):
|
|
@@ -30,7 +30,7 @@ class TransactionService:
|
|
|
30
30
|
"""Sign and send a transaction with retry logic for gas."""
|
|
31
31
|
chain_interface = ChainInterfaces().get(chain_name)
|
|
32
32
|
tx = dict(transaction)
|
|
33
|
-
max_retries =
|
|
33
|
+
max_retries = 10
|
|
34
34
|
|
|
35
35
|
if not self._prepare_transaction(tx, signer_address_or_tag, chain_interface):
|
|
36
36
|
return False, {}
|
|
@@ -39,7 +39,14 @@ class TransactionService:
|
|
|
39
39
|
try:
|
|
40
40
|
signed_txn = self.key_storage.sign_transaction(tx, signer_address_or_tag)
|
|
41
41
|
txn_hash = chain_interface.web3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# Use chain_interface.with_retry for waiting for receipt to handle timeouts/RPC errors
|
|
44
|
+
def wait_for_receipt(tx_h=txn_hash):
|
|
45
|
+
return chain_interface.web3.eth.wait_for_transaction_receipt(tx_h)
|
|
46
|
+
|
|
47
|
+
receipt = chain_interface.with_retry(
|
|
48
|
+
wait_for_receipt, operation_name="wait_for_receipt"
|
|
49
|
+
)
|
|
43
50
|
|
|
44
51
|
if receipt and getattr(receipt, "status", None) == 1:
|
|
45
52
|
signer_account = self.account_service.resolve_account(signer_address_or_tag)
|
|
@@ -89,7 +96,9 @@ class TransactionService:
|
|
|
89
96
|
)
|
|
90
97
|
current_gas = int(tx.get("gas", 30_000))
|
|
91
98
|
tx["gas"] = int(current_gas * 1.5)
|
|
92
|
-
|
|
99
|
+
tx["gas"] = int(current_gas * 1.5)
|
|
100
|
+
# Exponential backoff for gas errors
|
|
101
|
+
time.sleep(min(2**attempt, 30))
|
|
93
102
|
return True
|
|
94
103
|
logger.exception(f"Error sending transaction: {e}")
|
|
95
104
|
return False
|
|
@@ -97,9 +106,11 @@ class TransactionService:
|
|
|
97
106
|
def _handle_generic_error(self, e, chain_interface, attempt, max_retries) -> bool:
|
|
98
107
|
if attempt < max_retries:
|
|
99
108
|
logger.warning(f"Error encountered: {e}. Attempting to rotate RPC...")
|
|
109
|
+
|
|
100
110
|
if chain_interface.rotate_rpc():
|
|
101
111
|
logger.info("Retrying with new RPC...")
|
|
102
|
-
|
|
112
|
+
# Exponential backoff
|
|
113
|
+
time.sleep(min(2**attempt, 30))
|
|
103
114
|
return True
|
|
104
115
|
logger.exception(f"Unexpected error sending transaction: {e}")
|
|
105
116
|
return False
|
|
@@ -38,24 +38,46 @@ def get_safe_proxy_factory_address(target_version: str = "1.4.1") -> str:
|
|
|
38
38
|
raise ValueError(f"Did not find proxy factory for version {target_version}")
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
def get_tx_hash(receipt: dict) -> str:
|
|
42
|
+
"""Safely extract transaction hash from receipt (handles bytes/str/None)."""
|
|
43
|
+
if not receipt:
|
|
44
|
+
return "unknown"
|
|
45
|
+
|
|
46
|
+
tx_hash = receipt.get("transactionHash", "")
|
|
47
|
+
if hasattr(tx_hash, "hex"):
|
|
48
|
+
return tx_hash.hex()
|
|
49
|
+
return str(tx_hash) if tx_hash else "unknown"
|
|
50
|
+
|
|
51
|
+
|
|
41
52
|
def configure_logger():
|
|
42
53
|
"""Configure the logger for the application."""
|
|
43
54
|
if hasattr(configure_logger, "configured"):
|
|
44
55
|
return logger
|
|
45
56
|
|
|
57
|
+
import logging
|
|
58
|
+
|
|
46
59
|
from iwa.core.constants import DATA_DIR
|
|
47
60
|
|
|
61
|
+
# Silence noisy third-party loggers (these use stdlib logging, not loguru)
|
|
62
|
+
logging.getLogger("apscheduler.scheduler").setLevel(logging.WARNING)
|
|
63
|
+
logging.getLogger("apscheduler.executors.default").setLevel(logging.WARNING)
|
|
64
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
65
|
+
|
|
48
66
|
logger.remove()
|
|
49
67
|
|
|
50
68
|
# Ensure data directory exists
|
|
51
69
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
52
70
|
|
|
71
|
+
import sys
|
|
72
|
+
|
|
53
73
|
logger.add(
|
|
54
74
|
DATA_DIR / "iwa.log",
|
|
55
75
|
rotation="10 MB",
|
|
56
76
|
level="INFO",
|
|
57
77
|
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}",
|
|
58
78
|
)
|
|
79
|
+
# Restore console logging (stderr) so logs are visible in docker/systemd/frontend streams
|
|
80
|
+
logger.add(sys.stderr, level="INFO")
|
|
59
81
|
# Also keep stderr for console if needed, but Textual captures it?
|
|
60
82
|
# Textual usually captures stderr. Writing to file is safer for debugging.
|
|
61
83
|
# Users previous logs show stdout format?
|
|
@@ -8,7 +8,6 @@ from safe_eth.safe import Safe, SafeOperationEnum
|
|
|
8
8
|
from safe_eth.safe.safe_tx import SafeTx
|
|
9
9
|
|
|
10
10
|
from iwa.core.models import StoredSafeAccount
|
|
11
|
-
from iwa.core.secrets import secrets
|
|
12
11
|
from iwa.core.utils import configure_logger
|
|
13
12
|
|
|
14
13
|
logger = configure_logger()
|
|
@@ -28,8 +27,10 @@ class SafeMultisig:
|
|
|
28
27
|
if chain_name.lower() not in normalized_chains:
|
|
29
28
|
raise ValueError(f"Safe account is not deployed on chain: {chain_name}")
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
from iwa.core.chain import ChainInterfaces
|
|
31
|
+
|
|
32
|
+
chain_interface = ChainInterfaces().get(chain_name.lower())
|
|
33
|
+
ethereum_client = EthereumClient(chain_interface.chain.rpc)
|
|
33
34
|
self.multisig = Safe(safe_account.address, ethereum_client)
|
|
34
35
|
self.ethereum_client = ethereum_client
|
|
35
36
|
|
|
@@ -11,9 +11,8 @@ from iwa.plugins.gnosis.safe import SafeMultisig
|
|
|
11
11
|
@pytest.fixture
|
|
12
12
|
def mock_settings():
|
|
13
13
|
"""Mock settings."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
yield mock
|
|
14
|
+
# secrets is no longer used in this module, so we don't need to patch it here
|
|
15
|
+
yield None
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
@pytest.fixture
|
|
@@ -41,10 +40,13 @@ def safe_account():
|
|
|
41
40
|
|
|
42
41
|
def test_init(safe_account, mock_settings, mock_safe_eth):
|
|
43
42
|
"""Test initialization."""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
with patch("iwa.core.chain.ChainInterfaces") as mock_ci_cls:
|
|
44
|
+
mock_ci = mock_ci_cls.return_value
|
|
45
|
+
mock_ci.get.return_value.chain.rpc = "http://rpc"
|
|
46
|
+
ms = SafeMultisig(safe_account, "gnosis")
|
|
47
|
+
assert ms.multisig is not None
|
|
48
|
+
mock_safe_eth[0].assert_called_with("http://rpc") # EthereumClient init
|
|
49
|
+
mock_safe_eth[1].assert_called() # Safe init
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def test_init_invalid_chain(safe_account, mock_settings, mock_safe_eth):
|
|
@@ -64,10 +64,10 @@ class ServiceRegistryContract(ContractInstance):
|
|
|
64
64
|
"""Get the token address for a service."""
|
|
65
65
|
return self.call("token", service_id)
|
|
66
66
|
|
|
67
|
-
def get_agent_params(self, service_id: int
|
|
68
|
-
"""Get agent params (slots, bond) for a service."""
|
|
69
|
-
|
|
70
|
-
return {"slots":
|
|
67
|
+
def get_agent_params(self, service_id: int) -> list:
|
|
68
|
+
"""Get agent params (slots, bond) for all agents in a service."""
|
|
69
|
+
num_ids, params = self.call("getAgentParams", service_id)
|
|
70
|
+
return [{"slots": p[0], "bond": p[1]} for p in params]
|
|
71
71
|
|
|
72
72
|
def prepare_approve_tx(
|
|
73
73
|
self,
|
|
@@ -30,20 +30,19 @@ The staking contract checks that:
|
|
|
30
30
|
4. Agent bond was deposited during service registration
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
-
import logging
|
|
34
33
|
import math
|
|
35
34
|
import time
|
|
36
35
|
from datetime import datetime, timezone
|
|
37
36
|
from enum import Enum
|
|
38
37
|
from typing import Dict, List, Optional, Union
|
|
39
38
|
|
|
39
|
+
from loguru import logger
|
|
40
|
+
|
|
40
41
|
from iwa.core.contracts.contract import ContractInstance
|
|
41
42
|
from iwa.core.types import EthereumAddress
|
|
42
43
|
from iwa.plugins.olas.contracts.activity_checker import ActivityCheckerContract
|
|
43
44
|
from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH
|
|
44
45
|
|
|
45
|
-
logger = logging.getLogger(__name__)
|
|
46
|
-
|
|
47
46
|
|
|
48
47
|
class StakingState(Enum):
|
|
49
48
|
"""Enum representing the staking state of a service."""
|
|
@@ -48,8 +48,12 @@ class OlasPlugin(Plugin):
|
|
|
48
48
|
"""Create a new Olas service"""
|
|
49
49
|
wallet = Wallet()
|
|
50
50
|
manager = ServiceManager(wallet)
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
manager.create(
|
|
52
|
+
chain_name=chain_name,
|
|
53
|
+
service_owner_address_or_tag=owner,
|
|
54
|
+
token_address_or_tag=token,
|
|
55
|
+
bond_amount_wei=bond,
|
|
56
|
+
)
|
|
53
57
|
|
|
54
58
|
def _get_safe_signers(self, safe_address: str, chain_name: str) -> tuple:
|
|
55
59
|
"""Query Safe signers on-chain.
|
|
@@ -65,13 +69,16 @@ class OlasPlugin(Plugin):
|
|
|
65
69
|
from safe_eth.eth import EthereumClient
|
|
66
70
|
from safe_eth.safe import Safe
|
|
67
71
|
|
|
68
|
-
from iwa.core.
|
|
72
|
+
from iwa.core.chain import ChainInterfaces
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
try:
|
|
75
|
+
chain_interface = ChainInterfaces().get(chain_name)
|
|
76
|
+
if not chain_interface.chain.rpcs:
|
|
77
|
+
return None, None
|
|
78
|
+
except ValueError:
|
|
79
|
+
return None, None # Chain not supported/configured
|
|
73
80
|
|
|
74
|
-
ethereum_client = EthereumClient(
|
|
81
|
+
ethereum_client = EthereumClient(chain_interface.chain.rpc)
|
|
75
82
|
safe = Safe(safe_address, ethereum_client)
|
|
76
83
|
owners = safe.retrieve_owners()
|
|
77
84
|
return owners, True
|