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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
# NOTE: this is moved to a separate file to avoid circular imports
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OnSchemaBreak(Enum):
|
|
7
|
+
"""Action to take if an Application's schema has breaking changes"""
|
|
8
|
+
|
|
9
|
+
Fail = 0
|
|
10
|
+
"""Fail the deployment"""
|
|
11
|
+
ReplaceApp = 2
|
|
12
|
+
"""Create a new Application and delete the old Application in a single transaction"""
|
|
13
|
+
AppendApp = 3
|
|
14
|
+
"""Create a new Application"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OnUpdate(Enum):
|
|
18
|
+
"""Action to take if an Application has been updated"""
|
|
19
|
+
|
|
20
|
+
Fail = 0
|
|
21
|
+
"""Fail the deployment"""
|
|
22
|
+
UpdateApp = 1
|
|
23
|
+
"""Update the Application with the new approval and clear programs"""
|
|
24
|
+
ReplaceApp = 2
|
|
25
|
+
"""Create a new Application and delete the old Application in a single transaction"""
|
|
26
|
+
AppendApp = 3
|
|
27
|
+
"""Create a new application"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OperationPerformed(Enum):
|
|
31
|
+
"""Describes the actions taken during deployment"""
|
|
32
|
+
|
|
33
|
+
Nothing = 0
|
|
34
|
+
"""An existing Application was found"""
|
|
35
|
+
Create = 1
|
|
36
|
+
"""No existing Application was found, created a new Application"""
|
|
37
|
+
Update = 2
|
|
38
|
+
"""An existing Application was found, but was out of date, updated to latest version"""
|
|
39
|
+
Replace = 3
|
|
40
|
+
"""An existing Application was found, but was out of date, created a new Application and deleted the original"""
|
algokit_utils/asset.py
CHANGED
|
@@ -1,168 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _ensure_asset_balance_conditions(
|
|
34
|
-
algod_client: "AlgodClient", account: Account, asset_ids: list, validation_type: ValidationType
|
|
35
|
-
) -> None:
|
|
36
|
-
invalid_asset_ids = []
|
|
37
|
-
account_info = algod_client.account_info(account.address)
|
|
38
|
-
account_assets = account_info.get("assets", []) # type: ignore # noqa: PGH003
|
|
39
|
-
for asset_id in asset_ids:
|
|
40
|
-
asset_exists_in_account_info = any(asset["asset-id"] == asset_id for asset in account_assets)
|
|
41
|
-
if validation_type == ValidationType.OPTIN:
|
|
42
|
-
if asset_exists_in_account_info:
|
|
43
|
-
logger.debug(f"Asset {asset_id} is already opted in for account {account.address}")
|
|
44
|
-
invalid_asset_ids.append(asset_id)
|
|
45
|
-
|
|
46
|
-
elif validation_type == ValidationType.OPTOUT:
|
|
47
|
-
if not account_assets or not asset_exists_in_account_info:
|
|
48
|
-
logger.debug(f"Account {account.address} does not have asset {asset_id}")
|
|
49
|
-
invalid_asset_ids.append(asset_id)
|
|
50
|
-
else:
|
|
51
|
-
asset_balance = next((asset["amount"] for asset in account_assets if asset["asset-id"] == asset_id), 0)
|
|
52
|
-
if asset_balance != 0:
|
|
53
|
-
logger.debug(f"Asset {asset_id} balance is not zero")
|
|
54
|
-
invalid_asset_ids.append(asset_id)
|
|
55
|
-
|
|
56
|
-
if len(invalid_asset_ids) > 0:
|
|
57
|
-
action = "opted out" if validation_type == ValidationType.OPTOUT else "opted in"
|
|
58
|
-
condition_message = (
|
|
59
|
-
"their amount is zero and that the account has"
|
|
60
|
-
if validation_type == ValidationType.OPTOUT
|
|
61
|
-
else "they are valid and that the account has not"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
error_message = (
|
|
65
|
-
f"Assets {invalid_asset_ids} cannot be {action}. Ensure that "
|
|
66
|
-
f"{condition_message} previously opted into them."
|
|
67
|
-
)
|
|
68
|
-
raise ValueError(error_message)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def opt_in(algod_client: "AlgodClient", account: Account, asset_ids: list[int]) -> dict[int, str]:
|
|
72
|
-
"""
|
|
73
|
-
Opt-in to a list of assets on the Algorand blockchain. Before an account can receive a specific asset,
|
|
74
|
-
it must `opt-in` to receive it. An opt-in transaction places an asset holding of 0 into the account and increases
|
|
75
|
-
its minimum balance by [100,000 microAlgos](https://developer.algorand.org/docs/get-details/asa/#assets-overview).
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
algod_client (AlgodClient): An instance of the AlgodClient class from the algosdk library.
|
|
79
|
-
account (Account): An instance of the Account class representing the account that wants to opt-in to the assets.
|
|
80
|
-
asset_ids (list[int]): A list of integers representing the asset IDs to opt-in to.
|
|
81
|
-
Returns:
|
|
82
|
-
dict[int, str]: A dictionary where the keys are the asset IDs and the values
|
|
83
|
-
are the transaction IDs for opting-in to each asset.
|
|
84
|
-
"""
|
|
85
|
-
_ensure_account_is_valid(algod_client, account)
|
|
86
|
-
_ensure_asset_balance_conditions(algod_client, account, asset_ids, ValidationType.OPTIN)
|
|
87
|
-
suggested_params = algod_client.suggested_params()
|
|
88
|
-
result = {}
|
|
89
|
-
for i in range(0, len(asset_ids), TX_GROUP_LIMIT):
|
|
90
|
-
atc = AtomicTransactionComposer()
|
|
91
|
-
chunk = asset_ids[i : i + TX_GROUP_LIMIT]
|
|
92
|
-
for asset_id in chunk:
|
|
93
|
-
asset = algod_client.asset_info(asset_id)
|
|
94
|
-
xfer_txn = AssetTransferTxn(
|
|
95
|
-
sp=suggested_params,
|
|
96
|
-
sender=account.address,
|
|
97
|
-
receiver=account.address,
|
|
98
|
-
close_assets_to=None,
|
|
99
|
-
revocation_target=None,
|
|
100
|
-
amt=0,
|
|
101
|
-
note=f"opt in asset id ${asset_id}",
|
|
102
|
-
index=asset["index"], # type: ignore # noqa: PGH003
|
|
103
|
-
rekey_to=None,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
transaction_with_signer = TransactionWithSigner(
|
|
107
|
-
txn=xfer_txn,
|
|
108
|
-
signer=account.signer,
|
|
109
|
-
)
|
|
110
|
-
atc.add_transaction(transaction_with_signer)
|
|
111
|
-
atc.execute(algod_client, 4)
|
|
112
|
-
|
|
113
|
-
for index, asset_id in enumerate(chunk):
|
|
114
|
-
result[asset_id] = atc.tx_ids[index]
|
|
115
|
-
|
|
116
|
-
return result
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def opt_out(algod_client: "AlgodClient", account: Account, asset_ids: list[int]) -> dict[int, str]:
|
|
120
|
-
"""
|
|
121
|
-
Opt out from a list of Algorand Standard Assets (ASAs) by transferring them back to their creators.
|
|
122
|
-
The account also recovers the Minimum Balance Requirement for the asset (100,000 microAlgos)
|
|
123
|
-
The `optOut` function manages the opt-out process, permitting the account to discontinue holding a group of assets.
|
|
124
|
-
|
|
125
|
-
It's essential to note that an account can only opt_out of an asset if its balance of that asset is zero.
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
algod_client (AlgodClient): An instance of the AlgodClient class from the `algosdk` library.
|
|
129
|
-
account (Account): An instance of the Account class that holds the private key and address for an account.
|
|
130
|
-
asset_ids (list[int]): A list of integers representing the asset IDs of the ASAs to opt out from.
|
|
131
|
-
Returns:
|
|
132
|
-
dict[int, str]: A dictionary where the keys are the asset IDs and the values are the transaction IDs of
|
|
133
|
-
the executed transactions.
|
|
134
|
-
|
|
135
|
-
"""
|
|
136
|
-
_ensure_account_is_valid(algod_client, account)
|
|
137
|
-
_ensure_asset_balance_conditions(algod_client, account, asset_ids, ValidationType.OPTOUT)
|
|
138
|
-
suggested_params = algod_client.suggested_params()
|
|
139
|
-
result = {}
|
|
140
|
-
for i in range(0, len(asset_ids), TX_GROUP_LIMIT):
|
|
141
|
-
atc = AtomicTransactionComposer()
|
|
142
|
-
chunk = asset_ids[i : i + TX_GROUP_LIMIT]
|
|
143
|
-
for asset_id in chunk:
|
|
144
|
-
asset = algod_client.asset_info(asset_id)
|
|
145
|
-
asset_creator = asset["params"]["creator"] # type: ignore # noqa: PGH003
|
|
146
|
-
xfer_txn = AssetTransferTxn(
|
|
147
|
-
sp=suggested_params,
|
|
148
|
-
sender=account.address,
|
|
149
|
-
receiver=account.address,
|
|
150
|
-
close_assets_to=asset_creator,
|
|
151
|
-
revocation_target=None,
|
|
152
|
-
amt=0,
|
|
153
|
-
note=f"opt out asset id ${asset_id}",
|
|
154
|
-
index=asset["index"], # type: ignore # noqa: PGH003
|
|
155
|
-
rekey_to=None,
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
transaction_with_signer = TransactionWithSigner(
|
|
159
|
-
txn=xfer_txn,
|
|
160
|
-
signer=account.signer,
|
|
161
|
-
)
|
|
162
|
-
atc.add_transaction(transaction_with_signer)
|
|
163
|
-
atc.execute(algod_client, 4)
|
|
164
|
-
|
|
165
|
-
for index, asset_id in enumerate(chunk):
|
|
166
|
-
result[asset_id] = atc.tx_ids[index]
|
|
167
|
-
|
|
168
|
-
return result
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
warnings.warn(
|
|
4
|
+
"""The legacy v2 asset module is deprecated and will be removed in a future version.
|
|
5
|
+
|
|
6
|
+
Replacements for opt_in/opt_out functionality:
|
|
7
|
+
|
|
8
|
+
1. Using TransactionComposer:
|
|
9
|
+
composer.add_asset_opt_in(AssetOptInParams(
|
|
10
|
+
sender=account.address,
|
|
11
|
+
asset_id=123
|
|
12
|
+
))
|
|
13
|
+
composer.add_asset_opt_out(AssetOptOutParams(
|
|
14
|
+
sender=account.address,
|
|
15
|
+
asset_id=123,
|
|
16
|
+
creator=creator_address
|
|
17
|
+
))
|
|
18
|
+
|
|
19
|
+
2. Using AlgorandClient:
|
|
20
|
+
client.asset.opt_in(AssetOptInParams(...))
|
|
21
|
+
client.asset.opt_out(AssetOptOutParams(...))
|
|
22
|
+
|
|
23
|
+
3. For bulk operations:
|
|
24
|
+
client.asset.bulk_opt_in(account, [asset_ids])
|
|
25
|
+
client.asset.bulk_opt_out(account, [asset_ids])
|
|
26
|
+
|
|
27
|
+
Refer to AssetManager class from algokit_utils for more functionality.""",
|
|
28
|
+
DeprecationWarning,
|
|
29
|
+
stacklevel=2,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
from algokit_utils._legacy_v2.asset import * # noqa: F403, E402
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from algokit_utils.assets.asset_manager import * # noqa: F403
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import algosdk
|
|
5
|
+
from algosdk.atomic_transaction_composer import AccountTransactionSigner, TransactionSigner
|
|
6
|
+
from algosdk.v2client import algod
|
|
7
|
+
|
|
8
|
+
from algokit_utils.models.account import SigningAccount
|
|
9
|
+
from algokit_utils.models.amount import AlgoAmount
|
|
10
|
+
from algokit_utils.models.transaction import SendParams
|
|
11
|
+
from algokit_utils.transactions.transaction_composer import (
|
|
12
|
+
AssetOptInParams,
|
|
13
|
+
AssetOptOutParams,
|
|
14
|
+
TransactionComposer,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = ["AccountAssetInformation", "AssetInformation", "AssetManager", "BulkAssetOptInOutResult"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(kw_only=True, frozen=True)
|
|
21
|
+
class AccountAssetInformation:
|
|
22
|
+
"""Information about an account's holding of a particular asset.
|
|
23
|
+
|
|
24
|
+
:ivar asset_id: The ID of the asset
|
|
25
|
+
:ivar balance: The amount of the asset held by the account
|
|
26
|
+
:ivar frozen: Whether the asset is frozen for this account
|
|
27
|
+
:ivar round: The round this information was retrieved at
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
asset_id: int
|
|
31
|
+
balance: int
|
|
32
|
+
frozen: bool
|
|
33
|
+
round: int
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(kw_only=True, frozen=True)
|
|
37
|
+
class AssetInformation:
|
|
38
|
+
"""Information about an Algorand Standard Asset (ASA).
|
|
39
|
+
|
|
40
|
+
:ivar asset_id: The ID of the asset
|
|
41
|
+
:ivar creator: The address of the account that created the asset
|
|
42
|
+
:ivar total: The total amount of the smallest divisible units that were created of the asset
|
|
43
|
+
:ivar decimals: The amount of decimal places the asset was created with
|
|
44
|
+
:ivar default_frozen: Whether the asset was frozen by default for all accounts, defaults to None
|
|
45
|
+
:ivar manager: The address of the optional account that can manage the configuration of the asset and destroy it,
|
|
46
|
+
defaults to None
|
|
47
|
+
:ivar reserve: The address of the optional account that holds the reserve (uncirculated supply) units of the asset,
|
|
48
|
+
defaults to None
|
|
49
|
+
:ivar freeze: The address of the optional account that can be used to freeze or unfreeze holdings of this asset,
|
|
50
|
+
defaults to None
|
|
51
|
+
:ivar clawback: The address of the optional account that can clawback holdings of this asset from any account,
|
|
52
|
+
defaults to None
|
|
53
|
+
:ivar unit_name: The optional name of the unit of this asset (e.g. ticker name), defaults to None
|
|
54
|
+
:ivar unit_name_b64: The optional name of the unit of this asset as bytes, defaults to None
|
|
55
|
+
:ivar asset_name: The optional name of the asset, defaults to None
|
|
56
|
+
:ivar asset_name_b64: The optional name of the asset as bytes, defaults to None
|
|
57
|
+
:ivar url: Optional URL where more information about the asset can be retrieved, defaults to None
|
|
58
|
+
:ivar url_b64: Optional URL where more information about the asset can be retrieved as bytes, defaults to None
|
|
59
|
+
:ivar metadata_hash: 32-byte hash of some metadata that is relevant to the asset and/or asset holders,
|
|
60
|
+
defaults to None
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
asset_id: int
|
|
64
|
+
creator: str
|
|
65
|
+
total: int
|
|
66
|
+
decimals: int
|
|
67
|
+
default_frozen: bool | None = None
|
|
68
|
+
manager: str | None = None
|
|
69
|
+
reserve: str | None = None
|
|
70
|
+
freeze: str | None = None
|
|
71
|
+
clawback: str | None = None
|
|
72
|
+
unit_name: str | None = None
|
|
73
|
+
unit_name_b64: bytes | None = None
|
|
74
|
+
asset_name: str | None = None
|
|
75
|
+
asset_name_b64: bytes | None = None
|
|
76
|
+
url: str | None = None
|
|
77
|
+
url_b64: bytes | None = None
|
|
78
|
+
metadata_hash: bytes | None = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(kw_only=True, frozen=True)
|
|
82
|
+
class BulkAssetOptInOutResult:
|
|
83
|
+
"""Result from performing a bulk opt-in or bulk opt-out for an account against a series of assets.
|
|
84
|
+
|
|
85
|
+
:ivar asset_id: The ID of the asset opted into / out of
|
|
86
|
+
:ivar transaction_id: The transaction ID of the resulting opt in / out
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
asset_id: int
|
|
90
|
+
transaction_id: str
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class AssetManager:
|
|
94
|
+
"""A manager for Algorand Standard Assets (ASAs).
|
|
95
|
+
|
|
96
|
+
:param algod_client: An algod client
|
|
97
|
+
:param new_group: A function that creates a new TransactionComposer transaction group
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, algod_client: algod.AlgodClient, new_group: Callable[[], TransactionComposer]):
|
|
101
|
+
self._algod = algod_client
|
|
102
|
+
self._new_group = new_group
|
|
103
|
+
|
|
104
|
+
def get_by_id(self, asset_id: int) -> AssetInformation:
|
|
105
|
+
"""Returns the current asset information for the asset with the given ID.
|
|
106
|
+
|
|
107
|
+
:param asset_id: The ID of the asset
|
|
108
|
+
:return: The asset information
|
|
109
|
+
"""
|
|
110
|
+
asset = self._algod.asset_info(asset_id)
|
|
111
|
+
assert isinstance(asset, dict)
|
|
112
|
+
params = asset["params"]
|
|
113
|
+
|
|
114
|
+
return AssetInformation(
|
|
115
|
+
asset_id=asset_id,
|
|
116
|
+
total=params["total"],
|
|
117
|
+
decimals=params["decimals"],
|
|
118
|
+
asset_name=params.get("name"),
|
|
119
|
+
asset_name_b64=params.get("name-b64"),
|
|
120
|
+
unit_name=params.get("unit-name"),
|
|
121
|
+
unit_name_b64=params.get("unit-name-b64"),
|
|
122
|
+
url=params.get("url"),
|
|
123
|
+
url_b64=params.get("url-b64"),
|
|
124
|
+
creator=params["creator"],
|
|
125
|
+
manager=params.get("manager"),
|
|
126
|
+
clawback=params.get("clawback"),
|
|
127
|
+
freeze=params.get("freeze"),
|
|
128
|
+
reserve=params.get("reserve"),
|
|
129
|
+
default_frozen=params.get("default-frozen"),
|
|
130
|
+
metadata_hash=params.get("metadata-hash"),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def get_account_information(
|
|
134
|
+
self, sender: str | SigningAccount | TransactionSigner, asset_id: int
|
|
135
|
+
) -> AccountAssetInformation:
|
|
136
|
+
"""Returns the given sender account's asset holding for a given asset.
|
|
137
|
+
|
|
138
|
+
:param sender: The address of the sender/account to look up
|
|
139
|
+
:param asset_id: The ID of the asset to return a holding for
|
|
140
|
+
:return: The account asset holding information
|
|
141
|
+
"""
|
|
142
|
+
address = self._get_address_from_sender(sender)
|
|
143
|
+
info = self._algod.account_asset_info(address, asset_id)
|
|
144
|
+
assert isinstance(info, dict)
|
|
145
|
+
|
|
146
|
+
return AccountAssetInformation(
|
|
147
|
+
asset_id=asset_id,
|
|
148
|
+
balance=info["asset-holding"]["amount"],
|
|
149
|
+
frozen=info["asset-holding"]["is-frozen"],
|
|
150
|
+
round=info["round"],
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def bulk_opt_in( # noqa: PLR0913
|
|
154
|
+
self,
|
|
155
|
+
account: str,
|
|
156
|
+
asset_ids: list[int],
|
|
157
|
+
signer: TransactionSigner | None = None,
|
|
158
|
+
rekey_to: str | None = None,
|
|
159
|
+
note: bytes | None = None,
|
|
160
|
+
lease: bytes | None = None,
|
|
161
|
+
static_fee: AlgoAmount | None = None,
|
|
162
|
+
extra_fee: AlgoAmount | None = None,
|
|
163
|
+
max_fee: AlgoAmount | None = None,
|
|
164
|
+
validity_window: int | None = None,
|
|
165
|
+
first_valid_round: int | None = None,
|
|
166
|
+
last_valid_round: int | None = None,
|
|
167
|
+
send_params: SendParams | None = None,
|
|
168
|
+
) -> list[BulkAssetOptInOutResult]:
|
|
169
|
+
"""Opt an account in to a list of Algorand Standard Assets.
|
|
170
|
+
|
|
171
|
+
:param account: The account to opt-in
|
|
172
|
+
:param asset_ids: The list of asset IDs to opt-in to
|
|
173
|
+
:param signer: The signer to use for the transaction, defaults to None
|
|
174
|
+
:param rekey_to: The address to rekey the account to, defaults to None
|
|
175
|
+
:param note: The note to include in the transaction, defaults to None
|
|
176
|
+
:param lease: The lease to include in the transaction, defaults to None
|
|
177
|
+
:param static_fee: The static fee to include in the transaction, defaults to None
|
|
178
|
+
:param extra_fee: The extra fee to include in the transaction, defaults to None
|
|
179
|
+
:param max_fee: The maximum fee to include in the transaction, defaults to None
|
|
180
|
+
:param validity_window: The validity window to include in the transaction, defaults to None
|
|
181
|
+
:param first_valid_round: The first valid round to include in the transaction, defaults to None
|
|
182
|
+
:param last_valid_round: The last valid round to include in the transaction, defaults to None
|
|
183
|
+
:param send_params: The send parameters to use for the transaction, defaults to None
|
|
184
|
+
:return: An array of records matching asset ID to transaction ID of the opt in
|
|
185
|
+
"""
|
|
186
|
+
results: list[BulkAssetOptInOutResult] = []
|
|
187
|
+
sender = self._get_address_from_sender(account)
|
|
188
|
+
|
|
189
|
+
for asset_group in _chunk_array(asset_ids, algosdk.constants.TX_GROUP_LIMIT):
|
|
190
|
+
composer = self._new_group()
|
|
191
|
+
|
|
192
|
+
for asset_id in asset_group:
|
|
193
|
+
params = AssetOptInParams(
|
|
194
|
+
sender=sender,
|
|
195
|
+
asset_id=asset_id,
|
|
196
|
+
signer=signer,
|
|
197
|
+
rekey_to=rekey_to,
|
|
198
|
+
note=note,
|
|
199
|
+
lease=lease,
|
|
200
|
+
static_fee=static_fee,
|
|
201
|
+
extra_fee=extra_fee,
|
|
202
|
+
max_fee=max_fee,
|
|
203
|
+
validity_window=validity_window,
|
|
204
|
+
first_valid_round=first_valid_round,
|
|
205
|
+
last_valid_round=last_valid_round,
|
|
206
|
+
)
|
|
207
|
+
composer.add_asset_opt_in(params)
|
|
208
|
+
|
|
209
|
+
result = composer.send(send_params)
|
|
210
|
+
|
|
211
|
+
for i, asset_id in enumerate(asset_group):
|
|
212
|
+
results.append(BulkAssetOptInOutResult(asset_id=asset_id, transaction_id=result.tx_ids[i]))
|
|
213
|
+
|
|
214
|
+
return results
|
|
215
|
+
|
|
216
|
+
def bulk_opt_out( # noqa: C901, PLR0913
|
|
217
|
+
self,
|
|
218
|
+
*,
|
|
219
|
+
account: str,
|
|
220
|
+
asset_ids: list[int],
|
|
221
|
+
ensure_zero_balance: bool = True,
|
|
222
|
+
signer: TransactionSigner | None = None,
|
|
223
|
+
rekey_to: str | None = None,
|
|
224
|
+
note: bytes | None = None,
|
|
225
|
+
lease: bytes | None = None,
|
|
226
|
+
static_fee: AlgoAmount | None = None,
|
|
227
|
+
extra_fee: AlgoAmount | None = None,
|
|
228
|
+
max_fee: AlgoAmount | None = None,
|
|
229
|
+
validity_window: int | None = None,
|
|
230
|
+
first_valid_round: int | None = None,
|
|
231
|
+
last_valid_round: int | None = None,
|
|
232
|
+
send_params: SendParams | None = None,
|
|
233
|
+
) -> list[BulkAssetOptInOutResult]:
|
|
234
|
+
"""Opt an account out of a list of Algorand Standard Assets.
|
|
235
|
+
|
|
236
|
+
:param account: The account to opt-out
|
|
237
|
+
:param asset_ids: The list of asset IDs to opt-out of
|
|
238
|
+
:param ensure_zero_balance: Whether to check if the account has a zero balance first, defaults to True
|
|
239
|
+
:param signer: The signer to use for the transaction, defaults to None
|
|
240
|
+
:param rekey_to: The address to rekey the account to, defaults to None
|
|
241
|
+
:param note: The note to include in the transaction, defaults to None
|
|
242
|
+
:param lease: The lease to include in the transaction, defaults to None
|
|
243
|
+
:param static_fee: The static fee to include in the transaction, defaults to None
|
|
244
|
+
:param extra_fee: The extra fee to include in the transaction, defaults to None
|
|
245
|
+
:param max_fee: The maximum fee to include in the transaction, defaults to None
|
|
246
|
+
:param validity_window: The validity window to include in the transaction, defaults to None
|
|
247
|
+
:param first_valid_round: The first valid round to include in the transaction, defaults to None
|
|
248
|
+
:param last_valid_round: The last valid round to include in the transaction, defaults to None
|
|
249
|
+
:param send_params: The send parameters to use for the transaction, defaults to None
|
|
250
|
+
:raises ValueError: If ensure_zero_balance is True and account has non-zero balance or is not opted in
|
|
251
|
+
:return: An array of records matching asset ID to transaction ID of the opt out
|
|
252
|
+
"""
|
|
253
|
+
results: list[BulkAssetOptInOutResult] = []
|
|
254
|
+
sender = self._get_address_from_sender(account)
|
|
255
|
+
|
|
256
|
+
for asset_group in _chunk_array(asset_ids, algosdk.constants.TX_GROUP_LIMIT):
|
|
257
|
+
composer = self._new_group()
|
|
258
|
+
|
|
259
|
+
not_opted_in_asset_ids: list[int] = []
|
|
260
|
+
non_zero_balance_asset_ids: list[int] = []
|
|
261
|
+
|
|
262
|
+
if ensure_zero_balance:
|
|
263
|
+
for asset_id in asset_group:
|
|
264
|
+
try:
|
|
265
|
+
account_asset_info = self.get_account_information(sender, asset_id)
|
|
266
|
+
if account_asset_info.balance != 0:
|
|
267
|
+
non_zero_balance_asset_ids.append(asset_id)
|
|
268
|
+
except Exception:
|
|
269
|
+
not_opted_in_asset_ids.append(asset_id)
|
|
270
|
+
|
|
271
|
+
if not_opted_in_asset_ids or non_zero_balance_asset_ids:
|
|
272
|
+
error_message = f"Account {sender}"
|
|
273
|
+
if not_opted_in_asset_ids:
|
|
274
|
+
error_message += f" is not opted-in to Asset(s) {', '.join(map(str, not_opted_in_asset_ids))}"
|
|
275
|
+
if non_zero_balance_asset_ids:
|
|
276
|
+
error_message += (
|
|
277
|
+
f" has non-zero balance for Asset(s) {', '.join(map(str, non_zero_balance_asset_ids))}"
|
|
278
|
+
)
|
|
279
|
+
error_message += "; can't opt-out."
|
|
280
|
+
raise ValueError(error_message)
|
|
281
|
+
|
|
282
|
+
for asset_id in asset_group:
|
|
283
|
+
asset_info = self.get_by_id(asset_id)
|
|
284
|
+
params = AssetOptOutParams(
|
|
285
|
+
sender=sender,
|
|
286
|
+
asset_id=asset_id,
|
|
287
|
+
creator=asset_info.creator,
|
|
288
|
+
signer=signer,
|
|
289
|
+
rekey_to=rekey_to,
|
|
290
|
+
note=note,
|
|
291
|
+
lease=lease,
|
|
292
|
+
static_fee=static_fee,
|
|
293
|
+
extra_fee=extra_fee,
|
|
294
|
+
max_fee=max_fee,
|
|
295
|
+
validity_window=validity_window,
|
|
296
|
+
first_valid_round=first_valid_round,
|
|
297
|
+
last_valid_round=last_valid_round,
|
|
298
|
+
)
|
|
299
|
+
composer.add_asset_opt_out(params)
|
|
300
|
+
|
|
301
|
+
result = composer.send(send_params)
|
|
302
|
+
|
|
303
|
+
for i, asset_id in enumerate(asset_group):
|
|
304
|
+
results.append(BulkAssetOptInOutResult(asset_id=asset_id, transaction_id=result.tx_ids[i]))
|
|
305
|
+
|
|
306
|
+
return results
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def _get_address_from_sender(sender: str | SigningAccount | TransactionSigner) -> str:
|
|
310
|
+
if isinstance(sender, str):
|
|
311
|
+
return sender
|
|
312
|
+
if isinstance(sender, SigningAccount):
|
|
313
|
+
return sender.address
|
|
314
|
+
if isinstance(sender, AccountTransactionSigner):
|
|
315
|
+
return str(algosdk.account.address_from_private_key(sender.private_key))
|
|
316
|
+
raise ValueError(f"Unsupported sender type: {type(sender)}")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _chunk_array(array: list, size: int) -> list[list]:
|
|
320
|
+
return [array[i : i + size] for i in range(0, len(array), size)]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import NoReturn
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def deprecated_import_error(old_path: str, new_path: str) -> NoReturn:
|
|
5
|
+
"""Helper to create consistent deprecation error messages"""
|
|
6
|
+
raise ImportError(
|
|
7
|
+
f"WARNING: The module '{old_path}' has been removed in algokit-utils v3. "
|
|
8
|
+
f"Please update your imports to use '{new_path}' instead. "
|
|
9
|
+
"See the migration guide for more details: "
|
|
10
|
+
"https://github.com/algorandfoundation/algokit-utils-py/blob/main/docs/source/v3-migration-guide.md"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def handle_getattr(name: str) -> NoReturn:
|
|
15
|
+
param_mappings = {
|
|
16
|
+
"ClientManager": "algokit_utils.ClientManager",
|
|
17
|
+
"AlgorandClient": "algokit_utils.AlgorandClient",
|
|
18
|
+
"AlgoSdkClients": "algokit_utils.AlgoSdkClients",
|
|
19
|
+
"AccountManager": "algokit_utils.AccountManager",
|
|
20
|
+
"PayParams": "algokit_utils.transactions.PaymentParams",
|
|
21
|
+
"AlgokitComposer": "algokit_utils.TransactionComposer",
|
|
22
|
+
"AssetCreateParams": "algokit_utils.transactions.AssetCreateParams",
|
|
23
|
+
"AssetConfigParams": "algokit_utils.transactions.AssetConfigParams",
|
|
24
|
+
"AssetFreezeParams": "algokit_utils.transactions.AssetFreezeParams",
|
|
25
|
+
"AssetDestroyParams": "algokit_utils.transactions.AssetDestroyParams",
|
|
26
|
+
"AssetTransferParams": "algokit_utils.transactions.AssetTransferParams",
|
|
27
|
+
"AssetOptInParams": "algokit_utils.transactions.AssetOptInParams",
|
|
28
|
+
"AppCallParams": "algokit_utils.transactions.AppCallParams",
|
|
29
|
+
"MethodCallParams": "algokit_utils.transactions.MethodCallParams",
|
|
30
|
+
"OnlineKeyRegParams": "algokit_utils.transactions.OnlineKeyRegistrationParams",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if name in param_mappings:
|
|
34
|
+
deprecated_import_error(f"algokit_utils.beta.{name}", param_mappings[name])
|
|
35
|
+
|
|
36
|
+
raise AttributeError(f"module 'algokit_utils.beta' has no attribute '{name}'")
|