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
@@ -1,178 +1,10 @@
1
- import contextlib
2
- import enum
3
- import logging
4
- import os
5
- from dataclasses import dataclass
1
+ import warnings
6
2
 
7
- import httpx
3
+ warnings.warn(
4
+ "The legacy v2 dispenser api module is deprecated and will be removed in a future version. "
5
+ "Import from 'algokit_utils.clients.dispenser_api_client' instead.",
6
+ DeprecationWarning,
7
+ stacklevel=2,
8
+ )
8
9
 
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- class DispenserApiConfig:
13
- BASE_URL = "https://api.dispenser.algorandfoundation.tools"
14
-
15
-
16
- class DispenserAssetName(enum.IntEnum):
17
- ALGO = 0
18
-
19
-
20
- @dataclass
21
- class DispenserAsset:
22
- asset_id: int
23
- decimals: int
24
- description: str
25
-
26
-
27
- @dataclass
28
- class DispenserFundResponse:
29
- tx_id: str
30
- amount: int
31
-
32
-
33
- @dataclass
34
- class DispenserLimitResponse:
35
- amount: int
36
-
37
-
38
- DISPENSER_ASSETS = {
39
- DispenserAssetName.ALGO: DispenserAsset(
40
- asset_id=0,
41
- decimals=6,
42
- description="Algo",
43
- ),
44
- }
45
- DISPENSER_REQUEST_TIMEOUT = 15
46
- DISPENSER_ACCESS_TOKEN_KEY = "ALGOKIT_DISPENSER_ACCESS_TOKEN"
47
-
48
-
49
- class TestNetDispenserApiClient:
50
- """
51
- Client for interacting with the [AlgoKit TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md).
52
- To get started create a new access token via `algokit dispenser login --ci`
53
- and pass it to the client constructor as `auth_token`.
54
- Alternatively set the access token as environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`,
55
- and it will be auto loaded. If both are set, the constructor argument takes precedence.
56
-
57
- Default request timeout is 15 seconds. Modify by passing `request_timeout` to the constructor.
58
- """
59
-
60
- auth_token: str
61
- request_timeout = DISPENSER_REQUEST_TIMEOUT
62
-
63
- def __init__(self, auth_token: str | None = None, request_timeout: int = DISPENSER_REQUEST_TIMEOUT):
64
- auth_token_from_env = os.getenv(DISPENSER_ACCESS_TOKEN_KEY)
65
-
66
- if auth_token:
67
- self.auth_token = auth_token
68
- elif auth_token_from_env:
69
- self.auth_token = auth_token_from_env
70
- else:
71
- raise Exception(
72
- f"Can't init AlgoKit TestNet Dispenser API client "
73
- f"because neither environment variable {DISPENSER_ACCESS_TOKEN_KEY} or "
74
- "the auth_token were provided."
75
- )
76
-
77
- self.request_timeout = request_timeout
78
-
79
- def _process_dispenser_request(
80
- self, *, auth_token: str, url_suffix: str, data: dict | None = None, method: str = "POST"
81
- ) -> httpx.Response:
82
- """
83
- Generalized method to process http requests to dispenser API
84
- """
85
-
86
- headers = {"Authorization": f"Bearer {(auth_token)}"}
87
-
88
- # Set request arguments
89
- request_args = {
90
- "url": f"{DispenserApiConfig.BASE_URL}/{url_suffix}",
91
- "headers": headers,
92
- "timeout": self.request_timeout,
93
- }
94
-
95
- if method.upper() != "GET" and data is not None:
96
- request_args["json"] = data
97
-
98
- try:
99
- response: httpx.Response = getattr(httpx, method.lower())(**request_args)
100
- response.raise_for_status()
101
- return response
102
-
103
- except httpx.HTTPStatusError as err:
104
- error_message = f"Error processing dispenser API request: {err.response.status_code}"
105
- error_response = None
106
- with contextlib.suppress(Exception):
107
- error_response = err.response.json()
108
-
109
- if error_response and error_response.get("code"):
110
- error_message = error_response.get("code")
111
-
112
- elif err.response.status_code == httpx.codes.BAD_REQUEST:
113
- error_message = err.response.json()["message"]
114
-
115
- raise Exception(error_message) from err
116
-
117
- except Exception as err:
118
- error_message = "Error processing dispenser API request"
119
- logger.debug(f"{error_message}: {err}", exc_info=True)
120
- raise err
121
-
122
- def fund(self, address: str, amount: int, asset_id: int) -> DispenserFundResponse:
123
- """
124
- Fund an account with Algos from the dispenser API
125
- """
126
-
127
- try:
128
- response = self._process_dispenser_request(
129
- auth_token=self.auth_token,
130
- url_suffix=f"fund/{asset_id}",
131
- data={"receiver": address, "amount": amount, "assetID": asset_id},
132
- method="POST",
133
- )
134
-
135
- content = response.json()
136
- return DispenserFundResponse(tx_id=content["txID"], amount=content["amount"])
137
-
138
- except Exception as err:
139
- logger.exception(f"Error funding account {address}: {err}")
140
- raise err
141
-
142
- def refund(self, refund_txn_id: str) -> None:
143
- """
144
- Register a refund for a transaction with the dispenser API
145
- """
146
-
147
- try:
148
- self._process_dispenser_request(
149
- auth_token=self.auth_token,
150
- url_suffix="refund",
151
- data={"refundTransactionID": refund_txn_id},
152
- method="POST",
153
- )
154
-
155
- except Exception as err:
156
- logger.exception(f"Error issuing refund for txn_id {refund_txn_id}: {err}")
157
- raise err
158
-
159
- def get_limit(
160
- self,
161
- address: str,
162
- ) -> DispenserLimitResponse:
163
- """
164
- Get current limit for an account with Algos from the dispenser API
165
- """
166
-
167
- try:
168
- response = self._process_dispenser_request(
169
- auth_token=self.auth_token,
170
- url_suffix=f"fund/{DISPENSER_ASSETS[DispenserAssetName.ALGO].asset_id}/limit",
171
- method="GET",
172
- )
173
- content = response.json()
174
-
175
- return DispenserLimitResponse(amount=content["amount"])
176
- except Exception as err:
177
- logger.exception(f"Error setting limit for account {address}: {err}")
178
- raise err
10
+ from algokit_utils.clients.dispenser_api_client import * # noqa: F403, E402
@@ -0,0 +1 @@
1
+ from algokit_utils.errors.logic_error import * # noqa: F403
@@ -0,0 +1,121 @@
1
+ import base64
2
+ import re
3
+ from collections.abc import Callable
4
+ from copy import copy
5
+ from typing import TYPE_CHECKING, TypedDict
6
+
7
+ from algosdk.atomic_transaction_composer import (
8
+ SimulateAtomicTransactionResponse,
9
+ )
10
+
11
+ from algokit_utils.models.simulate import SimulationTrace
12
+
13
+ if TYPE_CHECKING:
14
+ from algosdk.source_map import SourceMap as AlgoSourceMap
15
+ __all__ = [
16
+ "LogicError",
17
+ "LogicErrorData",
18
+ "parse_logic_error",
19
+ ]
20
+
21
+
22
+ LOGIC_ERROR = (
23
+ ".*transaction (?P<transaction_id>[A-Z0-9]+): logic eval error: (?P<message>.*). Details: .*pc=(?P<pc>[0-9]+).*"
24
+ )
25
+
26
+
27
+ class LogicErrorData(TypedDict):
28
+ transaction_id: str
29
+ message: str
30
+ pc: int
31
+
32
+
33
+ def parse_logic_error(
34
+ error_str: str,
35
+ ) -> LogicErrorData | None:
36
+ match = re.match(LOGIC_ERROR, error_str)
37
+ if match is None:
38
+ return None
39
+
40
+ return {
41
+ "transaction_id": match.group("transaction_id"),
42
+ "message": match.group("message"),
43
+ "pc": int(match.group("pc")),
44
+ }
45
+
46
+
47
+ class LogicError(Exception):
48
+ def __init__(
49
+ self,
50
+ *,
51
+ logic_error_str: str,
52
+ program: str,
53
+ source_map: "AlgoSourceMap | None",
54
+ transaction_id: str,
55
+ message: str,
56
+ pc: int,
57
+ logic_error: Exception | None = None,
58
+ traces: list[SimulationTrace] | None = None,
59
+ get_line_for_pc: Callable[[int], int | None] | None = None,
60
+ ):
61
+ self.logic_error = logic_error
62
+ self.logic_error_str = logic_error_str
63
+ try:
64
+ self.program = base64.b64decode(program).decode("utf-8")
65
+ except Exception:
66
+ self.program = program
67
+ self.source_map = source_map
68
+ self.lines = self.program.split("\n")
69
+ self.transaction_id = transaction_id
70
+ self.message = message
71
+ self.pc = pc
72
+ self.traces = traces
73
+ self.line_no = (
74
+ self.source_map.get_line_for_pc(self.pc)
75
+ if self.source_map
76
+ else get_line_for_pc(self.pc)
77
+ if get_line_for_pc
78
+ else None
79
+ )
80
+
81
+ def __str__(self) -> str:
82
+ return (
83
+ f"Txn {self.transaction_id} had error '{self.message}' at PC {self.pc}"
84
+ + (":" if self.line_no is None else f" and Source Line {self.line_no}:")
85
+ + f"\n{self.trace()}"
86
+ )
87
+
88
+ def trace(self, lines: int = 5) -> str:
89
+ if self.line_no is None:
90
+ return """
91
+ Could not determine TEAL source line for the error as no approval source map was provided, to receive a trace of the
92
+ error please provide an approval SourceMap. Either by:
93
+ 1.Providing template_values when creating the ApplicationClient, so a SourceMap can be obtained automatically OR
94
+ 2.Set approval_source_map from a previously compiled approval program OR
95
+ 3.Import a previously exported source map using import_source_map"""
96
+
97
+ program_lines = copy(self.lines)
98
+ program_lines[self.line_no] += "\t\t<-- Error"
99
+ lines_before = max(0, self.line_no - lines)
100
+ lines_after = min(len(program_lines), self.line_no + lines)
101
+ return "\n\t" + "\n\t".join(program_lines[lines_before:lines_after])
102
+
103
+
104
+ def create_simulate_traces_for_logic_error(simulate: SimulateAtomicTransactionResponse) -> list[SimulationTrace]:
105
+ traces = []
106
+ if hasattr(simulate, "simulate_response") and hasattr(simulate, "failed_at") and simulate.failed_at:
107
+ for txn_group in simulate.simulate_response["txn-groups"]:
108
+ app_budget_added = txn_group.get("app-budget-added", None)
109
+ app_budget_consumed = txn_group.get("app-budget-consumed", None)
110
+ failure_message = txn_group.get("failure-message", None)
111
+ txn_result = txn_group.get("txn-results", [{}])[0]
112
+ exec_trace = txn_result.get("exec-trace", {})
113
+ traces.append(
114
+ SimulationTrace(
115
+ app_budget_added=app_budget_added,
116
+ app_budget_consumed=app_budget_consumed,
117
+ failure_message=failure_message,
118
+ exec_trace=exec_trace,
119
+ )
120
+ )
121
+ return traces
@@ -1,85 +1,10 @@
1
- import re
2
- from copy import copy
3
- from typing import TYPE_CHECKING, TypedDict
1
+ import warnings
4
2
 
5
- from algokit_utils.models import SimulationTrace
6
-
7
- if TYPE_CHECKING:
8
- from algosdk.source_map import SourceMap as AlgoSourceMap
9
-
10
- __all__ = [
11
- "LogicError",
12
- "parse_logic_error",
13
- ]
14
-
15
- LOGIC_ERROR = (
16
- ".*transaction (?P<transaction_id>[A-Z0-9]+): logic eval error: (?P<message>.*). Details: .*pc=(?P<pc>[0-9]+).*"
3
+ warnings.warn(
4
+ "The legacy v2 logic error module is deprecated and will be removed in a future version. "
5
+ "Use 'from algokit_utils.errors import LogicError' instead.",
6
+ DeprecationWarning,
7
+ stacklevel=2,
17
8
  )
18
9
 
19
-
20
- class LogicErrorData(TypedDict):
21
- transaction_id: str
22
- message: str
23
- pc: int
24
-
25
-
26
- def parse_logic_error(
27
- error_str: str,
28
- ) -> LogicErrorData | None:
29
- match = re.match(LOGIC_ERROR, error_str)
30
- if match is None:
31
- return None
32
-
33
- return {
34
- "transaction_id": match.group("transaction_id"),
35
- "message": match.group("message"),
36
- "pc": int(match.group("pc")),
37
- }
38
-
39
-
40
- class LogicError(Exception):
41
- def __init__( # noqa: PLR0913
42
- self,
43
- *,
44
- logic_error_str: str,
45
- program: str,
46
- source_map: "AlgoSourceMap | None",
47
- transaction_id: str,
48
- message: str,
49
- pc: int,
50
- logic_error: Exception | None = None,
51
- traces: list[SimulationTrace] | None = None,
52
- ):
53
- self.logic_error = logic_error
54
- self.logic_error_str = logic_error_str
55
- self.program = program
56
- self.source_map = source_map
57
- self.lines = program.split("\n")
58
- self.transaction_id = transaction_id
59
- self.message = message
60
- self.pc = pc
61
- self.traces = traces
62
-
63
- self.line_no = self.source_map.get_line_for_pc(self.pc) if self.source_map else None
64
-
65
- def __str__(self) -> str:
66
- return (
67
- f"Txn {self.transaction_id} had error '{self.message}' at PC {self.pc}"
68
- + (":" if self.line_no is None else f" and Source Line {self.line_no}:")
69
- + f"\n{self.trace()}"
70
- )
71
-
72
- def trace(self, lines: int = 5) -> str:
73
- if self.line_no is None:
74
- return """
75
- Could not determine TEAL source line for the error as no approval source map was provided, to receive a trace of the
76
- error please provide an approval SourceMap. Either by:
77
- 1.) Providing template_values when creating the ApplicationClient, so a SourceMap can be obtained automatically OR
78
- 2.) Set approval_source_map from a previously compiled approval program OR
79
- 3.) Import a previously exported source map using import_source_map"""
80
-
81
- program_lines = copy(self.lines)
82
- program_lines[self.line_no] += "\t\t<-- Error"
83
- lines_before = max(0, self.line_no - lines)
84
- lines_after = min(len(program_lines), self.line_no + lines)
85
- return "\n\t" + "\n\t".join(program_lines[lines_before:lines_after])
10
+ from algokit_utils.errors.logic_error import * # noqa: F403, E402
@@ -0,0 +1,8 @@
1
+ from algokit_utils._legacy_v2.models import * # noqa: F403
2
+ from algokit_utils.models.account import * # noqa: F403
3
+ from algokit_utils.models.amount import * # noqa: F403
4
+ from algokit_utils.models.application import * # noqa: F403
5
+ from algokit_utils.models.network import * # noqa: F403
6
+ from algokit_utils.models.simulate import * # noqa: F403
7
+ from algokit_utils.models.state import * # noqa: F403
8
+ from algokit_utils.models.transaction import * # noqa: F403
@@ -0,0 +1,217 @@
1
+ import dataclasses
2
+
3
+ import algosdk
4
+ import algosdk.atomic_transaction_composer
5
+ from algosdk.atomic_transaction_composer import AccountTransactionSigner, LogicSigTransactionSigner, TransactionSigner
6
+ from algosdk.transaction import LogicSigAccount as AlgosdkLogicSigAccount
7
+ from algosdk.transaction import Multisig, MultisigTransaction
8
+ from typing_extensions import deprecated
9
+
10
+ __all__ = [
11
+ "DISPENSER_ACCOUNT_NAME",
12
+ "LogicSigAccount",
13
+ "MultiSigAccount",
14
+ "MultisigMetadata",
15
+ "SigningAccount",
16
+ "TransactionSignerAccount",
17
+ ]
18
+
19
+
20
+ DISPENSER_ACCOUNT_NAME = "DISPENSER"
21
+
22
+
23
+ @dataclasses.dataclass(kw_only=True)
24
+ class TransactionSignerAccount:
25
+ """A basic transaction signer account."""
26
+
27
+ address: str
28
+ signer: TransactionSigner
29
+
30
+ def __post_init__(self) -> None:
31
+ if not isinstance(self.address, str):
32
+ raise TypeError("Address must be a string")
33
+ if not isinstance(self.signer, TransactionSigner):
34
+ raise TypeError("Signer must be a TransactionSigner instance")
35
+
36
+
37
+ @dataclasses.dataclass(kw_only=True)
38
+ class SigningAccount:
39
+ """Holds the private key and address for an account.
40
+
41
+ Provides access to the account's private key, address, public key and transaction signer.
42
+ """
43
+
44
+ private_key: str
45
+ """Base64 encoded private key"""
46
+ address: str = dataclasses.field(default="")
47
+ """Address for this account"""
48
+
49
+ def __post_init__(self) -> None:
50
+ if not self.address:
51
+ self.address = str(algosdk.account.address_from_private_key(self.private_key))
52
+
53
+ @property
54
+ def public_key(self) -> bytes:
55
+ """The public key for this account.
56
+
57
+ :return: The public key as bytes
58
+ """
59
+ public_key = algosdk.encoding.decode_address(self.address)
60
+ assert isinstance(public_key, bytes)
61
+ return public_key
62
+
63
+ @property
64
+ def signer(self) -> AccountTransactionSigner:
65
+ """Get an AccountTransactionSigner for this account.
66
+
67
+ :return: A transaction signer for this account
68
+ """
69
+ return AccountTransactionSigner(self.private_key)
70
+
71
+ @deprecated(
72
+ "Use `algorand.account.random()` or `SigningAccount(private_key=algosdk.account.generate_account()[0])` instead"
73
+ )
74
+ @staticmethod
75
+ def new_account() -> "SigningAccount":
76
+ """Create a new random account.
77
+
78
+ :return: A new Account instance
79
+ """
80
+ private_key, address = algosdk.account.generate_account()
81
+ return SigningAccount(private_key=private_key)
82
+
83
+
84
+ @dataclasses.dataclass(kw_only=True)
85
+ class MultisigMetadata:
86
+ """Metadata for a multisig account.
87
+
88
+ Contains the version, threshold and addresses for a multisig account.
89
+ """
90
+
91
+ version: int
92
+ threshold: int
93
+ addresses: list[str]
94
+
95
+
96
+ @dataclasses.dataclass(kw_only=True)
97
+ class MultiSigAccount:
98
+ """Account wrapper that supports partial or full multisig signing.
99
+
100
+ Provides functionality to manage and sign transactions for a multisig account.
101
+
102
+ :param multisig_params: The parameters for the multisig account
103
+ :param signing_accounts: The list of accounts that can sign
104
+ """
105
+
106
+ _params: MultisigMetadata
107
+ _signing_accounts: list[SigningAccount]
108
+ _addr: str
109
+ _signer: TransactionSigner
110
+ _multisig: Multisig
111
+
112
+ def __init__(self, multisig_params: MultisigMetadata, signing_accounts: list[SigningAccount]) -> None:
113
+ self._params = multisig_params
114
+ self._signing_accounts = signing_accounts
115
+ self._multisig = Multisig(multisig_params.version, multisig_params.threshold, multisig_params.addresses)
116
+ self._addr = str(self._multisig.address())
117
+ self._signer = algosdk.atomic_transaction_composer.MultisigTransactionSigner(
118
+ self._multisig,
119
+ [account.private_key for account in signing_accounts],
120
+ )
121
+
122
+ @property
123
+ def multisig(self) -> Multisig:
124
+ """Get the underlying `algosdk.transaction.Multisig` object instance.
125
+
126
+ :return: The `algosdk.transaction.Multisig` object instance
127
+ """
128
+ return self._multisig
129
+
130
+ @property
131
+ def params(self) -> MultisigMetadata:
132
+ """Get the parameters for the multisig account.
133
+
134
+ :return: The multisig account parameters
135
+ """
136
+ return self._params
137
+
138
+ @property
139
+ def signing_accounts(self) -> list[SigningAccount]:
140
+ """Get the list of accounts that are present to sign.
141
+
142
+ :return: The list of signing accounts
143
+ """
144
+ return self._signing_accounts
145
+
146
+ @property
147
+ def address(self) -> str:
148
+ """Get the address of the multisig account.
149
+
150
+ :return: The multisig account address
151
+ """
152
+ return self._addr
153
+
154
+ @property
155
+ def signer(self) -> TransactionSigner:
156
+ """Get the transaction signer for this multisig account.
157
+
158
+ :return: The multisig transaction signer
159
+ """
160
+ return self._signer
161
+
162
+ def sign(self, transaction: algosdk.transaction.Transaction) -> MultisigTransaction:
163
+ """Sign the given transaction with all present signers.
164
+
165
+ :param transaction: Either a transaction object or a raw, partially signed transaction
166
+ :return: The transaction signed by the present signers
167
+ """
168
+ msig_txn = MultisigTransaction(
169
+ transaction,
170
+ self._multisig,
171
+ )
172
+ for signer in self._signing_accounts:
173
+ msig_txn.sign(signer.private_key)
174
+
175
+ return msig_txn
176
+
177
+
178
+ @dataclasses.dataclass(kw_only=True)
179
+ class LogicSigAccount:
180
+ """Account wrapper that supports logic sig signing.
181
+
182
+ Provides functionality to manage and sign transactions for a logic sig account.
183
+ """
184
+
185
+ _signer: LogicSigTransactionSigner
186
+
187
+ def __init__(self, program: bytes, args: list[bytes] | None) -> None:
188
+ self._signer = LogicSigTransactionSigner(AlgosdkLogicSigAccount(program, args))
189
+
190
+ @property
191
+ def lsig(self) -> AlgosdkLogicSigAccount:
192
+ """Get the underlying `algosdk.transaction.LogicSigAccount` object instance.
193
+
194
+ :return: The `algosdk.transaction.LogicSigAccount` object instance
195
+ """
196
+ return self._signer.lsig
197
+
198
+ @property
199
+ def address(self) -> str:
200
+ """Get the address of the logic sig account.
201
+
202
+ If the LogicSig is delegated to another account, this will return the address of that account.
203
+
204
+ If the LogicSig is not delegated to another account, this will return an escrow address that is the hash of
205
+ the LogicSig's program code.
206
+
207
+ :return: The logic sig account address
208
+ """
209
+ return self._signer.lsig.address()
210
+
211
+ @property
212
+ def signer(self) -> LogicSigTransactionSigner:
213
+ """Get the transaction signer for this multisig account.
214
+
215
+ :return: The multisig transaction signer
216
+ """
217
+ return self._signer