iwa 0.0.1a2__py3-none-any.whl → 0.0.1a4__py3-none-any.whl
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/core/chain/interface.py +51 -61
- iwa/core/chain/models.py +7 -7
- iwa/core/chain/rate_limiter.py +21 -10
- iwa/core/cli.py +27 -2
- iwa/core/constants.py +6 -5
- iwa/core/contracts/abis/erc20.json +930 -0
- iwa/core/contracts/abis/multisend.json +24 -0
- iwa/core/contracts/abis/multisend_call_only.json +17 -0
- iwa/core/contracts/contract.py +16 -4
- iwa/core/ipfs.py +149 -0
- iwa/core/keys.py +259 -29
- iwa/core/mnemonic.py +3 -13
- iwa/core/models.py +28 -6
- iwa/core/pricing.py +4 -4
- iwa/core/secrets.py +77 -0
- iwa/core/services/safe.py +3 -3
- iwa/core/utils.py +6 -1
- iwa/core/wallet.py +4 -0
- iwa/plugins/gnosis/safe.py +2 -2
- iwa/plugins/gnosis/tests/test_safe.py +1 -1
- iwa/plugins/olas/constants.py +8 -0
- iwa/plugins/olas/contracts/abis/activity_checker.json +110 -0
- iwa/plugins/olas/contracts/abis/mech.json +740 -0
- iwa/plugins/olas/contracts/abis/mech_marketplace.json +1293 -0
- iwa/plugins/olas/contracts/abis/mech_new.json +954 -0
- iwa/plugins/olas/contracts/abis/service_manager.json +1382 -0
- iwa/plugins/olas/contracts/abis/service_registry.json +1909 -0
- iwa/plugins/olas/contracts/abis/staking.json +1400 -0
- iwa/plugins/olas/contracts/abis/staking_token.json +1274 -0
- iwa/plugins/olas/contracts/mech.py +30 -2
- iwa/plugins/olas/plugin.py +2 -2
- iwa/plugins/olas/tests/test_plugin_full.py +3 -3
- iwa/plugins/olas/tests/test_staking_integration.py +2 -2
- iwa/tools/__init__.py +1 -0
- iwa/tools/check_profile.py +6 -5
- iwa/tools/list_contracts.py +136 -0
- iwa/tools/release.py +9 -3
- iwa/tools/reset_env.py +2 -2
- iwa/tools/reset_tenderly.py +26 -24
- iwa/tools/wallet_check.py +150 -0
- iwa/web/dependencies.py +4 -4
- iwa/web/routers/state.py +1 -0
- iwa/web/static/app.js +3096 -0
- iwa/web/static/index.html +543 -0
- iwa/web/static/style.css +1443 -0
- iwa/web/tests/test_web_endpoints.py +3 -2
- iwa/web/tests/test_web_swap_coverage.py +156 -0
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/METADATA +6 -3
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/RECORD +64 -44
- iwa-0.0.1a4.dist-info/entry_points.txt +6 -0
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/top_level.txt +0 -1
- tests/test_chain.py +1 -1
- tests/test_chain_interface_coverage.py +92 -0
- tests/test_contract.py +2 -0
- tests/test_keys.py +58 -15
- tests/test_migration.py +52 -0
- tests/test_mnemonic.py +1 -1
- tests/test_pricing.py +7 -7
- tests/test_safe_coverage.py +1 -1
- tests/test_safe_service.py +3 -3
- tests/test_staking_router.py +13 -1
- tools/verify_drain.py +1 -1
- conftest.py +0 -22
- iwa/core/settings.py +0 -95
- iwa-0.0.1a2.dist-info/entry_points.txt +0 -2
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/WHEEL +0 -0
- {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/licenses/LICENSE +0 -0
iwa/core/models.py
CHANGED
|
@@ -14,6 +14,20 @@ from iwa.core.types import EthereumAddress # noqa: F401 - re-exported for backw
|
|
|
14
14
|
from iwa.core.utils import singleton
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
class EncryptedData(BaseModel):
|
|
18
|
+
"""Encrypted data structure with explicit KDF parameters."""
|
|
19
|
+
|
|
20
|
+
kdf: str = "scrypt"
|
|
21
|
+
kdf_salt: str
|
|
22
|
+
kdf_n: int = 16384 # 2**14
|
|
23
|
+
kdf_r: int = 8
|
|
24
|
+
kdf_p: int = 1
|
|
25
|
+
kdf_len: int = 32
|
|
26
|
+
cipher: str = "aesgcm"
|
|
27
|
+
nonce: str
|
|
28
|
+
ciphertext: str
|
|
29
|
+
|
|
30
|
+
|
|
17
31
|
class StoredAccount(BaseModel):
|
|
18
32
|
"""StoredAccount representing an EOA or contract account."""
|
|
19
33
|
|
|
@@ -32,12 +46,6 @@ class StoredSafeAccount(StoredAccount):
|
|
|
32
46
|
class CoreConfig(BaseModel):
|
|
33
47
|
"""Core configuration settings."""
|
|
34
48
|
|
|
35
|
-
manual_claim_enabled: bool = Field(
|
|
36
|
-
default=False, description="Enable manual claiming of rewards"
|
|
37
|
-
)
|
|
38
|
-
request_activity_alert_enabled: bool = Field(
|
|
39
|
-
default=True, description="Enable alerts for suspicious activity"
|
|
40
|
-
)
|
|
41
49
|
whitelist: Dict[str, EthereumAddress] = Field(
|
|
42
50
|
default_factory=dict, description="Address whitelist for security"
|
|
43
51
|
)
|
|
@@ -45,6 +53,20 @@ class CoreConfig(BaseModel):
|
|
|
45
53
|
default_factory=dict, description="Custom token definitions per chain"
|
|
46
54
|
)
|
|
47
55
|
|
|
56
|
+
# Web UI Configuration
|
|
57
|
+
web_enabled: bool = Field(default=False, description="Enable Web UI")
|
|
58
|
+
web_port: int = Field(default=8080, description="Web UI port")
|
|
59
|
+
|
|
60
|
+
# IPFS Configuration
|
|
61
|
+
ipfs_api_url: str = Field(default="http://localhost:5001", description="IPFS API URL")
|
|
62
|
+
|
|
63
|
+
# Tenderly Configuration
|
|
64
|
+
tenderly_profile: int = Field(default=1, description="Tenderly profile ID (1, 2, 3)")
|
|
65
|
+
tenderly_native_funds: float = Field(
|
|
66
|
+
default=1000.0, description="Native ETH amount for vNet funding"
|
|
67
|
+
)
|
|
68
|
+
tenderly_olas_funds: float = Field(default=100000.0, description="OLAS amount for vNet funding")
|
|
69
|
+
|
|
48
70
|
|
|
49
71
|
T = TypeVar("T", bound="StorableModel")
|
|
50
72
|
|
iwa/core/pricing.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import Dict, Optional
|
|
|
7
7
|
import requests
|
|
8
8
|
from loguru import logger
|
|
9
9
|
|
|
10
|
-
from iwa.core.
|
|
10
|
+
from iwa.core.secrets import secrets
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class PriceService:
|
|
@@ -17,12 +17,12 @@ class PriceService:
|
|
|
17
17
|
|
|
18
18
|
def __init__(self, cache_ttl_minutes: int = 5):
|
|
19
19
|
"""Initialize PriceService."""
|
|
20
|
-
self.
|
|
20
|
+
self.secrets = secrets
|
|
21
21
|
self.cache: Dict[str, Dict] = {} # {id_currency: {"price": float, "timestamp": datetime}}
|
|
22
22
|
self.cache_ttl = timedelta(minutes=cache_ttl_minutes)
|
|
23
23
|
self.api_key = (
|
|
24
|
-
self.
|
|
25
|
-
if self.
|
|
24
|
+
self.secrets.coingecko_api_key.get_secret_value()
|
|
25
|
+
if self.secrets.coingecko_api_key
|
|
26
26
|
else None
|
|
27
27
|
)
|
|
28
28
|
|
iwa/core/secrets.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Secrets module - loads sensitive values from environment variables.
|
|
2
|
+
|
|
3
|
+
Secrets are loaded from:
|
|
4
|
+
1. Environment variables (injected by docker-compose env_file in production)
|
|
5
|
+
2. secrets.env file at project root (for local development)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import SecretStr, model_validator
|
|
12
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
13
|
+
|
|
14
|
+
# secrets.env is at project root (not in data/)
|
|
15
|
+
SECRETS_FILE = Path("secrets.env")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Secrets(BaseSettings):
|
|
19
|
+
"""Application Secrets loaded from environment variables.
|
|
20
|
+
|
|
21
|
+
In production, these are injected via docker-compose:
|
|
22
|
+
env_file:
|
|
23
|
+
- ./secrets.env
|
|
24
|
+
|
|
25
|
+
For local development, secrets are loaded from secrets.env at project root.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Testing mode - when True, uses Tenderly test RPCs; when False, uses production RPCs
|
|
29
|
+
testing: bool = False
|
|
30
|
+
|
|
31
|
+
# RPC endpoints
|
|
32
|
+
# When testing=True, these get overwritten with *_test_rpc values
|
|
33
|
+
gnosis_rpc: Optional[SecretStr] = None
|
|
34
|
+
base_rpc: Optional[SecretStr] = None
|
|
35
|
+
ethereum_rpc: Optional[SecretStr] = None
|
|
36
|
+
|
|
37
|
+
# Test RPCs (Tenderly)
|
|
38
|
+
gnosis_test_rpc: Optional[SecretStr] = None
|
|
39
|
+
ethereum_test_rpc: Optional[SecretStr] = None
|
|
40
|
+
base_test_rpc: Optional[SecretStr] = None
|
|
41
|
+
|
|
42
|
+
coingecko_api_key: Optional[SecretStr] = None
|
|
43
|
+
wallet_password: Optional[SecretStr] = None
|
|
44
|
+
|
|
45
|
+
webui_password: Optional[SecretStr] = None
|
|
46
|
+
|
|
47
|
+
# Load from environment AND secrets.env file (for local dev)
|
|
48
|
+
model_config = SettingsConfigDict(
|
|
49
|
+
env_file=str(SECRETS_FILE) if SECRETS_FILE.exists() else None,
|
|
50
|
+
env_file_encoding="utf-8",
|
|
51
|
+
extra="ignore",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@model_validator(mode="after")
|
|
55
|
+
def load_tenderly_profile_credentials(self) -> "Secrets":
|
|
56
|
+
"""Load Tenderly credentials based on the selected profile."""
|
|
57
|
+
# Note: Logic moved to dynamic loading in tools/reset_tenderly.py
|
|
58
|
+
# using Config().core.tenderly_profile
|
|
59
|
+
|
|
60
|
+
# When in testing mode, override RPCs with test RPCs (Tenderly)
|
|
61
|
+
if self.testing:
|
|
62
|
+
if self.gnosis_test_rpc:
|
|
63
|
+
self.gnosis_rpc = self.gnosis_test_rpc
|
|
64
|
+
if self.ethereum_test_rpc:
|
|
65
|
+
self.ethereum_rpc = self.ethereum_test_rpc
|
|
66
|
+
if self.base_test_rpc:
|
|
67
|
+
self.base_rpc = self.base_test_rpc
|
|
68
|
+
|
|
69
|
+
# Convert empty webui_password to None (no auth required)
|
|
70
|
+
if self.webui_password and not self.webui_password.get_secret_value():
|
|
71
|
+
self.webui_password = None
|
|
72
|
+
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Global secrets instance
|
|
77
|
+
secrets = Secrets()
|
iwa/core/services/safe.py
CHANGED
|
@@ -11,7 +11,7 @@ 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.
|
|
14
|
+
from iwa.core.secrets import secrets
|
|
15
15
|
from iwa.core.utils import (
|
|
16
16
|
get_safe_master_copy_address,
|
|
17
17
|
get_safe_proxy_factory_address,
|
|
@@ -99,7 +99,7 @@ class SafeService:
|
|
|
99
99
|
return owner_addresses
|
|
100
100
|
|
|
101
101
|
def _get_ethereum_client(self, chain_name: str) -> EthereumClient:
|
|
102
|
-
rpc_secret = getattr(
|
|
102
|
+
rpc_secret = getattr(secrets, f"{chain_name}_rpc")
|
|
103
103
|
return EthereumClient(rpc_secret.get_secret_value())
|
|
104
104
|
|
|
105
105
|
def _deploy_safe_contract(
|
|
@@ -248,7 +248,7 @@ class SafeService:
|
|
|
248
248
|
continue
|
|
249
249
|
|
|
250
250
|
for chain in account.chains:
|
|
251
|
-
rpc_secret = getattr(
|
|
251
|
+
rpc_secret = getattr(secrets, f"{chain}_rpc")
|
|
252
252
|
ethereum_client = EthereumClient(rpc_secret.get_secret_value())
|
|
253
253
|
|
|
254
254
|
code = ethereum_client.w3.eth.get_code(account.address)
|
iwa/core/utils.py
CHANGED
|
@@ -43,10 +43,15 @@ def configure_logger():
|
|
|
43
43
|
if hasattr(configure_logger, "configured"):
|
|
44
44
|
return logger
|
|
45
45
|
|
|
46
|
+
from iwa.core.constants import DATA_DIR
|
|
47
|
+
|
|
46
48
|
logger.remove()
|
|
47
49
|
|
|
50
|
+
# Ensure data directory exists
|
|
51
|
+
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
48
53
|
logger.add(
|
|
49
|
-
"iwa.log",
|
|
54
|
+
DATA_DIR / "iwa.log",
|
|
50
55
|
rotation="10 MB",
|
|
51
56
|
level="INFO",
|
|
52
57
|
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}",
|
iwa/core/wallet.py
CHANGED
|
@@ -29,6 +29,10 @@ class Wallet:
|
|
|
29
29
|
def __init__(self):
|
|
30
30
|
"""Initialize wallet."""
|
|
31
31
|
self.key_storage = KeyStorage()
|
|
32
|
+
|
|
33
|
+
# Display mnemonic if a new master account was just created
|
|
34
|
+
self.key_storage.display_pending_mnemonic()
|
|
35
|
+
|
|
32
36
|
self.account_service = AccountService(self.key_storage)
|
|
33
37
|
self.balance_service = BalanceService(self.key_storage, self.account_service)
|
|
34
38
|
self.safe_service = SafeService(self.key_storage, self.account_service)
|
iwa/plugins/gnosis/safe.py
CHANGED
|
@@ -8,7 +8,7 @@ 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.
|
|
11
|
+
from iwa.core.secrets import secrets
|
|
12
12
|
from iwa.core.utils import configure_logger
|
|
13
13
|
|
|
14
14
|
logger = configure_logger()
|
|
@@ -28,7 +28,7 @@ class SafeMultisig:
|
|
|
28
28
|
if chain_name.lower() not in normalized_chains:
|
|
29
29
|
raise ValueError(f"Safe account is not deployed on chain: {chain_name}")
|
|
30
30
|
|
|
31
|
-
rpc_secret = getattr(
|
|
31
|
+
rpc_secret = getattr(secrets, f"{chain_name.lower()}_rpc")
|
|
32
32
|
ethereum_client = EthereumClient(rpc_secret.get_secret_value())
|
|
33
33
|
self.multisig = Safe(safe_account.address, ethereum_client)
|
|
34
34
|
self.ethereum_client = ethereum_client
|
|
@@ -11,7 +11,7 @@ from iwa.plugins.gnosis.safe import SafeMultisig
|
|
|
11
11
|
@pytest.fixture
|
|
12
12
|
def mock_settings():
|
|
13
13
|
"""Mock settings."""
|
|
14
|
-
with patch("iwa.plugins.gnosis.safe.
|
|
14
|
+
with patch("iwa.plugins.gnosis.safe.secrets") as mock:
|
|
15
15
|
mock.gnosis_rpc.get_secret_value.return_value = "http://rpc"
|
|
16
16
|
yield mock
|
|
17
17
|
|
iwa/plugins/olas/constants.py
CHANGED
|
@@ -100,6 +100,14 @@ OLAS_TRADER_STAKING_CONTRACTS: Dict[str, Dict[str, EthereumAddress]] = {
|
|
|
100
100
|
"Expert 16 (10k OLAS)": EthereumAddress("0x6c65430515c70a3f5E62107CC301685B7D46f991"),
|
|
101
101
|
"Expert 17 (10k OLAS)": EthereumAddress("0x1430107A785C3A36a0C1FC0ee09B9631e2E72aFf"),
|
|
102
102
|
"Expert 18 (10k OLAS)": EthereumAddress("0x041e679d04Fc0D4f75Eb937Dea729Df09a58e454"),
|
|
103
|
+
"Expert 3 MM (1k OLAS)": EthereumAddress("0x75eeca6207be98cac3fde8a20ecd7b01e50b3472"),
|
|
104
|
+
"Expert 4 MM (2k OLAS)": EthereumAddress("0x9c7f6103e3a72e4d1805b9c683ea5b370ec1a99f"),
|
|
105
|
+
"Expert 5 MM (10k OLAS)": EthereumAddress("0xcdC603e0Ee55Aae92519f9770f214b2Be4967f7d"),
|
|
106
|
+
"Expert 6 MM (10k OLAS)": EthereumAddress("0x22d6cd3d587d8391c3aae83a783f26c67ab54a85"),
|
|
107
|
+
"Expert 7 MM (10k OLAS)": EthereumAddress("0xaaecdf4d0cbd6ca0622892ac6044472f3912a5f3"),
|
|
108
|
+
"Expert 8 MM (10k OLAS)": EthereumAddress("0x168aed532a0cd8868c22fc77937af78b363652b1"),
|
|
109
|
+
"Expert 9 MM (10k OLAS)": EthereumAddress("0xdda9cd214f12e7c2d58e871404a0a3b1177065c8"),
|
|
110
|
+
"Expert 10 MM (10k OLAS)": EthereumAddress("0x53a38655b4e659ef4c7f88a26fbf5c67932c7156"),
|
|
103
111
|
},
|
|
104
112
|
"ethereum": {},
|
|
105
113
|
"base": {},
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"inputs":[
|
|
4
|
+
{
|
|
5
|
+
"internalType":"address",
|
|
6
|
+
"name":"_mechMarketplace",
|
|
7
|
+
"type":"address"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"internalType":"uint256",
|
|
11
|
+
"name":"_livenessRatio",
|
|
12
|
+
"type":"uint256"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"stateMutability":"nonpayable",
|
|
16
|
+
"type":"constructor"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"inputs":[
|
|
20
|
+
|
|
21
|
+
],
|
|
22
|
+
"name":"ZeroAddress",
|
|
23
|
+
"type":"error"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"inputs":[
|
|
27
|
+
|
|
28
|
+
],
|
|
29
|
+
"name":"ZeroValue",
|
|
30
|
+
"type":"error"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"inputs":[
|
|
34
|
+
{
|
|
35
|
+
"internalType":"address",
|
|
36
|
+
"name":"multisig",
|
|
37
|
+
"type":"address"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"name":"getMultisigNonces",
|
|
41
|
+
"outputs":[
|
|
42
|
+
{
|
|
43
|
+
"internalType":"uint256[]",
|
|
44
|
+
"name":"nonces",
|
|
45
|
+
"type":"uint256[]"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"stateMutability":"view",
|
|
49
|
+
"type":"function"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"inputs":[
|
|
53
|
+
{
|
|
54
|
+
"internalType":"uint256[]",
|
|
55
|
+
"name":"curNonces",
|
|
56
|
+
"type":"uint256[]"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"internalType":"uint256[]",
|
|
60
|
+
"name":"lastNonces",
|
|
61
|
+
"type":"uint256[]"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"internalType":"uint256",
|
|
65
|
+
"name":"ts",
|
|
66
|
+
"type":"uint256"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"name":"isRatioPass",
|
|
70
|
+
"outputs":[
|
|
71
|
+
{
|
|
72
|
+
"internalType":"bool",
|
|
73
|
+
"name":"ratioPass",
|
|
74
|
+
"type":"bool"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"stateMutability":"view",
|
|
78
|
+
"type":"function"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"inputs":[
|
|
82
|
+
|
|
83
|
+
],
|
|
84
|
+
"name":"livenessRatio",
|
|
85
|
+
"outputs":[
|
|
86
|
+
{
|
|
87
|
+
"internalType":"uint256",
|
|
88
|
+
"name":"",
|
|
89
|
+
"type":"uint256"
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"stateMutability":"view",
|
|
93
|
+
"type":"function"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"inputs":[
|
|
97
|
+
|
|
98
|
+
],
|
|
99
|
+
"name":"mechMarketplace",
|
|
100
|
+
"outputs":[
|
|
101
|
+
{
|
|
102
|
+
"internalType":"address",
|
|
103
|
+
"name":"",
|
|
104
|
+
"type":"address"
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"stateMutability":"view",
|
|
108
|
+
"type":"function"
|
|
109
|
+
}
|
|
110
|
+
]
|