algokit-utils 2.4.0b1__py3-none-any.whl → 3.0.0__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 -181
  2. algokit_utils/_debugging.py +89 -45
  3. algokit_utils/_legacy_v2/__init__.py +177 -0
  4. algokit_utils/{_ensure_funded.py → _legacy_v2/_ensure_funded.py} +21 -24
  5. algokit_utils/{_transfer.py → _legacy_v2/_transfer.py} +26 -23
  6. algokit_utils/_legacy_v2/account.py +203 -0
  7. algokit_utils/_legacy_v2/application_client.py +1472 -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} +16 -45
  14. algokit_utils/_legacy_v2/network_clients.py +144 -0
  15. algokit_utils/account.py +12 -183
  16. algokit_utils/accounts/__init__.py +2 -0
  17. algokit_utils/accounts/account_manager.py +912 -0
  18. algokit_utils/accounts/kmd_account_manager.py +161 -0
  19. algokit_utils/algorand.py +359 -0
  20. algokit_utils/application_client.py +9 -1447
  21. algokit_utils/application_specification.py +39 -197
  22. algokit_utils/applications/__init__.py +7 -0
  23. algokit_utils/applications/abi.py +275 -0
  24. algokit_utils/applications/app_client.py +2108 -0
  25. algokit_utils/applications/app_deployer.py +725 -0
  26. algokit_utils/applications/app_factory.py +1134 -0
  27. algokit_utils/applications/app_manager.py +578 -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 +989 -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 +336 -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 +738 -0
  42. algokit_utils/clients/dispenser_api_client.py +224 -0
  43. algokit_utils/common.py +8 -26
  44. algokit_utils/config.py +76 -29
  45. algokit_utils/deploy.py +7 -894
  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 -82
  50. algokit_utils/models/__init__.py +8 -0
  51. algokit_utils/models/account.py +217 -0
  52. algokit_utils/models/amount.py +200 -0
  53. algokit_utils/models/application.py +91 -0
  54. algokit_utils/models/network.py +29 -0
  55. algokit_utils/models/simulate.py +11 -0
  56. algokit_utils/models/state.py +68 -0
  57. algokit_utils/models/transaction.py +100 -0
  58. algokit_utils/network_clients.py +7 -128
  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 +2499 -0
  64. algokit_utils/transactions/transaction_creator.py +688 -0
  65. algokit_utils/transactions/transaction_sender.py +1219 -0
  66. {algokit_utils-2.4.0b1.dist-info → algokit_utils-3.0.0.dist-info}/METADATA +11 -7
  67. algokit_utils-3.0.0.dist-info/RECORD +70 -0
  68. {algokit_utils-2.4.0b1.dist-info → algokit_utils-3.0.0.dist-info}/WHEEL +1 -1
  69. algokit_utils-2.4.0b1.dist-info/RECORD +0 -24
  70. {algokit_utils-2.4.0b1.dist-info → algokit_utils-3.0.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,161 @@
1
+ from collections.abc import Callable
2
+ from typing import Any, cast
3
+
4
+ from algosdk.kmd import KMDClient
5
+
6
+ from algokit_utils.clients.client_manager import ClientManager
7
+ from algokit_utils.config import config
8
+ from algokit_utils.models.account import SigningAccount
9
+ from algokit_utils.models.amount import AlgoAmount
10
+ from algokit_utils.transactions.transaction_composer import PaymentParams, TransactionComposer
11
+
12
+ __all__ = ["KmdAccount", "KmdAccountManager"]
13
+
14
+ logger = config.logger
15
+
16
+
17
+ class KmdAccount(SigningAccount):
18
+ """Account retrieved from KMD with signing capabilities, extending base Account.
19
+
20
+ Provides an account implementation that can be used to sign transactions using keys stored in KMD.
21
+
22
+ :param private_key: Base64 encoded private key
23
+ :param address: Optional address override for rekeyed accounts, defaults to None
24
+ """
25
+
26
+ def __init__(self, private_key: str, address: str | None = None) -> None:
27
+ super().__init__(private_key=private_key, address=address or "")
28
+
29
+
30
+ class KmdAccountManager:
31
+ """Provides abstractions over KMD that makes it easier to get and manage accounts."""
32
+
33
+ _kmd: KMDClient | None
34
+
35
+ def __init__(self, client_manager: ClientManager) -> None:
36
+ self._client_manager = client_manager
37
+ try:
38
+ self._kmd = client_manager.kmd
39
+ except ValueError:
40
+ self._kmd = None
41
+
42
+ def kmd(self) -> KMDClient:
43
+ """Returns the KMD client, initializing it if needed.
44
+
45
+ :raises Exception: If KMD client is not configured and not running against LocalNet
46
+ :return: The KMD client
47
+ """
48
+ if self._kmd is None:
49
+ if self._client_manager.is_localnet():
50
+ kmd_config = ClientManager.get_config_from_environment_or_localnet()
51
+ if not kmd_config.kmd_config:
52
+ raise Exception("Attempt to use KMD client with no KMD configured")
53
+ self._kmd = ClientManager.get_kmd_client(kmd_config.kmd_config)
54
+ return self._kmd
55
+ raise Exception("Attempt to use KMD client with no KMD configured")
56
+ return self._kmd
57
+
58
+ def get_wallet_account(
59
+ self,
60
+ wallet_name: str,
61
+ predicate: Callable[[dict[str, Any]], bool] | None = None,
62
+ sender: str | None = None,
63
+ ) -> KmdAccount | None:
64
+ """Returns an Algorand signing account with private key loaded from the given KMD wallet.
65
+
66
+ Retrieves an account from a KMD wallet that matches the given predicate, or a random account
67
+ if no predicate is provided.
68
+
69
+ :param wallet_name: The name of the wallet to retrieve an account from
70
+ :param predicate: Optional filter to use to find the account (otherwise gets a random account from the wallet)
71
+ :param sender: Optional sender address to use this signer for (aka a rekeyed account)
72
+ :return: The signing account or None if no matching wallet or account was found
73
+ """
74
+
75
+ kmd_client = self.kmd()
76
+ wallets = kmd_client.list_wallets()
77
+ wallet = next((w for w in wallets if w["name"] == wallet_name), None)
78
+ if not wallet:
79
+ return None
80
+
81
+ wallet_id = wallet["id"]
82
+ wallet_handle = kmd_client.init_wallet_handle(wallet_id, "")
83
+ addresses = kmd_client.list_keys(wallet_handle)
84
+
85
+ matched_address = None
86
+ if predicate:
87
+ for address in addresses:
88
+ account_info = self._client_manager.algod.account_info(address)
89
+ if predicate(cast(dict[str, Any], account_info)):
90
+ matched_address = address
91
+ break
92
+ else:
93
+ matched_address = next(iter(addresses), None)
94
+
95
+ if not matched_address:
96
+ return None
97
+
98
+ private_key = kmd_client.export_key(wallet_handle, "", matched_address)
99
+ return KmdAccount(private_key=private_key, address=sender)
100
+
101
+ def get_or_create_wallet_account(self, name: str, fund_with: AlgoAmount | None = None) -> KmdAccount:
102
+ """Gets or creates a funded account in a KMD wallet of the given name.
103
+
104
+ Provides idempotent access to accounts from LocalNet without specifying the private key.
105
+
106
+ :param name: The name of the wallet to retrieve / create
107
+ :param fund_with: The number of Algos to fund the account with when created
108
+ :return: An Algorand account with private key loaded
109
+ """
110
+ fund_with = fund_with or AlgoAmount.from_algo(1000)
111
+
112
+ existing = self.get_wallet_account(name)
113
+ if existing:
114
+ return existing
115
+
116
+ kmd_client = self.kmd()
117
+ wallet_id = kmd_client.create_wallet(name, "")["id"]
118
+ wallet_handle = kmd_client.init_wallet_handle(wallet_id, "")
119
+ kmd_client.generate_key(wallet_handle)
120
+
121
+ account = self.get_wallet_account(name)
122
+ assert account is not None
123
+
124
+ logger.info(
125
+ f"LocalNet account '{name}' doesn't yet exist; created account {account.address} "
126
+ f"with keys stored in KMD and funding with {fund_with} ALGO"
127
+ )
128
+
129
+ dispenser = self.get_localnet_dispenser_account()
130
+ TransactionComposer(
131
+ algod=self._client_manager.algod,
132
+ get_signer=lambda _: dispenser.signer,
133
+ get_suggested_params=self._client_manager.algod.suggested_params,
134
+ ).add_payment(
135
+ PaymentParams(
136
+ sender=dispenser.address,
137
+ receiver=account.address,
138
+ amount=fund_with,
139
+ )
140
+ ).send()
141
+ return account
142
+
143
+ def get_localnet_dispenser_account(self) -> KmdAccount:
144
+ """Returns an Algorand account with private key loaded for the default LocalNet dispenser account.
145
+
146
+ Retrieves the default funded account from LocalNet that can be used to fund other accounts.
147
+
148
+ :raises Exception: If not running against LocalNet or dispenser account not found
149
+ :return: The default LocalNet dispenser account
150
+ """
151
+ if not self._client_manager.is_localnet():
152
+ raise Exception("Can't get LocalNet dispenser account from non LocalNet network")
153
+
154
+ dispenser = self.get_wallet_account(
155
+ "unencrypted-default-wallet",
156
+ lambda a: a["status"] != "Offline" and a["amount"] > 1_000_000_000, # noqa: PLR2004
157
+ )
158
+ if not dispenser:
159
+ raise Exception("Error retrieving LocalNet dispenser account; couldn't find the default account in KMD")
160
+
161
+ return dispenser
@@ -0,0 +1,359 @@
1
+ import copy
2
+ import time
3
+
4
+ import typing_extensions
5
+ from algosdk.atomic_transaction_composer import TransactionSigner
6
+ from algosdk.kmd import KMDClient
7
+ from algosdk.transaction import SuggestedParams
8
+ from algosdk.v2client.algod import AlgodClient
9
+ from algosdk.v2client.indexer import IndexerClient
10
+
11
+ from algokit_utils.accounts.account_manager import AccountManager
12
+ from algokit_utils.applications.app_deployer import AppDeployer
13
+ from algokit_utils.applications.app_manager import AppManager
14
+ from algokit_utils.assets.asset_manager import AssetManager
15
+ from algokit_utils.clients.client_manager import AlgoSdkClients, ClientManager
16
+ from algokit_utils.models.network import AlgoClientConfigs, AlgoClientNetworkConfig
17
+ from algokit_utils.protocols.account import TransactionSignerAccountProtocol
18
+ from algokit_utils.transactions.transaction_composer import (
19
+ TransactionComposer,
20
+ )
21
+ from algokit_utils.transactions.transaction_creator import AlgorandClientTransactionCreator
22
+ from algokit_utils.transactions.transaction_sender import AlgorandClientTransactionSender
23
+
24
+ __all__ = [
25
+ "AlgorandClient",
26
+ ]
27
+
28
+
29
+ class AlgorandClient:
30
+ """A client that brokers easy access to Algorand functionality."""
31
+
32
+ def __init__(self, config: AlgoClientConfigs | AlgoSdkClients):
33
+ self._client_manager: ClientManager = ClientManager(clients_or_configs=config, algorand_client=self)
34
+ self._account_manager: AccountManager = AccountManager(self._client_manager)
35
+ self._asset_manager: AssetManager = AssetManager(self._client_manager.algod, lambda: self.new_group())
36
+ self._app_manager: AppManager = AppManager(self._client_manager.algod)
37
+ self._transaction_sender = AlgorandClientTransactionSender(
38
+ new_group=lambda: self.new_group(),
39
+ asset_manager=self._asset_manager,
40
+ app_manager=self._app_manager,
41
+ algod_client=self._client_manager.algod,
42
+ )
43
+ self._app_deployer: AppDeployer = AppDeployer(
44
+ self._app_manager, self._transaction_sender, self._client_manager.indexer_if_present
45
+ )
46
+ self._transaction_creator = AlgorandClientTransactionCreator(
47
+ new_group=lambda: self.new_group(),
48
+ )
49
+
50
+ self._cached_suggested_params: SuggestedParams | None = None
51
+ self._cached_suggested_params_expiry: float | None = None
52
+ self._cached_suggested_params_timeout: int = 3_000 # three seconds
53
+ self._default_validity_window: int | None = None
54
+
55
+ def set_default_validity_window(self, validity_window: int) -> typing_extensions.Self:
56
+ """
57
+ Sets the default validity window for transactions.
58
+
59
+ :param validity_window: The number of rounds between the first and last valid rounds
60
+ :return: The `AlgorandClient` so method calls can be chained
61
+ :example:
62
+ >>> algorand = AlgorandClient.mainnet().set_default_validity_window(1000);
63
+ """
64
+ self._default_validity_window = validity_window
65
+ return self
66
+
67
+ def set_default_signer(
68
+ self, signer: TransactionSigner | TransactionSignerAccountProtocol
69
+ ) -> typing_extensions.Self:
70
+ """
71
+ Sets the default signer to use if no other signer is specified.
72
+
73
+ :param signer: The signer to use, either a `TransactionSigner` or a `TransactionSignerAccountProtocol`
74
+ :return: The `AlgorandClient` so method calls can be chained
75
+ :example:
76
+ >>> signer = SigningAccount(private_key=..., address=...)
77
+ >>> algorand = AlgorandClient.mainnet().set_default_signer(signer)
78
+ """
79
+ self._account_manager.set_default_signer(signer)
80
+ return self
81
+
82
+ def set_signer(self, sender: str, signer: TransactionSigner) -> typing_extensions.Self:
83
+ """
84
+ Tracks the given account for later signing.
85
+
86
+ :param sender: The sender address to use this signer for
87
+ :param signer: The signer to sign transactions with for the given sender
88
+ :return: The `AlgorandClient` so method calls can be chained
89
+ :example:
90
+ >>> signer = SigningAccount(private_key=..., address=...)
91
+ >>> algorand = AlgorandClient.mainnet().set_signer(signer.addr, signer.signer)
92
+ """
93
+ self._account_manager.set_signer(sender, signer)
94
+ return self
95
+
96
+ def set_signer_from_account(self, signer: TransactionSignerAccountProtocol) -> typing_extensions.Self:
97
+ """
98
+ Sets the default signer to use if no other signer is specified.
99
+
100
+ :param signer: The signer to use, either a `TransactionSigner` or a `TransactionSignerAccountProtocol`
101
+ :return: The `AlgorandClient` so method calls can be chained
102
+ :example:
103
+ >>> accountManager = AlgorandClient.mainnet()
104
+ >>> accountManager.set_signer_from_account(TransactionSignerAccount(address=..., signer=...))
105
+ >>> accountManager.set_signer_from_account(algosdk.LogicSigAccount(program, args))
106
+ >>> accountManager.set_signer_from_account(SigningAccount(private_key=..., address=...))
107
+ >>> accountManager.set_signer_from_account(MultisigAccount(metadata, signing_accounts))
108
+ >>> accountManager.set_signer_from_account(account)
109
+ """
110
+ self._account_manager.set_default_signer(signer)
111
+ return self
112
+
113
+ def set_suggested_params_cache(
114
+ self, suggested_params: SuggestedParams, until: float | None = None
115
+ ) -> typing_extensions.Self:
116
+ """
117
+ Sets a cache value to use for suggested params.
118
+
119
+ :param suggested_params: The suggested params to use
120
+ :param until: A timestamp until which to cache, or if not specified then the timeout is used
121
+ :return: The `AlgorandClient` so method calls can be chained
122
+ :example:
123
+ >>> algorand = AlgorandClient.mainnet().set_suggested_params_cache(suggested_params, time.time() + 3.6e6)
124
+ """
125
+ self._cached_suggested_params = suggested_params
126
+ self._cached_suggested_params_expiry = until or time.time() + self._cached_suggested_params_timeout
127
+ return self
128
+
129
+ def set_suggested_params_cache_timeout(self, timeout: int) -> typing_extensions.Self:
130
+ """
131
+ Sets the timeout for caching suggested params.
132
+
133
+ :param timeout: The timeout in milliseconds
134
+ :return: The `AlgorandClient` so method calls can be chained
135
+ :example:
136
+ >>> algorand = AlgorandClient.mainnet().set_suggested_params_cache_timeout(10_000)
137
+ """
138
+ self._cached_suggested_params_timeout = timeout
139
+ return self
140
+
141
+ def get_suggested_params(self) -> SuggestedParams:
142
+ """
143
+ Get suggested params for a transaction (either cached or from algod if the cache is stale or empty)
144
+
145
+ :example:
146
+ >>> algorand = AlgorandClient.mainnet().get_suggested_params()
147
+ """
148
+ if self._cached_suggested_params and (
149
+ self._cached_suggested_params_expiry is None or self._cached_suggested_params_expiry > time.time()
150
+ ):
151
+ return copy.deepcopy(self._cached_suggested_params)
152
+
153
+ self._cached_suggested_params = self._client_manager.algod.suggested_params()
154
+ self._cached_suggested_params_expiry = time.time() + self._cached_suggested_params_timeout
155
+
156
+ return copy.deepcopy(self._cached_suggested_params)
157
+
158
+ def new_group(self) -> TransactionComposer:
159
+ """
160
+ Start a new `TransactionComposer` transaction group
161
+
162
+ :example:
163
+ >>> composer = AlgorandClient.mainnet().new_group()
164
+ >>> result = await composer.add_transaction(payment).send()
165
+ """
166
+
167
+ return TransactionComposer(
168
+ algod=self.client.algod,
169
+ get_signer=lambda addr: self.account.get_signer(addr),
170
+ get_suggested_params=self.get_suggested_params,
171
+ default_validity_window=self._default_validity_window,
172
+ )
173
+
174
+ @property
175
+ def client(self) -> ClientManager:
176
+ """
177
+ Get clients, including algosdk clients and app clients.
178
+
179
+ :example:
180
+ >>> clientManager = AlgorandClient.mainnet().client
181
+ """
182
+ return self._client_manager
183
+
184
+ @property
185
+ def account(self) -> AccountManager:
186
+ """Get or create accounts that can sign transactions.
187
+
188
+ :example:
189
+ >>> accountManager = AlgorandClient.mainnet().account
190
+ """
191
+ return self._account_manager
192
+
193
+ @property
194
+ def asset(self) -> AssetManager:
195
+ """
196
+ Get or create assets.
197
+
198
+ :example:
199
+ >>> assetManager = AlgorandClient.mainnet().asset
200
+ """
201
+ return self._asset_manager
202
+
203
+ @property
204
+ def app(self) -> AppManager:
205
+ """
206
+ Get or create applications.
207
+
208
+ :example:
209
+ >>> appManager = AlgorandClient.mainnet().app
210
+ """
211
+ return self._app_manager
212
+
213
+ @property
214
+ def app_deployer(self) -> AppDeployer:
215
+ """
216
+ Get or create applications.
217
+
218
+ :example:
219
+ >>> appDeployer = AlgorandClient.mainnet().app_deployer
220
+ """
221
+ return self._app_deployer
222
+
223
+ @property
224
+ def send(self) -> AlgorandClientTransactionSender:
225
+ """
226
+ Methods for sending a transaction and waiting for confirmation
227
+
228
+ :example:
229
+ >>> result = await AlgorandClient.mainnet().send.payment(
230
+ >>> PaymentParams(
231
+ >>> sender="SENDERADDRESS",
232
+ >>> receiver="RECEIVERADDRESS",
233
+ >>> amount=AlgoAmount(algo-1)
234
+ >>> ))
235
+ """
236
+ return self._transaction_sender
237
+
238
+ @property
239
+ def create_transaction(self) -> AlgorandClientTransactionCreator:
240
+ """
241
+ Methods for building transactions
242
+
243
+ :example:
244
+ >>> transaction = AlgorandClient.mainnet().create_transaction.payment(
245
+ >>> PaymentParams(
246
+ >>> sender="SENDERADDRESS",
247
+ >>> receiver="RECEIVERADDRESS",
248
+ >>> amount=AlgoAmount(algo=1)
249
+ >>> ))
250
+ """
251
+ return self._transaction_creator
252
+
253
+ @staticmethod
254
+ def default_localnet() -> "AlgorandClient":
255
+ """
256
+ Returns an `AlgorandClient` pointing at default LocalNet ports and API token.
257
+
258
+ :return: The `AlgorandClient`
259
+
260
+ :example:
261
+ >>> algorand = AlgorandClient.default_localnet()
262
+ """
263
+ return AlgorandClient(
264
+ AlgoClientConfigs(
265
+ algod_config=ClientManager.get_default_localnet_config("algod"),
266
+ indexer_config=ClientManager.get_default_localnet_config("indexer"),
267
+ kmd_config=ClientManager.get_default_localnet_config("kmd"),
268
+ )
269
+ )
270
+
271
+ @staticmethod
272
+ def testnet() -> "AlgorandClient":
273
+ """
274
+ Returns an `AlgorandClient` pointing at TestNet using AlgoNode.
275
+
276
+ :return: The `AlgorandClient`
277
+
278
+ :example:
279
+ >>> algorand = AlgorandClient.testnet()
280
+ """
281
+ return AlgorandClient(
282
+ AlgoClientConfigs(
283
+ algod_config=ClientManager.get_algonode_config("testnet", "algod"),
284
+ indexer_config=ClientManager.get_algonode_config("testnet", "indexer"),
285
+ kmd_config=None,
286
+ )
287
+ )
288
+
289
+ @staticmethod
290
+ def mainnet() -> "AlgorandClient":
291
+ """
292
+ Returns an `AlgorandClient` pointing at MainNet using AlgoNode.
293
+
294
+ :return: The `AlgorandClient`
295
+
296
+ :example:
297
+ >>> algorand = AlgorandClient.mainnet()
298
+ """
299
+ return AlgorandClient(
300
+ AlgoClientConfigs(
301
+ algod_config=ClientManager.get_algonode_config("mainnet", "algod"),
302
+ indexer_config=ClientManager.get_algonode_config("mainnet", "indexer"),
303
+ kmd_config=None,
304
+ )
305
+ )
306
+
307
+ @staticmethod
308
+ def from_clients(
309
+ algod: AlgodClient, indexer: IndexerClient | None = None, kmd: KMDClient | None = None
310
+ ) -> "AlgorandClient":
311
+ """
312
+ Returns an `AlgorandClient` pointing to the given client(s).
313
+
314
+ :param algod: The algod client to use
315
+ :param indexer: The indexer client to use
316
+ :param kmd: The kmd client to use
317
+ :return: The `AlgorandClient`
318
+
319
+ :example:
320
+ >>> algorand = AlgorandClient.from_clients(algod, indexer, kmd)
321
+ """
322
+ return AlgorandClient(AlgoSdkClients(algod=algod, indexer=indexer, kmd=kmd))
323
+
324
+ @staticmethod
325
+ def from_environment() -> "AlgorandClient":
326
+ """
327
+ Returns an `AlgorandClient` loading the configuration from environment variables.
328
+
329
+ Retrieve configurations from environment variables when defined or get defaults.
330
+
331
+ Expects to be called from a Python environment.
332
+
333
+ :return: The `AlgorandClient`
334
+
335
+ :example:
336
+ >>> algorand = AlgorandClient.from_environment()
337
+ """
338
+ return AlgorandClient(ClientManager.get_config_from_environment_or_localnet())
339
+
340
+ @staticmethod
341
+ def from_config(
342
+ algod_config: AlgoClientNetworkConfig,
343
+ indexer_config: AlgoClientNetworkConfig | None = None,
344
+ kmd_config: AlgoClientNetworkConfig | None = None,
345
+ ) -> "AlgorandClient":
346
+ """
347
+ Returns an `AlgorandClient` from the given config.
348
+
349
+ :param algod_config: The config to use for the algod client
350
+ :param indexer_config: The config to use for the indexer client
351
+ :param kmd_config: The config to use for the kmd client
352
+ :return: The `AlgorandClient`
353
+
354
+ :example:
355
+ >>> algorand = AlgorandClient.from_config(algod_config, indexer_config, kmd_config)
356
+ """
357
+ return AlgorandClient(
358
+ AlgoClientConfigs(algod_config=algod_config, indexer_config=indexer_config, kmd_config=kmd_config)
359
+ )