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.
Files changed (67) hide show
  1. iwa/core/chain/interface.py +51 -61
  2. iwa/core/chain/models.py +7 -7
  3. iwa/core/chain/rate_limiter.py +21 -10
  4. iwa/core/cli.py +27 -2
  5. iwa/core/constants.py +6 -5
  6. iwa/core/contracts/abis/erc20.json +930 -0
  7. iwa/core/contracts/abis/multisend.json +24 -0
  8. iwa/core/contracts/abis/multisend_call_only.json +17 -0
  9. iwa/core/contracts/contract.py +16 -4
  10. iwa/core/ipfs.py +149 -0
  11. iwa/core/keys.py +259 -29
  12. iwa/core/mnemonic.py +3 -13
  13. iwa/core/models.py +28 -6
  14. iwa/core/pricing.py +4 -4
  15. iwa/core/secrets.py +77 -0
  16. iwa/core/services/safe.py +3 -3
  17. iwa/core/utils.py +6 -1
  18. iwa/core/wallet.py +4 -0
  19. iwa/plugins/gnosis/safe.py +2 -2
  20. iwa/plugins/gnosis/tests/test_safe.py +1 -1
  21. iwa/plugins/olas/constants.py +8 -0
  22. iwa/plugins/olas/contracts/abis/activity_checker.json +110 -0
  23. iwa/plugins/olas/contracts/abis/mech.json +740 -0
  24. iwa/plugins/olas/contracts/abis/mech_marketplace.json +1293 -0
  25. iwa/plugins/olas/contracts/abis/mech_new.json +954 -0
  26. iwa/plugins/olas/contracts/abis/service_manager.json +1382 -0
  27. iwa/plugins/olas/contracts/abis/service_registry.json +1909 -0
  28. iwa/plugins/olas/contracts/abis/staking.json +1400 -0
  29. iwa/plugins/olas/contracts/abis/staking_token.json +1274 -0
  30. iwa/plugins/olas/contracts/mech.py +30 -2
  31. iwa/plugins/olas/plugin.py +2 -2
  32. iwa/plugins/olas/tests/test_plugin_full.py +3 -3
  33. iwa/plugins/olas/tests/test_staking_integration.py +2 -2
  34. iwa/tools/__init__.py +1 -0
  35. iwa/tools/check_profile.py +6 -5
  36. iwa/tools/list_contracts.py +136 -0
  37. iwa/tools/release.py +9 -3
  38. iwa/tools/reset_env.py +2 -2
  39. iwa/tools/reset_tenderly.py +26 -24
  40. iwa/tools/wallet_check.py +150 -0
  41. iwa/web/dependencies.py +4 -4
  42. iwa/web/routers/state.py +1 -0
  43. iwa/web/static/app.js +3096 -0
  44. iwa/web/static/index.html +543 -0
  45. iwa/web/static/style.css +1443 -0
  46. iwa/web/tests/test_web_endpoints.py +3 -2
  47. iwa/web/tests/test_web_swap_coverage.py +156 -0
  48. {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/METADATA +6 -3
  49. {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/RECORD +64 -44
  50. iwa-0.0.1a4.dist-info/entry_points.txt +6 -0
  51. {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/top_level.txt +0 -1
  52. tests/test_chain.py +1 -1
  53. tests/test_chain_interface_coverage.py +92 -0
  54. tests/test_contract.py +2 -0
  55. tests/test_keys.py +58 -15
  56. tests/test_migration.py +52 -0
  57. tests/test_mnemonic.py +1 -1
  58. tests/test_pricing.py +7 -7
  59. tests/test_safe_coverage.py +1 -1
  60. tests/test_safe_service.py +3 -3
  61. tests/test_staking_router.py +13 -1
  62. tools/verify_drain.py +1 -1
  63. conftest.py +0 -22
  64. iwa/core/settings.py +0 -95
  65. iwa-0.0.1a2.dist-info/entry_points.txt +0 -2
  66. {iwa-0.0.1a2.dist-info → iwa-0.0.1a4.dist-info}/WHEEL +0 -0
  67. {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.settings import settings
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.settings = settings
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.settings.coingecko_api_key.get_secret_value()
25
- if self.settings.coingecko_api_key
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.settings import settings
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(settings, f"{chain_name}_rpc")
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(settings, f"{chain}_rpc")
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)
@@ -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.settings import settings
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(settings, f"{chain_name.lower()}_rpc")
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.settings") as mock:
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
 
@@ -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
+ ]