algokit-utils 3.0.0b1__py3-none-any.whl → 3.0.0b3__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 +2054 -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 +197 -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 +2287 -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.0b3.dist-info}/METADATA +13 -8
  67. algokit_utils-3.0.0b3.dist-info/RECORD +70 -0
  68. {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b3.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.0b3.dist-info}/LICENSE +0 -0
@@ -0,0 +1,198 @@
1
+ from __future__ import annotations
2
+
3
+ from decimal import Decimal
4
+
5
+ import algosdk
6
+ from typing_extensions import Self
7
+
8
+ __all__ = ["AlgoAmount"]
9
+
10
+
11
+ class AlgoAmount:
12
+ """Wrapper class to ensure safe, explicit conversion between µAlgo, Algo and numbers.
13
+
14
+ :param amount: A dictionary containing either algos, algo, microAlgos, or microAlgo as key
15
+ and their corresponding value as an integer or Decimal.
16
+ :raises ValueError: If an invalid amount format is provided.
17
+
18
+ :example:
19
+ >>> amount = AlgoAmount({"algos": 1})
20
+ >>> amount = AlgoAmount({"microAlgos": 1_000_000})
21
+ """
22
+
23
+ def __init__(self, amount: dict[str, int | Decimal]):
24
+ if "microAlgos" in amount:
25
+ self.amount_in_micro_algo = int(amount["microAlgos"])
26
+ elif "microAlgo" in amount:
27
+ self.amount_in_micro_algo = int(amount["microAlgo"])
28
+ elif "algos" in amount:
29
+ self.amount_in_micro_algo = int(amount["algos"] * algosdk.constants.MICROALGOS_TO_ALGOS_RATIO)
30
+ elif "algo" in amount:
31
+ self.amount_in_micro_algo = int(amount["algo"] * algosdk.constants.MICROALGOS_TO_ALGOS_RATIO)
32
+ else:
33
+ raise ValueError("Invalid amount provided")
34
+
35
+ @property
36
+ def micro_algos(self) -> int:
37
+ """Return the amount as a number in µAlgo.
38
+
39
+ :returns: The amount in µAlgo.
40
+ """
41
+ return self.amount_in_micro_algo
42
+
43
+ @property
44
+ def micro_algo(self) -> int:
45
+ """Return the amount as a number in µAlgo.
46
+
47
+ :returns: The amount in µAlgo.
48
+ """
49
+ return self.amount_in_micro_algo
50
+
51
+ @property
52
+ def algos(self) -> Decimal:
53
+ """Return the amount as a number in Algo.
54
+
55
+ :returns: The amount in Algo.
56
+ """
57
+ return algosdk.util.microalgos_to_algos(self.amount_in_micro_algo) # type: ignore[no-any-return]
58
+
59
+ @property
60
+ def algo(self) -> Decimal:
61
+ """Return the amount as a number in Algo.
62
+
63
+ :returns: The amount in Algo.
64
+ """
65
+ return algosdk.util.microalgos_to_algos(self.amount_in_micro_algo) # type: ignore[no-any-return]
66
+
67
+ @staticmethod
68
+ def from_algos(amount: int | Decimal) -> AlgoAmount:
69
+ """Create an AlgoAmount object representing the given number of Algo.
70
+
71
+ :param amount: The amount in Algo.
72
+ :returns: An AlgoAmount instance.
73
+
74
+ :example:
75
+ >>> amount = AlgoAmount.from_algos(1)
76
+ """
77
+ return AlgoAmount({"algos": amount})
78
+
79
+ @staticmethod
80
+ def from_algo(amount: int | Decimal) -> AlgoAmount:
81
+ """Create an AlgoAmount object representing the given number of Algo.
82
+
83
+ :param amount: The amount in Algo.
84
+ :returns: An AlgoAmount instance.
85
+
86
+ :example:
87
+ >>> amount = AlgoAmount.from_algo(1)
88
+ """
89
+ return AlgoAmount({"algo": amount})
90
+
91
+ @staticmethod
92
+ def from_micro_algos(amount: int) -> AlgoAmount:
93
+ """Create an AlgoAmount object representing the given number of µAlgo.
94
+
95
+ :param amount: The amount in µAlgo.
96
+ :returns: An AlgoAmount instance.
97
+
98
+ :example:
99
+ >>> amount = AlgoAmount.from_micro_algos(1_000_000)
100
+ """
101
+ return AlgoAmount({"microAlgos": amount})
102
+
103
+ @staticmethod
104
+ def from_micro_algo(amount: int) -> AlgoAmount:
105
+ """Create an AlgoAmount object representing the given number of µAlgo.
106
+
107
+ :param amount: The amount in µAlgo.
108
+ :returns: An AlgoAmount instance.
109
+
110
+ :example:
111
+ >>> amount = AlgoAmount.from_micro_algo(1_000_000)
112
+ """
113
+ return AlgoAmount({"microAlgo": amount})
114
+
115
+ def __str__(self) -> str:
116
+ return f"{self.micro_algo:,} µALGO"
117
+
118
+ def __int__(self) -> int:
119
+ return self.micro_algos
120
+
121
+ def __add__(self, other: AlgoAmount) -> AlgoAmount:
122
+ if isinstance(other, AlgoAmount):
123
+ total_micro_algos = self.micro_algos + other.micro_algos
124
+ else:
125
+ raise TypeError(f"Unsupported operand type(s) for +: 'AlgoAmount' and '{type(other).__name__}'")
126
+ return AlgoAmount.from_micro_algos(total_micro_algos)
127
+
128
+ def __radd__(self, other: AlgoAmount) -> AlgoAmount:
129
+ return self.__add__(other)
130
+
131
+ def __iadd__(self, other: AlgoAmount) -> Self:
132
+ if isinstance(other, AlgoAmount):
133
+ self.amount_in_micro_algo += other.micro_algos
134
+ else:
135
+ raise TypeError(f"Unsupported operand type(s) for +: 'AlgoAmount' and '{type(other).__name__}'")
136
+ return self
137
+
138
+ def __eq__(self, other: object) -> bool:
139
+ if isinstance(other, AlgoAmount):
140
+ return self.amount_in_micro_algo == other.amount_in_micro_algo
141
+ elif isinstance(other, int):
142
+ return self.amount_in_micro_algo == int(other)
143
+ raise TypeError(f"Unsupported operand type(s) for ==: 'AlgoAmount' and '{type(other).__name__}'")
144
+
145
+ def __ne__(self, other: object) -> bool:
146
+ if isinstance(other, AlgoAmount):
147
+ return self.amount_in_micro_algo != other.amount_in_micro_algo
148
+ elif isinstance(other, int):
149
+ return self.amount_in_micro_algo != int(other)
150
+ raise TypeError(f"Unsupported operand type(s) for !=: 'AlgoAmount' and '{type(other).__name__}'")
151
+
152
+ def __lt__(self, other: object) -> bool:
153
+ if isinstance(other, AlgoAmount):
154
+ return self.amount_in_micro_algo < other.amount_in_micro_algo
155
+ elif isinstance(other, int):
156
+ return self.amount_in_micro_algo < int(other)
157
+ raise TypeError(f"Unsupported operand type(s) for <: 'AlgoAmount' and '{type(other).__name__}'")
158
+
159
+ def __le__(self, other: object) -> bool:
160
+ if isinstance(other, AlgoAmount):
161
+ return self.amount_in_micro_algo <= other.amount_in_micro_algo
162
+ elif isinstance(other, int):
163
+ return self.amount_in_micro_algo <= int(other)
164
+ raise TypeError(f"Unsupported operand type(s) for <=: 'AlgoAmount' and '{type(other).__name__}'")
165
+
166
+ def __gt__(self, other: object) -> bool:
167
+ if isinstance(other, AlgoAmount):
168
+ return self.amount_in_micro_algo > other.amount_in_micro_algo
169
+ elif isinstance(other, int):
170
+ return self.amount_in_micro_algo > int(other)
171
+ raise TypeError(f"Unsupported operand type(s) for >: 'AlgoAmount' and '{type(other).__name__}'")
172
+
173
+ def __ge__(self, other: object) -> bool:
174
+ if isinstance(other, AlgoAmount):
175
+ return self.amount_in_micro_algo >= other.amount_in_micro_algo
176
+ elif isinstance(other, int):
177
+ return self.amount_in_micro_algo >= int(other)
178
+ raise TypeError(f"Unsupported operand type(s) for >=: 'AlgoAmount' and '{type(other).__name__}'")
179
+
180
+ def __sub__(self, other: AlgoAmount) -> AlgoAmount:
181
+ if isinstance(other, AlgoAmount):
182
+ total_micro_algos = self.micro_algos - other.micro_algos
183
+ else:
184
+ raise TypeError(f"Unsupported operand type(s) for -: 'AlgoAmount' and '{type(other).__name__}'")
185
+ return AlgoAmount.from_micro_algos(total_micro_algos)
186
+
187
+ def __rsub__(self, other: int) -> AlgoAmount:
188
+ if isinstance(other, (int)):
189
+ total_micro_algos = int(other) - self.micro_algos
190
+ return AlgoAmount.from_micro_algos(total_micro_algos)
191
+ raise TypeError(f"Unsupported operand type(s) for -: '{type(other).__name__}' and 'AlgoAmount'")
192
+
193
+ def __isub__(self, other: AlgoAmount) -> Self:
194
+ if isinstance(other, AlgoAmount):
195
+ self.amount_in_micro_algo -= other.micro_algos
196
+ else:
197
+ raise TypeError(f"Unsupported operand type(s) for -: 'AlgoAmount' and '{type(other).__name__}'")
198
+ return self
@@ -0,0 +1,61 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING
3
+
4
+ import algosdk
5
+ from algosdk.source_map import SourceMap
6
+
7
+ if TYPE_CHECKING:
8
+ pass
9
+
10
+ __all__ = [
11
+ "AppCompilationResult",
12
+ "AppInformation",
13
+ "AppSourceMaps",
14
+ "AppState",
15
+ "CompiledTeal",
16
+ ]
17
+
18
+
19
+ @dataclass(kw_only=True, frozen=True)
20
+ class AppState:
21
+ key_raw: bytes
22
+ key_base64: str
23
+ value_raw: bytes | None
24
+ value_base64: str | None
25
+ value: str | int
26
+
27
+
28
+ @dataclass(kw_only=True, frozen=True)
29
+ class AppInformation:
30
+ app_id: int
31
+ app_address: str
32
+ approval_program: bytes
33
+ clear_state_program: bytes
34
+ creator: str
35
+ global_state: dict[str, AppState]
36
+ local_ints: int
37
+ local_byte_slices: int
38
+ global_ints: int
39
+ global_byte_slices: int
40
+ extra_program_pages: int | None
41
+
42
+
43
+ @dataclass(kw_only=True, frozen=True)
44
+ class CompiledTeal:
45
+ teal: str
46
+ compiled: str
47
+ compiled_hash: str
48
+ compiled_base64_to_bytes: bytes
49
+ source_map: algosdk.source_map.SourceMap | None
50
+
51
+
52
+ @dataclass(kw_only=True, frozen=True)
53
+ class AppCompilationResult:
54
+ compiled_approval: CompiledTeal
55
+ compiled_clear: CompiledTeal
56
+
57
+
58
+ @dataclass(kw_only=True, frozen=True)
59
+ class AppSourceMaps:
60
+ approval_source_map: SourceMap | None = None
61
+ clear_source_map: SourceMap | None = None
@@ -0,0 +1,25 @@
1
+ import dataclasses
2
+
3
+ __all__ = [
4
+ "AlgoClientConfigs",
5
+ "AlgoClientNetworkConfig",
6
+ ]
7
+
8
+
9
+ @dataclasses.dataclass
10
+ class AlgoClientNetworkConfig:
11
+ """Connection details for connecting to an {py:class}`algosdk.v2client.algod.AlgodClient` or
12
+ {py:class}`algosdk.v2client.indexer.IndexerClient`"""
13
+
14
+ server: str
15
+ """URL for the service e.g. `http://localhost:4001` or `https://testnet-api.algonode.cloud`"""
16
+ token: str | None = None
17
+ """API Token to authenticate with the service"""
18
+ port: str | int | None = None
19
+
20
+
21
+ @dataclasses.dataclass
22
+ class AlgoClientConfigs:
23
+ algod_config: AlgoClientNetworkConfig
24
+ indexer_config: AlgoClientNetworkConfig | None
25
+ kmd_config: AlgoClientNetworkConfig | None
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass
2
+
3
+ __all__ = ["SimulationTrace"]
4
+
5
+
6
+ @dataclass
7
+ class SimulationTrace:
8
+ app_budget_added: int | None
9
+ app_budget_consumed: int | None
10
+ failure_message: str | None
11
+ exec_trace: dict[str, object]
@@ -0,0 +1,59 @@
1
+ import base64
2
+ from collections.abc import Mapping
3
+ from dataclasses import dataclass
4
+ from enum import IntEnum
5
+ from typing import TypeAlias
6
+
7
+ from algosdk.atomic_transaction_composer import AccountTransactionSigner
8
+ from algosdk.box_reference import BoxReference as AlgosdkBoxReference
9
+
10
+ __all__ = [
11
+ "BoxIdentifier",
12
+ "BoxName",
13
+ "BoxReference",
14
+ "BoxValue",
15
+ "DataTypeFlag",
16
+ "TealTemplateParams",
17
+ ]
18
+
19
+
20
+ @dataclass(kw_only=True, frozen=True)
21
+ class BoxName:
22
+ name: str
23
+ name_raw: bytes
24
+ name_base64: str
25
+
26
+
27
+ @dataclass(kw_only=True, frozen=True)
28
+ class BoxValue:
29
+ name: BoxName
30
+ value: bytes
31
+
32
+
33
+ class DataTypeFlag(IntEnum):
34
+ BYTES = 1
35
+ UINT = 2
36
+
37
+
38
+ TealTemplateParams: TypeAlias = Mapping[str, str | int | bytes] | dict[str, str | int | bytes]
39
+
40
+
41
+ BoxIdentifier: TypeAlias = str | bytes | AccountTransactionSigner
42
+
43
+
44
+ class BoxReference(AlgosdkBoxReference):
45
+ def __init__(self, app_id: int, name: bytes | str):
46
+ super().__init__(app_index=app_id, name=self._b64_decode(name))
47
+
48
+ def __eq__(self, other: object) -> bool:
49
+ if isinstance(other, (BoxReference | AlgosdkBoxReference)):
50
+ return self.app_index == other.app_index and self.name == other.name
51
+ return False
52
+
53
+ def _b64_decode(self, value: str | bytes) -> bytes:
54
+ if isinstance(value, str):
55
+ try:
56
+ return base64.b64decode(value)
57
+ except Exception:
58
+ return value.encode("utf-8")
59
+ return value
@@ -0,0 +1,100 @@
1
+ from typing import Any, Literal, TypedDict, TypeVar
2
+
3
+ import algosdk
4
+
5
+ __all__ = [
6
+ "Arc2TransactionNote",
7
+ "BaseArc2Note",
8
+ "JsonFormatArc2Note",
9
+ "SendParams",
10
+ "StringFormatArc2Note",
11
+ "TransactionNote",
12
+ "TransactionNoteData",
13
+ "TransactionWrapper",
14
+ ]
15
+
16
+
17
+ # Define specific types for different formats
18
+ class BaseArc2Note(TypedDict):
19
+ """Base ARC-0002 transaction note structure"""
20
+
21
+ dapp_name: str
22
+
23
+
24
+ class StringFormatArc2Note(BaseArc2Note):
25
+ """ARC-0002 note for string-based formats (m/b/u)"""
26
+
27
+ format: Literal["m", "b", "u"]
28
+ data: str
29
+
30
+
31
+ class JsonFormatArc2Note(BaseArc2Note):
32
+ """ARC-0002 note for JSON format"""
33
+
34
+ format: Literal["j"]
35
+ data: str | dict[str, Any] | list[Any] | int | None
36
+
37
+
38
+ # Combined type for all valid ARC-0002 notes
39
+ # See: https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md
40
+ Arc2TransactionNote = StringFormatArc2Note | JsonFormatArc2Note
41
+
42
+ TransactionNoteData = str | None | int | list[Any] | dict[str, Any]
43
+ TransactionNote = bytes | TransactionNoteData | Arc2TransactionNote
44
+
45
+ TxnTypeT = TypeVar("TxnTypeT", bound=algosdk.transaction.Transaction)
46
+
47
+
48
+ class TransactionWrapper(algosdk.transaction.Transaction):
49
+ """Wrapper around algosdk.transaction.Transaction with optional property validators"""
50
+
51
+ def __init__(self, transaction: algosdk.transaction.Transaction) -> None:
52
+ self._raw = transaction
53
+
54
+ @property
55
+ def raw(self) -> algosdk.transaction.Transaction:
56
+ return self._raw
57
+
58
+ @property
59
+ def payment(self) -> algosdk.transaction.PaymentTxn:
60
+ return self._return_if_type(
61
+ algosdk.transaction.PaymentTxn,
62
+ )
63
+
64
+ @property
65
+ def keyreg(self) -> algosdk.transaction.KeyregTxn:
66
+ return self._return_if_type(algosdk.transaction.KeyregTxn)
67
+
68
+ @property
69
+ def asset_config(self) -> algosdk.transaction.AssetConfigTxn:
70
+ return self._return_if_type(algosdk.transaction.AssetConfigTxn)
71
+
72
+ @property
73
+ def asset_transfer(self) -> algosdk.transaction.AssetTransferTxn:
74
+ return self._return_if_type(algosdk.transaction.AssetTransferTxn)
75
+
76
+ @property
77
+ def asset_freeze(self) -> algosdk.transaction.AssetFreezeTxn:
78
+ return self._return_if_type(algosdk.transaction.AssetFreezeTxn)
79
+
80
+ @property
81
+ def application_call(self) -> algosdk.transaction.ApplicationCallTxn:
82
+ return self._return_if_type(algosdk.transaction.ApplicationCallTxn)
83
+
84
+ @property
85
+ def state_proof(self) -> algosdk.transaction.StateProofTxn:
86
+ return self._return_if_type(algosdk.transaction.StateProofTxn)
87
+
88
+ def _return_if_type(self, txn_type: type[TxnTypeT]) -> TxnTypeT:
89
+ if isinstance(self._raw, txn_type):
90
+ return self._raw
91
+ raise ValueError(f"Transaction is not of type {txn_type.__name__}")
92
+
93
+
94
+ class SendParams(TypedDict, total=False):
95
+ """Parameters for sending a transaction"""
96
+
97
+ max_rounds_to_wait: int | None
98
+ suppress_log: bool | None
99
+ populate_app_call_resources: bool | None
100
+ cover_app_call_inner_transaction_fees: bool | None
@@ -1,154 +1,9 @@
1
- import dataclasses
2
- import os
3
- from typing import Literal
4
- from urllib import parse
1
+ import warnings
5
2
 
6
- from algosdk.kmd import KMDClient
7
- from algosdk.v2client.algod import AlgodClient
8
- from algosdk.v2client.indexer import IndexerClient
3
+ warnings.warn(
4
+ "The legacy v2 network clients module is deprecated and will be removed in a future version.",
5
+ DeprecationWarning,
6
+ stacklevel=2,
7
+ )
9
8
 
10
- __all__ = [
11
- "AlgoClientConfig",
12
- "get_algod_client",
13
- "get_algonode_config",
14
- "get_default_localnet_config",
15
- "get_indexer_client",
16
- "get_kmd_client_from_algod_client",
17
- "get_purestake_config",
18
- "is_localnet",
19
- "is_mainnet",
20
- "is_testnet",
21
- "AlgoClientConfigs",
22
- "get_kmd_client",
23
- ]
24
-
25
- _PURE_STAKE_HOST = "purestake.io"
26
-
27
-
28
- @dataclasses.dataclass
29
- class AlgoClientConfig:
30
- """Connection details for connecting to an {py:class}`algosdk.v2client.algod.AlgodClient` or
31
- {py:class}`algosdk.v2client.indexer.IndexerClient`"""
32
-
33
- server: str
34
- """URL for the service e.g. `http://localhost:4001` or `https://testnet-api.algonode.cloud`"""
35
- token: str
36
- """API Token to authenticate with the service"""
37
-
38
-
39
- @dataclasses.dataclass
40
- class AlgoClientConfigs:
41
- algod_config: AlgoClientConfig
42
- indexer_config: AlgoClientConfig
43
- kmd_config: AlgoClientConfig | None
44
-
45
-
46
- def get_default_localnet_config(config: Literal["algod", "indexer", "kmd"]) -> AlgoClientConfig:
47
- """Returns the client configuration to point to the default LocalNet"""
48
- port = {"algod": 4001, "indexer": 8980, "kmd": 4002}[config]
49
- return AlgoClientConfig(server=f"http://localhost:{port}", token="a" * 64)
50
-
51
-
52
- def get_algonode_config(
53
- network: Literal["testnet", "mainnet"], config: Literal["algod", "indexer"], token: str
54
- ) -> AlgoClientConfig:
55
- client = "api" if config == "algod" else "idx"
56
- return AlgoClientConfig(
57
- server=f"https://{network}-{client}.algonode.cloud",
58
- token=token,
59
- )
60
-
61
-
62
- def get_purestake_config(
63
- network: Literal["testnet", "mainnet"], config: Literal["algod", "indexer"], token: str
64
- ) -> AlgoClientConfig:
65
- client = "ps2" if config == "algod" else "idx2"
66
- return AlgoClientConfig(
67
- server=f"https://{network}-algorand.api.purestake.io/{client}",
68
- token=token,
69
- )
70
-
71
-
72
- def get_algod_client(config: AlgoClientConfig | None = None) -> AlgodClient:
73
- """Returns an {py:class}`algosdk.v2client.algod.AlgodClient` from `config` or environment
74
-
75
- If no configuration provided will use environment variables `ALGOD_SERVER`, `ALGOD_PORT` and `ALGOD_TOKEN`"""
76
- config = config or _get_config_from_environment("ALGOD")
77
- headers = _get_headers(config, "X-Algo-API-Token")
78
- return AlgodClient(config.token, config.server, headers)
79
-
80
-
81
- def get_kmd_client(config: AlgoClientConfig | None = None) -> KMDClient:
82
- """Returns an {py:class}`algosdk.kmd.KMDClient` from `config` or environment
83
-
84
- If no configuration provided will use environment variables `KMD_SERVER`, `KMD_PORT` and `KMD_TOKEN`"""
85
- config = config or _get_config_from_environment("KMD")
86
- return KMDClient(config.token, config.server) # type: ignore[no-untyped-call]
87
-
88
-
89
- def get_indexer_client(config: AlgoClientConfig | None = None) -> IndexerClient:
90
- """Returns an {py:class}`algosdk.v2client.indexer.IndexerClient` from `config` or environment.
91
-
92
- If no configuration provided will use environment variables `INDEXER_SERVER`, `INDEXER_PORT` and `INDEXER_TOKEN`"""
93
- config = config or _get_config_from_environment("INDEXER")
94
- headers = _get_headers(config, "X-Indexer-API-Token")
95
- return IndexerClient(config.token, config.server, headers) # type: ignore[no-untyped-call]
96
-
97
-
98
- def is_localnet(client: AlgodClient) -> bool:
99
- """Returns True if client genesis is `devnet-v1` or `sandnet-v1`"""
100
- params = client.suggested_params()
101
- return params.gen in ["devnet-v1", "sandnet-v1", "dockernet-v1"]
102
-
103
-
104
- def is_mainnet(client: AlgodClient) -> bool:
105
- """Returns True if client genesis is `mainnet-v1`"""
106
- params = client.suggested_params()
107
- return params.gen in ["mainnet-v1.0", "mainnet-v1", "mainnet"]
108
-
109
-
110
- def is_testnet(client: AlgodClient) -> bool:
111
- """Returns True if client genesis is `testnet-v1`"""
112
- params = client.suggested_params()
113
- return params.gen in ["testnet-v1.0", "testnet-v1", "testnet"]
114
-
115
-
116
- def get_kmd_client_from_algod_client(client: AlgodClient) -> KMDClient:
117
- """Returns an {py:class}`algosdk.kmd.KMDClient` from supplied `client`
118
-
119
- Will use the same address as provided `client` but on port specified by `KMD_PORT` environment variable,
120
- or 4002 by default"""
121
- # We can only use Kmd on the LocalNet otherwise it's not exposed so this makes some assumptions
122
- # (e.g. same token and server as algod and port 4002 by default)
123
- port = os.getenv("KMD_PORT", "4002")
124
- server = _replace_kmd_port(client.algod_address, port)
125
- return KMDClient(client.algod_token, server) # type: ignore[no-untyped-call]
126
-
127
-
128
- def _replace_kmd_port(address: str, port: str) -> str:
129
- parsed_algod = parse.urlparse(address)
130
- kmd_host = parsed_algod.netloc.split(":", maxsplit=1)[0] + f":{port}"
131
- kmd_parsed = parsed_algod._replace(netloc=kmd_host)
132
- return parse.urlunparse(kmd_parsed)
133
-
134
-
135
- def _get_config_from_environment(environment_prefix: str) -> AlgoClientConfig:
136
- server = os.getenv(f"{environment_prefix}_SERVER")
137
- if server is None:
138
- raise Exception(f"Server environment variable not set: {environment_prefix}_SERVER")
139
- port = os.getenv(f"{environment_prefix}_PORT")
140
- if port:
141
- parsed = parse.urlparse(server)
142
- server = parsed._replace(netloc=f"{parsed.hostname}:{port}").geturl()
143
- return AlgoClientConfig(server, os.getenv(f"{environment_prefix}_TOKEN", ""))
144
-
145
-
146
- def _is_pure_stake_url(url: str) -> bool:
147
- parsed = parse.urlparse(url)
148
- host = parsed.netloc.split(":")[0].lower()
149
- return host.endswith(_PURE_STAKE_HOST)
150
-
151
-
152
- def _get_headers(config: AlgoClientConfig, default_auth_header: str) -> dict[str, str] | None:
153
- auth_header = "X-API-Key" if _is_pure_stake_url(config.server) else default_auth_header
154
- return {auth_header: config.token}
9
+ from algokit_utils._legacy_v2.network_clients import * # noqa: F403, E402
@@ -0,0 +1,2 @@
1
+ from algokit_utils.protocols.account import * # noqa: F403
2
+ from algokit_utils.protocols.typed_clients import * # noqa: F403
@@ -0,0 +1,22 @@
1
+ from typing import Protocol, runtime_checkable
2
+
3
+ from algosdk.atomic_transaction_composer import TransactionSigner
4
+
5
+ __all__ = ["TransactionSignerAccountProtocol"]
6
+
7
+
8
+ @runtime_checkable
9
+ class TransactionSignerAccountProtocol(Protocol):
10
+ """An account that has a transaction signer.
11
+ Implemented by SigningAccount, LogicSigAccount, MultiSigAccount and TransactionSignerAccount abstractions.
12
+ """
13
+
14
+ @property
15
+ def address(self) -> str:
16
+ """The address of the account."""
17
+ ...
18
+
19
+ @property
20
+ def signer(self) -> TransactionSigner:
21
+ """The transaction signer for the account."""
22
+ ...