iwa 0.0.2__tar.gz → 0.0.11__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.11}/PKG-INFO +1 -1
- {iwa-0.0.2 → iwa-0.0.11}/pyproject.toml +2 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/chain/interface.py +51 -30
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/chain/models.py +9 -15
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/contracts/contract.py +8 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/pricing.py +10 -8
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/safe.py +13 -8
- iwa-0.0.11/src/iwa/core/services/transaction.py +376 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/utils.py +22 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/wallet.py +2 -1
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/safe.py +4 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/tests/test_safe.py +9 -7
- iwa-0.0.11/src/iwa/plugins/olas/contracts/abis/service_registry_token_utility.json +926 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/service.py +54 -4
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/staking.py +2 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/plugin.py +14 -7
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/service_manager/lifecycle.py +382 -85
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/service_manager/mech.py +1 -1
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/service_manager/staking.py +229 -82
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_olas_contracts.py +6 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_plugin.py +6 -1
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_plugin_full.py +12 -7
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_lifecycle.py +1 -4
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_manager.py +59 -89
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_manager_errors.py +1 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_manager_flows.py +5 -15
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_manager_validation.py +16 -15
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/list_contracts.py +2 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/dependencies.py +1 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/accounts.py +1 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/olas/admin.py +1 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/olas/funding.py +1 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/olas/general.py +1 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/olas/services.py +53 -21
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/olas/staking.py +27 -24
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/swap.py +1 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/transactions.py +0 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/server.py +8 -6
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/static/app.js +22 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/tests/test_web_endpoints.py +1 -1
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/tests/test_web_olas.py +1 -1
- {iwa-0.0.2 → iwa-0.0.11/src/iwa.egg-info}/PKG-INFO +1 -1
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa.egg-info/SOURCES.txt +2 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_chain.py +12 -7
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_chain_interface_coverage.py +3 -2
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_contract.py +165 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_keys.py +2 -1
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_legacy_wallet.py +11 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_pricing.py +32 -15
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_safe_coverage.py +3 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_safe_service.py +3 -6
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_service_transaction.py +8 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_staking_router.py +6 -3
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_transaction_service.py +4 -0
- iwa-0.0.11/src/tools/create_and_stake_service.py +103 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tools/verify_drain.py +1 -4
- iwa-0.0.2/src/iwa/core/services/transaction.py +0 -172
- {iwa-0.0.2 → iwa-0.0.11}/LICENSE +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/README.md +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/setup.cfg +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/__main__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/chain/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/chain/errors.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/chain/manager.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/chain/rate_limiter.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/cli.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/constants.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/contracts/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/contracts/abis/erc20.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/contracts/abis/multisend.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/contracts/abis/multisend_call_only.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/contracts/erc20.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/contracts/multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/db.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/ipfs.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/keys.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/mnemonic.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/monitor.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/plugins.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/secrets.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/account.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/balance.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/plugin.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/transfer/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/transfer/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/transfer/erc20.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/transfer/multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/transfer/native.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/services/transfer/swap.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/tables.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/test.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/tests/test_wallet.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/types.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/core/ui.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/cow/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/cow/quotes.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/cow/swap.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/cow/types.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/cow_utils.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/plugin.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/gnosis/tests/test_cow.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/constants.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/activity_checker.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/mech.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/mech_marketplace.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/mech_new.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/service_manager.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/service_registry.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/staking.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/abis/staking_token.json +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/activity_checker.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/mech.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/contracts/mech_marketplace.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/importer.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/mech_reference.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/scripts/test_full_mech_flow.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/scripts/test_simple_lifecycle.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/service_manager/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/service_manager/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/service_manager/drain.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/conftest.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_importer.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_importer_error_handling.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_mech_contracts.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_olas_integration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_olas_models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_olas_view.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_olas_view_actions.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_olas_view_modals.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_manager_mech.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_manager_rewards.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_service_staking.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_staking_integration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tests/test_staking_validation.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tui/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/plugins/olas/tui/olas_view.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/check_profile.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/release.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/reset_env.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/reset_tenderly.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/restore_backup.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tools/wallet_check.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/app.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/modals/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/modals/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/rpc.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/screens/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/screens/wallets.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/tests/test_app.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/tests/test_rpc.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/tests/test_wallets_refactor.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/tests/test_widgets.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/widgets/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/widgets/base.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/tui/workers.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/olas/__init__.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/routers/state.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/static/index.html +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/static/style.css +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/tests/test_web_swap.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa/web/tests/test_web_swap_coverage.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa.egg-info/dependency_links.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa.egg-info/entry_points.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa.egg-info/requires.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/iwa.egg-info/top_level.txt +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/legacy_cow.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/legacy_safe.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/legacy_transaction_retry_logic.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/legacy_tui.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/legacy_wallets_screen.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/legacy_web.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_account_service.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_balance_service.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_chain_interface.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_cli.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_db.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_drain_coverage.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_erc20.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_gnosis_plugin.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_main.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_migration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_mnemonic.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_modals.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_models.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_monitor.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_plugin_service.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_rate_limiter.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_reset_tenderly.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_rpc_view.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_service_manager_integration.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_service_manager_structure.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_staking_simple.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_tables.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_transfer_multisend.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_transfer_native.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_transfer_security.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_transfer_structure.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_transfer_swap_unit.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_ui_coverage.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/src/tests/test_utils.py +0 -0
- {iwa-0.0.2 → iwa-0.0.11}/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.11"
|
|
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.11"
|
|
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."""
|
|
@@ -358,15 +365,29 @@ class ChainInterface:
|
|
|
358
365
|
except Exception:
|
|
359
366
|
return address[:6] + "..." + address[-4:]
|
|
360
367
|
|
|
361
|
-
def get_token_decimals(self, address: EthereumAddress) -> int:
|
|
362
|
-
"""Get token decimals for an address.
|
|
363
|
-
try:
|
|
364
|
-
from iwa.core.contracts.erc20 import ERC20Contract
|
|
368
|
+
def get_token_decimals(self, address: EthereumAddress, fallback_to_18: bool = True) -> Optional[int]:
|
|
369
|
+
"""Get token decimals for an address.
|
|
365
370
|
|
|
366
|
-
|
|
367
|
-
|
|
371
|
+
Args:
|
|
372
|
+
address: Token contract address.
|
|
373
|
+
fallback_to_18: If True, return 18 on error (default).
|
|
374
|
+
If False, return None on error (useful for detecting NFTs).
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Decimals as int, or None if error and fallback_to_18 is False.
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
try:
|
|
381
|
+
# Call decimals() directly without with_retry to avoid error logging
|
|
382
|
+
contract = self.web3.eth.contract(
|
|
383
|
+
address=self.web3.to_checksum_address(address),
|
|
384
|
+
abi=[{"constant": True, "inputs": [], "name": "decimals", "outputs": [{"type": "uint8"}], "type": "function"}]
|
|
385
|
+
)
|
|
386
|
+
return contract.functions.decimals().call()
|
|
368
387
|
except Exception:
|
|
369
|
-
|
|
388
|
+
if fallback_to_18:
|
|
389
|
+
return 18
|
|
390
|
+
return None
|
|
370
391
|
|
|
371
392
|
def get_native_balance_wei(self, address: EthereumAddress):
|
|
372
393
|
"""Get the native balance in wei"""
|
|
@@ -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)):
|