algokit-utils 3.0.0b4__py3-none-any.whl → 3.0.0b6__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/_legacy_v2/account.py +4 -4
- algokit_utils/_legacy_v2/application_client.py +1 -0
- algokit_utils/accounts/account_manager.py +98 -98
- algokit_utils/accounts/kmd_account_manager.py +2 -0
- algokit_utils/applications/app_client.py +38 -14
- algokit_utils/applications/app_deployer.py +40 -6
- algokit_utils/assets/asset_manager.py +5 -5
- algokit_utils/clients/client_manager.py +17 -12
- algokit_utils/config.py +62 -59
- algokit_utils/models/amount.py +25 -81
- algokit_utils/models/network.py +6 -2
- algokit_utils/models/transaction.py +1 -1
- algokit_utils/transactions/transaction_composer.py +33 -25
- {algokit_utils-3.0.0b4.dist-info → algokit_utils-3.0.0b6.dist-info}/METADATA +1 -1
- {algokit_utils-3.0.0b4.dist-info → algokit_utils-3.0.0b6.dist-info}/RECORD +17 -17
- {algokit_utils-3.0.0b4.dist-info → algokit_utils-3.0.0b6.dist-info}/LICENSE +0 -0
- {algokit_utils-3.0.0b4.dist-info → algokit_utils-3.0.0b6.dist-info}/WHEEL +0 -0
|
@@ -177,10 +177,10 @@ def get_account(
|
|
|
177
177
|
For LocalNet environments, loads or creates an account from a KMD wallet named {name}.
|
|
178
178
|
|
|
179
179
|
:example:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
>>> # If you have a mnemonic secret loaded into `os.environ["ACCOUNT_MNEMONIC"]` then you can call:
|
|
181
|
+
>>> account = get_account('ACCOUNT', algod)
|
|
182
|
+
>>> # If that code runs against LocalNet then a wallet called 'ACCOUNT' will automatically be created
|
|
183
|
+
>>> # with an account that is automatically funded with 1000 (default) ALGOs from the default LocalNet dispenser.
|
|
184
184
|
|
|
185
185
|
:param client: The Algorand client to use
|
|
186
186
|
:param name: The name identifier to use for loading/creating the account
|
|
@@ -74,6 +74,7 @@ __all__ = [
|
|
|
74
74
|
representing an ABI method name or signature"""
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
@deprecated("Use 'algokit_utils.calculate_extra_program_pages' instead.")
|
|
77
78
|
def num_extra_program_pages(approval: bytes, clear: bytes) -> int:
|
|
78
79
|
"""Calculate minimum number of extra_pages required for provided approval and clear programs"""
|
|
79
80
|
|
|
@@ -148,7 +148,7 @@ class AccountManager:
|
|
|
148
148
|
:param client_manager: The ClientManager client to use for algod and kmd clients
|
|
149
149
|
|
|
150
150
|
:example:
|
|
151
|
-
|
|
151
|
+
>>> account_manager = AccountManager(client_manager)
|
|
152
152
|
"""
|
|
153
153
|
|
|
154
154
|
def __init__(self, client_manager: ClientManager):
|
|
@@ -172,11 +172,11 @@ class AccountManager:
|
|
|
172
172
|
:returns: The `AccountManager` so method calls can be chained
|
|
173
173
|
|
|
174
174
|
:example:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
>>> signer_account = account_manager.random()
|
|
176
|
+
>>> account_manager.set_default_signer(signer_account.signer)
|
|
177
|
+
>>> # When signing a transaction, if there is no signer registered for the sender
|
|
178
|
+
>>> # then the default signer will be used
|
|
179
|
+
>>> signer = account_manager.get_signer("{SENDERADDRESS}")
|
|
180
180
|
"""
|
|
181
181
|
self._default_signer = signer if isinstance(signer, TransactionSigner) else signer.signer
|
|
182
182
|
return self
|
|
@@ -190,7 +190,7 @@ class AccountManager:
|
|
|
190
190
|
:returns: The `AccountManager` instance for method chaining
|
|
191
191
|
|
|
192
192
|
:example:
|
|
193
|
-
|
|
193
|
+
>>> account_manager.set_signer("SENDERADDRESS", transaction_signer)
|
|
194
194
|
"""
|
|
195
195
|
self._accounts[sender] = TransactionSignerAccount(address=sender, signer=signer)
|
|
196
196
|
return self
|
|
@@ -221,11 +221,11 @@ class AccountManager:
|
|
|
221
221
|
:returns: The `AccountManager` instance for method chaining
|
|
222
222
|
|
|
223
223
|
:example:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"""
|
|
224
|
+
>>> account_manager = AccountManager(client_manager)
|
|
225
|
+
>>> account_manager.set_signer_from_account(SigningAccount(private_key=algosdk.account.generate_account()[0]))
|
|
226
|
+
>>> account_manager.set_signer_from_account(LogicSigAccount(AlgosdkLogicSigAccount(program, args)))
|
|
227
|
+
>>> account_manager.set_signer_from_account(MultiSigAccount(multisig_params, [account1, account2]))
|
|
228
|
+
""" # noqa: E501
|
|
229
229
|
self._accounts[account.address] = account
|
|
230
230
|
return self
|
|
231
231
|
|
|
@@ -240,7 +240,7 @@ class AccountManager:
|
|
|
240
240
|
:raises ValueError: If no signer is found and no default signer is set
|
|
241
241
|
|
|
242
242
|
:example:
|
|
243
|
-
|
|
243
|
+
>>> signer = account_manager.get_signer("SENDERADDRESS")
|
|
244
244
|
"""
|
|
245
245
|
signer = self._accounts.get(self._get_address(sender)) or self._default_signer
|
|
246
246
|
if not signer:
|
|
@@ -256,10 +256,10 @@ class AccountManager:
|
|
|
256
256
|
:raises ValueError: If no account is found or if the account is not a regular account
|
|
257
257
|
|
|
258
258
|
:example:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
>>> sender = account_manager.random().address
|
|
260
|
+
>>> # ...
|
|
261
|
+
>>> # Returns the `TransactionSignerAccountProtocol` for `sender` that has previously been registered
|
|
262
|
+
>>> account = account_manager.get_account(sender)
|
|
263
263
|
"""
|
|
264
264
|
account = self._accounts.get(sender)
|
|
265
265
|
if not account:
|
|
@@ -279,8 +279,8 @@ class AccountManager:
|
|
|
279
279
|
:returns: The account information
|
|
280
280
|
|
|
281
281
|
:example:
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
>>> address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA"
|
|
283
|
+
>>> account_info = account_manager.get_information(address)
|
|
284
284
|
"""
|
|
285
285
|
info = self._client_manager.algod.account_info(self._get_address(sender))
|
|
286
286
|
assert isinstance(info, dict)
|
|
@@ -342,7 +342,7 @@ class AccountManager:
|
|
|
342
342
|
from the environment (ideally via a secret storage service) rather than the file system.
|
|
343
343
|
|
|
344
344
|
:example:
|
|
345
|
-
|
|
345
|
+
>>> account = account_manager.from_mnemonic("mnemonic secret ...")
|
|
346
346
|
"""
|
|
347
347
|
return self._register_account(to_private_key(mnemonic), sender)
|
|
348
348
|
|
|
@@ -355,7 +355,7 @@ class AccountManager:
|
|
|
355
355
|
|
|
356
356
|
:param name: The name identifier of the account
|
|
357
357
|
:param fund_with: Optional amount to fund the account with when it gets created
|
|
358
|
-
|
|
358
|
+
(when targeting LocalNet)
|
|
359
359
|
:returns: The account
|
|
360
360
|
:raises ValueError: If environment variable {NAME}_MNEMONIC is missing when looking for account {NAME}
|
|
361
361
|
|
|
@@ -368,10 +368,10 @@ class AccountManager:
|
|
|
368
368
|
it will create it and fund the account for you
|
|
369
369
|
|
|
370
370
|
:example:
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
>>> # If you have a mnemonic secret loaded into `MY_ACCOUNT_MNEMONIC` then you can call:
|
|
372
|
+
>>> account = account_manager.from_environment('MY_ACCOUNT')
|
|
373
|
+
>>> # If that code runs against LocalNet then a wallet called `MY_ACCOUNT` will automatically be created
|
|
374
|
+
>>> # with an account that is automatically funded with the specified amount from the LocalNet dispenser
|
|
375
375
|
"""
|
|
376
376
|
account_mnemonic = os.getenv(f"{name.upper()}_MNEMONIC")
|
|
377
377
|
|
|
@@ -398,10 +398,10 @@ class AccountManager:
|
|
|
398
398
|
:raises ValueError: If unable to find KMD account with given name and predicate
|
|
399
399
|
|
|
400
400
|
:example:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
401
|
+
>>> # Get default funded account in a LocalNet:
|
|
402
|
+
>>> defaultDispenserAccount = account.from_kmd('unencrypted-default-wallet',
|
|
403
|
+
... lambda a: a.status != 'Offline' and a.amount > 1_000_000_000
|
|
404
|
+
... )
|
|
405
405
|
"""
|
|
406
406
|
kmd_account = self._kmd_account_manager.get_wallet_account(name, predicate, sender)
|
|
407
407
|
if not kmd_account:
|
|
@@ -418,7 +418,7 @@ class AccountManager:
|
|
|
418
418
|
:returns: A logic signature account wrapper
|
|
419
419
|
|
|
420
420
|
:example:
|
|
421
|
-
|
|
421
|
+
>>> account = account.logic_sig(program, [new Uint8Array(3, ...)])
|
|
422
422
|
"""
|
|
423
423
|
return self._register_logicsig(program, args)
|
|
424
424
|
|
|
@@ -431,12 +431,12 @@ class AccountManager:
|
|
|
431
431
|
:returns: A multisig account wrapper
|
|
432
432
|
|
|
433
433
|
:example:
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
434
|
+
>>> account = account_manager.multi_sig(
|
|
435
|
+
... version=1,
|
|
436
|
+
... threshold=1,
|
|
437
|
+
... addrs=["ADDRESS1...", "ADDRESS2..."],
|
|
438
|
+
... signing_accounts=[account1, account2]
|
|
439
|
+
... )
|
|
440
440
|
"""
|
|
441
441
|
return self._register_multisig(metadata, signing_accounts)
|
|
442
442
|
|
|
@@ -447,7 +447,7 @@ class AccountManager:
|
|
|
447
447
|
:returns: The account
|
|
448
448
|
|
|
449
449
|
:example:
|
|
450
|
-
|
|
450
|
+
>>> account = account_manager.random()
|
|
451
451
|
"""
|
|
452
452
|
private_key, _ = algosdk.account.generate_account()
|
|
453
453
|
return self._register_account(private_key)
|
|
@@ -461,7 +461,7 @@ class AccountManager:
|
|
|
461
461
|
:returns: The account
|
|
462
462
|
|
|
463
463
|
:example:
|
|
464
|
-
|
|
464
|
+
>>> account = account_manager.localnet_dispenser()
|
|
465
465
|
"""
|
|
466
466
|
kmd_account = self._kmd_account_manager.get_localnet_dispenser_account()
|
|
467
467
|
return self._register_account(kmd_account.private_key)
|
|
@@ -475,7 +475,7 @@ class AccountManager:
|
|
|
475
475
|
:returns: The account
|
|
476
476
|
|
|
477
477
|
:example:
|
|
478
|
-
|
|
478
|
+
>>> account = account_manager.dispenser_from_environment()
|
|
479
479
|
"""
|
|
480
480
|
name = os.getenv(f"{DISPENSER_ACCOUNT_NAME}_MNEMONIC")
|
|
481
481
|
if name:
|
|
@@ -493,8 +493,8 @@ class AccountManager:
|
|
|
493
493
|
:returns: The rekeyed account
|
|
494
494
|
|
|
495
495
|
:example:
|
|
496
|
-
|
|
497
|
-
|
|
496
|
+
>>> account = account.from_mnemonic("mnemonic secret ...")
|
|
497
|
+
>>> rekeyed_account = account_manager.rekeyed(account, "SENDERADDRESS...")
|
|
498
498
|
"""
|
|
499
499
|
sender_address = sender.address if isinstance(sender, SigningAccount) else sender
|
|
500
500
|
self._accounts[sender_address] = TransactionSignerAccount(address=sender_address, signer=account.signer)
|
|
@@ -540,23 +540,23 @@ class AccountManager:
|
|
|
540
540
|
`official rekey guidance <https://developer.algorand.org/docs/get-details/accounts/rekey/>`_.
|
|
541
541
|
|
|
542
542
|
:example:
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
543
|
+
>>> # Basic example (with string addresses):
|
|
544
|
+
>>> algorand.account.rekey_account({account: "ACCOUNTADDRESS", rekey_to: "NEWADDRESS"})
|
|
545
|
+
>>> # Basic example (with signer accounts):
|
|
546
|
+
>>> algorand.account.rekey_account({account: account1, rekey_to: newSignerAccount})
|
|
547
|
+
>>> # Advanced example:
|
|
548
|
+
>>> algorand.account.rekey_account({
|
|
549
|
+
... account: "ACCOUNTADDRESS",
|
|
550
|
+
... rekey_to: "NEWADDRESS",
|
|
551
|
+
... lease: 'lease',
|
|
552
|
+
... note: 'note',
|
|
553
|
+
... first_valid_round: 1000,
|
|
554
|
+
... validity_window: 10,
|
|
555
|
+
... extra_fee: AlgoAmount.from_micro_algo(1000),
|
|
556
|
+
... static_fee: AlgoAmount.from_micro_algo(1000),
|
|
557
|
+
... max_fee: AlgoAmount.from_micro_algo(3000),
|
|
558
|
+
... suppress_log: True,
|
|
559
|
+
... })
|
|
560
560
|
"""
|
|
561
561
|
sender_address = self._get_address(account)
|
|
562
562
|
rekey_address = self._get_address(rekey_to)
|
|
@@ -623,7 +623,7 @@ class AccountManager:
|
|
|
623
623
|
:param account_to_fund: The account to fund
|
|
624
624
|
:param dispenser_account: The account to use as a dispenser funding source
|
|
625
625
|
:param min_spending_balance: The minimum balance of Algo that the account
|
|
626
|
-
|
|
626
|
+
should have available to spend
|
|
627
627
|
:param min_funding_increment: Optional minimum funding increment
|
|
628
628
|
:param send_params: Parameters for the send operation, defaults to None
|
|
629
629
|
:param signer: Optional transaction signer
|
|
@@ -637,20 +637,20 @@ class AccountManager:
|
|
|
637
637
|
:param first_valid_round: Optional first valid round
|
|
638
638
|
:param last_valid_round: Optional last valid round
|
|
639
639
|
:returns: The result of executing the dispensing transaction and the `amountFunded` if funds were needed,
|
|
640
|
-
|
|
640
|
+
or None if no funds were needed
|
|
641
641
|
|
|
642
642
|
:example:
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
643
|
+
>>> # Basic example:
|
|
644
|
+
>>> algorand.account.ensure_funded("ACCOUNTADDRESS", "DISPENSERADDRESS", algokit.algo(1))
|
|
645
|
+
>>> # With configuration:
|
|
646
|
+
>>> algorand.account.ensure_funded(
|
|
647
|
+
... "ACCOUNTADDRESS",
|
|
648
|
+
... "DISPENSERADDRESS",
|
|
649
|
+
... algokit.algo(1),
|
|
650
|
+
... min_funding_increment=algokit.algo(2),
|
|
651
|
+
... fee=AlgoAmount.from_micro_algo(1000),
|
|
652
|
+
... suppress_log=True
|
|
653
|
+
... )
|
|
654
654
|
"""
|
|
655
655
|
account_to_fund = self._get_address(account_to_fund)
|
|
656
656
|
dispenser_account = self._get_address(dispenser_account)
|
|
@@ -724,7 +724,7 @@ class AccountManager:
|
|
|
724
724
|
|
|
725
725
|
:param account_to_fund: The account to fund
|
|
726
726
|
:param min_spending_balance: The minimum balance of Algo that the account should have available to
|
|
727
|
-
|
|
727
|
+
spend
|
|
728
728
|
:param min_funding_increment: Optional minimum funding increment
|
|
729
729
|
:param send_params: Parameters for the send operation, defaults to None
|
|
730
730
|
:param signer: Optional transaction signer
|
|
@@ -738,7 +738,7 @@ class AccountManager:
|
|
|
738
738
|
:param first_valid_round: Optional first valid round
|
|
739
739
|
:param last_valid_round: Optional last valid round
|
|
740
740
|
:returns: The result of executing the dispensing transaction and the `amountFunded` if funds were needed, or
|
|
741
|
-
|
|
741
|
+
None if no funds were needed
|
|
742
742
|
|
|
743
743
|
.. note::
|
|
744
744
|
The dispenser account is retrieved from the account mnemonic stored in
|
|
@@ -746,16 +746,16 @@ class AccountManager:
|
|
|
746
746
|
if it's a rekeyed account, or against default LocalNet if no environment variables present.
|
|
747
747
|
|
|
748
748
|
:example:
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
749
|
+
>>> # Basic example:
|
|
750
|
+
>>> algorand.account.ensure_funded_from_environment("ACCOUNTADDRESS", algokit.algo(1))
|
|
751
|
+
>>> # With configuration:
|
|
752
|
+
>>> algorand.account.ensure_funded_from_environment(
|
|
753
|
+
... "ACCOUNTADDRESS",
|
|
754
|
+
... algokit.algo(1),
|
|
755
|
+
... min_funding_increment=algokit.algo(2),
|
|
756
|
+
... fee=AlgoAmount.from_micro_algo(1000),
|
|
757
|
+
... suppress_log=True
|
|
758
|
+
... )
|
|
759
759
|
"""
|
|
760
760
|
account_to_fund = self._get_address(account_to_fund)
|
|
761
761
|
dispenser_account = self.dispenser_from_environment()
|
|
@@ -818,26 +818,26 @@ class AccountManager:
|
|
|
818
818
|
:param account_to_fund: The account to fund
|
|
819
819
|
:param dispenser_client: The TestNet dispenser funding client
|
|
820
820
|
:param min_spending_balance: The minimum balance of Algo that the account should have
|
|
821
|
-
|
|
821
|
+
available to spend
|
|
822
822
|
:param min_funding_increment: Optional minimum funding increment
|
|
823
823
|
:returns: The result of executing the dispensing transaction and the `amountFunded` if funds were needed, or
|
|
824
|
-
|
|
824
|
+
None if no funds were needed
|
|
825
825
|
:raises ValueError: If attempting to fund on non-TestNet network
|
|
826
826
|
|
|
827
827
|
:example:
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
828
|
+
>>> # Basic example:
|
|
829
|
+
>>> algorand.account.ensure_funded_from_testnet_dispenser_api(
|
|
830
|
+
... "ACCOUNTADDRESS",
|
|
831
|
+
... algorand.client.get_testnet_dispenser_from_environment(),
|
|
832
|
+
... algokit.algo(1)
|
|
833
|
+
... )
|
|
834
|
+
>>> # With configuration:
|
|
835
|
+
>>> algorand.account.ensure_funded_from_testnet_dispenser_api(
|
|
836
|
+
... "ACCOUNTADDRESS",
|
|
837
|
+
... algorand.client.get_testnet_dispenser_from_environment(),
|
|
838
|
+
... algokit.algo(1),
|
|
839
|
+
... min_funding_increment=algokit.algo(2)
|
|
840
|
+
... )
|
|
841
841
|
"""
|
|
842
842
|
account_to_fund = self._get_address(account_to_fund)
|
|
843
843
|
|
|
@@ -48,6 +48,8 @@ class KmdAccountManager:
|
|
|
48
48
|
if self._kmd is None:
|
|
49
49
|
if self._client_manager.is_localnet():
|
|
50
50
|
kmd_config = ClientManager.get_config_from_environment_or_localnet()
|
|
51
|
+
if not kmd_config.kmd_config:
|
|
52
|
+
raise Exception("Attempt to use KMD client with no KMD configured")
|
|
51
53
|
self._kmd = ClientManager.get_kmd_client(kmd_config.kmd_config)
|
|
52
54
|
return self._kmd
|
|
53
55
|
raise Exception("Attempt to use KMD client with no KMD configured")
|
|
@@ -5,7 +5,7 @@ import copy
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
from collections.abc import Sequence
|
|
8
|
-
from dataclasses import asdict, dataclass, fields
|
|
8
|
+
from dataclasses import asdict, dataclass, fields, replace
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Generic, Literal, TypedDict, TypeVar
|
|
10
10
|
|
|
11
11
|
import algosdk
|
|
@@ -54,6 +54,7 @@ from algokit_utils.transactions.transaction_composer import (
|
|
|
54
54
|
AppUpdateParams,
|
|
55
55
|
BuiltTransactions,
|
|
56
56
|
PaymentParams,
|
|
57
|
+
SendAtomicTransactionComposerResults,
|
|
57
58
|
)
|
|
58
59
|
from algokit_utils.transactions.transaction_sender import (
|
|
59
60
|
SendAppTransactionResult,
|
|
@@ -1189,28 +1190,51 @@ class _TransactionSender:
|
|
|
1189
1190
|
:param params: Parameters for the application call including method and transaction options
|
|
1190
1191
|
:param send_params: Send parameters
|
|
1191
1192
|
:return: The result of sending or simulating the transaction, including ABI return value if applicable
|
|
1193
|
+
:raises ValueError: If the transaction is read-only and `max_fee` is not provided
|
|
1192
1194
|
"""
|
|
1193
1195
|
is_read_only_call = (
|
|
1194
1196
|
params.on_complete == algosdk.transaction.OnComplete.NoOpOC or params.on_complete is None
|
|
1195
1197
|
) and self._app_spec.get_arc56_method(params.method).readonly
|
|
1196
1198
|
|
|
1197
1199
|
if is_read_only_call:
|
|
1200
|
+
readonly_params = params
|
|
1201
|
+
readonly_send_params = send_params or SendParams()
|
|
1202
|
+
|
|
1203
|
+
# Read-only calls do not require fees to be paid, as they are only simulated on the network.
|
|
1204
|
+
# Therefore there is no value in calculating the minimum fee needed for a successful app call with inners.
|
|
1205
|
+
# As a a result we only need to send a single simulate call,
|
|
1206
|
+
# however to do this successfully we need to ensure fees for the transaction are fully covered using maxFee.
|
|
1207
|
+
if readonly_send_params.get("cover_app_call_inner_transaction_fees"):
|
|
1208
|
+
if params.max_fee is None:
|
|
1209
|
+
raise ValueError(
|
|
1210
|
+
"Please provide a `max_fee` for the transaction when `cover_app_call_inner_transaction_fees` is enabled." # noqa: E501
|
|
1211
|
+
)
|
|
1212
|
+
readonly_params = replace(readonly_params, static_fee=params.max_fee, extra_fee=None)
|
|
1213
|
+
|
|
1198
1214
|
method_call_to_simulate = self._algorand.new_group().add_app_call_method_call(
|
|
1199
|
-
self._client.params.call(
|
|
1200
|
-
)
|
|
1201
|
-
send_params = send_params or SendParams()
|
|
1202
|
-
simulate_response = self._client._handle_call_errors(
|
|
1203
|
-
lambda: method_call_to_simulate.simulate(
|
|
1204
|
-
allow_unnamed_resources=send_params.get("populate_app_call_resources") or True,
|
|
1205
|
-
skip_signatures=True,
|
|
1206
|
-
allow_more_logs=True,
|
|
1207
|
-
allow_empty_signatures=True,
|
|
1208
|
-
extra_opcode_budget=None,
|
|
1209
|
-
exec_trace_config=None,
|
|
1210
|
-
simulation_round=None,
|
|
1211
|
-
)
|
|
1215
|
+
self._client.params.call(readonly_params)
|
|
1212
1216
|
)
|
|
1213
1217
|
|
|
1218
|
+
def run_simulate() -> SendAtomicTransactionComposerResults:
|
|
1219
|
+
try:
|
|
1220
|
+
return method_call_to_simulate.simulate(
|
|
1221
|
+
allow_unnamed_resources=readonly_send_params.get("populate_app_call_resources") or True,
|
|
1222
|
+
skip_signatures=True,
|
|
1223
|
+
allow_more_logs=True,
|
|
1224
|
+
allow_empty_signatures=True,
|
|
1225
|
+
extra_opcode_budget=None,
|
|
1226
|
+
exec_trace_config=None,
|
|
1227
|
+
simulation_round=None,
|
|
1228
|
+
)
|
|
1229
|
+
except Exception as e:
|
|
1230
|
+
if readonly_send_params.get("cover_app_call_inner_transaction_fees") and "fee too small" in str(e):
|
|
1231
|
+
raise ValueError(
|
|
1232
|
+
"Fees were too small. You may need to increase the transaction `maxFee`."
|
|
1233
|
+
) from e
|
|
1234
|
+
raise
|
|
1235
|
+
|
|
1236
|
+
simulate_response = self._client._handle_call_errors(run_simulate)
|
|
1237
|
+
|
|
1214
1238
|
return SendAppTransactionResult[Arc56ReturnValueType](
|
|
1215
1239
|
tx_ids=simulate_response.tx_ids,
|
|
1216
1240
|
transactions=simulate_response.transactions,
|
|
@@ -21,6 +21,7 @@ from algokit_utils.transactions.transaction_composer import (
|
|
|
21
21
|
AppUpdateMethodCallParams,
|
|
22
22
|
AppUpdateParams,
|
|
23
23
|
TransactionComposer,
|
|
24
|
+
calculate_extra_program_pages,
|
|
24
25
|
)
|
|
25
26
|
from algokit_utils.transactions.transaction_sender import (
|
|
26
27
|
AlgorandClientTransactionSender,
|
|
@@ -169,7 +170,7 @@ class AppDeployer:
|
|
|
169
170
|
f"{'teal code' if isinstance(deployment.create_params.approval_program, str) else 'AVM bytecode'} and "
|
|
170
171
|
f"{len(deployment.create_params.clear_state_program)} bytes of "
|
|
171
172
|
f"{'teal code' if isinstance(deployment.create_params.clear_state_program, str) else 'AVM bytecode'}",
|
|
172
|
-
|
|
173
|
+
extra={"suppress_log": suppress_log},
|
|
173
174
|
)
|
|
174
175
|
note = TransactionComposer.arc2_note(
|
|
175
176
|
{
|
|
@@ -242,9 +243,13 @@ class AppDeployer:
|
|
|
242
243
|
|
|
243
244
|
existing_approval = base64.b64encode(existing_app_record.approval_program).decode()
|
|
244
245
|
existing_clear = base64.b64encode(existing_app_record.clear_state_program).decode()
|
|
246
|
+
existing_extra_pages = calculate_extra_program_pages(
|
|
247
|
+
existing_app_record.approval_program, existing_app_record.clear_state_program
|
|
248
|
+
)
|
|
245
249
|
|
|
246
250
|
new_approval = base64.b64encode(approval_program).decode()
|
|
247
251
|
new_clear = base64.b64encode(clear_program).decode()
|
|
252
|
+
new_extra_pages = calculate_extra_program_pages(approval_program, clear_program)
|
|
248
253
|
|
|
249
254
|
is_update = new_approval != existing_approval or new_clear != existing_clear
|
|
250
255
|
is_schema_break = (
|
|
@@ -256,6 +261,7 @@ class AppDeployer:
|
|
|
256
261
|
< (deployment.create_params.schema.get("local_byte_slices", 0) if deployment.create_params.schema else 0)
|
|
257
262
|
or existing_app_record.global_byte_slices
|
|
258
263
|
< (deployment.create_params.schema.get("global_byte_slices", 0) if deployment.create_params.schema else 0)
|
|
264
|
+
or existing_extra_pages < new_extra_pages
|
|
259
265
|
)
|
|
260
266
|
|
|
261
267
|
if is_schema_break:
|
|
@@ -269,8 +275,8 @@ class AppDeployer:
|
|
|
269
275
|
"local_byte_slices": existing_app_record.local_byte_slices,
|
|
270
276
|
},
|
|
271
277
|
"to": deployment.create_params.schema,
|
|
278
|
+
"suppress_log": suppress_log,
|
|
272
279
|
},
|
|
273
|
-
suppress_log=suppress_log,
|
|
274
280
|
)
|
|
275
281
|
|
|
276
282
|
return self._handle_schema_break(
|
|
@@ -288,7 +294,7 @@ class AppDeployer:
|
|
|
288
294
|
clear_program=clear_program,
|
|
289
295
|
)
|
|
290
296
|
|
|
291
|
-
logger.debug("No detected changes in app, nothing to do.",
|
|
297
|
+
logger.debug("No detected changes in app, nothing to do.", extra={"suppress_log": suppress_log})
|
|
292
298
|
return AppDeployResult(
|
|
293
299
|
app=existing_app,
|
|
294
300
|
operation_performed=OperationPerformed.Nothing,
|
|
@@ -340,6 +346,12 @@ class AppDeployer:
|
|
|
340
346
|
)
|
|
341
347
|
|
|
342
348
|
self._update_app_lookup(deployment.create_params.sender, app_metadata)
|
|
349
|
+
logger.debug(
|
|
350
|
+
f"Sent transaction ID {create_result.app_id} (AppCreate) from {deployment.create_params.sender}",
|
|
351
|
+
extra={
|
|
352
|
+
"suppress_log": deployment.send_params.get("suppress_log") or False if deployment.send_params else False
|
|
353
|
+
},
|
|
354
|
+
)
|
|
343
355
|
|
|
344
356
|
return AppDeployResult(
|
|
345
357
|
app=app_metadata,
|
|
@@ -412,6 +424,13 @@ class AppDeployer:
|
|
|
412
424
|
deleted=False,
|
|
413
425
|
)
|
|
414
426
|
self._update_app_lookup(deployment.create_params.sender, app_metadata)
|
|
427
|
+
logger.debug(
|
|
428
|
+
f"Group transaction sent: Replaced app {existing_app.app_id} with new app {app_id} from "
|
|
429
|
+
f"{deployment.create_params.sender} (Composer group count: {composer.count()})",
|
|
430
|
+
extra={
|
|
431
|
+
"suppress_log": deployment.send_params.get("suppress_log") or False if deployment.send_params else False
|
|
432
|
+
},
|
|
433
|
+
)
|
|
415
434
|
|
|
416
435
|
return AppDeployResult(
|
|
417
436
|
app=app_metadata,
|
|
@@ -464,6 +483,12 @@ class AppDeployer:
|
|
|
464
483
|
)
|
|
465
484
|
|
|
466
485
|
self._update_app_lookup(deployment.create_params.sender, app_metadata)
|
|
486
|
+
logger.debug(
|
|
487
|
+
f"Sent transaction ID {existing_app.app_id} (AppUpdate) from {deployment.create_params.sender}",
|
|
488
|
+
extra={
|
|
489
|
+
"suppress_log": deployment.send_params.get("suppress_log") or False if deployment.send_params else False
|
|
490
|
+
},
|
|
491
|
+
)
|
|
467
492
|
|
|
468
493
|
return AppDeployResult(
|
|
469
494
|
app=app_metadata,
|
|
@@ -491,7 +516,10 @@ class AppDeployer:
|
|
|
491
516
|
if existing_app.deletable:
|
|
492
517
|
return self._replace_app(deployment, existing_app, approval_program, clear_program)
|
|
493
518
|
else:
|
|
494
|
-
raise ValueError(
|
|
519
|
+
raise ValueError(
|
|
520
|
+
f"App is {'not' if not existing_app.deletable else ''} deletable and onSchemaBreak=ReplaceApp, "
|
|
521
|
+
"cannot delete and recreate app"
|
|
522
|
+
)
|
|
495
523
|
|
|
496
524
|
def _handle_update(
|
|
497
525
|
self,
|
|
@@ -512,13 +540,19 @@ class AppDeployer:
|
|
|
512
540
|
if existing_app.updatable:
|
|
513
541
|
return self._update_app(deployment, existing_app, approval_program, clear_program)
|
|
514
542
|
else:
|
|
515
|
-
raise ValueError(
|
|
543
|
+
raise ValueError(
|
|
544
|
+
f"App is {'not' if not existing_app.updatable else ''} updatable and onUpdate=UpdateApp, "
|
|
545
|
+
"cannot update app"
|
|
546
|
+
)
|
|
516
547
|
|
|
517
548
|
if deployment.on_update in (OnUpdate.ReplaceApp, "replace"):
|
|
518
549
|
if existing_app.deletable:
|
|
519
550
|
return self._replace_app(deployment, existing_app, approval_program, clear_program)
|
|
520
551
|
else:
|
|
521
|
-
raise ValueError(
|
|
552
|
+
raise ValueError(
|
|
553
|
+
f"App is {'not' if not existing_app.deletable else ''} deletable and onUpdate=ReplaceApp, "
|
|
554
|
+
"cannot delete and recreate app"
|
|
555
|
+
)
|
|
522
556
|
|
|
523
557
|
raise ValueError(f"Unsupported onUpdate value: {deployment.on_update}")
|
|
524
558
|
|
|
@@ -43,13 +43,13 @@ class AssetInformation:
|
|
|
43
43
|
:ivar decimals: The amount of decimal places the asset was created with
|
|
44
44
|
:ivar default_frozen: Whether the asset was frozen by default for all accounts, defaults to None
|
|
45
45
|
:ivar manager: The address of the optional account that can manage the configuration of the asset and destroy it,
|
|
46
|
-
|
|
46
|
+
defaults to None
|
|
47
47
|
:ivar reserve: The address of the optional account that holds the reserve (uncirculated supply) units of the asset,
|
|
48
|
-
|
|
48
|
+
defaults to None
|
|
49
49
|
:ivar freeze: The address of the optional account that can be used to freeze or unfreeze holdings of this asset,
|
|
50
|
-
|
|
50
|
+
defaults to None
|
|
51
51
|
:ivar clawback: The address of the optional account that can clawback holdings of this asset from any account,
|
|
52
|
-
|
|
52
|
+
defaults to None
|
|
53
53
|
:ivar unit_name: The optional name of the unit of this asset (e.g. ticker name), defaults to None
|
|
54
54
|
:ivar unit_name_b64: The optional name of the unit of this asset as bytes, defaults to None
|
|
55
55
|
:ivar asset_name: The optional name of the asset, defaults to None
|
|
@@ -57,7 +57,7 @@ class AssetInformation:
|
|
|
57
57
|
:ivar url: Optional URL where more information about the asset can be retrieved, defaults to None
|
|
58
58
|
:ivar url_b64: Optional URL where more information about the asset can be retrieved as bytes, defaults to None
|
|
59
59
|
:ivar metadata_hash: 32-byte hash of some metadata that is relevant to the asset and/or asset holders,
|
|
60
|
-
|
|
60
|
+
defaults to None
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
63
|
asset_id: int
|