iwa 0.0.1a2__py3-none-any.whl → 0.0.1a3__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 (52) 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/contract.py +16 -4
  7. iwa/core/ipfs.py +149 -0
  8. iwa/core/keys.py +259 -29
  9. iwa/core/mnemonic.py +3 -13
  10. iwa/core/models.py +28 -6
  11. iwa/core/pricing.py +4 -4
  12. iwa/core/secrets.py +77 -0
  13. iwa/core/services/safe.py +3 -3
  14. iwa/core/utils.py +6 -1
  15. iwa/core/wallet.py +4 -0
  16. iwa/plugins/gnosis/safe.py +2 -2
  17. iwa/plugins/gnosis/tests/test_safe.py +1 -1
  18. iwa/plugins/olas/constants.py +8 -0
  19. iwa/plugins/olas/contracts/mech.py +30 -2
  20. iwa/plugins/olas/plugin.py +2 -2
  21. iwa/plugins/olas/tests/test_plugin_full.py +3 -3
  22. iwa/plugins/olas/tests/test_staking_integration.py +2 -2
  23. iwa/tools/__init__.py +1 -0
  24. iwa/tools/check_profile.py +6 -5
  25. iwa/tools/list_contracts.py +136 -0
  26. iwa/tools/release.py +9 -3
  27. iwa/tools/reset_env.py +2 -2
  28. iwa/tools/reset_tenderly.py +26 -24
  29. iwa/tools/wallet_check.py +150 -0
  30. iwa/web/dependencies.py +4 -4
  31. iwa/web/routers/state.py +1 -0
  32. iwa/web/tests/test_web_endpoints.py +3 -2
  33. iwa/web/tests/test_web_swap_coverage.py +156 -0
  34. {iwa-0.0.1a2.dist-info → iwa-0.0.1a3.dist-info}/METADATA +6 -3
  35. {iwa-0.0.1a2.dist-info → iwa-0.0.1a3.dist-info}/RECORD +50 -43
  36. iwa-0.0.1a3.dist-info/entry_points.txt +6 -0
  37. tests/test_chain.py +1 -1
  38. tests/test_chain_interface_coverage.py +92 -0
  39. tests/test_contract.py +2 -0
  40. tests/test_keys.py +58 -15
  41. tests/test_migration.py +52 -0
  42. tests/test_mnemonic.py +1 -1
  43. tests/test_pricing.py +7 -7
  44. tests/test_safe_coverage.py +1 -1
  45. tests/test_safe_service.py +3 -3
  46. tests/test_staking_router.py +13 -1
  47. tools/verify_drain.py +1 -1
  48. iwa/core/settings.py +0 -95
  49. iwa-0.0.1a2.dist-info/entry_points.txt +0 -2
  50. {iwa-0.0.1a2.dist-info → iwa-0.0.1a3.dist-info}/WHEEL +0 -0
  51. {iwa-0.0.1a2.dist-info → iwa-0.0.1a3.dist-info}/licenses/LICENSE +0 -0
  52. {iwa-0.0.1a2.dist-info → iwa-0.0.1a3.dist-info}/top_level.txt +0 -0
tests/test_keys.py CHANGED
@@ -11,8 +11,8 @@ from iwa.core.keys import EncryptedAccount, KeyStorage, StoredSafeAccount
11
11
 
12
12
  @pytest.fixture
13
13
  def mock_secrets():
14
- """Mock settings to provide test password."""
15
- with patch("iwa.core.keys.settings") as mock:
14
+ """Mock secrets to provide test password."""
15
+ with patch("iwa.core.keys.secrets") as mock:
16
16
  mock.wallet_password.get_secret_value.return_value = "test_password"
17
17
  mock.gnosis_rpc.get_secret_value.return_value = "http://rpc"
18
18
  yield mock
@@ -51,7 +51,12 @@ def mock_account():
51
51
  )
52
52
 
53
53
  def create_side_effect():
54
+ # Skip the first address if it's reserved for the master account
55
+ # (to avoid overwriting master if create_account is called immediately)
54
56
  addr = next(addresses)
57
+ if addr == "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4":
58
+ addr = next(addresses)
59
+
55
60
  m = MagicMock()
56
61
  m.key.hex.return_value = f"0xPrivateKey{addr}"
57
62
  m.address = addr
@@ -60,19 +65,49 @@ def mock_account():
60
65
  mock.create.side_effect = create_side_effect
61
66
 
62
67
  def from_key_side_effect(private_key):
63
- if isinstance(private_key, str) and "0xPrivateKey" in private_key:
68
+ # 1. Handle the master private key
69
+ if (
70
+ private_key == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" # gitleaks:allow
71
+ ):
72
+ addr = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
73
+ # 2. Handle our mock key format
74
+ elif isinstance(private_key, str) and private_key.startswith("0xPrivateKey"):
64
75
  addr = private_key.replace("0xPrivateKey", "")
76
+ # 3. Default fallback
65
77
  else:
66
- addr = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
78
+ addr = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"
79
+
67
80
  m = MagicMock()
68
81
  m.address = addr
69
- m.key = private_key.encode() if isinstance(private_key, str) else private_key
70
82
  return m
71
83
 
72
84
  mock.from_key.side_effect = from_key_side_effect
73
85
  yield mock
74
86
 
75
87
 
88
+ @pytest.fixture
89
+ def mock_bip_utils():
90
+ """Mock BIP-utils for mnemonic derivation."""
91
+ with (
92
+ patch("iwa.core.keys.Bip39MnemonicGenerator") as mock_gen,
93
+ patch("iwa.core.keys.Bip39SeedGenerator") as mock_seed,
94
+ patch("iwa.core.keys.Bip44") as mock_bip44,
95
+ ):
96
+ # Generator
97
+ mock_gen.return_value.FromWordsNumber.return_value.ToStr.return_value = (
98
+ "word " * 23 + "word"
99
+ )
100
+
101
+ # Derivation chain
102
+ mock_bip44.FromSeed.return_value.Purpose.return_value.Coin.return_value.Account.return_value.Change.return_value.AddressIndex.return_value.PrivateKey.return_value.Raw.return_value.ToHex.return_value = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
103
+
104
+ yield {
105
+ "gen": mock_gen,
106
+ "seed": mock_seed,
107
+ "bip44": mock_bip44,
108
+ }
109
+
110
+
76
111
  # --- EncryptedAccount tests (no file I/O needed) ---
77
112
 
78
113
 
@@ -109,7 +144,9 @@ def test_encrypted_account_decrypt_private_key(mock_scrypt, mock_aesgcm, mock_se
109
144
  # --- KeyStorage tests using tmp_path ---
110
145
 
111
146
 
112
- def test_keystorage_init_new(tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt):
147
+ def test_keystorage_init_new(
148
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
149
+ ):
113
150
  """Test initialization of new KeyStorage creates master account."""
114
151
  wallet_path = tmp_path / "wallet.json"
115
152
  storage = KeyStorage(wallet_path, password="test_password")
@@ -140,7 +177,9 @@ def test_keystorage_init_existing(tmp_path, mock_secrets):
140
177
  assert storage.get_account("master") is not None
141
178
 
142
179
 
143
- def test_keystorage_init_corrupted(tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt):
180
+ def test_keystorage_init_corrupted(
181
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
182
+ ):
144
183
  """Test handling of corrupted wallet file."""
145
184
  wallet_path = tmp_path / "wallet.json"
146
185
  wallet_path.write_text("{invalid json")
@@ -153,7 +192,9 @@ def test_keystorage_init_corrupted(tmp_path, mock_secrets, mock_account, mock_ae
153
192
  mock_logger.error.assert_called()
154
193
 
155
194
 
156
- def test_keystorage_save(tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt):
195
+ def test_keystorage_save(
196
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
197
+ ):
157
198
  """Test saving wallet to file."""
158
199
  wallet_path = tmp_path / "wallet.json"
159
200
  storage = KeyStorage(wallet_path, password="test_password")
@@ -165,7 +206,9 @@ def test_keystorage_save(tmp_path, mock_secrets, mock_account, mock_aesgcm, mock
165
206
  assert "accounts" in data
166
207
 
167
208
 
168
- def test_keystorage_create_account(tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt):
209
+ def test_keystorage_create_account(
210
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
211
+ ):
169
212
  """Test creating additional accounts."""
170
213
  wallet_path = tmp_path / "wallet.json"
171
214
  storage = KeyStorage(wallet_path, password="test_password")
@@ -177,7 +220,7 @@ def test_keystorage_create_account(tmp_path, mock_secrets, mock_account, mock_ae
177
220
 
178
221
 
179
222
  def test_keystorage_create_account_duplicate_tag(
180
- tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
223
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
181
224
  ):
182
225
  """Test creating account with duplicate tag raises error."""
183
226
  wallet_path = tmp_path / "wallet.json"
@@ -263,7 +306,7 @@ def test_keystorage_get_account(tmp_path, mock_secrets, mock_account, mock_aesgc
263
306
 
264
307
 
265
308
  def test_keystorage_get_tag_by_address(
266
- tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
309
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
267
310
  ):
268
311
  """Test getting tag by address."""
269
312
  wallet_path = tmp_path / "wallet.json"
@@ -315,7 +358,7 @@ def test_keystorage_master_account_fallback(tmp_path, mock_secrets):
315
358
 
316
359
 
317
360
  def test_keystorage_master_account_success(
318
- tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
361
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
319
362
  ):
320
363
  """Test master_account property returns master."""
321
364
  wallet_path = tmp_path / "wallet.json"
@@ -326,7 +369,7 @@ def test_keystorage_master_account_success(
326
369
 
327
370
 
328
371
  def test_keystorage_create_account_default_tag(
329
- tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
372
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
330
373
  ):
331
374
  """Test creating account with custom tag."""
332
375
  wallet_path = tmp_path / "wallet.json"
@@ -338,7 +381,7 @@ def test_keystorage_create_account_default_tag(
338
381
 
339
382
 
340
383
  def test_keystorage_remove_account_not_found(
341
- tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
384
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
342
385
  ):
343
386
  """Test removing non-existent account doesn't raise."""
344
387
  wallet_path = tmp_path / "wallet.json"
@@ -349,7 +392,7 @@ def test_keystorage_remove_account_not_found(
349
392
 
350
393
 
351
394
  def test_keystorage_get_account_auto_load_safe(
352
- tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt
395
+ tmp_path, mock_secrets, mock_account, mock_aesgcm, mock_scrypt, mock_bip_utils
353
396
  ):
354
397
  """Test getting StoredSafeAccount."""
355
398
  wallet_path = tmp_path / "wallet.json"
@@ -0,0 +1,52 @@
1
+ from iwa.core.keys import EncryptedAccount
2
+ from iwa.core.models import EncryptedData
3
+
4
+
5
+ def test_legacy_account_migration():
6
+ """Test that a legacy account dict is correctly upgraded."""
7
+ legacy_data = {
8
+ "address": "0xCD0184079fb3Bd15ddE5Bcbc248d81a893482280",
9
+ "tag": "master",
10
+ "salt": "FW19o5XPRxinb18TuQroxg==",
11
+ "nonce": "s1K+wNb24JY2BGeH",
12
+ "ciphertext": "i5BYYBjR7jiSW6OTNFuXJ03FaNQjlgbXMSo8uEgz71jtWQ24MAOZqJYckEjEqPFv4GX/Dwi5QVZFWV+tqECNh7AK0zY3/po4FDL7DU3s5ms=",
13
+ }
14
+
15
+ # Load into model
16
+ account = EncryptedAccount(**legacy_data)
17
+
18
+ # Verify legacy fields are mapped
19
+ assert account.kdf_salt == legacy_data["salt"]
20
+ assert account.nonce == legacy_data["nonce"]
21
+ assert account.ciphertext == legacy_data["ciphertext"]
22
+
23
+ # Verify default KDF params are set
24
+ assert account.kdf == "scrypt"
25
+ assert account.kdf_n == 16384
26
+ assert account.kdf_r == 8
27
+ assert account.kdf_p == 1
28
+ assert account.cipher == "aesgcm"
29
+
30
+ # Verify it is an instance of EncryptedData (implicitly checking inheritance)
31
+ assert isinstance(account, EncryptedData)
32
+
33
+
34
+ def test_new_account_format():
35
+ """Test that a new account is created with explicit params."""
36
+ # We use a mocked password and private key
37
+ # This just tests structure, not actual crypto (covered by other tests)
38
+ account = EncryptedAccount.encrypt_private_key(
39
+ "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
40
+ "password",
41
+ tag="new_test",
42
+ )
43
+
44
+ data = account.model_dump()
45
+
46
+ assert data["kdf"] == "scrypt"
47
+ assert "kdf_salt" in data
48
+ assert data["kdf_n"] == 16384
49
+ assert data["cipher"] == "aesgcm"
50
+
51
+ # Ensure legacy salt is NOT present (avoid duplication)
52
+ assert "salt" not in data
tests/test_mnemonic.py CHANGED
@@ -83,7 +83,7 @@ def test_encrypted_mnemonic_decrypt_unsupported_kdf():
83
83
 
84
84
  def test_encrypted_mnemonic_decrypt_unsupported_cipher():
85
85
  em = EncryptedMnemonic(
86
- cypher="unsupported", kdf_salt="salt", nonce="nonce", ciphertext="ciphertext"
86
+ cipher="unsupported", kdf_salt="salt", nonce="nonce", ciphertext="ciphertext"
87
87
  )
88
88
  with pytest.raises(ValueError, match="Unsupported cipher"):
89
89
  em.decrypt("password")
tests/test_pricing.py CHANGED
@@ -8,7 +8,7 @@ from iwa.core.pricing import PriceService
8
8
 
9
9
  @pytest.fixture
10
10
  def mock_secrets():
11
- with patch("iwa.core.pricing.settings") as mock:
11
+ with patch("iwa.core.pricing.secrets") as mock:
12
12
  mock.coingecko_api_key.get_secret_value.return_value = "test_api_key"
13
13
  yield mock
14
14
 
@@ -81,8 +81,8 @@ def test_get_token_price_key_not_found(price_service):
81
81
 
82
82
  def test_get_token_price_rate_limit():
83
83
  """Test rate limit (429) handling with retries."""
84
- with patch("iwa.core.pricing.settings") as mock_settings:
85
- mock_settings.coingecko_api_key = None
84
+ with patch("iwa.core.pricing.secrets") as mock_secrets:
85
+ mock_secrets.coingecko_api_key = None
86
86
 
87
87
  service = PriceService()
88
88
 
@@ -102,8 +102,8 @@ def test_get_token_price_rate_limit_then_success():
102
102
  """Test rate limit recovery on retry."""
103
103
  from unittest.mock import MagicMock
104
104
 
105
- with patch("iwa.core.pricing.settings") as mock_settings:
106
- mock_settings.coingecko_api_key = None
105
+ with patch("iwa.core.pricing.secrets") as mock_secrets:
106
+ mock_secrets.coingecko_api_key = None
107
107
 
108
108
  service = PriceService()
109
109
 
@@ -126,8 +126,8 @@ def test_get_token_price_rate_limit_then_success():
126
126
 
127
127
  def test_get_token_price_no_api_key():
128
128
  """Test getting price without API key."""
129
- with patch("iwa.core.pricing.settings") as mock_settings:
130
- mock_settings.coingecko_api_key = None
129
+ with patch("iwa.core.pricing.secrets") as mock_secrets:
130
+ mock_secrets.coingecko_api_key = None
131
131
 
132
132
  service = PriceService()
133
133
 
@@ -119,7 +119,7 @@ def test_redeploy_safes(safe_service, mock_deps):
119
119
 
120
120
  mock_deps["key_storage"].accounts = {"0xSafe1": account1}
121
121
 
122
- with patch("iwa.core.services.safe.settings") as mock_settings:
122
+ with patch("iwa.core.services.safe.secrets") as mock_settings:
123
123
  mock_settings.gnosis_rpc.get_secret_value.return_value = "http://rpc"
124
124
 
125
125
  with patch("iwa.core.services.safe.EthereumClient") as mock_eth_client:
@@ -53,12 +53,12 @@ def mock_dependencies():
53
53
  patch("iwa.core.services.safe.EthereumClient") as mock_client,
54
54
  patch("iwa.core.services.safe.Safe") as mock_safe,
55
55
  patch("iwa.core.services.safe.ProxyFactory") as mock_proxy_factory,
56
- patch("iwa.core.services.safe.settings") as mock_settings,
56
+ patch("iwa.core.services.safe.secrets") as mock_secrets,
57
57
  patch("iwa.core.services.safe.log_transaction") as mock_log,
58
58
  patch("iwa.core.services.safe.get_safe_master_copy_address") as mock_master,
59
59
  patch("iwa.core.services.safe.get_safe_proxy_factory_address") as mock_factory,
60
60
  ):
61
- mock_settings.gnosis_rpc.get_secret_value.return_value = "http://rpc"
61
+ mock_secrets.gnosis_rpc.get_secret_value.return_value = "http://rpc"
62
62
 
63
63
  # Setup Safe creation return
64
64
  mock_create_tx = MagicMock()
@@ -104,7 +104,7 @@ def mock_dependencies():
104
104
  "client": mock_client,
105
105
  "safe": mock_safe,
106
106
  "proxy_factory": mock_proxy_factory,
107
- "settings": mock_settings,
107
+ "secrets": mock_secrets,
108
108
  "log": mock_log,
109
109
  "master": mock_master,
110
110
  "factory": mock_factory,
@@ -1,6 +1,18 @@
1
1
  """Tests for staking.py router coverage."""
2
2
 
3
- from unittest.mock import MagicMock
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+
7
+
8
+ @pytest.fixture(autouse=True)
9
+ def mock_key_storage():
10
+ """Bypass KeyStorage security check."""
11
+ with (
12
+ patch("iwa.core.keys.KeyStorage.__init__", return_value=None),
13
+ patch("iwa.web.dependencies.wallet", MagicMock()),
14
+ ):
15
+ yield
4
16
 
5
17
 
6
18
  def test_check_availability_exception():
tools/verify_drain.py CHANGED
@@ -71,7 +71,7 @@ def verify_drain(): # noqa: C901, D103
71
71
  assert manager.olas_config is not None, "Olas config not initialized"
72
72
  service = manager.olas_config.get_service("gnosis", service_id)
73
73
  if not service:
74
- raise ValueError(f"Service {service_id} not found in config")
74
+ raise ValueError(f"Service {service_id} not found in config")
75
75
  safe_addr = service.multisig_address
76
76
  agent_addr = service.agent_address
77
77
 
iwa/core/settings.py DELETED
@@ -1,95 +0,0 @@
1
- """Configuration settings module."""
2
-
3
- import os
4
- from typing import Optional
5
-
6
- from dotenv import load_dotenv
7
- from pydantic import ConfigDict, SecretStr, model_validator
8
- from pydantic_settings import BaseSettings
9
-
10
- from iwa.core.constants import SECRETS_PATH
11
- from iwa.core.utils import singleton
12
-
13
-
14
- @singleton
15
- class Settings(BaseSettings):
16
- """Application Settings loaded from environment and secrets file."""
17
-
18
- # Testing mode - when True, uses Tenderly test RPCs; when False, uses production RPCs
19
- testing: bool = True
20
-
21
- # RPC endpoints (loaded from gnosis_rpc/ethereum_rpc/base_rpc in secrets.env)
22
- # When testing=True, these get overwritten with *_test_rpc values
23
- gnosis_rpc: Optional[SecretStr] = None
24
- base_rpc: Optional[SecretStr] = None
25
- ethereum_rpc: Optional[SecretStr] = None
26
-
27
- # Test RPCs
28
- gnosis_test_rpc: Optional[SecretStr] = None
29
- ethereum_test_rpc: Optional[SecretStr] = None
30
- base_test_rpc: Optional[SecretStr] = None
31
-
32
- gnosisscan_api_key: Optional[SecretStr] = None
33
- coingecko_api_key: Optional[SecretStr] = None
34
- wallet_password: Optional[SecretStr] = None
35
- security_word: Optional[SecretStr] = None
36
-
37
- # Tenderly profile (1 or 2) - determines which credentials to load
38
- tenderly_profile: int = 1
39
-
40
- # Tenderly credentials - loaded dynamically based on profile
41
- tenderly_account_slug: Optional[SecretStr] = None
42
- tenderly_project_slug: Optional[SecretStr] = None
43
- tenderly_access_key: Optional[SecretStr] = None
44
-
45
- # Tenderly funding configuration
46
- tenderly_native_funds: float = 1000.0
47
- tenderly_olas_funds: float = 100000.0
48
-
49
- web_enabled: bool = False
50
- web_port: int = 8080
51
- webui_password: Optional[SecretStr] = None
52
-
53
- model_config = ConfigDict(env_file=str(SECRETS_PATH), env_file_encoding="utf-8", extra="ignore")
54
-
55
- def __init__(self, **values):
56
- """Initialize Settings and load environment variables."""
57
- # Force load dotenv to ensure os.environ variables are set
58
- load_dotenv(SECRETS_PATH, override=True)
59
- super().__init__(**values)
60
-
61
- @model_validator(mode="after")
62
- def load_tenderly_profile_credentials(self) -> "Settings":
63
- """Load Tenderly credentials based on the selected profile."""
64
- profile = self.tenderly_profile
65
-
66
- # Load profile-specific credentials from environment
67
- account = os.getenv(f"tenderly_account_slug_{profile}")
68
- project = os.getenv(f"tenderly_project_slug_{profile}")
69
- access_key = os.getenv(f"tenderly_access_key_{profile}")
70
-
71
- if account:
72
- self.tenderly_account_slug = SecretStr(account)
73
- if project:
74
- self.tenderly_project_slug = SecretStr(project)
75
- if access_key:
76
- self.tenderly_access_key = SecretStr(access_key)
77
-
78
- # When in testing mode, override RPCs with test RPCs (Tenderly)
79
- if self.testing:
80
- if self.gnosis_test_rpc:
81
- self.gnosis_rpc = self.gnosis_test_rpc
82
- if self.ethereum_test_rpc:
83
- self.ethereum_rpc = self.ethereum_test_rpc
84
- if self.base_test_rpc:
85
- self.base_rpc = self.base_test_rpc
86
-
87
- # Convert empty webui_password to None (no auth required)
88
- if self.webui_password and not self.webui_password.get_secret_value():
89
- self.webui_password = None
90
-
91
- return self
92
-
93
-
94
- # Global settings instance
95
- settings = Settings()
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- iwa = iwa.core.cli:iwa_cli
File without changes