Skip to content

feat: TransactionComposer & AppManager implementation; various ongoing refactoring efforts #120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions legacy_v2_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,11 @@ def generate_test_asset(algod_client: "AlgodClient", sender: Account, total: int
note=None,
lease=None,
rekey_to=None,
) # type: ignore[no-untyped-call]
)

signed_transaction = txn.sign(sender.private_key) # type: ignore[no-untyped-call]
signed_transaction = txn.sign(sender.private_key)
algod_client.send_transaction(signed_transaction)
ptx = algod_client.pending_transaction_info(txn.get_txid()) # type: ignore[no-untyped-call]
ptx = algod_client.pending_transaction_info(txn.get_txid())

if isinstance(ptx, dict) and "asset-index" in ptx and isinstance(ptx["asset-index"], int):
return ptx["asset-index"]
Expand Down
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ suppress-none-returning = true
[tool.ruff.lint.per-file-ignores]
"src/algokit_utils/beta/*" = ["ERA001", "E501", "PLR0911"]
"path/to/file.py" = ["E402"]
"tests/clients/test_algorand_client.py" = ["ERA001"]

[tool.poe.tasks]
docs = ["docs-html-only", "docs-md-only"]
Expand All @@ -149,6 +150,14 @@ disallow_any_generics = false
implicit_reexport = false
show_error_codes = true

untyped_calls_exclude = [
"algosdk",
]

[[tool.mypy.overrides]]
module = ["algosdk", "algosdk.*"]
disallow_untyped_calls = false

[tool.semantic_release]
version_toml = "pyproject.toml:tool.poetry.version"
remove_dist = false
Expand Down
5 changes: 2 additions & 3 deletions src/algokit_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@
from algokit_utils._legacy_v2.asset import opt_in, opt_out
from algokit_utils._legacy_v2.common import Program
from algokit_utils._legacy_v2.deploy import (
DELETABLE_TEMPLATE_NAME,
NOTE_PREFIX,
UPDATABLE_TEMPLATE_NAME,
ABICallArgs,
ABICallArgsDict,
ABICreateCallArgs,
Expand Down Expand Up @@ -61,7 +59,6 @@
ABIArgsDict,
ABIMethod,
ABITransactionResponse,
Account,
CommonCallParameters,
CommonCallParametersDict,
CreateCallParameters,
Expand Down Expand Up @@ -91,6 +88,8 @@
DispenserLimitResponse,
TestNetDispenserApiClient,
)
from algokit_utils.models.account import Account
from algokit_utils.models.application import DELETABLE_TEMPLATE_NAME, UPDATABLE_TEMPLATE_NAME

__all__ = [
# ==== LEGACY V2 EXPORTS BEGIN ====
Expand Down
4 changes: 1 addition & 3 deletions src/algokit_utils/_debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,7 @@ def _build_avm_sourcemap( # noqa: PLR0913
raise ValueError("Either raw teal or compiled teal must be provided")

result = compiled_teal if compiled_teal else Program(str(raw_teal), client=client)
program_hash = base64.b64encode(
checksum(result.raw_binary) # type: ignore[no-untyped-call]
).decode()
program_hash = base64.b64encode(checksum(result.raw_binary)).decode()
source_map = result.source_map.__dict__
source_map["sources"] = [f"{file_name}{TEAL_FILE_EXT}"] if with_sources else []

Expand Down
6 changes: 3 additions & 3 deletions src/algokit_utils/_legacy_v2/_ensure_funded.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@

from algokit_utils._legacy_v2._transfer import TransferParameters, transfer
from algokit_utils._legacy_v2.account import get_dispenser_account
from algokit_utils._legacy_v2.models import Account
from algokit_utils._legacy_v2.network_clients import is_testnet
from algokit_utils.clients.dispenser_api_client import (
DispenserAssetName,
TestNetDispenserApiClient,
)
from algokit_utils.models.account import Account


@dataclass(kw_only=True)
Expand Down Expand Up @@ -63,7 +63,7 @@ def _get_address_to_fund(parameters: EnsureBalanceParameters) -> str:
if isinstance(parameters.account_to_fund, str):
return parameters.account_to_fund
else:
return str(address_from_private_key(parameters.account_to_fund.private_key)) # type: ignore[no-untyped-call]
return str(address_from_private_key(parameters.account_to_fund.private_key))


def _get_account_info(client: AlgodClient, address_to_fund: str) -> dict:
Expand Down Expand Up @@ -111,7 +111,7 @@ def _fund_using_transfer(
fee_micro_algos=parameters.fee_micro_algos,
),
)
transaction_id = response.get_txid() # type: ignore[no-untyped-call]
transaction_id = response.get_txid()
return EnsureFundedResponse(transaction_id=transaction_id, amount=response.amt)


Expand Down
8 changes: 4 additions & 4 deletions src/algokit_utils/_legacy_v2/_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from algosdk.atomic_transaction_composer import AccountTransactionSigner
from algosdk.transaction import AssetTransferTxn, PaymentTxn, SuggestedParams

from algokit_utils._legacy_v2.models import Account
from algokit_utils.models.account import Account

if TYPE_CHECKING:
from algosdk.v2client.algod import AlgodClient
Expand Down Expand Up @@ -93,7 +93,7 @@ def transfer(client: "AlgodClient", parameters: TransferParameters) -> PaymentTx
amt=params.micro_algos,
note=params.note.encode("utf-8") if isinstance(params.note, str) else params.note,
sp=params.suggested_params,
) # type: ignore[no-untyped-call]
)

result = _send_transaction(client=client, transaction=transaction, parameters=params)
assert isinstance(result, PaymentTxn)
Expand All @@ -117,7 +117,7 @@ def transfer_asset(client: "AlgodClient", parameters: TransferAssetParameters) -
note=params.note,
index=params.asset_id,
rekey_to=None,
) # type: ignore[no-untyped-call]
)

result = _send_transaction(client=client, transaction=xfer_txn, parameters=params)
assert isinstance(result, AssetTransferTxn)
Expand Down Expand Up @@ -148,5 +148,5 @@ def _get_address(account: Account | AccountTransactionSigner) -> str:
if type(account) is Account:
return account.address
else:
address = address_from_private_key(account.private_key) # type: ignore[no-untyped-call]
address = address_from_private_key(account.private_key)
return str(address)
14 changes: 7 additions & 7 deletions src/algokit_utils/_legacy_v2/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from algosdk.util import algos_to_microalgos

from algokit_utils._legacy_v2._transfer import TransferParameters, transfer
from algokit_utils._legacy_v2.models import Account
from algokit_utils._legacy_v2.network_clients import get_kmd_client_from_algod_client, is_localnet
from algokit_utils.models.account import Account

if TYPE_CHECKING:
from collections.abc import Callable
Expand All @@ -32,8 +32,8 @@

def get_account_from_mnemonic(mnemonic: str) -> Account:
"""Convert a mnemonic (25 word passphrase) into an Account"""
private_key = to_private_key(mnemonic) # type: ignore[no-untyped-call]
address = address_from_private_key(private_key) # type: ignore[no-untyped-call]
private_key = to_private_key(mnemonic)
address = address_from_private_key(private_key)
return Account(private_key=private_key, address=address)


Expand All @@ -47,7 +47,7 @@ def create_kmd_wallet_account(kmd_client: "KMDClient", name: str) -> Account:
account_key = key_ids[0]

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


def get_or_create_kmd_wallet_account(
Expand Down Expand Up @@ -79,7 +79,7 @@ def get_or_create_kmd_wallet_account(
TransferParameters(
from_account=get_dispenser_account(client),
to_address=account.address,
micro_algos=algos_to_microalgos(fund_with_algos), # type: ignore[no-untyped-call]
micro_algos=algos_to_microalgos(fund_with_algos),
),
)

Expand Down Expand Up @@ -139,7 +139,7 @@ def get_kmd_wallet_account(
return None

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


def get_account(
Expand Down Expand Up @@ -177,7 +177,7 @@ def get_account(

if is_localnet(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]
os.environ[mnemonic_key] = from_private_key(account.private_key)
return account

raise Exception(f"Missing environment variable '{mnemonic_key}' when looking for account '{name}'")
8 changes: 4 additions & 4 deletions src/algokit_utils/_legacy_v2/application_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
ABIArgType,
ABIMethod,
ABITransactionResponse,
Account,
CreateCallParameters,
CreateCallParametersDict,
OnCompleteCallParameters,
Expand All @@ -54,6 +53,7 @@
TransactionResponse,
)
from algokit_utils.config import config
from algokit_utils.models.account import Account

if typing.TYPE_CHECKING:
from algosdk.v2client.algod import AlgodClient
Expand Down Expand Up @@ -1021,7 +1021,7 @@ def add_method_call( # noqa: PLR0913
raise Exception(f"ABI arguments specified on a bare call: {', '.join(abi_args)}")
atc.add_transaction(
TransactionWithSigner(
txn=transaction.ApplicationCallTxn( # type: ignore[no-untyped-call]
txn=transaction.ApplicationCallTxn(
sender=sender,
sp=sp,
index=app_id,
Expand Down Expand Up @@ -1329,11 +1329,11 @@ def get_sender_from_signer(signer: TransactionSigner | None) -> str | None:
"""Returns the associated address of a signer, return None if no address found"""

if isinstance(signer, AccountTransactionSigner):
sender = address_from_private_key(signer.private_key) # type: ignore[no-untyped-call]
sender = address_from_private_key(signer.private_key)
assert isinstance(sender, str)
return sender
elif isinstance(signer, MultisigTransactionSigner):
sender = signer.msig.address() # type: ignore[no-untyped-call]
sender = signer.msig.address()
assert isinstance(sender, str)
return sender
elif isinstance(signer, LogicSigTransactionSigner):
Expand Down
4 changes: 2 additions & 2 deletions src/algokit_utils/_legacy_v2/application_specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def _encode_state_schema(schema: StateSchema) -> dict[str, int]:


def _decode_state_schema(data: dict[str, int]) -> StateSchema:
return StateSchema( # type: ignore[no-untyped-call]
return StateSchema(
num_byte_slices=data.get("num_byte_slices", 0),
num_uints=data.get("num_uints", 0),
)
Expand Down Expand Up @@ -203,4 +203,4 @@ def export(self, directory: Path | str | None = None) -> None:


def _state_schema(schema: dict[str, int]) -> StateSchema:
return StateSchema(schema.get("num-uint", 0), schema.get("num-byte-slice", 0)) # type: ignore[no-untyped-call]
return StateSchema(schema.get("num-uint", 0), schema.get("num-byte-slice", 0))
2 changes: 1 addition & 1 deletion src/algokit_utils/_legacy_v2/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from enum import Enum, auto

from algokit_utils._legacy_v2.models import Account
from algokit_utils.models.account import Account

__all__ = ["opt_in", "opt_out"]
logger = logging.getLogger(__name__)
Expand Down
56 changes: 8 additions & 48 deletions src/algokit_utils/_legacy_v2/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from algosdk.atomic_transaction_composer import AtomicTransactionComposer, TransactionSigner
from algosdk.logic import get_application_address
from algosdk.transaction import StateSchema
from deprecated import deprecated

from algokit_utils._legacy_v2.application_specification import (
ApplicationSpecification,
Expand All @@ -21,10 +22,11 @@
from algokit_utils._legacy_v2.models import (
ABIArgsDict,
ABIMethod,
Account,
CreateCallParameters,
TransactionResponse,
)
from algokit_utils.applications.app_manager import AppManager
from algokit_utils.models.account import Account

if TYPE_CHECKING:
from algosdk.v2client.algod import AlgodClient
Expand Down Expand Up @@ -185,7 +187,7 @@ def get_creator_apps(indexer: "IndexerClient", creator_account: Account | str) -
while True:
response = indexer.lookup_account_application_by_creator(
creator_address, limit=DEFAULT_INDEXER_MAX_API_RESOURCES_PER_ACCOUNT, next_page=token
) # type: ignore[no-untyped-call]
)
if "message" in response: # an error occurred
raise Exception(f"Error querying applications for {creator_address}: {response}")
for app in response["applications"]:
Expand All @@ -199,7 +201,7 @@ def get_creator_apps(indexer: "IndexerClient", creator_account: Account | str) -
address=creator_address,
address_role="sender",
note_prefix=NOTE_PREFIX.encode("utf-8"),
) # type: ignore[no-untyped-call]
)
transactions: list[dict] = search_transactions_response["transactions"]
if not transactions:
continue
Expand Down Expand Up @@ -236,7 +238,7 @@ def get_creator_apps(indexer: "IndexerClient", creator_account: Account | str) -


def _state_schema(schema: dict[str, int]) -> StateSchema:
return StateSchema(schema.get("num-uint", 0), schema.get("num-byte-slice", 0)) # type: ignore[no-untyped-call]
return StateSchema(schema.get("num-uint", 0), schema.get("num-byte-slice", 0))


def _describe_schema_breaks(prefix: str, from_schema: StateSchema, to_schema: StateSchema) -> Iterable[str]:
Expand Down Expand Up @@ -288,33 +290,6 @@ def _is_valid_token_character(char: str) -> bool:
return char.isalnum() or char == "_"


def _replace_template_variable(program_lines: list[str], template_variable: str, value: str) -> tuple[list[str], int]:
result: list[str] = []
match_count = 0
token = f"TMPL_{template_variable}"
token_idx_offset = len(value) - len(token)
for line in program_lines:
comment_idx = _find_unquoted_string(line, "//")
if comment_idx is None:
comment_idx = len(line)
code = line[:comment_idx]
comment = line[comment_idx:]
trailing_idx = 0
while True:
token_idx = _find_template_token(code, token, trailing_idx)
if token_idx is None:
break

trailing_idx = token_idx + len(token)
prefix = code[:token_idx]
suffix = code[trailing_idx:]
code = f"{prefix}{value}{suffix}"
match_count += 1
trailing_idx += token_idx_offset
result.append(code + comment)
return result, match_count


def add_deploy_template_variables(
template_values: TemplateValueDict, allow_update: bool | None, allow_delete: bool | None
) -> None:
Expand Down Expand Up @@ -437,30 +412,15 @@ def check_template_variables(approval_program: str, template_values: TemplateVal
logger.warning(f"{tmpl_variable} not found in approval program, but variable was provided")


@deprecated(reason="Use `AppManager.replace_template_variables` instead", version="3.0.0")
def replace_template_variables(program: str, template_values: TemplateValueMapping) -> str:
"""Replaces `TMPL_*` variables in `program` with `template_values`

```{note}
`template_values` keys should *NOT* be prefixed with `TMPL_`
```
"""
program_lines = program.splitlines()
for template_variable_name, template_value in template_values.items():
match template_value:
case int():
value = str(template_value)
case str():
value = "0x" + template_value.encode("utf-8").hex()
case bytes():
value = "0x" + template_value.hex()
case _:
raise DeploymentFailedError(
f"Unexpected template value type {template_variable_name}: {template_value.__class__}"
)

program_lines, matches = _replace_template_variable(program_lines, template_variable_name, value)

return "\n".join(program_lines)
return AppManager.replace_template_variables(program, template_values)


def has_template_vars(app_spec: ApplicationSpecification) -> bool:
Expand Down
Loading