|
22 | 22 | from packaging.version import Version |
23 | 23 |
|
24 | 24 | from .client import BaseSafeClient, MockSafeClient, SafeClient, SafeTx, SafeTxConfirmation, SafeTxID |
| 25 | +from .config import SafeConfig |
25 | 26 | from .exceptions import ( |
26 | 27 | ApeSafeError, |
27 | 28 | NoLocalSigners, |
|
32 | 33 | ) |
33 | 34 | from .factory import SafeFactory |
34 | 35 | from .packages import PackageType |
| 36 | +from .types import SafeCacheData |
35 | 37 | from .utils import get_safe_tx_hash, order_by_signer |
36 | 38 |
|
37 | 39 | if TYPE_CHECKING: |
|
42 | 44 | class SafeContainer(AccountContainerAPI): |
43 | 45 | _accounts: dict[str, "SafeAccount"] = {} |
44 | 46 |
|
| 47 | + @property |
| 48 | + def config(self) -> SafeConfig: |
| 49 | + return cast(SafeConfig, self.config_manager["safe"]) |
| 50 | + |
45 | 51 | @property |
46 | 52 | def _account_files(self) -> Iterator[Path]: |
47 | | - yield from self.data_folder.glob("*.json") |
| 53 | + account_files = list(self.data_folder.glob("*.json")) |
| 54 | + |
| 55 | + # NOTE: Make sure these Safes exist in our local cache |
| 56 | + for required_safe, safe_cache_data in self.config.require.items(): |
| 57 | + # NOTE: If alias `required_safe` already exists, skip overwriting it |
| 58 | + if required_safe not in map(lambda p: p.stem, account_files): |
| 59 | + safe_cache_file = self.data_folder / f"{required_safe}.json" |
| 60 | + safe_cache_file.write_text(safe_cache_data.model_dump_json(), encoding="utf-8") |
| 61 | + account_files.append(safe_cache_file) |
| 62 | + |
| 63 | + yield from account_files |
48 | 64 |
|
49 | 65 | @property |
50 | 66 | def aliases(self) -> Iterator[str]: |
@@ -204,17 +220,17 @@ def alias(self) -> str: |
204 | 220 | return self.account_file_path.stem |
205 | 221 |
|
206 | 222 | @property |
207 | | - def account_file(self) -> dict: |
208 | | - return json.loads(self.account_file_path.read_text()) |
| 223 | + def account_file(self) -> SafeCacheData: |
| 224 | + return SafeCacheData.model_validate_json(self.account_file_path.read_text(encoding="utf-8")) |
209 | 225 |
|
210 | | - @property |
| 226 | + @cached_property |
211 | 227 | def address(self) -> AddressType: |
212 | 228 | try: |
213 | 229 | ecosystem = self.provider.network.ecosystem |
214 | 230 | except ProviderNotConnectedError: |
215 | 231 | ecosystem = self.network_manager.ethereum |
216 | 232 |
|
217 | | - return ecosystem.decode_address(self.account_file["address"]) |
| 233 | + return ecosystem.decode_address(self.account_file.address) |
218 | 234 |
|
219 | 235 | @cached_property |
220 | 236 | def contract(self) -> "ContractInstance": |
@@ -280,15 +296,15 @@ def client(self) -> BaseSafeClient: |
280 | 296 | if self.provider.network.is_local: |
281 | 297 | return MockSafeClient(contract=self.contract) |
282 | 298 |
|
283 | | - elif chain_id in self.account_file["deployed_chain_ids"]: |
| 299 | + elif chain_id in self.account_file.deployed_chain_ids: |
284 | 300 | return SafeClient( |
285 | 301 | address=self.address, chain_id=self.provider.chain_id, override_url=override_url |
286 | 302 | ) |
287 | 303 |
|
288 | 304 | elif ( |
289 | 305 | self.provider.network.name.endswith("-fork") |
290 | 306 | and isinstance(self.provider.network, ForkedNetworkAPI) |
291 | | - and self.provider.network.upstream_chain_id in self.account_file["deployed_chain_ids"] |
| 307 | + and self.provider.network.upstream_chain_id in self.account_file.deployed_chain_ids |
292 | 308 | ): |
293 | 309 | return SafeClient( |
294 | 310 | address=self.address, |
|
0 commit comments