From 86280753db88fd99bc540e3405da104d6b09d961 Mon Sep 17 00:00:00 2001 From: Al Date: Mon, 23 Dec 2024 16:46:40 +0100 Subject: [PATCH] feat: add offline_key_reg transaction to sender/creator/composer abstractions (#129) --- src/algokit_utils/applications/app_client.py | 4 -- .../transactions/transaction_composer.py | 71 ++++++++++++++----- .../transactions/transaction_creator.py | 6 ++ .../transactions/transaction_sender.py | 10 +++ tests/transactions/test_transaction_sender.py | 14 +++- 5 files changed, 81 insertions(+), 24 deletions(-) diff --git a/src/algokit_utils/applications/app_client.py b/src/algokit_utils/applications/app_client.py index a3c5e586..85ef23e9 100644 --- a/src/algokit_utils/applications/app_client.py +++ b/src/algokit_utils/applications/app_client.py @@ -63,7 +63,6 @@ from algokit_utils.models.amount import AlgoAmount from algokit_utils.models.state import BoxIdentifier, BoxReference, TealTemplateParams from algokit_utils.protocols.client import AlgorandClientProtocol - from algokit_utils.transactions.transaction_composer import TransactionComposer __all__ = [ "AppClient", @@ -1299,9 +1298,6 @@ def get_box_values_from_abi_type( # Return list of BoxABIValue objects return [BoxABIValue(name=name, value=values[i]) for i, name in enumerate(names)] - def new_group(self) -> TransactionComposer: - return self._algorand.new_group() - def fund_app_account(self, params: FundAppAccountParams) -> SendSingleTransactionResult: return self.send.fund_app_account(params) diff --git a/src/algokit_utils/transactions/transaction_composer.py b/src/algokit_utils/transactions/transaction_composer.py index b3062d39..7f7597cb 100644 --- a/src/algokit_utils/transactions/transaction_composer.py +++ b/src/algokit_utils/transactions/transaction_composer.py @@ -49,6 +49,7 @@ "AssetOptInParams", "AssetOptOutParams", "AssetTransferParams", + "OfflineKeyRegistrationParams", "OnlineKeyRegistrationParams", "PaymentParams", "SendAtomicTransactionComposerResults", @@ -232,6 +233,15 @@ class OnlineKeyRegistrationParams( state_proof_key: bytes | None = None +@dataclass(kw_only=True, frozen=True) +class OfflineKeyRegistrationParams(_CommonTxnWithSendParams): + """ + Offline key registration parameters. + """ + + prevent_account_from_ever_participating_again: bool + + @dataclass(kw_only=True, frozen=True) class AssetTransferParams( _CommonTxnWithSendParams, @@ -306,7 +316,7 @@ class AppCallParams(_CommonTxnWithSendParams): app_references: list[int] | None = None asset_references: list[int] | None = None extra_pages: int | None = None - box_references: list[BoxReference] | None = None + box_references: list[BoxReference | BoxIdentifier] | None = None @dataclass(kw_only=True, frozen=True) @@ -336,7 +346,7 @@ class AppCreateParams(_CommonTxnWithSendParams): account_references: list[str] | None = None app_references: list[int] | None = None asset_references: list[int] | None = None - box_references: list[BoxReference] | None = None + box_references: list[BoxReference | BoxIdentifier] | None = None extra_program_pages: int | None = None @@ -416,7 +426,7 @@ class AppMethodCallParams(_CommonTxnWithSendParams): account_references: list[str] | None = None app_references: list[int] | None = None asset_references: list[int] | None = None - box_references: list[BoxReference] | None = None + box_references: list[BoxReference | BoxIdentifier] | None = None @dataclass(kw_only=True, frozen=True) @@ -513,6 +523,7 @@ class AppDeleteMethodCallParams(_BaseAppMethodCall): AppUpdateParams, AppDeleteParams, MethodCallParams, + OfflineKeyRegistrationParams, ] @@ -802,6 +813,10 @@ def add_online_key_registration(self, params: OnlineKeyRegistrationParams) -> Tr self._txns.append(params) return self + def add_offline_key_registration(self, params: OfflineKeyRegistrationParams) -> TransactionComposer: + self._txns.append(params) + return self + def add_atc(self, atc: AtomicTransactionComposer) -> TransactionComposer: self._txns.append(atc) return self @@ -1080,7 +1095,7 @@ def _build_method_call( # noqa: C901, PLR0912 txn = self._build_asset_freeze(arg, suggested_params) case AssetTransferParams(): txn = self._build_asset_transfer(arg, suggested_params) - case OnlineKeyRegistrationParams(): + case OnlineKeyRegistrationParams() | OfflineKeyRegistrationParams(): txn = self._build_key_reg(arg, suggested_params) case _: raise ValueError(f"Unsupported method arg transaction type: {arg!s}") @@ -1288,22 +1303,40 @@ def _build_asset_transfer( return self._common_txn_build_step(lambda x: algosdk.transaction.AssetTransferTxn(**x), params, txn_params) def _build_key_reg( - self, params: OnlineKeyRegistrationParams, suggested_params: algosdk.transaction.SuggestedParams + self, + params: OnlineKeyRegistrationParams | OfflineKeyRegistrationParams, + suggested_params: algosdk.transaction.SuggestedParams, ) -> algosdk.transaction.Transaction: - txn_params = { - "sender": params.sender, - "sp": suggested_params, - "votekey": params.vote_key, - "selkey": params.selection_key, - "votefst": params.vote_first, - "votelst": params.vote_last, - "votekd": params.vote_key_dilution, - "rekey_to": params.rekey_to, - "nonpart": False, - "sprfkey": params.state_proof_key, - } + if isinstance(params, OnlineKeyRegistrationParams): + txn_params = { + "sender": params.sender, + "sp": suggested_params, + "votekey": params.vote_key, + "selkey": params.selection_key, + "votefst": params.vote_first, + "votelst": params.vote_last, + "votekd": params.vote_key_dilution, + "rekey_to": params.rekey_to, + "nonpart": False, + "sprfkey": params.state_proof_key, + } - return self._common_txn_build_step(lambda x: algosdk.transaction.KeyregTxn(**x), params, txn_params) + return self._common_txn_build_step(lambda x: algosdk.transaction.KeyregTxn(**x), params, txn_params) + + return self._common_txn_build_step( + lambda x: algosdk.transaction.KeyregTxn(**x), + params, + { + "sender": params.sender, + "sp": suggested_params, + "nonpart": params.prevent_account_from_ever_participating_again, + "votekey": None, + "selkey": None, + "votefst": None, + "votelst": None, + "votekd": None, + }, + ) def _is_abi_value(self, x: bool | float | str | bytes | list | TxnParams) -> bool: if isinstance(x, list | tuple): @@ -1369,7 +1402,7 @@ def _build_txn( # noqa: C901, PLR0912, PLR0911 suggested_params, ) return [TransactionWithSigner(txn=asset_transfer, signer=signer)] - case OnlineKeyRegistrationParams(): + case OnlineKeyRegistrationParams() | OfflineKeyRegistrationParams(): key_reg = self._build_key_reg(txn, suggested_params) return [TransactionWithSigner(txn=key_reg, signer=signer)] case _: diff --git a/src/algokit_utils/transactions/transaction_creator.py b/src/algokit_utils/transactions/transaction_creator.py index 7bd4899a..f0c5397f 100644 --- a/src/algokit_utils/transactions/transaction_creator.py +++ b/src/algokit_utils/transactions/transaction_creator.py @@ -20,6 +20,7 @@ AssetOptOutParams, AssetTransferParams, BuiltTransactions, + OfflineKeyRegistrationParams, OnlineKeyRegistrationParams, PaymentParams, TransactionComposer, @@ -152,3 +153,8 @@ def app_call_method_call(self) -> Callable[[AppCallMethodCallParams], BuiltTrans def online_key_registration(self) -> Callable[[OnlineKeyRegistrationParams], Transaction]: """Create an online key registration transaction.""" return self._transaction(lambda c: c.add_online_key_registration) + + @property + def offline_key_registration(self) -> Callable[[OfflineKeyRegistrationParams], Transaction]: + """Create an offline key registration transaction.""" + return self._transaction(lambda c: c.add_offline_key_registration) diff --git a/src/algokit_utils/transactions/transaction_sender.py b/src/algokit_utils/transactions/transaction_sender.py index 6c072edc..f0ffdf83 100644 --- a/src/algokit_utils/transactions/transaction_sender.py +++ b/src/algokit_utils/transactions/transaction_sender.py @@ -28,6 +28,7 @@ AssetOptInParams, AssetOptOutParams, AssetTransferParams, + OfflineKeyRegistrationParams, OnlineKeyRegistrationParams, PaymentParams, SendAtomicTransactionComposerResults, @@ -397,3 +398,12 @@ def online_key_registration(self, params: OnlineKeyRegistrationParams) -> SendSi f"Registering online key for {params.sender} via transaction {transaction.get_txid()}" ), )(params) + + def offline_key_registration(self, params: OfflineKeyRegistrationParams) -> SendSingleTransactionResult: + """Register an offline key.""" + return self._send( + lambda c: c.add_offline_key_registration, + pre_log=lambda params, transaction: ( + f"Registering offline key for {params.sender} via transaction {transaction.get_txid()}" + ), + )(params) diff --git a/tests/transactions/test_transaction_sender.py b/tests/transactions/test_transaction_sender.py index 1d1cf2a8..47000dde 100644 --- a/tests/transactions/test_transaction_sender.py +++ b/tests/transactions/test_transaction_sender.py @@ -22,6 +22,7 @@ AssetOptInParams, AssetOptOutParams, AssetTransferParams, + OfflineKeyRegistrationParams, OnlineKeyRegistrationParams, PaymentParams, TransactionComposer, @@ -449,7 +450,7 @@ def test_payment_logging( assert receiver.address in log_message -def test_online_key_registration(transaction_sender: AlgorandClientTransactionSender, sender: Account) -> None: +def test_key_registration(transaction_sender: AlgorandClientTransactionSender, sender: Account) -> None: sp = transaction_sender._algod.suggested_params() # noqa: SLF001 params = OnlineKeyRegistrationParams( @@ -465,3 +466,14 @@ def test_online_key_registration(transaction_sender: AlgorandClientTransactionSe result = transaction_sender.online_key_registration(params) assert len(result.tx_ids) == 1 assert result.confirmations[-1]["confirmed-round"] > 0 # type: ignore[call-overload] + + sp = transaction_sender._algod.suggested_params() # noqa: SLF001 + + off_key_reg_params = OfflineKeyRegistrationParams( + sender=sender.address, + prevent_account_from_ever_participating_again=True, + ) + + result = transaction_sender.offline_key_registration(off_key_reg_params) + assert len(result.tx_ids) == 1 + assert result.confirmations[-1]["confirmed-round"] > 0 # type: ignore[call-overload]