algokit-utils 3.0.0b1__py3-none-any.whl → 3.0.0b2__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.

Potentially problematic release.


This version of algokit-utils might be problematic. Click here for more details.

Files changed (70) hide show
  1. algokit_utils/__init__.py +23 -183
  2. algokit_utils/_debugging.py +123 -97
  3. algokit_utils/_legacy_v2/__init__.py +177 -0
  4. algokit_utils/{_ensure_funded.py → _legacy_v2/_ensure_funded.py} +19 -18
  5. algokit_utils/{_transfer.py → _legacy_v2/_transfer.py} +24 -23
  6. algokit_utils/_legacy_v2/account.py +203 -0
  7. algokit_utils/_legacy_v2/application_client.py +1471 -0
  8. algokit_utils/_legacy_v2/application_specification.py +21 -0
  9. algokit_utils/_legacy_v2/asset.py +168 -0
  10. algokit_utils/_legacy_v2/common.py +28 -0
  11. algokit_utils/_legacy_v2/deploy.py +822 -0
  12. algokit_utils/_legacy_v2/logic_error.py +14 -0
  13. algokit_utils/{models.py → _legacy_v2/models.py} +19 -142
  14. algokit_utils/_legacy_v2/network_clients.py +140 -0
  15. algokit_utils/account.py +12 -183
  16. algokit_utils/accounts/__init__.py +2 -0
  17. algokit_utils/accounts/account_manager.py +909 -0
  18. algokit_utils/accounts/kmd_account_manager.py +159 -0
  19. algokit_utils/algorand.py +265 -0
  20. algokit_utils/application_client.py +9 -1453
  21. algokit_utils/application_specification.py +39 -197
  22. algokit_utils/applications/__init__.py +7 -0
  23. algokit_utils/applications/abi.py +276 -0
  24. algokit_utils/applications/app_client.py +2056 -0
  25. algokit_utils/applications/app_deployer.py +600 -0
  26. algokit_utils/applications/app_factory.py +826 -0
  27. algokit_utils/applications/app_manager.py +470 -0
  28. algokit_utils/applications/app_spec/__init__.py +2 -0
  29. algokit_utils/applications/app_spec/arc32.py +207 -0
  30. algokit_utils/applications/app_spec/arc56.py +1023 -0
  31. algokit_utils/applications/enums.py +40 -0
  32. algokit_utils/asset.py +32 -168
  33. algokit_utils/assets/__init__.py +1 -0
  34. algokit_utils/assets/asset_manager.py +320 -0
  35. algokit_utils/beta/_utils.py +36 -0
  36. algokit_utils/beta/account_manager.py +4 -195
  37. algokit_utils/beta/algorand_client.py +4 -314
  38. algokit_utils/beta/client_manager.py +5 -74
  39. algokit_utils/beta/composer.py +5 -712
  40. algokit_utils/clients/__init__.py +2 -0
  41. algokit_utils/clients/client_manager.py +656 -0
  42. algokit_utils/clients/dispenser_api_client.py +192 -0
  43. algokit_utils/common.py +8 -26
  44. algokit_utils/config.py +71 -18
  45. algokit_utils/deploy.py +7 -892
  46. algokit_utils/dispenser_api.py +8 -176
  47. algokit_utils/errors/__init__.py +1 -0
  48. algokit_utils/errors/logic_error.py +121 -0
  49. algokit_utils/logic_error.py +7 -80
  50. algokit_utils/models/__init__.py +8 -0
  51. algokit_utils/models/account.py +193 -0
  52. algokit_utils/models/amount.py +198 -0
  53. algokit_utils/models/application.py +61 -0
  54. algokit_utils/models/network.py +25 -0
  55. algokit_utils/models/simulate.py +11 -0
  56. algokit_utils/models/state.py +59 -0
  57. algokit_utils/models/transaction.py +100 -0
  58. algokit_utils/network_clients.py +7 -152
  59. algokit_utils/protocols/__init__.py +2 -0
  60. algokit_utils/protocols/account.py +22 -0
  61. algokit_utils/protocols/typed_clients.py +108 -0
  62. algokit_utils/transactions/__init__.py +3 -0
  63. algokit_utils/transactions/transaction_composer.py +2293 -0
  64. algokit_utils/transactions/transaction_creator.py +156 -0
  65. algokit_utils/transactions/transaction_sender.py +574 -0
  66. {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/METADATA +12 -7
  67. algokit_utils-3.0.0b2.dist-info/RECORD +70 -0
  68. {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/WHEEL +1 -1
  69. algokit_utils-3.0.0b1.dist-info/RECORD +0 -24
  70. {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/LICENSE +0 -0
@@ -0,0 +1,2 @@
1
+ from algokit_utils.clients.client_manager import * # noqa: F403
2
+ from algokit_utils.clients.dispenser_api_client import * # noqa: F403
@@ -0,0 +1,656 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING, Literal, TypeVar
6
+ from urllib import parse
7
+
8
+ import algosdk
9
+ from algosdk.atomic_transaction_composer import TransactionSigner
10
+ from algosdk.kmd import KMDClient
11
+ from algosdk.source_map import SourceMap
12
+ from algosdk.transaction import SuggestedParams
13
+ from algosdk.v2client.algod import AlgodClient
14
+ from algosdk.v2client.indexer import IndexerClient
15
+
16
+ from algokit_utils._legacy_v2.application_specification import ApplicationSpecification
17
+ from algokit_utils.applications.app_deployer import ApplicationLookup
18
+ from algokit_utils.applications.app_spec.arc56 import Arc56Contract
19
+ from algokit_utils.clients.dispenser_api_client import TestNetDispenserApiClient
20
+ from algokit_utils.models.network import AlgoClientConfigs, AlgoClientNetworkConfig
21
+ from algokit_utils.protocols.typed_clients import TypedAppClientProtocol, TypedAppFactoryProtocol
22
+
23
+ if TYPE_CHECKING:
24
+ from algokit_utils.algorand import AlgorandClient
25
+ from algokit_utils.applications.app_client import AppClient, AppClientCompilationParams
26
+ from algokit_utils.applications.app_factory import AppFactory
27
+
28
+ __all__ = [
29
+ "AlgoSdkClients",
30
+ "ClientManager",
31
+ "NetworkDetail",
32
+ ]
33
+
34
+ TypedFactoryT = TypeVar("TypedFactoryT", bound=TypedAppFactoryProtocol)
35
+ TypedAppClientT = TypeVar("TypedAppClientT", bound=TypedAppClientProtocol)
36
+
37
+
38
+ class AlgoSdkClients:
39
+ """Container for Algorand SDK client instances.
40
+
41
+ Holds references to Algod, Indexer and KMD clients.
42
+
43
+ :param algod: Algod client instance
44
+ :param indexer: Optional Indexer client instance
45
+ :param kmd: Optional KMD client instance
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ algod: algosdk.v2client.algod.AlgodClient,
51
+ indexer: IndexerClient | None = None,
52
+ kmd: KMDClient | None = None,
53
+ ):
54
+ self.algod = algod
55
+ self.indexer = indexer
56
+ self.kmd = kmd
57
+
58
+
59
+ @dataclass(kw_only=True, frozen=True)
60
+ class NetworkDetail:
61
+ """Details about an Algorand network.
62
+
63
+ Contains network type flags and genesis information.
64
+ """
65
+
66
+ is_testnet: bool
67
+ is_mainnet: bool
68
+ is_localnet: bool
69
+ genesis_id: str
70
+ genesis_hash: str
71
+
72
+
73
+ def _get_config_from_environment(environment_prefix: str) -> AlgoClientNetworkConfig:
74
+ server = os.getenv(f"{environment_prefix}_SERVER")
75
+ if server is None:
76
+ raise Exception(f"Server environment variable not set: {environment_prefix}_SERVER")
77
+ port = os.getenv(f"{environment_prefix}_PORT")
78
+ if port:
79
+ parsed = parse.urlparse(server)
80
+ server = parsed._replace(netloc=f"{parsed.hostname}:{port}").geturl()
81
+ return AlgoClientNetworkConfig(server, os.getenv(f"{environment_prefix}_TOKEN", ""))
82
+
83
+
84
+ class ClientManager:
85
+ """Manager for Algorand SDK clients.
86
+
87
+ Provides access to Algod, Indexer and KMD clients and helper methods for working with them.
88
+
89
+ :param clients_or_configs: Either client instances or client configurations
90
+ :param algorand_client: AlgorandClient instance
91
+ """
92
+
93
+ def __init__(self, clients_or_configs: AlgoClientConfigs | AlgoSdkClients, algorand_client: AlgorandClient):
94
+ if isinstance(clients_or_configs, AlgoSdkClients):
95
+ _clients = clients_or_configs
96
+ elif isinstance(clients_or_configs, AlgoClientConfigs):
97
+ _clients = AlgoSdkClients(
98
+ algod=ClientManager.get_algod_client(clients_or_configs.algod_config),
99
+ indexer=ClientManager.get_indexer_client(clients_or_configs.indexer_config)
100
+ if clients_or_configs.indexer_config
101
+ else None,
102
+ kmd=ClientManager.get_kmd_client(clients_or_configs.kmd_config)
103
+ if clients_or_configs.kmd_config
104
+ else None,
105
+ )
106
+ self._algod = _clients.algod
107
+ self._indexer = _clients.indexer
108
+ self._kmd = _clients.kmd
109
+ self._algorand = algorand_client
110
+ self._suggested_params: SuggestedParams | None = None
111
+
112
+ @property
113
+ def algod(self) -> AlgodClient:
114
+ """Returns an algosdk Algod API client.
115
+
116
+ :return: Algod client instance
117
+ """
118
+ return self._algod
119
+
120
+ @property
121
+ def indexer(self) -> IndexerClient:
122
+ """Returns an algosdk Indexer API client.
123
+
124
+ :raises ValueError: If no Indexer client is configured
125
+ :return: Indexer client instance
126
+ """
127
+ if not self._indexer:
128
+ raise ValueError("Attempt to use Indexer client in AlgoKit instance with no Indexer configured")
129
+ return self._indexer
130
+
131
+ @property
132
+ def indexer_if_present(self) -> IndexerClient | None:
133
+ """Returns the Indexer client if configured, otherwise None.
134
+
135
+ :return: Indexer client instance or None
136
+ """
137
+ return self._indexer
138
+
139
+ @property
140
+ def kmd(self) -> KMDClient:
141
+ """Returns an algosdk KMD API client.
142
+
143
+ :raises ValueError: If no KMD client is configured
144
+ :return: KMD client instance
145
+ """
146
+ if not self._kmd:
147
+ raise ValueError("Attempt to use Kmd client in AlgoKit instance with no Kmd configured")
148
+ return self._kmd
149
+
150
+ def network(self) -> NetworkDetail:
151
+ """Get details about the connected Algorand network.
152
+
153
+ :return: Network details including type and genesis information
154
+ """
155
+ if self._suggested_params is None:
156
+ self._suggested_params = self._algod.suggested_params()
157
+ sp = self._suggested_params
158
+ return NetworkDetail(
159
+ is_testnet=sp.gen in ["testnet-v1.0", "testnet-v1", "testnet"],
160
+ is_mainnet=sp.gen in ["mainnet-v1.0", "mainnet-v1", "mainnet"],
161
+ is_localnet=ClientManager.genesis_id_is_localnet(str(sp.gen)),
162
+ genesis_id=str(sp.gen),
163
+ genesis_hash=sp.gh,
164
+ )
165
+
166
+ def is_localnet(self) -> bool:
167
+ """Check if connected to a local network.
168
+
169
+ :return: True if connected to a local network
170
+ """
171
+ return self.network().is_localnet
172
+
173
+ def is_testnet(self) -> bool:
174
+ """Check if connected to TestNet.
175
+
176
+ :return: True if connected to TestNet
177
+ """
178
+ return self.network().is_testnet
179
+
180
+ def is_mainnet(self) -> bool:
181
+ """Check if connected to MainNet.
182
+
183
+ :return: True if connected to MainNet
184
+ """
185
+ return self.network().is_mainnet
186
+
187
+ def get_testnet_dispenser(
188
+ self, auth_token: str | None = None, request_timeout: int | None = None
189
+ ) -> TestNetDispenserApiClient:
190
+ """Get a TestNet dispenser API client.
191
+
192
+ :param auth_token: Optional authentication token
193
+ :param request_timeout: Optional request timeout in seconds
194
+ :return: TestNet dispenser client instance
195
+ """
196
+ if request_timeout:
197
+ return TestNetDispenserApiClient(auth_token=auth_token, request_timeout=request_timeout)
198
+
199
+ return TestNetDispenserApiClient(auth_token=auth_token)
200
+
201
+ def get_app_factory(
202
+ self,
203
+ app_spec: Arc56Contract | ApplicationSpecification | str,
204
+ app_name: str | None = None,
205
+ default_sender: str | None = None,
206
+ default_signer: TransactionSigner | None = None,
207
+ version: str | None = None,
208
+ compilation_params: AppClientCompilationParams | None = None,
209
+ ) -> AppFactory:
210
+ """Get an application factory for deploying smart contracts.
211
+
212
+ :param app_spec: Application specification
213
+ :param app_name: Optional application name
214
+ :param default_sender: Optional default sender address
215
+ :param default_signer: Optional default transaction signer
216
+ :param version: Optional version string
217
+ :param compilation_params: Optional compilation parameters
218
+ :raises ValueError: If no Algorand client is configured
219
+ :return: Application factory instance
220
+ """
221
+ from algokit_utils.applications.app_factory import AppFactory, AppFactoryParams
222
+
223
+ if not self._algorand:
224
+ raise ValueError("Attempt to get app factory from a ClientManager without an Algorand client")
225
+
226
+ return AppFactory(
227
+ AppFactoryParams(
228
+ algorand=self._algorand,
229
+ app_spec=app_spec,
230
+ app_name=app_name,
231
+ default_sender=default_sender,
232
+ default_signer=default_signer,
233
+ version=version,
234
+ compilation_params=compilation_params,
235
+ )
236
+ )
237
+
238
+ def get_app_client_by_id(
239
+ self,
240
+ app_spec: (Arc56Contract | ApplicationSpecification | str),
241
+ app_id: int,
242
+ app_name: str | None = None,
243
+ default_sender: str | None = None,
244
+ default_signer: TransactionSigner | None = None,
245
+ approval_source_map: SourceMap | None = None,
246
+ clear_source_map: SourceMap | None = None,
247
+ ) -> AppClient:
248
+ """Get an application client for an existing application by ID.
249
+
250
+ :param app_spec: Application specification
251
+ :param app_id: Application ID
252
+ :param app_name: Optional application name
253
+ :param default_sender: Optional default sender address
254
+ :param default_signer: Optional default transaction signer
255
+ :param approval_source_map: Optional approval program source map
256
+ :param clear_source_map: Optional clear program source map
257
+ :raises ValueError: If no Algorand client is configured
258
+ :return: Application client instance
259
+ """
260
+ from algokit_utils.applications.app_client import AppClient, AppClientParams
261
+
262
+ if not self._algorand:
263
+ raise ValueError("Attempt to get app client from a ClientManager without an Algorand client")
264
+
265
+ return AppClient(
266
+ AppClientParams(
267
+ app_spec=app_spec,
268
+ algorand=self._algorand,
269
+ app_id=app_id,
270
+ app_name=app_name,
271
+ default_sender=default_sender,
272
+ default_signer=default_signer,
273
+ approval_source_map=approval_source_map,
274
+ clear_source_map=clear_source_map,
275
+ )
276
+ )
277
+
278
+ def get_app_client_by_network(
279
+ self,
280
+ app_spec: (Arc56Contract | ApplicationSpecification | str),
281
+ app_name: str | None = None,
282
+ default_sender: str | None = None,
283
+ default_signer: TransactionSigner | None = None,
284
+ approval_source_map: SourceMap | None = None,
285
+ clear_source_map: SourceMap | None = None,
286
+ ) -> AppClient:
287
+ """Get an application client for an existing application by network.
288
+
289
+ :param app_spec: Application specification
290
+ :param app_name: Optional application name
291
+ :param default_sender: Optional default sender address
292
+ :param default_signer: Optional default transaction signer
293
+ :param approval_source_map: Optional approval program source map
294
+ :param clear_source_map: Optional clear program source map
295
+ :raises ValueError: If no Algorand client is configured
296
+ :return: Application client instance
297
+ """
298
+ from algokit_utils.applications.app_client import AppClient
299
+
300
+ if not self._algorand:
301
+ raise ValueError("Attempt to get app client from a ClientManager without an Algorand client")
302
+
303
+ return AppClient.from_network(
304
+ app_spec=app_spec,
305
+ app_name=app_name,
306
+ default_sender=default_sender,
307
+ default_signer=default_signer,
308
+ approval_source_map=approval_source_map,
309
+ clear_source_map=clear_source_map,
310
+ algorand=self._algorand,
311
+ )
312
+
313
+ def get_app_client_by_creator_and_name(
314
+ self,
315
+ creator_address: str,
316
+ app_name: str,
317
+ app_spec: Arc56Contract | ApplicationSpecification | str,
318
+ default_sender: str | None = None,
319
+ default_signer: TransactionSigner | None = None,
320
+ ignore_cache: bool | None = None,
321
+ app_lookup_cache: ApplicationLookup | None = None,
322
+ approval_source_map: SourceMap | None = None,
323
+ clear_source_map: SourceMap | None = None,
324
+ ) -> AppClient:
325
+ """Get an application client by creator address and name.
326
+
327
+ :param creator_address: Creator address
328
+ :param app_name: Application name
329
+ :param app_spec: Application specification
330
+ :param default_sender: Optional default sender address
331
+ :param default_signer: Optional default transaction signer
332
+ :param ignore_cache: Optional flag to ignore cache
333
+ :param app_lookup_cache: Optional app lookup cache
334
+ :param approval_source_map: Optional approval program source map
335
+ :param clear_source_map: Optional clear program source map
336
+ :return: Application client instance
337
+ """
338
+ from algokit_utils.applications.app_client import AppClient
339
+
340
+ return AppClient.from_creator_and_name(
341
+ creator_address=creator_address,
342
+ app_name=app_name,
343
+ default_sender=default_sender,
344
+ default_signer=default_signer,
345
+ ignore_cache=ignore_cache,
346
+ app_lookup_cache=app_lookup_cache,
347
+ app_spec=app_spec,
348
+ approval_source_map=approval_source_map,
349
+ clear_source_map=clear_source_map,
350
+ algorand=self._algorand,
351
+ )
352
+
353
+ @staticmethod
354
+ def get_algod_client(config: AlgoClientNetworkConfig | None = None) -> AlgodClient:
355
+ """Get an Algod client from config or environment.
356
+
357
+ :param config: Optional client configuration
358
+ :return: Algod client instance
359
+ """
360
+ config = config or _get_config_from_environment("ALGOD")
361
+ headers = {"X-Algo-API-Token": config.token or ""}
362
+ return AlgodClient(algod_token=config.token or "", algod_address=config.server, headers=headers)
363
+
364
+ @staticmethod
365
+ def get_algod_client_from_environment() -> AlgodClient:
366
+ """Get an Algod client from environment variables.
367
+
368
+ :return: Algod client instance
369
+ """
370
+ return ClientManager.get_algod_client(ClientManager.get_algod_config_from_environment())
371
+
372
+ @staticmethod
373
+ def get_kmd_client(config: AlgoClientNetworkConfig | None = None) -> KMDClient:
374
+ """Get a KMD client from config or environment.
375
+
376
+ :param config: Optional client configuration
377
+ :return: KMD client instance
378
+ """
379
+ config = config or _get_config_from_environment("KMD")
380
+ return KMDClient(config.token, config.server)
381
+
382
+ @staticmethod
383
+ def get_kmd_client_from_environment() -> KMDClient:
384
+ """Get a KMD client from environment variables.
385
+
386
+ :return: KMD client instance
387
+ """
388
+ return ClientManager.get_kmd_client(ClientManager.get_kmd_config_from_environment())
389
+
390
+ @staticmethod
391
+ def get_indexer_client(config: AlgoClientNetworkConfig | None = None) -> IndexerClient:
392
+ """Get an Indexer client from config or environment.
393
+
394
+ :param config: Optional client configuration
395
+ :return: Indexer client instance
396
+ """
397
+ config = config or _get_config_from_environment("INDEXER")
398
+ headers = {"X-Indexer-API-Token": config.token}
399
+ return IndexerClient(indexer_token=config.token, indexer_address=config.server, headers=headers)
400
+
401
+ @staticmethod
402
+ def get_indexer_client_from_environment() -> IndexerClient:
403
+ """Get an Indexer client from environment variables.
404
+
405
+ :return: Indexer client instance
406
+ """
407
+ return ClientManager.get_indexer_client(ClientManager.get_indexer_config_from_environment())
408
+
409
+ @staticmethod
410
+ def genesis_id_is_localnet(genesis_id: str | None) -> bool:
411
+ """Check if a genesis ID indicates a local network.
412
+
413
+ :param genesis_id: Genesis ID to check
414
+ :return: True if genesis ID indicates a local network
415
+ """
416
+ return genesis_id in ["devnet-v1", "sandnet-v1", "dockernet-v1"]
417
+
418
+ def get_typed_app_client_by_creator_and_name(
419
+ self,
420
+ typed_client: type[TypedAppClientT],
421
+ *,
422
+ creator_address: str,
423
+ app_name: str,
424
+ default_sender: str | None = None,
425
+ default_signer: TransactionSigner | None = None,
426
+ ignore_cache: bool | None = None,
427
+ app_lookup_cache: ApplicationLookup | None = None,
428
+ ) -> TypedAppClientT:
429
+ """Get a typed application client by creator address and name.
430
+
431
+ :param typed_client: Typed client class
432
+ :param creator_address: Creator address
433
+ :param app_name: Application name
434
+ :param default_sender: Optional default sender address
435
+ :param default_signer: Optional default transaction signer
436
+ :param ignore_cache: Optional flag to ignore cache
437
+ :param app_lookup_cache: Optional app lookup cache
438
+ :raises ValueError: If no Algorand client is configured
439
+ :return: Typed application client instance
440
+ """
441
+ if not self._algorand:
442
+ raise ValueError("Attempt to get app client from a ClientManager without an Algorand client")
443
+
444
+ return typed_client.from_creator_and_name(
445
+ creator_address=creator_address,
446
+ app_name=app_name,
447
+ default_sender=default_sender,
448
+ default_signer=default_signer,
449
+ ignore_cache=ignore_cache,
450
+ app_lookup_cache=app_lookup_cache,
451
+ algorand=self._algorand,
452
+ )
453
+
454
+ def get_typed_app_client_by_id(
455
+ self,
456
+ typed_client: type[TypedAppClientT],
457
+ *,
458
+ app_id: int,
459
+ app_name: str | None = None,
460
+ default_sender: str | None = None,
461
+ default_signer: TransactionSigner | None = None,
462
+ approval_source_map: SourceMap | None = None,
463
+ clear_source_map: SourceMap | None = None,
464
+ ) -> TypedAppClientT:
465
+ """Get a typed application client by ID.
466
+
467
+ :param typed_client: Typed client class
468
+ :param app_id: Application ID
469
+ :param app_name: Optional application name
470
+ :param default_sender: Optional default sender address
471
+ :param default_signer: Optional default transaction signer
472
+ :param approval_source_map: Optional approval program source map
473
+ :param clear_source_map: Optional clear program source map
474
+ :raises ValueError: If no Algorand client is configured
475
+ :return: Typed application client instance
476
+ """
477
+ if not self._algorand:
478
+ raise ValueError("Attempt to get app client from a ClientManager without an Algorand client")
479
+
480
+ return typed_client(
481
+ app_id=app_id,
482
+ app_name=app_name,
483
+ default_sender=default_sender,
484
+ default_signer=default_signer,
485
+ approval_source_map=approval_source_map,
486
+ clear_source_map=clear_source_map,
487
+ algorand=self._algorand,
488
+ )
489
+
490
+ def get_typed_app_client_by_network(
491
+ self,
492
+ typed_client: type[TypedAppClientT],
493
+ *,
494
+ app_name: str | None = None,
495
+ default_sender: str | None = None,
496
+ default_signer: TransactionSigner | None = None,
497
+ approval_source_map: SourceMap | None = None,
498
+ clear_source_map: SourceMap | None = None,
499
+ ) -> TypedAppClientT:
500
+ """Returns a new typed client, resolves the app ID for the current network.
501
+
502
+ Uses pre-determined network-specific app IDs specified in the ARC-56 app spec.
503
+ If no IDs are in the app spec or the network isn't recognised, an error is thrown.
504
+
505
+ :param typed_client: The typed client class to instantiate
506
+ :param app_name: Optional application name
507
+ :param default_sender: Optional default sender address
508
+ :param default_signer: Optional default transaction signer
509
+ :param approval_source_map: Optional approval program source map
510
+ :param clear_source_map: Optional clear program source map
511
+ :raises ValueError: If no Algorand client is configured
512
+ :return: The typed client instance
513
+ """
514
+ if not self._algorand:
515
+ raise ValueError("Attempt to get app client from a ClientManager without an Algorand client")
516
+
517
+ return typed_client.from_network(
518
+ app_name=app_name,
519
+ default_sender=default_sender,
520
+ default_signer=default_signer,
521
+ approval_source_map=approval_source_map,
522
+ clear_source_map=clear_source_map,
523
+ algorand=self._algorand,
524
+ )
525
+
526
+ def get_typed_app_factory(
527
+ self,
528
+ typed_factory: type[TypedFactoryT],
529
+ *,
530
+ app_name: str | None = None,
531
+ default_sender: str | None = None,
532
+ default_signer: TransactionSigner | None = None,
533
+ version: str | None = None,
534
+ compilation_params: AppClientCompilationParams | None = None,
535
+ ) -> TypedFactoryT:
536
+ """Get a typed application factory.
537
+
538
+ :param typed_factory: Typed factory class
539
+ :param app_name: Optional application name
540
+ :param default_sender: Optional default sender address
541
+ :param default_signer: Optional default transaction signer
542
+ :param version: Optional version string
543
+ :param compilation_params: Optional compilation parameters
544
+ :raises ValueError: If no Algorand client is configured
545
+ :return: Typed application factory instance
546
+ """
547
+ if not self._algorand:
548
+ raise ValueError("Attempt to get app factory from a ClientManager without an Algorand client")
549
+
550
+ return typed_factory(
551
+ algorand=self._algorand,
552
+ app_name=app_name,
553
+ default_sender=default_sender,
554
+ default_signer=default_signer,
555
+ version=version,
556
+ compilation_params=compilation_params,
557
+ )
558
+
559
+ @staticmethod
560
+ def get_config_from_environment_or_localnet() -> AlgoClientConfigs:
561
+ """Retrieve client configuration from environment variables or fallback to localnet defaults.
562
+
563
+ If ALGOD_SERVER is set in environment variables, it will use environment configuration,
564
+ otherwise it will use default localnet configuration.
565
+
566
+ :return: Configuration for algod, indexer, and optionally kmd
567
+ """
568
+ algod_server = os.getenv("ALGOD_SERVER")
569
+
570
+ if algod_server:
571
+ # Use environment configuration
572
+ algod_config = ClientManager.get_algod_config_from_environment()
573
+
574
+ # Only include indexer if INDEXER_SERVER is set
575
+ indexer_config = (
576
+ ClientManager.get_indexer_config_from_environment() if os.getenv("INDEXER_SERVER") else None
577
+ )
578
+
579
+ # Include KMD config only for local networks (not mainnet/testnet)
580
+ kmd_config = (
581
+ AlgoClientNetworkConfig(
582
+ server=algod_config.server, token=algod_config.token, port=os.getenv("KMD_PORT", "4002")
583
+ )
584
+ if not any(net in algod_server.lower() for net in ["mainnet", "testnet"])
585
+ else None
586
+ )
587
+ else:
588
+ # Use localnet defaults
589
+ algod_config = ClientManager.get_default_localnet_config("algod")
590
+ indexer_config = ClientManager.get_default_localnet_config("indexer")
591
+ kmd_config = ClientManager.get_default_localnet_config("kmd")
592
+
593
+ return AlgoClientConfigs(
594
+ algod_config=algod_config,
595
+ indexer_config=indexer_config,
596
+ kmd_config=kmd_config,
597
+ )
598
+
599
+ @staticmethod
600
+ def get_default_localnet_config(
601
+ config_or_port: Literal["algod", "indexer", "kmd"] | int,
602
+ ) -> AlgoClientNetworkConfig:
603
+ """Get default configuration for local network services.
604
+
605
+ :param config_or_port: Service name or port number
606
+ :return: Client configuration for local network
607
+ """
608
+ port = (
609
+ config_or_port
610
+ if isinstance(config_or_port, int)
611
+ else {"algod": 4001, "indexer": 8980, "kmd": 4002}[config_or_port]
612
+ )
613
+
614
+ return AlgoClientNetworkConfig(server=f"http://localhost:{port}", token="a" * 64)
615
+
616
+ @staticmethod
617
+ def get_algod_config_from_environment() -> AlgoClientNetworkConfig:
618
+ """Retrieve the algod configuration from environment variables.
619
+ Will raise an error if ALGOD_SERVER environment variable is not set
620
+
621
+ :return: Algod client configuration
622
+ """
623
+ return _get_config_from_environment("ALGOD")
624
+
625
+ @staticmethod
626
+ def get_indexer_config_from_environment() -> AlgoClientNetworkConfig:
627
+ """Retrieve the indexer configuration from environment variables.
628
+ Will raise an error if INDEXER_SERVER environment variable is not set
629
+
630
+ :return: Indexer client configuration
631
+ """
632
+ return _get_config_from_environment("INDEXER")
633
+
634
+ @staticmethod
635
+ def get_kmd_config_from_environment() -> AlgoClientNetworkConfig:
636
+ """Retrieve the kmd configuration from environment variables.
637
+
638
+ :return: KMD client configuration
639
+ """
640
+ return _get_config_from_environment("KMD")
641
+
642
+ @staticmethod
643
+ def get_algonode_config(
644
+ network: Literal["testnet", "mainnet"], config: Literal["algod", "indexer"]
645
+ ) -> AlgoClientNetworkConfig:
646
+ """Returns the Algorand configuration to point to the free tier of the AlgoNode service.
647
+
648
+ :param network: Which network to connect to - TestNet or MainNet
649
+ :param config: Which algod config to return - Algod or Indexer
650
+ :return: Configuration for the specified network and service
651
+ """
652
+ service_type = "api" if config == "algod" else "idx"
653
+ return AlgoClientNetworkConfig(
654
+ server=f"https://{network}-{service_type}.algonode.cloud",
655
+ port=443,
656
+ )