algokit-utils 2.4.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.
- algokit_utils/__init__.py +23 -181
- algokit_utils/_debugging.py +89 -45
- algokit_utils/_legacy_v2/__init__.py +177 -0
- algokit_utils/{_ensure_funded.py → _legacy_v2/_ensure_funded.py} +19 -18
- algokit_utils/{_transfer.py → _legacy_v2/_transfer.py} +24 -23
- algokit_utils/_legacy_v2/account.py +203 -0
- algokit_utils/_legacy_v2/application_client.py +1471 -0
- algokit_utils/_legacy_v2/application_specification.py +21 -0
- algokit_utils/_legacy_v2/asset.py +168 -0
- algokit_utils/_legacy_v2/common.py +28 -0
- algokit_utils/_legacy_v2/deploy.py +822 -0
- algokit_utils/_legacy_v2/logic_error.py +14 -0
- algokit_utils/{models.py → _legacy_v2/models.py} +16 -45
- algokit_utils/_legacy_v2/network_clients.py +140 -0
- algokit_utils/account.py +12 -183
- algokit_utils/accounts/__init__.py +2 -0
- algokit_utils/accounts/account_manager.py +909 -0
- algokit_utils/accounts/kmd_account_manager.py +159 -0
- algokit_utils/algorand.py +265 -0
- algokit_utils/application_client.py +9 -1447
- algokit_utils/application_specification.py +39 -197
- algokit_utils/applications/__init__.py +7 -0
- algokit_utils/applications/abi.py +276 -0
- algokit_utils/applications/app_client.py +2056 -0
- algokit_utils/applications/app_deployer.py +600 -0
- algokit_utils/applications/app_factory.py +826 -0
- algokit_utils/applications/app_manager.py +470 -0
- algokit_utils/applications/app_spec/__init__.py +2 -0
- algokit_utils/applications/app_spec/arc32.py +207 -0
- algokit_utils/applications/app_spec/arc56.py +1023 -0
- algokit_utils/applications/enums.py +40 -0
- algokit_utils/asset.py +32 -168
- algokit_utils/assets/__init__.py +1 -0
- algokit_utils/assets/asset_manager.py +320 -0
- algokit_utils/beta/_utils.py +36 -0
- algokit_utils/beta/account_manager.py +4 -195
- algokit_utils/beta/algorand_client.py +4 -314
- algokit_utils/beta/client_manager.py +5 -74
- algokit_utils/beta/composer.py +5 -712
- algokit_utils/clients/__init__.py +2 -0
- algokit_utils/clients/client_manager.py +656 -0
- algokit_utils/clients/dispenser_api_client.py +192 -0
- algokit_utils/common.py +8 -26
- algokit_utils/config.py +71 -18
- algokit_utils/deploy.py +7 -894
- algokit_utils/dispenser_api.py +8 -176
- algokit_utils/errors/__init__.py +1 -0
- algokit_utils/errors/logic_error.py +121 -0
- algokit_utils/logic_error.py +7 -82
- algokit_utils/models/__init__.py +8 -0
- algokit_utils/models/account.py +193 -0
- algokit_utils/models/amount.py +198 -0
- algokit_utils/models/application.py +61 -0
- algokit_utils/models/network.py +25 -0
- algokit_utils/models/simulate.py +11 -0
- algokit_utils/models/state.py +59 -0
- algokit_utils/models/transaction.py +100 -0
- algokit_utils/network_clients.py +7 -128
- algokit_utils/protocols/__init__.py +2 -0
- algokit_utils/protocols/account.py +22 -0
- algokit_utils/protocols/typed_clients.py +108 -0
- algokit_utils/transactions/__init__.py +3 -0
- algokit_utils/transactions/transaction_composer.py +2293 -0
- algokit_utils/transactions/transaction_creator.py +156 -0
- algokit_utils/transactions/transaction_sender.py +574 -0
- {algokit_utils-2.4.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/METADATA +11 -7
- algokit_utils-3.0.0b2.dist-info/RECORD +70 -0
- {algokit_utils-2.4.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/WHEEL +1 -1
- algokit_utils-2.4.0b1.dist-info/RECORD +0 -24
- {algokit_utils-2.4.0b1.dist-info → algokit_utils-3.0.0b2.dist-info}/LICENSE +0 -0
algokit_utils/dispenser_api.py
CHANGED
|
@@ -1,178 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import enum
|
|
3
|
-
import logging
|
|
4
|
-
import os
|
|
5
|
-
from dataclasses import dataclass
|
|
1
|
+
import warnings
|
|
6
2
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
algokit_utils/logic_error.py
CHANGED
|
@@ -1,85 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
from copy import copy
|
|
3
|
-
from typing import TYPE_CHECKING, TypedDict
|
|
1
|
+
import warnings
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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,193 @@
|
|
|
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
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"DISPENSER_ACCOUNT_NAME",
|
|
11
|
+
"MultiSigAccount",
|
|
12
|
+
"MultisigMetadata",
|
|
13
|
+
"SigningAccount",
|
|
14
|
+
"TransactionSignerAccount",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DISPENSER_ACCOUNT_NAME = "DISPENSER"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclasses.dataclass(kw_only=True)
|
|
22
|
+
class TransactionSignerAccount:
|
|
23
|
+
"""A basic transaction signer account."""
|
|
24
|
+
|
|
25
|
+
address: str
|
|
26
|
+
signer: TransactionSigner
|
|
27
|
+
|
|
28
|
+
def __post_init__(self) -> None:
|
|
29
|
+
if not isinstance(self.address, str):
|
|
30
|
+
raise TypeError("Address must be a string")
|
|
31
|
+
if not isinstance(self.signer, TransactionSigner):
|
|
32
|
+
raise TypeError("Signer must be a TransactionSigner instance")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclasses.dataclass(kw_only=True)
|
|
36
|
+
class SigningAccount:
|
|
37
|
+
"""Holds the private key and address for an account.
|
|
38
|
+
|
|
39
|
+
Provides access to the account's private key, address, public key and transaction signer.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
private_key: str
|
|
43
|
+
"""Base64 encoded private key"""
|
|
44
|
+
address: str = dataclasses.field(default="")
|
|
45
|
+
"""Address for this account"""
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
if not self.address:
|
|
49
|
+
self.address = str(algosdk.account.address_from_private_key(self.private_key))
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def public_key(self) -> bytes:
|
|
53
|
+
"""The public key for this account.
|
|
54
|
+
|
|
55
|
+
:return: The public key as bytes
|
|
56
|
+
"""
|
|
57
|
+
public_key = algosdk.encoding.decode_address(self.address)
|
|
58
|
+
assert isinstance(public_key, bytes)
|
|
59
|
+
return public_key
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def signer(self) -> AccountTransactionSigner:
|
|
63
|
+
"""Get an AccountTransactionSigner for this account.
|
|
64
|
+
|
|
65
|
+
:return: A transaction signer for this account
|
|
66
|
+
"""
|
|
67
|
+
return AccountTransactionSigner(self.private_key)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def new_account() -> "SigningAccount":
|
|
71
|
+
"""Create a new random account.
|
|
72
|
+
|
|
73
|
+
:return: A new Account instance
|
|
74
|
+
"""
|
|
75
|
+
private_key, address = algosdk.account.generate_account()
|
|
76
|
+
return SigningAccount(private_key=private_key)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclasses.dataclass(kw_only=True)
|
|
80
|
+
class MultisigMetadata:
|
|
81
|
+
"""Metadata for a multisig account.
|
|
82
|
+
|
|
83
|
+
Contains the version, threshold and addresses for a multisig account.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
version: int
|
|
87
|
+
threshold: int
|
|
88
|
+
addresses: list[str]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclasses.dataclass(kw_only=True)
|
|
92
|
+
class MultiSigAccount:
|
|
93
|
+
"""Account wrapper that supports partial or full multisig signing.
|
|
94
|
+
|
|
95
|
+
Provides functionality to manage and sign transactions for a multisig account.
|
|
96
|
+
|
|
97
|
+
:param multisig_params: The parameters for the multisig account
|
|
98
|
+
:param signing_accounts: The list of accounts that can sign
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
_params: MultisigMetadata
|
|
102
|
+
_signing_accounts: list[SigningAccount]
|
|
103
|
+
_addr: str
|
|
104
|
+
_signer: TransactionSigner
|
|
105
|
+
_multisig: Multisig
|
|
106
|
+
|
|
107
|
+
def __init__(self, multisig_params: MultisigMetadata, signing_accounts: list[SigningAccount]) -> None:
|
|
108
|
+
self._params = multisig_params
|
|
109
|
+
self._signing_accounts = signing_accounts
|
|
110
|
+
self._multisig = Multisig(multisig_params.version, multisig_params.threshold, multisig_params.addresses)
|
|
111
|
+
self._addr = str(self._multisig.address())
|
|
112
|
+
self._signer = algosdk.atomic_transaction_composer.MultisigTransactionSigner(
|
|
113
|
+
self._multisig,
|
|
114
|
+
[account.private_key for account in signing_accounts],
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def params(self) -> MultisigMetadata:
|
|
119
|
+
"""Get the parameters for the multisig account.
|
|
120
|
+
|
|
121
|
+
:return: The multisig account parameters
|
|
122
|
+
"""
|
|
123
|
+
return self._params
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def signing_accounts(self) -> list[SigningAccount]:
|
|
127
|
+
"""Get the list of accounts that are present to sign.
|
|
128
|
+
|
|
129
|
+
:return: The list of signing accounts
|
|
130
|
+
"""
|
|
131
|
+
return self._signing_accounts
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def address(self) -> str:
|
|
135
|
+
"""Get the address of the multisig account.
|
|
136
|
+
|
|
137
|
+
:return: The multisig account address
|
|
138
|
+
"""
|
|
139
|
+
return self._addr
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def signer(self) -> TransactionSigner:
|
|
143
|
+
"""Get the transaction signer for this multisig account.
|
|
144
|
+
|
|
145
|
+
:return: The multisig transaction signer
|
|
146
|
+
"""
|
|
147
|
+
return self._signer
|
|
148
|
+
|
|
149
|
+
def sign(self, transaction: algosdk.transaction.Transaction) -> MultisigTransaction:
|
|
150
|
+
"""Sign the given transaction with all present signers.
|
|
151
|
+
|
|
152
|
+
:param transaction: Either a transaction object or a raw, partially signed transaction
|
|
153
|
+
:return: The transaction signed by the present signers
|
|
154
|
+
"""
|
|
155
|
+
msig_txn = MultisigTransaction(
|
|
156
|
+
transaction,
|
|
157
|
+
self._multisig,
|
|
158
|
+
)
|
|
159
|
+
for signer in self._signing_accounts:
|
|
160
|
+
msig_txn.sign(signer.private_key)
|
|
161
|
+
|
|
162
|
+
return msig_txn
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclasses.dataclass(kw_only=True)
|
|
166
|
+
class LogicSigAccount:
|
|
167
|
+
"""Account wrapper that supports logic sig signing.
|
|
168
|
+
|
|
169
|
+
Provides functionality to manage and sign transactions for a logic sig account.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
_account: AlgosdkLogicSigAccount
|
|
173
|
+
_signer: LogicSigTransactionSigner
|
|
174
|
+
|
|
175
|
+
def __init__(self, account: AlgosdkLogicSigAccount) -> None:
|
|
176
|
+
self._account = account
|
|
177
|
+
self._signer = LogicSigTransactionSigner(account)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def address(self) -> str:
|
|
181
|
+
"""Get the address of the multisig account.
|
|
182
|
+
|
|
183
|
+
:return: The multisig account address
|
|
184
|
+
"""
|
|
185
|
+
return self._account.address()
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def signer(self) -> LogicSigTransactionSigner:
|
|
189
|
+
"""Get the transaction signer for this multisig account.
|
|
190
|
+
|
|
191
|
+
:return: The multisig transaction signer
|
|
192
|
+
"""
|
|
193
|
+
return self._signer
|