Skip to content

Commit

Permalink
feat: make ABI call arguments kwargs instead of a dict (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-makerx authored Mar 28, 2023
1 parent eed9b65 commit 574b147
Show file tree
Hide file tree
Showing 44 changed files with 2,290 additions and 1,019 deletions.
63 changes: 42 additions & 21 deletions src/algokit_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,28 @@
transfer,
)
from algokit_utils.account import (
create_kmd_wallet_account,
get_account,
get_account_from_mnemonic,
get_dispenser_account,
get_kmd_wallet_account,
get_or_create_kmd_wallet_account,
get_sandbox_default_account,
)
from algokit_utils.app import (
DELETABLE_TEMPLATE_NAME,
NOTE_PREFIX,
UPDATABLE_TEMPLATE_NAME,
AppDeployMetaData,
AppLookup,
AppMetaData,
AppReference,
DeploymentFailedError,
get_creator_apps,
replace_template_variables,
)
from algokit_utils.application_client import (
ABICallArgs,
ABITransactionResponse,
ABICallArgsDict,
ABICreateCallArgs,
ABICreateCallArgsDict,
ApplicationClient,
DeployResponse,
OnSchemaBreak,
OnUpdate,
OperationPerformed,
CommonCallParameters,
CommonCallParametersDict,
CreateCallParameters,
CreateCallParametersDict,
OnCompleteCallParameters,
OnCompleteCallParametersDict,
Program,
TransactionResponse,
execute_atc_with_logic_error,
get_app_id_from_tx_id,
get_next_version,
num_extra_program_pages,
Expand All @@ -46,8 +39,24 @@
MethodHints,
OnCompleteActionName,
)
from algokit_utils.deploy import (
DELETABLE_TEMPLATE_NAME,
NOTE_PREFIX,
UPDATABLE_TEMPLATE_NAME,
AppDeployMetaData,
AppLookup,
AppMetaData,
AppReference,
DeploymentFailedError,
DeployResponse,
OnSchemaBreak,
OnUpdate,
OperationPerformed,
get_creator_apps,
replace_template_variables,
)
from algokit_utils.logic_error import LogicError
from algokit_utils.models import Account
from algokit_utils.models import ABITransactionResponse, Account, TransactionResponse
from algokit_utils.network_clients import (
AlgoClientConfig,
get_algod_client,
Expand All @@ -57,6 +66,7 @@
)

__all__ = [
"create_kmd_wallet_account",
"get_account_from_mnemonic",
"get_or_create_kmd_wallet_account",
"get_sandbox_default_account",
Expand All @@ -74,14 +84,23 @@
"get_creator_apps",
"replace_template_variables",
"ABICallArgs",
"ABITransactionResponse",
"ABICallArgsDict",
"ABICreateCallArgs",
"ABICreateCallArgsDict",
"ApplicationClient",
"CommonCallParameters",
"CommonCallParametersDict",
"CreateCallParameters",
"CreateCallParametersDict",
"OnCompleteCallParameters",
"OnCompleteCallParametersDict",
"ApplicationClient",
"DeployResponse",
"OnUpdate",
"OnSchemaBreak",
"OperationPerformed",
"Program",
"TransactionResponse",
"execute_atc_with_logic_error",
"get_app_id_from_tx_id",
"get_next_version",
"num_extra_program_pages",
Expand All @@ -94,7 +113,9 @@
"OnCompleteActionName",
"MethodHints",
"LogicError",
"ABITransactionResponse",
"Account",
"TransactionResponse",
"AlgoClientConfig",
"get_algod_client",
"get_indexer_client",
Expand Down
72 changes: 54 additions & 18 deletions src/algokit_utils/_transfer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import dataclasses
import logging

from algosdk.transaction import PaymentTxn
import algosdk.transaction
from algosdk.account import address_from_private_key
from algosdk.atomic_transaction_composer import AccountTransactionSigner
from algosdk.transaction import PaymentTxn, SuggestedParams
from algosdk.v2client.algod import AlgodClient

from algokit_utils.models import Account
Expand All @@ -12,30 +15,63 @@

@dataclasses.dataclass(kw_only=True)
class TransferParameters:
from_account: Account
from_account: Account | AccountTransactionSigner
"""The account (with private key) or signer that will send the µALGOs"""
to_address: str
amount: int
note: str | None = None
max_fee_in_algos: float | None = None
"""The account address that will receive the µALGOs"""
micro_algos: int
"""The amount of µALGOs to send"""
suggested_params: SuggestedParams | None = None
"""(optional) transaction parameters"""
note: str | bytes | None = None
"""(optional) transaction note"""
fee_micro_algos: int | None = None
"""(optional) The flat fee you want to pay, useful for covering extra fees in a transaction group or app call"""
max_fee_micro_algos: int | None = None
"""(optional)The maximum fee that you are happy to pay (default: unbounded) -
if this is set it's possible the transaction could get rejected during network congestion"""


def transfer(transfer_parameters: TransferParameters, client: AlgodClient) -> tuple[PaymentTxn, str]:
suggested_params = client.suggested_params()
def _check_fee(transaction: PaymentTxn, max_fee: int | None) -> None:
if max_fee is not None:
# Once a transaction has been constructed by algosdk, transaction.fee indicates what the total transaction fee
# Will be based on the current suggested fee-per-byte value.
if transaction.fee > max_fee:
raise Exception(
f"Cancelled transaction due to high network congestion fees. "
f"Algorand suggested fees would cause this transaction to cost {transaction.fee} µALGOs. "
f"Cap for this transaction is {max_fee} µALGOs."
)
elif transaction.fee > algosdk.constants.MIN_TXN_FEE:
logger.warning(
f"Algorand network congestion fees are in effect. "
f"This transaction will incur a fee of {transaction.fee} µALGOs."
)


def transfer(client: AlgodClient, parameters: TransferParameters) -> PaymentTxn:
suggested_params = parameters.suggested_params or client.suggested_params()
from_account = parameters.from_account
sender = address_from_private_key(from_account.private_key) # type: ignore[no-untyped-call]
transaction = PaymentTxn(
sender=transfer_parameters.from_account.address,
sender=sender,
receiver=parameters.to_address,
amt=parameters.micro_algos,
note=parameters.note.encode("utf-8") if isinstance(parameters.note, str) else parameters.note,
sp=suggested_params,
receiver=transfer_parameters.to_address,
amt=transfer_parameters.amount,
close_remainder_to=None,
note=transfer_parameters.note.encode("utf-8") if transfer_parameters.note else None,
rekey_to=None,
) # type: ignore[no-untyped-call]
# TODO: max fee
from_account = transfer_parameters.from_account
if parameters.fee_micro_algos:
transaction.fee = parameters.fee_micro_algos

if not suggested_params.flat_fee:
_check_fee(transaction, parameters.max_fee_micro_algos)
signed_transaction = transaction.sign(from_account.private_key) # type: ignore[no-untyped-call]
send_response = client.send_transaction(signed_transaction)
client.send_transaction(signed_transaction)

txid = transaction.get_txid() # type: ignore[no-untyped-call]
logger.debug(f"Sent transaction {txid} type={transaction.type} from {from_account.address}")
logger.debug(
f"Sent transaction {txid} type={transaction.type} from "
f"{address_from_private_key(from_account.private_key)}" # type: ignore[no-untyped-call]
)

return transaction, send_response
return transaction
54 changes: 32 additions & 22 deletions src/algokit_utils/account.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os
from collections.abc import Callable
from typing import Any, cast
from typing import Any

from algosdk.account import address_from_private_key
from algosdk.kmd import KMDClient
Expand Down Expand Up @@ -31,40 +31,49 @@ def get_account_from_mnemonic(mnemonic: str) -> Account:
return Account(private_key, address)


def create_kmd_wallet_account(kmd_client: KMDClient, name: str) -> Account:
wallet_id = kmd_client.create_wallet(name, "")["id"] # type: ignore[no-untyped-call]
wallet_handle = kmd_client.init_wallet_handle(wallet_id, "") # type: ignore[no-untyped-call]
kmd_client.generate_key(wallet_handle) # type: ignore[no-untyped-call]

key_ids: list[str] = kmd_client.list_keys(wallet_handle) # type: ignore[no-untyped-call]
account_key = key_ids[0]

private_account_key = kmd_client.export_key(wallet_handle, "", account_key) # type: ignore[no-untyped-call]
return get_account_from_mnemonic(from_private_key(private_account_key)) # type: ignore[no-untyped-call]


def get_or_create_kmd_wallet_account(
client: AlgodClient, name: str, fund_with: int | None, kmd_client: KMDClient | None = None
client: AlgodClient, name: str, fund_with_algos: float = 1000, kmd_client: KMDClient | None = None
) -> Account:
kmd_client = kmd_client or get_kmd_client_from_algod_client(client)
fund_with = 1000 if fund_with is None else fund_with
account = get_kmd_wallet_account(client, kmd_client, name)

if account:
account_info = cast(dict[str, Any], client.account_info(account.address))
account_info = client.account_info(account.address)
assert isinstance(account_info, dict)
if account_info["amount"] > 0:
return account
logger.debug(f"Found existing account in Sandbox with name '{name}'." f"But no funds in the account.")
logger.debug(f"Found existing account in Sandbox with name '{name}', but no funds in the account.")
else:
wallet_id = kmd_client.create_wallet(name, "")["id"] # type: ignore[no-untyped-call]
wallet_handle = kmd_client.init_wallet_handle(wallet_id, "") # type: ignore[no-untyped-call]
kmd_client.generate_key(wallet_handle) # type: ignore[no-untyped-call]
account = create_kmd_wallet_account(kmd_client, name)

account = get_kmd_wallet_account(client, kmd_client, name)
assert account
logger.debug(
f"Couldn't find existing account in Sandbox with name '{name}'. "
f"So created account {account.address} with keys stored in KMD."
)

logger.debug(f"Funding account {account.address} with {fund_with} ALGOs")
logger.debug(f"Funding account {account.address} with {fund_with_algos} ALGOs")

transfer(
TransferParameters(
from_account=get_dispenser_account(client),
to_address=account.address,
amount=algos_to_microalgos(fund_with), # type: ignore[no-untyped-call]
),
client,
)
if fund_with_algos:
transfer(
client,
TransferParameters(
from_account=get_dispenser_account(client),
to_address=account.address,
micro_algos=algos_to_microalgos(fund_with_algos), # type: ignore[no-untyped-call]
),
)

return account

Expand Down Expand Up @@ -105,7 +114,8 @@ def get_kmd_wallet_account(
matched_account_key = None
if predicate:
for key in key_ids:
account = cast(dict[str, Any], client.account_info(key))
account = client.account_info(key)
assert isinstance(account, dict)
if predicate(account):
matched_account_key = key
else:
Expand All @@ -119,15 +129,15 @@ def get_kmd_wallet_account(


def get_account(
client: AlgodClient, name: str, fund_with: int | None = None, kmd_client: KMDClient | None = None
client: AlgodClient, name: str, fund_with_algos: float = 1000, kmd_client: KMDClient | None = None
) -> Account:
mnemonic_key = f"{name.upper()}_MNEMONIC"
mnemonic = os.getenv(mnemonic_key)
if mnemonic:
return get_account_from_mnemonic(mnemonic)

if is_sandbox(client):
account = get_or_create_kmd_wallet_account(client, name, fund_with, kmd_client)
account = get_or_create_kmd_wallet_account(client, name, fund_with_algos, kmd_client)
os.environ[mnemonic_key] = from_private_key(account.private_key) # type: ignore[no-untyped-call]
return account

Expand Down
Loading

1 comment on commit 574b147

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/algokit_utils
   _transfer.py46296%45–46
   account.py831088%53–57, 87, 99, 122, 125, 144
   application_client.py6209085%167, 183, 205, 220, 225, 243, 245, 249, 262, 271–275, 287, 347, 352, 354, 356, 539, 548, 557, 607, 615, 624, 668, 676, 685, 729, 737, 746, 773, 799, 807, 816, 858, 866, 875, 935, 948, 966–969, 973, 985–986, 990, 1011, 1049, 1057, 1093, 1129–1135, 1139–1144, 1146, 1207, 1230, 1297, 1356–1363, 1379, 1384–1394, 1400, 1406, 1413–1416, 1440–1445, 1465–1468, 1482
   application_specification.py892572%75, 78–87, 100, 108, 116, 158, 174, 196–205, 209
   deploy.py2522092%124, 158, 162–163, 194, 270, 274, 280–288, 299–302, 320, 399, 443–445
   logic_error.py35197%29
   network_clients.py47394%61, 64–65
TOTAL119915187% 

Tests Skipped Failures Errors Time
94 0 💤 0 ❌ 0 🔥 1m 7s ⏱️

Please sign in to comment.