From 1e3797e38a4cf5ca3032cac267e3a736b6c71b77 Mon Sep 17 00:00:00 2001 From: Joe Polny Date: Mon, 15 Apr 2024 08:12:32 -0400 Subject: [PATCH] ruff --- src/algokit_utils/beta/account_manager.py | 6 +- src/algokit_utils/beta/algorand_client.py | 44 ++-- src/algokit_utils/beta/client_manager.py | 18 +- src/algokit_utils/beta/composer.py | 308 ++++++++++++---------- src/algokit_utils/network_clients.py | 4 +- tests/test_algorand_client.py | 175 +++++++----- 6 files changed, 313 insertions(+), 242 deletions(-) diff --git a/src/algokit_utils/beta/account_manager.py b/src/algokit_utils/beta/account_manager.py index 7a55a3ab..b68a6d1c 100644 --- a/src/algokit_utils/beta/account_manager.py +++ b/src/algokit_utils/beta/account_manager.py @@ -101,9 +101,9 @@ def get_asset_information(self, sender: str, asset_id: int) -> dict[str, Any]: # return self.signer_account(rekeyed_account(account, sender) if sender else account) def from_kmd( - self, - name: str, - predicate: Callable[[dict[str, Any]], bool] | None = None, + self, + name: str, + predicate: Callable[[dict[str, Any]], bool] | None = None, ) -> AddressAndSigner: """ Tracks and returns an Algorand account with private key loaded from the given KMD wallet (identified by name). diff --git a/src/algokit_utils/beta/algorand_client.py b/src/algokit_utils/beta/algorand_client.py index faca9db1..91551204 100644 --- a/src/algokit_utils/beta/algorand_client.py +++ b/src/algokit_utils/beta/algorand_client.py @@ -36,6 +36,7 @@ class AlgorandClientSendMethods: """ Methods used to send a transaction to the network and wait for confirmation """ + payment: Callable[[PayParams], dict[str, Any]] asset_create: Callable[[AssetCreateParams], dict[str, Any]] asset_config: Callable[[AssetConfigParams], dict[str, Any]] @@ -53,6 +54,7 @@ class AlgorandClientTransactionMethods: """ Methods used to form a transaction without signing or sending to the network """ + payment: Callable[[PayParams], Transaction] asset_create: Callable[[AssetCreateParams], Transaction] asset_config: Callable[[AssetConfigParams], Transaction] @@ -81,7 +83,7 @@ def __init__(self, config: AlgoClientConfigs | AlgoSdkClients): def _unwrap_single_send_result(self, results: AtomicTransactionResponse) -> dict[str, Any]: return { "confirmation": wait_for_confirmation(self._client_manager.algod, results.tx_ids[0]), - "tx_id": results.tx_ids[0] + "tx_id": results.tx_ids[0], } def set_default_validity_window(self, validity_window: int) -> Self: @@ -140,7 +142,7 @@ def set_suggested_params_timeout(self, timeout: int) -> Self: def get_suggested_params(self) -> SuggestedParams: """Get suggested params for a transaction (either cached or from algod if the cache is stale or empty)""" if self._cached_suggested_params and ( - self._cached_suggested_params_expiry is None or self._cached_suggested_params_expiry > time.time() + self._cached_suggested_params_expiry is None or self._cached_suggested_params_expiry > time.time() ): return copy.deepcopy(self._cached_suggested_params) @@ -174,22 +176,30 @@ def send(self) -> AlgorandClientSendMethods: return AlgorandClientSendMethods( payment=lambda params: self._unwrap_single_send_result(self.new_group().add_payment(params).execute()), asset_create=lambda params: self._unwrap_single_send_result( - self.new_group().add_asset_create(params).execute()), + self.new_group().add_asset_create(params).execute() + ), asset_config=lambda params: self._unwrap_single_send_result( - self.new_group().add_asset_config(params).execute()), + self.new_group().add_asset_config(params).execute() + ), asset_freeze=lambda params: self._unwrap_single_send_result( - self.new_group().add_asset_freeze(params).execute()), + self.new_group().add_asset_freeze(params).execute() + ), asset_destroy=lambda params: self._unwrap_single_send_result( - self.new_group().add_asset_destroy(params).execute()), + self.new_group().add_asset_destroy(params).execute() + ), asset_transfer=lambda params: self._unwrap_single_send_result( - self.new_group().add_asset_transfer(params).execute()), + self.new_group().add_asset_transfer(params).execute() + ), app_call=lambda params: self._unwrap_single_send_result(self.new_group().add_app_call(params).execute()), online_key_reg=lambda params: self._unwrap_single_send_result( - self.new_group().add_online_key_reg(params).execute()), + self.new_group().add_online_key_reg(params).execute() + ), method_call=lambda params: self._unwrap_single_send_result( - self.new_group().add_method_call(params).execute()), + self.new_group().add_method_call(params).execute() + ), asset_opt_in=lambda params: self._unwrap_single_send_result( - self.new_group().add_asset_opt_in(params).execute()) + self.new_group().add_asset_opt_in(params).execute() + ), ) @property @@ -206,7 +216,7 @@ def transactions(self) -> AlgorandClientTransactionMethods: app_call=lambda params: self.new_group().add_app_call(params).build_group()[0].txn, online_key_reg=lambda params: self.new_group().add_online_key_reg(params).build_group()[0].txn, method_call=lambda params: [txn.txn for txn in self.new_group().add_method_call(params).build_group()], - asset_opt_in=lambda params: self.new_group().add_asset_opt_in(params).build_group()[0].txn + asset_opt_in=lambda params: self.new_group().add_asset_opt_in(params).build_group()[0].txn, ) @staticmethod @@ -275,11 +285,13 @@ def from_environment() -> Self: :return: The `AlgorandClient` """ - return AlgorandClient(AlgoSdkClients( - algod=get_algod_client(), - kmd=get_kmd_client(), - indexer=get_indexer_client(), - )) + return AlgorandClient( + AlgoSdkClients( + algod=get_algod_client(), + kmd=get_kmd_client(), + indexer=get_indexer_client(), + ) + ) @staticmethod def from_config(config: AlgoClientConfigs) -> Self: diff --git a/src/algokit_utils/beta/client_manager.py b/src/algokit_utils/beta/client_manager.py index d287578e..1069eacf 100644 --- a/src/algokit_utils/beta/client_manager.py +++ b/src/algokit_utils/beta/client_manager.py @@ -1,4 +1,3 @@ - import algosdk from algokit_utils.dispenser_api import TestNetDispenserApiClient from algokit_utils.network_clients import AlgoClientConfigs, get_algod_client, get_indexer_client, get_kmd_client @@ -17,8 +16,12 @@ class AlgoSdkClients: kmd (Optional[KMDClient]): Optional KMD client, see https://developer.algorand.org/docs/rest-apis/kmd/ """ - def __init__(self, algod: algosdk.v2client.algod.AlgodClient, indexer: IndexerClient | None = None, - kmd: KMDClient | None = None): + def __init__( + self, + algod: algosdk.v2client.algod.AlgodClient, + indexer: IndexerClient | None = None, + kmd: KMDClient | None = None, + ): self.algod = algod self.indexer = indexer self.kmd = kmd @@ -38,8 +41,9 @@ def __init__(self, clients_or_configs: AlgoClientConfigs | AlgoSdkClients): elif isinstance(clients_or_configs, AlgoClientConfigs): _clients = AlgoSdkClients( algod=get_algod_client(clients_or_configs.algod_config), - indexer=get_indexer_client( - clients_or_configs.indexer_config) if clients_or_configs.indexer_config else None, + indexer=get_indexer_client(clients_or_configs.indexer_config) + if clients_or_configs.indexer_config + else None, kmd=get_kmd_client(clients_or_configs.kmd_config) if clients_or_configs.kmd_config else None, ) self._algod = _clients.algod @@ -66,9 +70,7 @@ def kmd(self) -> KMDClient: return self._kmd def get_testnet_dispenser( - self, - auth_token: str | None = None, - request_timeout: int | None = None + self, auth_token: str | None = None, request_timeout: int | None = None ) -> TestNetDispenserApiClient: if request_timeout: return TestNetDispenserApiClient(auth_token=auth_token, request_timeout=request_timeout) diff --git a/src/algokit_utils/beta/composer.py b/src/algokit_utils/beta/composer.py index 9b88421d..ae13f5c0 100644 --- a/src/algokit_utils/beta/composer.py +++ b/src/algokit_utils/beta/composer.py @@ -23,19 +23,20 @@ class SenderParam: @dataclass(frozen=True) class CommonTxnParams: """ - Common transaction parameters. - - :param signer: The function used to sign transactions. - :param rekey_to: Change the signing key of the sender to the given address. - :param note: Note to attach to the transaction. - :param lease: Prevent multiple transactions with the same lease being included within the validity window. - :param static_fee: The transaction fee. In most cases you want to use `extra_fee` unless setting the fee to 0 to be covered by another transaction. - :param extra_fee: The fee to pay IN ADDITION to the suggested fee. Useful for covering inner transaction fees. - :param max_fee: Throw an error if the fee for the transaction is more than this amount. - :param validity_window: How many rounds the transaction should be valid for. - :param first_valid_round: Set the first round this transaction is valid. If left undefined, the value from algod will be used. Only set this when you intentionally want this to be some time in the future. - :param last_valid_round: The last round this transaction is valid. It is recommended to use validity_window instead. + Common transaction parameters. + + :param signer: The function used to sign transactions. + :param rekey_to: Change the signing key of the sender to the given address. + :param note: Note to attach to the transaction. + :param lease: Prevent multiple transactions with the same lease being included within the validity window. + :param static_fee: The transaction fee. In most cases you want to use `extra_fee` unless setting the fee to 0 to be covered by another transaction. + :param extra_fee: The fee to pay IN ADDITION to the suggested fee. Useful for covering inner transaction fees. + :param max_fee: Throw an error if the fee for the transaction is more than this amount. + :param validity_window: How many rounds the transaction should be valid for. + :param first_valid_round: Set the first round this transaction is valid. If left undefined, the value from algod will be used. Only set this when you intentionally want this to be some time in the future. + :param last_valid_round: The last round this transaction is valid. It is recommended to use validity_window instead. """ + signer: TransactionSigner | None = None rekey_to: str | None = None note: bytes | None = None @@ -57,12 +58,13 @@ class _RequiredPayTxnParams(SenderParam): @dataclass(frozen=True) class PayParams(CommonTxnParams, _RequiredPayTxnParams): """ - Payment transaction parameters. + Payment transaction parameters. - :param receiver: The account that will receive the ALGO. - :param amount: Amount to send. - :param close_remainder_to: If given, close the sender account and send the remaining balance to this address. + :param receiver: The account that will receive the ALGO. + :param amount: Amount to send. + :param close_remainder_to: If given, close the sender account and send the remaining balance to this address. """ + close_remainder_to: str | None = None @@ -74,20 +76,21 @@ class _RequiredAssetCreateParams(SenderParam): @dataclass(frozen=True) class AssetCreateParams(CommonTxnParams, _RequiredAssetCreateParams): """ - Asset creation parameters. - - :param total: The total amount of the smallest divisible unit to create. - :param decimals: The amount of decimal places the asset should have. - :param default_frozen: Whether the asset is frozen by default in the creator address. - :param manager: The address that can change the manager, reserve, clawback, and freeze addresses. There will permanently be no manager if undefined or an empty string. - :param reserve: The address that holds the uncirculated supply. - :param freeze: The address that can freeze the asset in any account. Freezing will be permanently disabled if undefined or an empty string. - :param clawback: The address that can clawback the asset from any account. Clawback will be permanently disabled if undefined or an empty string. - :param unit_name: The short ticker name for the asset. - :param asset_name: The full name of the asset. - :param url: The metadata URL for the asset. - :param metadata_hash: Hash of the metadata contained in the metadata URL. + Asset creation parameters. + + :param total: The total amount of the smallest divisible unit to create. + :param decimals: The amount of decimal places the asset should have. + :param default_frozen: Whether the asset is frozen by default in the creator address. + :param manager: The address that can change the manager, reserve, clawback, and freeze addresses. There will permanently be no manager if undefined or an empty string. + :param reserve: The address that holds the uncirculated supply. + :param freeze: The address that can freeze the asset in any account. Freezing will be permanently disabled if undefined or an empty string. + :param clawback: The address that can clawback the asset from any account. Clawback will be permanently disabled if undefined or an empty string. + :param unit_name: The short ticker name for the asset. + :param asset_name: The full name of the asset. + :param url: The metadata URL for the asset. + :param metadata_hash: Hash of the metadata contained in the metadata URL. """ + decimals: int | None = None default_frozen: bool | None = None manager: str | None = None @@ -108,14 +111,15 @@ class _RequiredAssetConfigParams(SenderParam): @dataclass(frozen=True) class AssetConfigParams(CommonTxnParams, _RequiredAssetConfigParams): """ - Asset configuration parameters. + Asset configuration parameters. - :param asset_id: ID of the asset. - :param manager: The address that can change the manager, reserve, clawback, and freeze addresses. There will permanently be no manager if undefined or an empty string. - :param reserve: The address that holds the uncirculated supply. - :param freeze: The address that can freeze the asset in any account. Freezing will be permanently disabled if undefined or an empty string. - :param clawback: The address that can clawback the asset from any account. Clawback will be permanently disabled if undefined or an empty string. + :param asset_id: ID of the asset. + :param manager: The address that can change the manager, reserve, clawback, and freeze addresses. There will permanently be no manager if undefined or an empty string. + :param reserve: The address that holds the uncirculated supply. + :param freeze: The address that can freeze the asset in any account. Freezing will be permanently disabled if undefined or an empty string. + :param clawback: The address that can clawback the asset from any account. Clawback will be permanently disabled if undefined or an empty string. """ + manager: str | None = None reserve: str | None = None freeze: str | None = None @@ -132,11 +136,11 @@ class _RequiredAssetFreezeParams(SenderParam): @dataclass(frozen=True) class AssetFreezeParams(CommonTxnParams, _RequiredAssetFreezeParams): """ - Asset freeze parameters. + Asset freeze parameters. - :param asset_id: The ID of the asset. - :param account: The account to freeze or unfreeze. - :param frozen: Whether the assets in the account should be frozen. + :param asset_id: The ID of the asset. + :param account: The account to freeze or unfreeze. + :param frozen: Whether the assets in the account should be frozen. """ @@ -148,9 +152,9 @@ class _RequiredAssetDestroyParams(SenderParam): @dataclass(frozen=True) class AssetDestroyParams(CommonTxnParams, _RequiredAssetDestroyParams): """ - Asset destruction parameters. + Asset destruction parameters. - :param asset_id: ID of the asset. + :param asset_id: ID of the asset. """ @@ -166,15 +170,16 @@ class _RequiredOnlineKeyRegParams(SenderParam): @dataclass(frozen=True) class OnlineKeyRegParams(CommonTxnParams, _RequiredOnlineKeyRegParams): """ - Online key registration parameters. - - :param vote_key: The root participation public key. - :param selection_key: The VRF public key. - :param vote_first: The first round that the participation key is valid. Not to be confused with the `first_valid` round of the keyreg transaction. - :param vote_last: The last round that the participation key is valid. Not to be confused with the `last_valid` round of the keyreg transaction. - :param vote_key_dilution: This is the dilution for the 2-level participation key. It determines the interval (number of rounds) for generating new ephemeral keys. - :param state_proof_key: The 64 byte state proof public key commitment. + Online key registration parameters. + + :param vote_key: The root participation public key. + :param selection_key: The VRF public key. + :param vote_first: The first round that the participation key is valid. Not to be confused with the `first_valid` round of the keyreg transaction. + :param vote_last: The last round that the participation key is valid. Not to be confused with the `last_valid` round of the keyreg transaction. + :param vote_key_dilution: This is the dilution for the 2-level participation key. It determines the interval (number of rounds) for generating new ephemeral keys. + :param state_proof_key: The 64 byte state proof public key commitment. """ + state_proof_key: bytes | None = None @@ -188,14 +193,15 @@ class _RequiredAssetTransferParams(SenderParam): @dataclass(frozen=True) class AssetTransferParams(CommonTxnParams, _RequiredAssetTransferParams): """ - Asset transfer parameters. + Asset transfer parameters. - :param asset_id: ID of the asset. - :param amount: Amount of the asset to transfer (smallest divisible unit). - :param receiver: The account to send the asset to. - :param clawback_target: The account to take the asset from. - :param close_asset_to: The account to close the asset to. + :param asset_id: ID of the asset. + :param amount: Amount of the asset to transfer (smallest divisible unit). + :param receiver: The account to send the asset to. + :param clawback_target: The account to take the asset from. + :param close_asset_to: The account to close the asset to. """ + clawback_target: str | None = None close_asset_to: str | None = None @@ -208,29 +214,30 @@ class _RequiredAssetOptInParams(SenderParam): @dataclass(frozen=True) class AssetOptInParams(CommonTxnParams, _RequiredAssetOptInParams): """ - Asset opt-in parameters. + Asset opt-in parameters. - :param asset_id: ID of the asset. + :param asset_id: ID of the asset. """ @dataclass(frozen=True) class AppCallParams(CommonTxnParams, SenderParam): """ - Application call parameters. - - :param on_complete: The OnComplete action. - :param app_id: ID of the application. - :param approval_program: The program to execute for all OnCompletes other than ClearState. - :param clear_program: The program to execute for ClearState OnComplete. - :param schema: The state schema for the app. This is immutable. - :param args: Application arguments. - :param account_references: Account references. - :param app_references: App references. - :param asset_references: Asset references. - :param extra_pages: Number of extra pages required for the programs. - :param box_references: Box references. + Application call parameters. + + :param on_complete: The OnComplete action. + :param app_id: ID of the application. + :param approval_program: The program to execute for all OnCompletes other than ClearState. + :param clear_program: The program to execute for ClearState OnComplete. + :param schema: The state schema for the app. This is immutable. + :param args: Application arguments. + :param account_references: Account references. + :param app_references: App references. + :param asset_references: Asset references. + :param extra_pages: Number of extra pages required for the programs. + :param box_references: Box references. """ + on_complete: OnComplete | None = None app_id: int | None = None approval_program: bytes | None = None @@ -253,12 +260,13 @@ class _RequiredMethodCallParams(SenderParam): @dataclass(frozen=True) class MethodCallParams(CommonTxnParams, _RequiredMethodCallParams): """ - Method call parameters. + Method call parameters. - :param app_id: ID of the application. - :param method: The ABI method to call. - :param args: Arguments to the ABI method. + :param app_id: ID of the application. + :param method: The ABI method to call. + :param args: Arguments to the ABI method. """ + args: list | None = None @@ -275,6 +283,7 @@ class MethodCallParams(CommonTxnParams, _RequiredMethodCallParams): MethodCallParams, ] + class AlgokitComposer: """ A class for composing and managing Algorand transactions using the Algosdk library. @@ -290,11 +299,11 @@ class AlgokitComposer: """ def __init__( - self, - algod: AlgodClient, - get_signer: Callable[[str], TransactionSigner], - get_suggested_params: Callable[[], algosdk.transaction.SuggestedParams] | None = None, - default_validity_window: int | None = None, + self, + algod: AlgodClient, + get_signer: Callable[[str], TransactionSigner], + get_suggested_params: Callable[[], algosdk.transaction.SuggestedParams] | None = None, + default_validity_window: int | None = None, ): """ Initialize an instance of the AlgokitComposer class. @@ -314,47 +323,47 @@ def __init__( self.get_signer: Callable[[str], TransactionSigner] = get_signer self.default_validity_window: int = default_validity_window or 10 - def add_payment(self, params: PayParams) -> 'AlgokitComposer': + def add_payment(self, params: PayParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_asset_create(self, params: AssetCreateParams) -> 'AlgokitComposer': + def add_asset_create(self, params: AssetCreateParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_asset_config(self, params: AssetConfigParams) -> 'AlgokitComposer': + def add_asset_config(self, params: AssetConfigParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_asset_freeze(self, params: AssetFreezeParams) -> 'AlgokitComposer': + def add_asset_freeze(self, params: AssetFreezeParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_asset_destroy(self, params: AssetDestroyParams) -> 'AlgokitComposer': + def add_asset_destroy(self, params: AssetDestroyParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_asset_transfer(self, params: AssetTransferParams) -> 'AlgokitComposer': + def add_asset_transfer(self, params: AssetTransferParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_asset_opt_in(self, params: AssetOptInParams) -> 'AlgokitComposer': + def add_asset_opt_in(self, params: AssetOptInParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_app_call(self, params: AppCallParams) -> 'AlgokitComposer': + def add_app_call(self, params: AppCallParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_online_key_reg(self, params: OnlineKeyRegParams) -> 'AlgokitComposer': + def add_online_key_reg(self, params: OnlineKeyRegParams) -> "AlgokitComposer": self.txns.append(params) return self - def add_atc(self, atc: AtomicTransactionComposer) -> 'AlgokitComposer': + def add_atc(self, atc: AtomicTransactionComposer) -> "AlgokitComposer": self.txns.append(atc) return self - def add_method_call(self, params: MethodCallParams) -> 'AlgokitComposer': + def add_method_call(self, params: MethodCallParams) -> "AlgokitComposer": self.txns.append(params) return self @@ -371,10 +380,10 @@ def _build_atc(self, atc: AtomicTransactionComposer) -> list[TransactionWithSign return group def _common_txn_build_step( - self, - params: CommonTxnParams, - txn: algosdk.transaction.Transaction, - suggested_params: algosdk.transaction.SuggestedParams + self, + params: CommonTxnParams, + txn: algosdk.transaction.Transaction, + suggested_params: algosdk.transaction.SuggestedParams, ) -> algosdk.transaction.Transaction: if params.lease: txn.lease = params.lease @@ -392,7 +401,7 @@ def _common_txn_build_step( txn.last_valid_round = txn.first_valid_round + (params.validity_window or self.default_validity_window) if params.static_fee is not None and params.extra_fee is not None: - raise ValueError('Cannot set both static_fee and extra_fee') + raise ValueError("Cannot set both static_fee and extra_fee") if params.static_fee is not None: txn.fee = params.static_fee @@ -402,12 +411,13 @@ def _common_txn_build_step( txn.fee += params.extra_fee if params.max_fee is not None and txn.fee > params.max_fee: - raise ValueError(f'Transaction fee {txn.fee} is greater than max_fee {params.max_fee}') + raise ValueError(f"Transaction fee {txn.fee} is greater than max_fee {params.max_fee}") return txn - def _build_payment(self, params: PayParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_payment( + self, params: PayParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: txn = algosdk.transaction.PaymentTxn( sender=params.sender, sp=suggested_params, @@ -418,8 +428,9 @@ def _build_payment(self, params: PayParams, return self._common_txn_build_step(params, txn, suggested_params) - def _build_asset_create(self, params: AssetCreateParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_asset_create( + self, params: AssetCreateParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: txn = algosdk.transaction.AssetConfigTxn( sender=params.sender, sp=suggested_params, @@ -439,33 +450,37 @@ def _build_asset_create(self, params: AssetCreateParams, return self._common_txn_build_step(params, txn, suggested_params) - def _build_app_call(self, params: AppCallParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_app_call( + self, params: AppCallParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: sdk_params = { - 'sender': params.sender, - 'sp': suggested_params, - 'index': params.app_id or 0, - 'on_complete': params.on_complete or algosdk.transaction.OnComplete.NoOpOC, - 'approval_program': params.approval_program, - 'clear_program': params.clear_program, - 'app_args': params.args, - 'accounts': params.account_references, - 'foreign_apps': params.app_references, - 'foreign_assets': params.asset_references, - 'extra_pages': params.extra_pages, - 'local_schema': algosdk.transaction.StateSchema( - num_uints=params.schema.get("local_uints", 0), - num_byte_slices=params.schema.get("local_byte_slices", 0) - ) if params.schema else None, - 'global_schema': algosdk.transaction.StateSchema( + "sender": params.sender, + "sp": suggested_params, + "index": params.app_id or 0, + "on_complete": params.on_complete or algosdk.transaction.OnComplete.NoOpOC, + "approval_program": params.approval_program, + "clear_program": params.clear_program, + "app_args": params.args, + "accounts": params.account_references, + "foreign_apps": params.app_references, + "foreign_assets": params.asset_references, + "extra_pages": params.extra_pages, + "local_schema": algosdk.transaction.StateSchema( + num_uints=params.schema.get("local_uints", 0), num_byte_slices=params.schema.get("local_byte_slices", 0) + ) + if params.schema + else None, + "global_schema": algosdk.transaction.StateSchema( num_uints=params.schema.get("global_uints", 0), - num_byte_slices=params.schema.get("global_byte_slices", 0) - ) if params.schema else None, + num_byte_slices=params.schema.get("global_byte_slices", 0), + ) + if params.schema + else None, } if not params.app_id: if params.approval_program is None or params.clear_program is None: - raise ValueError('approval_program and clear_program are required for application creation') + raise ValueError("approval_program and clear_program are required for application creation") txn = algosdk.transaction.ApplicationCreateTxn(**sdk_params) else: @@ -473,8 +488,9 @@ def _build_app_call(self, params: AppCallParams, return self._common_txn_build_step(params, txn, suggested_params) - def _build_asset_config(self, params: AssetConfigParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_asset_config( + self, params: AssetConfigParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: txn = algosdk.transaction.AssetConfigTxn( sender=params.sender, sp=suggested_params, @@ -488,8 +504,9 @@ def _build_asset_config(self, params: AssetConfigParams, return self._common_txn_build_step(params, txn, suggested_params) - def _build_asset_destroy(self, params: AssetDestroyParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_asset_destroy( + self, params: AssetDestroyParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: txn = algosdk.transaction.AssetDestroyTxn( sender=params.sender, sp=suggested_params, @@ -498,8 +515,9 @@ def _build_asset_destroy(self, params: AssetDestroyParams, return self._common_txn_build_step(params, txn, suggested_params) - def _build_asset_freeze(self, params: AssetFreezeParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_asset_freeze( + self, params: AssetFreezeParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: txn = algosdk.transaction.AssetFreezeTxn( sender=params.sender, sp=suggested_params, @@ -510,8 +528,9 @@ def _build_asset_freeze(self, params: AssetFreezeParams, return self._common_txn_build_step(params, txn, suggested_params) - def _build_asset_transfer(self, params: AssetTransferParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_asset_transfer( + self, params: AssetTransferParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: txn = algosdk.transaction.AssetTransferTxn( sender=params.sender, sp=suggested_params, @@ -524,8 +543,9 @@ def _build_asset_transfer(self, params: AssetTransferParams, return self._common_txn_build_step(params, txn, suggested_params) - def _build_key_reg(self, params: OnlineKeyRegParams, - suggested_params: algosdk.transaction.SuggestedParams) -> algosdk.transaction.Transaction: + def _build_key_reg( + self, params: OnlineKeyRegParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> algosdk.transaction.Transaction: txn = algosdk.transaction.KeyregTxn( sender=params.sender, sp=suggested_params, @@ -547,8 +567,9 @@ def _is_abi_value(self, x: bool | float | str | bytes | list | TxnParams) -> boo return isinstance(x, bool | int | float | str | bytes) - def _build_method_call(self, params: MethodCallParams, suggested_params: algosdk.transaction.SuggestedParams) -> \ - list[TransactionWithSigner]: + def _build_method_call( + self, params: MethodCallParams, suggested_params: algosdk.transaction.SuggestedParams + ) -> list[TransactionWithSigner]: method_args = [] arg_offset = 0 @@ -571,7 +592,8 @@ def _build_method_call(self, params: MethodCallParams, suggested_params: algosdk txn = self._build_payment(arg, suggested_params) case AssetOptInParams(): txn = self._build_asset_transfer( - AssetTransferParams(**arg.__dict__, receiver=arg.sender, amount=0), suggested_params) + AssetTransferParams(**arg.__dict__, receiver=arg.sender, amount=0), suggested_params + ) case AssetCreateParams(): txn = self._build_asset_create(arg, suggested_params) case AssetConfigParams(): @@ -585,7 +607,7 @@ def _build_method_call(self, params: MethodCallParams, suggested_params: algosdk case OnlineKeyRegParams(): txn = self._build_key_reg(arg, suggested_params) case _: - raise ValueError(f'Unsupported method arg transaction type: {arg}') + raise ValueError(f"Unsupported method arg transaction type: {arg}") method_args.append( TransactionWithSigner(txn=txn, signer=params.signer or self.get_signer(params.sender)) @@ -593,7 +615,7 @@ def _build_method_call(self, params: MethodCallParams, suggested_params: algosdk continue - raise ValueError(f'Unsupported method arg: {arg}') + raise ValueError(f"Unsupported method arg: {arg}") method_atc = AtomicTransactionComposer() @@ -606,13 +628,16 @@ def _build_method_call(self, params: MethodCallParams, suggested_params: algosdk method_args=method_args, on_complete=algosdk.transaction.OnComplete.NoOpOC, note=params.note, - lease=params.lease + lease=params.lease, ) return self._build_atc(method_atc) - def _build_txn(self, txn: TransactionWithSigner | TxnParams | AtomicTransactionComposer, # noqa: PLR0911 - suggested_params: algosdk.transaction.SuggestedParams) -> list[TransactionWithSigner]: + def _build_txn( + self, + txn: TransactionWithSigner | TxnParams | AtomicTransactionComposer, # noqa: PLR0911 + suggested_params: algosdk.transaction.SuggestedParams, + ) -> list[TransactionWithSigner]: match txn: case TransactionWithSigner(): return [txn] @@ -647,13 +672,14 @@ def _build_txn(self, txn: TransactionWithSigner | TxnParams | AtomicTransactionC return [TransactionWithSigner(txn=asset_transfer, signer=signer)] case AssetOptInParams(): asset_transfer = self._build_asset_transfer( - AssetTransferParams(**txn.__dict__, receiver=txn.sender, amount=0), suggested_params) + AssetTransferParams(**txn.__dict__, receiver=txn.sender, amount=0), suggested_params + ) return [TransactionWithSigner(txn=asset_transfer, signer=signer)] case OnlineKeyRegParams(): key_reg = self._build_key_reg(txn, suggested_params) return [TransactionWithSigner(txn=key_reg, signer=signer)] case _: - raise ValueError(f'Unsupported txn: {txn}') + raise ValueError(f"Unsupported txn: {txn}") def build_group(self) -> list[TransactionWithSigner]: suggested_params = self.get_suggested_params() diff --git a/src/algokit_utils/network_clients.py b/src/algokit_utils/network_clients.py index c6508f2e..be9e48fc 100644 --- a/src/algokit_utils/network_clients.py +++ b/src/algokit_utils/network_clients.py @@ -19,7 +19,7 @@ "is_mainnet", "is_testnet", "AlgoClientConfigs", - "get_kmd_client" + "get_kmd_client", ] _PURE_STAKE_HOST = "purestake.io" @@ -42,6 +42,7 @@ class AlgoClientConfigs: indexer_config: AlgoClientConfig kmd_config: AlgoClientConfig | None + def get_default_localnet_config(config: Literal["algod", "indexer", "kmd"]) -> AlgoClientConfig: """Returns the client configuration to point to the default LocalNet""" port = {"algod": 4001, "indexer": 8980, "kmd": 4002}[config] @@ -76,6 +77,7 @@ def get_algod_client(config: AlgoClientConfig | None = None) -> AlgodClient: headers = _get_headers(config, "X-Algo-API-Token") return AlgodClient(config.token, config.server, headers) + def get_kmd_client(config: AlgoClientConfig | None = None) -> KMDClient: """Returns an {py:class}`algosdk.kmd.KMDClient` from `config` or environment diff --git a/tests/test_algorand_client.py b/tests/test_algorand_client.py index e951d38f..4b82cb48 100644 --- a/tests/test_algorand_client.py +++ b/tests/test_algorand_client.py @@ -25,53 +25,45 @@ def algorand(funded_account: Account) -> AlgorandClient: @pytest.fixture() def alice(algorand: AlgorandClient, funded_account: Account) -> AddressAndSigner: acct = algorand.account.random() - algorand.send.payment(PayParams( - sender=funded_account.address, - receiver=acct.address, - amount=1_000_000 - )) + algorand.send.payment(PayParams(sender=funded_account.address, receiver=acct.address, amount=1_000_000)) return acct @pytest.fixture() def bob(algorand: AlgorandClient, funded_account: Account) -> AddressAndSigner: acct = algorand.account.random() - algorand.send.payment(PayParams( - sender=funded_account.address, - receiver=acct.address, - amount=1_000_000 - )) + algorand.send.payment(PayParams(sender=funded_account.address, receiver=acct.address, amount=1_000_000)) return acct @pytest.fixture() def app_client(algorand: AlgorandClient, alice: AddressAndSigner) -> ApplicationClient: - client = ApplicationClient(algorand.client.algod, Path(__file__).parent / "app_algorand_client.json", - sender=alice.address, signer=alice.signer) - client.create(call_abi_method='createApplication') + client = ApplicationClient( + algorand.client.algod, + Path(__file__).parent / "app_algorand_client.json", + sender=alice.address, + signer=alice.signer, + ) + client.create(call_abi_method="createApplication") return client @pytest.fixture() def contract() -> Contract: with Path.open(Path(__file__).parent / "app_algorand_client.json") as f: - return Contract.from_json(json.dumps(json.load(f)['contract'])) + return Contract.from_json(json.dumps(json.load(f)["contract"])) def test_send_payment(algorand: AlgorandClient, alice: AddressAndSigner, bob: AddressAndSigner): amount = 100_000 - alice_pre_balance = algorand.account.get_information(alice.address)['amount'] - bob_pre_balance = algorand.account.get_information(bob.address)['amount'] - result = algorand.send.payment(PayParams( - sender=alice.address, - receiver=bob.address, - amount=amount - )) - alice_post_balance = algorand.account.get_information(alice.address)['amount'] - bob_post_balance = algorand.account.get_information(bob.address)['amount'] + alice_pre_balance = algorand.account.get_information(alice.address)["amount"] + bob_pre_balance = algorand.account.get_information(bob.address)["amount"] + result = algorand.send.payment(PayParams(sender=alice.address, receiver=bob.address, amount=amount)) + alice_post_balance = algorand.account.get_information(alice.address)["amount"] + bob_post_balance = algorand.account.get_information(bob.address)["amount"] - assert result['confirmation'] is not None + assert result["confirmation"] is not None assert alice_post_balance == alice_pre_balance - 1000 - amount assert bob_post_balance == bob_pre_balance + amount @@ -80,7 +72,7 @@ def test_send_asset_create(algorand: AlgorandClient, alice: AddressAndSigner): total = 100 result = algorand.send.asset_create(AssetCreateParams(sender=alice.address, total=total)) - asset_index = result['confirmation']['asset-index'] + asset_index = result["confirmation"]["asset-index"] assert asset_index > 0 @@ -89,105 +81,142 @@ def test_asset_opt_in(algorand: AlgorandClient, alice: AddressAndSigner, bob: Ad total = 100 result = algorand.send.asset_create(AssetCreateParams(sender=alice.address, total=total)) - asset_index = result['confirmation']['asset-index'] + asset_index = result["confirmation"]["asset-index"] algorand.send.asset_opt_in(AssetOptInParams(sender=bob.address, asset_id=asset_index)) assert algorand.account.get_asset_information(bob.address, asset_index) is not None + DO_MATH_VALUE = 3 + def test_add_atc(algorand: AlgorandClient, app_client: ApplicationClient, alice: AddressAndSigner): atc = AtomicTransactionComposer() - app_client.compose_call(atc, call_abi_method='doMath', a=1, b=2, operation='sum') + app_client.compose_call(atc, call_abi_method="doMath", a=1, b=2, operation="sum") - result = algorand.new_group().add_payment( - PayParams(sender=alice.address, amount=0, receiver=alice.address)).add_atc(atc).execute() + result = ( + algorand.new_group() + .add_payment(PayParams(sender=alice.address, amount=0, receiver=alice.address)) + .add_atc(atc) + .execute() + ) assert result.abi_results[0].return_value == DO_MATH_VALUE -def test_add_method_call(algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, - app_client: ApplicationClient): - result = algorand \ - .new_group() \ - .add_payment(PayParams(sender=alice.address, amount=0, receiver=alice.address)) \ +def test_add_method_call( + algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, app_client: ApplicationClient +): + result = ( + algorand.new_group() + .add_payment(PayParams(sender=alice.address, amount=0, receiver=alice.address)) .add_method_call( MethodCallParams( - method=contract.get_method_by_name('doMath'), + method=contract.get_method_by_name("doMath"), sender=alice.address, app_id=app_client.app_id, - args=[1, 2, 'sum']) - ) \ + args=[1, 2, "sum"], + ) + ) .execute() + ) assert result.abi_results[0].return_value == DO_MATH_VALUE -def test_add_method_with_txn_arg(algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, - app_client: ApplicationClient): +def test_add_method_with_txn_arg( + algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, app_client: ApplicationClient +): pay_arg = PayParams(sender=alice.address, receiver=alice.address, amount=1) - result = algorand \ - .new_group() \ - .add_payment(PayParams(sender=alice.address, amount=0, receiver=alice.address)) \ + result = ( + algorand.new_group() + .add_payment(PayParams(sender=alice.address, amount=0, receiver=alice.address)) .add_method_call( MethodCallParams( - method=contract.get_method_by_name('txnArg'), + method=contract.get_method_by_name("txnArg"), sender=alice.address, app_id=app_client.app_id, - args=[pay_arg] + args=[pay_arg], ) - ) \ + ) .execute() + ) assert result.abi_results[0].return_value == alice.address -def test_add_method_call_with_method_call_arg(algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, - app_client: ApplicationClient): - hello_world_call = MethodCallParams(method=contract.get_method_by_name('helloWorld'), sender=alice.address, - app_id=app_client.app_id) - result = algorand \ - .new_group() \ - .add_method_call(MethodCallParams(method=contract.get_method_by_name('methodArg'), sender=alice.address, - app_id=app_client.app_id, args=[hello_world_call])) \ +def test_add_method_call_with_method_call_arg( + algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, app_client: ApplicationClient +): + hello_world_call = MethodCallParams( + method=contract.get_method_by_name("helloWorld"), sender=alice.address, app_id=app_client.app_id + ) + result = ( + algorand.new_group() + .add_method_call( + MethodCallParams( + method=contract.get_method_by_name("methodArg"), + sender=alice.address, + app_id=app_client.app_id, + args=[hello_world_call], + ) + ) .execute() - assert result.abi_results[0].return_value == 'Hello, World!' + ) + assert result.abi_results[0].return_value == "Hello, World!" assert result.abi_results[1].return_value == app_client.app_id -def test_add_method_call_with_method_call_arg_with_txn_arg(algorand: AlgorandClient, contract: Contract, - alice: AddressAndSigner, app_client: ApplicationClient): +def test_add_method_call_with_method_call_arg_with_txn_arg( + algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, app_client: ApplicationClient +): pay_arg = PayParams(sender=alice.address, receiver=alice.address, amount=1) - txn_arg_call = MethodCallParams(method=contract.get_method_by_name('txnArg'), sender=alice.address, - app_id=app_client.app_id, args=[pay_arg]) - result = algorand \ - .new_group() \ - .add_method_call(MethodCallParams(method=contract.get_method_by_name('nestedTxnArg'), sender=alice.address, - app_id=app_client.app_id, args=[txn_arg_call])) \ + txn_arg_call = MethodCallParams( + method=contract.get_method_by_name("txnArg"), sender=alice.address, app_id=app_client.app_id, args=[pay_arg] + ) + result = ( + algorand.new_group() + .add_method_call( + MethodCallParams( + method=contract.get_method_by_name("nestedTxnArg"), + sender=alice.address, + app_id=app_client.app_id, + args=[txn_arg_call], + ) + ) .execute() + ) assert result.abi_results[0].return_value == alice.address assert result.abi_results[1].return_value == app_client.app_id -def test_add_method_call_with_two_method_call_args_with_txn_arg(algorand: AlgorandClient, contract: Contract, - alice: AddressAndSigner, app_client: ApplicationClient): +def test_add_method_call_with_two_method_call_args_with_txn_arg( + algorand: AlgorandClient, contract: Contract, alice: AddressAndSigner, app_client: ApplicationClient +): pay_arg_1 = PayParams(sender=alice.address, receiver=alice.address, amount=1) - txn_arg_call_1 = MethodCallParams(method=contract.get_method_by_name('txnArg'), sender=alice.address, - app_id=app_client.app_id, args=[pay_arg_1], note=b'1') + txn_arg_call_1 = MethodCallParams( + method=contract.get_method_by_name("txnArg"), + sender=alice.address, + app_id=app_client.app_id, + args=[pay_arg_1], + note=b"1", + ) pay_arg_2 = PayParams(sender=alice.address, receiver=alice.address, amount=2) - txn_arg_call_2 = MethodCallParams(method=contract.get_method_by_name('txnArg'), sender=alice.address, - app_id=app_client.app_id, args=[pay_arg_2]) + txn_arg_call_2 = MethodCallParams( + method=contract.get_method_by_name("txnArg"), sender=alice.address, app_id=app_client.app_id, args=[pay_arg_2] + ) - result = algorand \ - .new_group() \ + result = ( + algorand.new_group() .add_method_call( MethodCallParams( - method=contract.get_method_by_name('doubleNestedTxnArg'), + method=contract.get_method_by_name("doubleNestedTxnArg"), sender=alice.address, app_id=app_client.app_id, - args=[txn_arg_call_1, txn_arg_call_2] + args=[txn_arg_call_1, txn_arg_call_2], ) - ) \ + ) .execute() + ) assert result.abi_results[0].return_value == alice.address assert result.abi_results[1].return_value == alice.address assert result.abi_results[2].return_value == app_client.app_id