├── tests ├── __init__.py ├── accounts │ ├── __init__.py │ └── test_account_manager.py ├── assets │ └── __init__.py ├── clients │ ├── __init__.py │ └── algorand_client │ │ └── __init__.py ├── applications │ ├── __init__.py │ ├── _snapshots │ │ ├── test_app_manager.approvals │ │ │ ├── test_comment_stripping.approved.txt │ │ │ └── test_template_substitution.approved.txt │ │ └── test_arc56.approvals │ │ │ ├── test_arc56_from_arc32_json.approved.txt │ │ │ └── test_arc56_from_arc32_instance.approved.txt │ └── test_app_manager.py ├── transactions │ └── __init__.py ├── artifacts │ ├── resource-packer │ │ ├── .gitignore │ │ ├── ExternalAppV8.arc32.json │ │ └── InnerBoxApp.arc32.json │ ├── hello_world │ │ ├── clear.teal │ │ ├── approval.teal │ │ └── app_spec.arc32.json │ ├── bare_create_abi_delete │ │ └── contract.py │ ├── nested_struct │ │ └── nested_struct.algo.ts │ ├── delete_abi_with_inner │ │ └── contract.py │ ├── testing_app_puya │ │ └── contract.py │ └── inner-fee │ │ └── contract.py ├── _snapshots │ └── test_debug_utils.approvals │ │ ├── test_build_teal_sourcemaps.approved.txt │ │ └── test_build_teal_sourcemaps_without_sources.approved.txt ├── utils.py └── models │ └── test_algo_amount.py ├── legacy_v2_tests ├── __init__.py ├── test_app_client_call.approvals │ ├── test_readonly_call_with_error_debug_mode_disabled.approved.txt │ ├── test_readonly_call_with_error.approved.txt │ ├── test_readonly_call_with_error_with_imported_source_map.approved.txt │ ├── test_readonly_call_with_error_with_new_client_provided_source_map.approved.txt │ ├── test_readonly_call_with_error_with_new_client_provided_template_values.approved.txt │ └── test_readonly_call_with_error_with_new_client_missing_source_map.approved.txt ├── test_deploy_scenarios.approvals │ ├── test_deploy_app_with_no_existing_app_succeeds.approved.txt │ ├── test_deploy_with_update_append.approved.txt │ ├── test_deploy_app_with_existing_updatable_app_succeeds.approved.txt │ ├── test_deploy_with_update[OnUpdate.UpdateApp-Updatable.Yes-Deletable.No].approved.txt │ ├── test_deploy_with_update[OnUpdate.Fail-Updatable.No-Deletable.No].approved.txt │ ├── test_deploy_with_update[OnUpdate.UpdateApp-Updatable.Yes-Deletable.Yes].approved.txt │ ├── test_deploy_with_update[OnUpdate.Fail-Updatable.No-Deletable.Yes].approved.txt │ ├── test_deploy_with_update[OnUpdate.Fail-Updatable.Yes-Deletable.No].approved.txt │ ├── test_deploy_with_update[OnUpdate.Fail-Updatable.Yes-Deletable.Yes].approved.txt │ ├── test_deploy_with_schema_breaking_change_append.approved.txt │ ├── test_deploy_app_with_existing_immutable_app_fails.approved.txt │ ├── test_deploy_app_with_existing_immutable_app_cannot_determine_if_updatable.approved.txt │ ├── test_deploy_app_with_existing_permanent_app_fails.approved.txt │ ├── test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.Yes-Deletable.No].approved.txt │ ├── test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.No-Deletable.No].approved.txt │ ├── test_deploy_with_update[OnUpdate.UpdateApp-Updatable.No-Deletable.No].approved.txt │ ├── test_deploy_with_update[OnUpdate.UpdateApp-Updatable.No-Deletable.Yes].approved.txt │ ├── test_deploy_app_with_existing_permanent_app_cannot_determine_if_deletable.approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.No-Deletable.No].approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.No-Deletable.Yes].approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.Yes-Deletable.No].approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.Yes-Deletable.Yes].approved.txt │ ├── test_deploy_app_with_existing_permanent_app_on_update_equals_replace_app_fails_and_doesnt_create_2nd_app.approved.txt │ ├── test_deploy_app_with_existing_permanent_app_and_on_schema_break_equals_replace_app_fails.approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.No-Deletable.No].approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.Yes-Deletable.No].approved.txt │ ├── test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.Yes-Deletable.Yes].approved.txt │ ├── test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.No-Deletable.Yes].approved.txt │ ├── test_deploy_app_with_existing_immutable_app_and_on_update_equals_replace_app_succeeds.approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.No-Deletable.Yes].approved.txt │ ├── test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.Yes-Deletable.Yes].approved.txt │ └── test_deploy_templated_app_with_changing_parameters_succeeds.approved.txt ├── test_transfer.approvals │ ├── test_transfer_algo_max_fee_fails.approved.txt │ └── test_transfer_asset_max_fee_fails.approved.txt ├── test_app_client_create.approvals │ └── test_create_auto_find_ambiguous.approved.txt ├── test_debug_utils.approvals │ ├── test_legacy_build_teal_sourcemaps.approved.txt │ └── test_legacy_build_teal_sourcemaps_without_sources.approved.txt ├── test_app_client_close_out.approvals │ └── test_abi_close_out_args_fails.approved.txt ├── test_app_client_delete.approvals │ └── test_abi_delete_args_fails.approved.txt ├── test_app_client_update.approvals │ └── test_abi_update_args_fails.approved.txt ├── test_app_client_opt_in.approvals │ └── test_abi_update_args_fails.approved.txt ├── test_account.py ├── test_app.py ├── test_deploy.approvals │ ├── test_comment_stripping.approved.txt │ └── test_template_substitution.approved.txt ├── test_app_client.py ├── test_network_clients.py ├── test_app_client_delete.py ├── test_app_client_update.py ├── test_app_client_prepare.py ├── test_app_client_opt_in.py ├── test_app_client_clear_state.py ├── test_deploy.py ├── test_app_client_resolve.py ├── test_app_client_deploy.py ├── test_app_client_close_out.py ├── test_app_client_signer_sender.py ├── test_dispenser_api_client.py ├── app_v1.json └── app_v2.json ├── src └── algokit_utils │ ├── py.typed │ ├── assets │ └── __init__.py │ ├── errors │ └── __init__.py │ ├── protocols │ ├── __init__.py │ ├── account.py │ └── typed_clients.py │ ├── clients │ └── __init__.py │ ├── accounts │ └── __init__.py │ ├── applications │ ├── app_spec │ │ └── __init__.py │ ├── __init__.py │ └── enums.py │ ├── transactions │ └── __init__.py │ ├── beta │ ├── composer.py │ ├── account_manager.py │ ├── algorand_client.py │ ├── client_manager.py │ └── _utils.py │ ├── models │ ├── simulate.py │ ├── __init__.py │ ├── network.py │ ├── state.py │ ├── application.py │ └── transaction.py │ ├── network_clients.py │ ├── common.py │ ├── logic_error.py │ ├── deploy.py │ ├── dispenser_api.py │ ├── application_client.py │ ├── _legacy_v2 │ ├── logic_error.py │ ├── application_specification.py │ └── common.py │ ├── account.py │ ├── asset.py │ ├── __init__.py │ └── application_specification.py ├── poetry.toml ├── .gitattributes ├── .github ├── pull_request_template.md ├── dependabot.yml ├── workflows │ ├── pr.yaml │ ├── check-docs.yaml │ ├── build-python.yaml │ ├── check-python.yaml │ └── cd.yaml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── actions │ └── setup-poetry │ └── action.yaml ├── docs ├── source │ ├── images │ │ └── lifecycle.jpg │ └── capabilities │ │ ├── amount.md │ │ └── dispenser-client.md ├── markdown │ ├── images │ │ └── lifecycle.jpg │ ├── autoapi │ │ ├── algokit_utils │ │ │ ├── assets │ │ │ │ └── index.md │ │ │ ├── errors │ │ │ │ ├── index.md │ │ │ │ └── logic_error │ │ │ │ │ └── index.md │ │ │ ├── protocols │ │ │ │ ├── index.md │ │ │ │ └── account │ │ │ │ │ └── index.md │ │ │ ├── clients │ │ │ │ └── index.md │ │ │ ├── accounts │ │ │ │ ├── index.md │ │ │ │ └── kmd_account_manager │ │ │ │ │ └── index.md │ │ │ ├── applications │ │ │ │ ├── app_spec │ │ │ │ │ └── index.md │ │ │ │ ├── index.md │ │ │ │ └── enums │ │ │ │ │ └── index.md │ │ │ ├── transactions │ │ │ │ └── index.md │ │ │ ├── models │ │ │ │ ├── index.md │ │ │ │ ├── simulate │ │ │ │ │ └── index.md │ │ │ │ ├── network │ │ │ │ │ └── index.md │ │ │ │ ├── state │ │ │ │ │ └── index.md │ │ │ │ ├── application │ │ │ │ │ └── index.md │ │ │ │ ├── transaction │ │ │ │ │ └── index.md │ │ │ │ └── amount │ │ │ │ │ └── index.md │ │ │ └── index.md │ │ └── index.md │ └── capabilities │ │ ├── amount.md │ │ └── dispenser-client.md ├── Makefile └── make.bat ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE ├── example.env ├── .pre-commit-config.yaml ├── README.md └── .gitignore /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /legacy_v2_tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/algokit_utils/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/assets/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/clients/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/applications/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/transactions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/clients/algorand_client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Treat text as lf 2 | * text=auto 3 | * eol=lf 4 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | - 4 | - 5 | - 6 | -------------------------------------------------------------------------------- /tests/artifacts/resource-packer/.gitignore: -------------------------------------------------------------------------------- 1 | *.teal 2 | *.json 3 | !*.arc32.json 4 | -------------------------------------------------------------------------------- /src/algokit_utils/assets/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.assets.asset_manager import * # noqa: F403 2 | -------------------------------------------------------------------------------- /src/algokit_utils/errors/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.errors.logic_error import * # noqa: F403 2 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_call.approvals/test_readonly_call_with_error_debug_mode_disabled.approved.txt: -------------------------------------------------------------------------------- 1 | None -------------------------------------------------------------------------------- /docs/source/images/lifecycle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorandfoundation/algokit-utils-py/HEAD/docs/source/images/lifecycle.jpg -------------------------------------------------------------------------------- /docs/markdown/images/lifecycle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorandfoundation/algokit-utils-py/HEAD/docs/markdown/images/lifecycle.jpg -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/assets/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.assets 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.assets.asset_manager](asset_manager/index.md) 6 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/errors/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.errors 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.errors.logic_error](logic_error/index.md) 6 | -------------------------------------------------------------------------------- /src/algokit_utils/protocols/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.protocols.account import * # noqa: F403 2 | from algokit_utils.protocols.typed_clients import * # noqa: F403 3 | -------------------------------------------------------------------------------- /tests/artifacts/hello_world/clear.teal: -------------------------------------------------------------------------------- 1 | #pragma version 10 2 | 3 | smart_contracts.hello_world.contract.HelloWorld.clear_state_program: 4 | pushint 1 // 1 5 | return 6 | -------------------------------------------------------------------------------- /src/algokit_utils/clients/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.clients.client_manager import * # noqa: F403 2 | from algokit_utils.clients.dispenser_api_client import * # noqa: F403 3 | -------------------------------------------------------------------------------- /src/algokit_utils/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.accounts.account_manager import * # noqa: F403 2 | from algokit_utils.accounts.kmd_account_manager import * # noqa: F403 3 | -------------------------------------------------------------------------------- /src/algokit_utils/applications/app_spec/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.applications.app_spec.arc32 import * # noqa: F403 2 | from algokit_utils.applications.app_spec.arc56 import * # noqa: F403 3 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/protocols/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.protocols 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.protocols.account](account/index.md) 6 | * [algokit_utils.protocols.typed_clients](typed_clients/index.md) 7 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_no_existing_app_succeeds.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. -------------------------------------------------------------------------------- /legacy_v2_tests/test_transfer.approvals/test_transfer_algo_max_fee_fails.approved.txt: -------------------------------------------------------------------------------- 1 | Cancelled transaction due to high network congestion fees. Algorand suggested fees would cause this transaction to cost 1000 µALGOs. Cap for this transaction is 123 µALGOs. -------------------------------------------------------------------------------- /legacy_v2_tests/test_transfer.approvals/test_transfer_asset_max_fee_fails.approved.txt: -------------------------------------------------------------------------------- 1 | Cancelled transaction due to high network congestion fees. Algorand suggested fees would cause this transaction to cost 1000 µALGOs. Cap for this transaction is 123 µALGOs. -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/clients/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.clients 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.clients.client_manager](client_manager/index.md) 6 | * [algokit_utils.clients.dispenser_api_client](dispenser_api_client/index.md) 7 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_create.approvals/test_create_auto_find_ambiguous.approved.txt: -------------------------------------------------------------------------------- 1 | Could not find an exact method to use for NoOpOC with call_config of CREATE, specify the exact method using abi_method and args parameters, considered: create()void, bare -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/accounts/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.accounts 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.accounts.account_manager](account_manager/index.md) 6 | * [algokit_utils.accounts.kmd_account_manager](kmd_account_manager/index.md) 7 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/applications/app_spec/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.applications.app_spec 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.applications.app_spec.arc32](arc32/index.md) 6 | * [algokit_utils.applications.app_spec.arc56](arc56/index.md) 7 | -------------------------------------------------------------------------------- /src/algokit_utils/transactions/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.transactions.transaction_composer import * # noqa: F403 2 | from algokit_utils.transactions.transaction_creator import * # noqa: F403 3 | from algokit_utils.transactions.transaction_sender import * # noqa: F403 4 | -------------------------------------------------------------------------------- /tests/_snapshots/test_debug_utils.approvals/test_build_teal_sourcemaps.approved.txt: -------------------------------------------------------------------------------- 1 | {"txn-group-sources": [{"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}, {"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}]} -------------------------------------------------------------------------------- /legacy_v2_tests/test_debug_utils.approvals/test_legacy_build_teal_sourcemaps.approved.txt: -------------------------------------------------------------------------------- 1 | {"txn-group-sources": [{"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}, {"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}]} -------------------------------------------------------------------------------- /src/algokit_utils/beta/composer.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from algokit_utils.beta._utils import handle_getattr 4 | 5 | 6 | def __getattr__(name: str) -> Any: # noqa: ANN401 7 | """Handle deprecated imports of parameter classes""" 8 | 9 | handle_getattr(name) 10 | -------------------------------------------------------------------------------- /src/algokit_utils/beta/account_manager.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from algokit_utils.beta._utils import handle_getattr 4 | 5 | 6 | def __getattr__(name: str) -> Any: # noqa: ANN401 7 | """Handle deprecated imports of parameter classes""" 8 | 9 | handle_getattr(name) 10 | -------------------------------------------------------------------------------- /src/algokit_utils/beta/algorand_client.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from algokit_utils.beta._utils import handle_getattr 4 | 5 | 6 | def __getattr__(name: str) -> Any: # noqa: ANN401 7 | """Handle deprecated imports of parameter classes""" 8 | 9 | handle_getattr(name) 10 | -------------------------------------------------------------------------------- /src/algokit_utils/beta/client_manager.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from algokit_utils.beta._utils import handle_getattr 4 | 5 | 6 | def __getattr__(name: str) -> Any: # noqa: ANN401 7 | """Handle deprecated imports of parameter classes""" 8 | 9 | handle_getattr(name) 10 | -------------------------------------------------------------------------------- /tests/_snapshots/test_debug_utils.approvals/test_build_teal_sourcemaps_without_sources.approved.txt: -------------------------------------------------------------------------------- 1 | {"txn-group-sources": [{"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}, {"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}]} -------------------------------------------------------------------------------- /legacy_v2_tests/test_debug_utils.approvals/test_legacy_build_teal_sourcemaps_without_sources.approved.txt: -------------------------------------------------------------------------------- 1 | {"txn-group-sources": [{"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}, {"sourcemap-location": "dummy", "hash": "EC1P8unO+zjVbdF8XZOs1rp+uaGNk7vXtZ/IYsN/sug="}]} -------------------------------------------------------------------------------- /src/algokit_utils/models/simulate.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | __all__ = ["SimulationTrace"] 4 | 5 | 6 | @dataclass 7 | class SimulationTrace: 8 | app_budget_added: int | None 9 | app_budget_consumed: int | None 10 | failure_message: str | None 11 | exec_trace: dict[str, object] 12 | -------------------------------------------------------------------------------- /src/algokit_utils/network_clients.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | "The legacy v2 network clients module is deprecated and will be removed in a future version.", 5 | DeprecationWarning, 6 | stacklevel=2, 7 | ) 8 | 9 | from algokit_utils._legacy_v2.network_clients import * # noqa: F403, E402 10 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_call.approvals/test_readonly_call_with_error.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=743' at PC 743 and Source Line 425: 2 | 3 | intc_1 // 1 4 | return 5 | readonly_1_l2: 6 | intc_0 // 0 7 | // An error 8 | assert <-- Error 9 | retsub 10 | 11 | // set_box 12 | setbox_2: -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_call.approvals/test_readonly_call_with_error_with_imported_source_map.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=743' at PC 743 and Source Line 425: 2 | 3 | intc_1 // 1 4 | return 5 | readonly_1_l2: 6 | intc_0 // 0 7 | // An error 8 | assert <-- Error 9 | retsub 10 | 11 | // set_box 12 | setbox_2: -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_close_out.approvals/test_abi_close_out_args_fails.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=1228' at PC 1228 and Source Line 747: 2 | 3 | frame_dig -1 4 | extract 2 0 5 | bytec 4 // "Yes" 6 | == 7 | // passes close_out check 8 | assert <-- Error 9 | intc_1 // 1 10 | return 11 | 12 | // call_with_payment -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_delete.approvals/test_abi_delete_args_fails.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=961' at PC 961 and Source Line 575: 2 | 3 | frame_dig -1 4 | extract 2 0 5 | bytec 4 // "Yes" 6 | == 7 | // passes delete check 8 | assert <-- Error 9 | intc 5 // deletable 10 | // is deletable 11 | assert 12 | retsub -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "chore(deps)" 9 | groups: 10 | all: 11 | patterns: 12 | - "*" 13 | update-types: 14 | - "minor" 15 | - "patch" 16 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/transactions/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.transactions 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.transactions.transaction_composer](transaction_composer/index.md) 6 | * [algokit_utils.transactions.transaction_creator](transaction_creator/index.md) 7 | * [algokit_utils.transactions.transaction_sender](transaction_sender/index.md) 8 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_call.approvals/test_readonly_call_with_error_with_new_client_provided_source_map.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=743' at PC 743 and Source Line 425: 2 | 3 | intc_1 // 1 4 | return 5 | readonly_1_l2: 6 | intc_0 // 0 7 | // An error 8 | assert <-- Error 9 | retsub 10 | 11 | // set_box 12 | setbox_2: -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_call.approvals/test_readonly_call_with_error_with_new_client_provided_template_values.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=743' at PC 743 and Source Line 425: 2 | 3 | intc_1 // 1 4 | return 5 | readonly_1_l2: 6 | intc_0 // 0 7 | // An error 8 | assert <-- Error 9 | retsub 10 | 11 | // set_box 12 | setbox_2: -------------------------------------------------------------------------------- /src/algokit_utils/common.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | "The legacy v2 common module is deprecated and will be removed in a future version. " 5 | "Refer to `CompiledTeal` class from `algokit_utils` instead.", 6 | DeprecationWarning, 7 | stacklevel=2, 8 | ) 9 | 10 | from algokit_utils._legacy_v2.common import * # noqa: F403, E402 11 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_update.approvals/test_abi_update_args_fails.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=897' at PC 897 and Source Line 527: 2 | 3 | frame_dig -1 4 | extract 2 0 5 | bytec 4 // "Yes" 6 | == 7 | // passes update check 8 | assert <-- Error 9 | intc 4 // updatable 10 | // is updatable 11 | assert 12 | bytec_1 // "greeting" -------------------------------------------------------------------------------- /src/algokit_utils/logic_error.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | "The legacy v2 logic error module is deprecated and will be removed in a future version. " 5 | "Use 'from algokit_utils.errors import LogicError' instead.", 6 | DeprecationWarning, 7 | stacklevel=2, 8 | ) 9 | 10 | from algokit_utils.errors.logic_error import * # noqa: F403, E402 11 | -------------------------------------------------------------------------------- /src/algokit_utils/deploy.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | "The legacy v2 deploy module is deprecated and will be removed in a future version. " 5 | "Refer to `AppFactory` and `AppDeployer` abstractions from `algokit_utils` module instead.", 6 | DeprecationWarning, 7 | stacklevel=2, 8 | ) 9 | 10 | from algokit_utils._legacy_v2.deploy import * # noqa: F403, E402 11 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_opt_in.approvals/test_abi_update_args_fails.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=1187' at PC 1187 and Source Line 719: 2 | 3 | frame_dig -1 4 | extract 2 0 5 | bytec 4 // "Yes" 6 | == 7 | // passes opt_in check 8 | assert <-- Error 9 | txn Sender 10 | bytec_3 // "last" 11 | pushbytes 0x4f707420496e2041726773 // "Opt In Args" 12 | app_local_put -------------------------------------------------------------------------------- /src/algokit_utils/dispenser_api.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | "The legacy v2 dispenser api module is deprecated and will be removed in a future version. " 5 | "Import from 'algokit_utils.clients.dispenser_api_client' instead.", 6 | DeprecationWarning, 7 | stacklevel=2, 8 | ) 9 | 10 | from algokit_utils.clients.dispenser_api_client import * # noqa: F403, E402 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "ms-python.python", 5 | "ms-python.vscode-pylance", 6 | "charliermarsh.ruff", 7 | "tamasfe.even-better-toml", 8 | "editorconfig.editorconfig", 9 | "emeraldwalk.runonsave", 10 | "matangover.mypy", 11 | "runarsf.platform-settings" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/algokit_utils/application_client.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | """The legacy v2 application_client module is deprecated and will be removed in a future version. 5 | Use `AppClient` abstraction from `algokit_utils.applications` instead. 6 | """, 7 | DeprecationWarning, 8 | stacklevel=2, 9 | ) 10 | 11 | from algokit_utils._legacy_v2.application_client import * # noqa: F403, E402 12 | -------------------------------------------------------------------------------- /tests/artifacts/bare_create_abi_delete/contract.py: -------------------------------------------------------------------------------- 1 | from algopy import ARC4Contract, String, TemplateVar 2 | from algopy.arc4 import abimethod 3 | 4 | 5 | class DeleteAbiWithInner(ARC4Contract): 6 | def __init__(self) -> None: 7 | self.greeting = TemplateVar[String]("GREETING") 8 | 9 | @abimethod(allow_actions=["DeleteApplication"]) 10 | def delete(self) -> None: 11 | assert TemplateVar[bool]("DELETABLE") 12 | -------------------------------------------------------------------------------- /src/algokit_utils/_legacy_v2/logic_error.py: -------------------------------------------------------------------------------- 1 | from typing_extensions import deprecated 2 | 3 | from algokit_utils.errors.logic_error import LogicError as NewLogicError 4 | from algokit_utils.errors.logic_error import parse_logic_error 5 | 6 | __all__ = [ 7 | "LogicError", 8 | "parse_logic_error", 9 | ] 10 | 11 | 12 | @deprecated("Use algokit_utils.models.error.LogicError instead") 13 | class LogicError(NewLogicError): 14 | pass 15 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request validation 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | pr-check: 7 | name: Check Python 8 | uses: ./.github/workflows/check-python.yaml 9 | 10 | pr-check-docs: 11 | name: Check Documentation 12 | uses: ./.github/workflows/check-docs.yaml 13 | 14 | pr-build: 15 | name: Build and Test Python 16 | needs: [pr-check, pr-check-docs] 17 | uses: ./.github/workflows/build-python.yaml 18 | -------------------------------------------------------------------------------- /src/algokit_utils/account.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | """The legacy v2 account module is deprecated and will be removed in a future version. 5 | Use `SigningAccount` abstraction from `algokit_utils.models` instead or 6 | classes compliant with `TransactionSignerAccountProtocol` obtained from `AccountManager`. 7 | """, 8 | DeprecationWarning, 9 | stacklevel=2, 10 | ) 11 | 12 | from algokit_utils._legacy_v2.account import * # noqa: F403, E402 13 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/models/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.models 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.models.account](account/index.md) 6 | * [algokit_utils.models.amount](amount/index.md) 7 | * [algokit_utils.models.application](application/index.md) 8 | * [algokit_utils.models.network](network/index.md) 9 | * [algokit_utils.models.simulate](simulate/index.md) 10 | * [algokit_utils.models.state](state/index.md) 11 | * [algokit_utils.models.transaction](transaction/index.md) 12 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update_append.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | INFO: Update detected and on_update=AppendApp, will attempt to create new app 6 | INFO: SampleApp (2.0) deployed successfully, with app id {app1}. -------------------------------------------------------------------------------- /src/algokit_utils/applications/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.applications.abi import * # noqa: F403 2 | from algokit_utils.applications.app_client import * # noqa: F403 3 | from algokit_utils.applications.app_deployer import * # noqa: F403 4 | from algokit_utils.applications.app_factory import * # noqa: F403 5 | from algokit_utils.applications.app_manager import * # noqa: F403 6 | from algokit_utils.applications.app_spec import * # noqa: F403 7 | from algokit_utils.applications.enums import * # noqa: F403 8 | -------------------------------------------------------------------------------- /src/algokit_utils/models/__init__.py: -------------------------------------------------------------------------------- 1 | from algokit_utils._legacy_v2.models import * # noqa: F403 2 | from algokit_utils.models.account import * # noqa: F403 3 | from algokit_utils.models.amount import * # noqa: F403 4 | from algokit_utils.models.application import * # noqa: F403 5 | from algokit_utils.models.network import * # noqa: F403 6 | from algokit_utils.models.simulate import * # noqa: F403 7 | from algokit_utils.models.state import * # noqa: F403 8 | from algokit_utils.models.transaction import * # noqa: F403 9 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_updatable_app_succeeds.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | INFO: App is updatable and on_update=UpdateApp, will update app 6 | INFO: Updating SampleApp to 2.0 in {creator_account} account, with app id {app0} -------------------------------------------------------------------------------- /legacy_v2_tests/test_account.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from algokit_utils import get_account 4 | from legacy_v2_tests.conftest import get_unique_name 5 | 6 | if TYPE_CHECKING: 7 | from algosdk.v2client.algod import AlgodClient 8 | 9 | 10 | def test_account_can_be_called_twice(algod_client: "AlgodClient") -> None: 11 | account_name = get_unique_name() 12 | account1 = get_account(algod_client, account_name) 13 | account2 = get_account(algod_client, account_name) 14 | 15 | assert account1 == account2 16 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from algokit_utils import AppDeployMetaData 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "app_note_json", 8 | [ 9 | b'ALGOKIT_DEPLOYER:j{"name":"VotingRoundApp","version":"1.0","deletable":true,"updatable":true}', 10 | b'ALGOKIT_DEPLOYER:j{"name":"VotingRoundApp","version":"1.0","deletable":true}', 11 | ], 12 | ) 13 | def test_metadata_serialization(app_note_json: bytes) -> None: 14 | metadata = AppDeployMetaData.decode(app_note_json) 15 | assert metadata 16 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.UpdateApp-Updatable.Yes-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | INFO: App is updatable and on_update=UpdateApp, will update app 6 | INFO: Updating SampleApp to 2.0 in {creator_account} account, with app id {app0} -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.Fail-Updatable.No-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | ERROR: DeploymentFailedError: Update detected and on_update=Fail, stopping deployment. If you want to try updating the app then re-run with on_update=UpdateApp -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.UpdateApp-Updatable.Yes-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | INFO: App is updatable and on_update=UpdateApp, will update app 6 | INFO: Updating SampleApp to 2.0 in {creator_account} account, with app id {app0} -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41C Bug report" 3 | about: Report a reproducible bug. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ### Subject of the issue 10 | 11 | 12 | 13 | ### Your environment 14 | 15 | 19 | 20 | ### Steps to reproduce 21 | 22 | 1. 23 | 2. 24 | 25 | ### Expected behaviour 26 | 27 | ### Actual behaviour 28 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.Fail-Updatable.No-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | ERROR: DeploymentFailedError: Update detected and on_update=Fail, stopping deployment. If you want to try updating the app then re-run with on_update=UpdateApp -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.Fail-Updatable.Yes-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | ERROR: DeploymentFailedError: Update detected and on_update=Fail, stopping deployment. If you want to try updating the app then re-run with on_update=UpdateApp -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.Fail-Updatable.Yes-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | ERROR: DeploymentFailedError: Update detected and on_update=Fail, stopping deployment. If you want to try updating the app then re-run with on_update=UpdateApp -------------------------------------------------------------------------------- /tests/artifacts/nested_struct/nested_struct.algo.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from '@algorandfoundation/tealscript' 2 | 3 | type Struct1 = { a: string } 4 | type Struct2 = { x: Struct1 } 5 | 6 | export class NestedStruct extends Contract { 7 | createApplication() {} 8 | 9 | state = GlobalStateMap({ prefix: '', maxKeys: 10 }) 10 | 11 | setValue(key: uint64, value: string): void { 12 | this.state(key).value = { x: { a: value } } 13 | } 14 | 15 | getValue(key: uint64): Struct2 { 16 | return this.state(key).value 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/applications/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.applications 2 | 3 | ## Submodules 4 | 5 | * [algokit_utils.applications.abi](abi/index.md) 6 | * [algokit_utils.applications.app_client](app_client/index.md) 7 | * [algokit_utils.applications.app_deployer](app_deployer/index.md) 8 | * [algokit_utils.applications.app_factory](app_factory/index.md) 9 | * [algokit_utils.applications.app_manager](app_manager/index.md) 10 | * [algokit_utils.applications.app_spec](app_spec/index.md) 11 | * [algokit_utils.applications.enums](enums/index.md) 12 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/models/simulate/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.models.simulate 2 | 3 | ## Classes 4 | 5 | | [`SimulationTrace`](#algokit_utils.models.simulate.SimulationTrace) | | 6 | |-----------------------------------------------------------------------|----| 7 | 8 | ## Module Contents 9 | 10 | ### *class* algokit_utils.models.simulate.SimulationTrace 11 | 12 | #### app_budget_added *: int | None* 13 | 14 | #### app_budget_consumed *: int | None* 15 | 16 | #### failure_message *: str | None* 17 | 18 | #### exec_trace *: dict[str, object]* 19 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change_append.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | INFO: Schema break detected and on_schema_break=AppendApp, will attempt to create new app 6 | INFO: SampleApp (2.0) deployed successfully, with app id {app1}. -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy.approvals/test_comment_stripping.approved.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | op arg 4 | op "arg" 5 | op "//" 6 | op " //comment " 7 | op "\" //" 8 | op "// \" //" 9 | op "" 10 | 11 | op 123 12 | op 123 13 | op "" 14 | op "//" 15 | op "//" 16 | pushbytes base64(//8=) 17 | pushbytes b64(//8=) 18 | 19 | pushbytes base64(//8=) 20 | pushbytes b64(//8=) 21 | pushbytes "base64(//8=)" 22 | pushbytes "b64(//8=)" 23 | 24 | pushbytes base64 //8= 25 | pushbytes b64 //8= 26 | 27 | pushbytes base64 //8= 28 | pushbytes b64 //8= 29 | pushbytes "base64 //8=" 30 | pushbytes "b64 //8=" 31 | -------------------------------------------------------------------------------- /tests/artifacts/delete_abi_with_inner/contract.py: -------------------------------------------------------------------------------- 1 | from algopy import ARC4Contract, arc4, UInt64, TemplateVar, String 2 | 3 | 4 | class DeleteAbiWithInner(ARC4Contract): 5 | def __init__(self) -> None: 6 | self.greeting = TemplateVar[String]("GREETING") 7 | 8 | @arc4.abimethod(create="require") 9 | def create(self) -> None: 10 | return 11 | 12 | @arc4.abimethod(allow_actions=["DeleteApplication"]) 13 | def delete(self, app_id: UInt64) -> None: 14 | arc4.abi_call("no_op", app_id=app_id) 15 | assert TemplateVar[bool]("DELETABLE") 16 | -------------------------------------------------------------------------------- /tests/applications/_snapshots/test_app_manager.approvals/test_comment_stripping.approved.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | op arg 4 | op "arg" 5 | op "//" 6 | op " //comment " 7 | op "\" //" 8 | op "// \" //" 9 | op "" 10 | 11 | op 123 12 | op 123 13 | op "" 14 | op "//" 15 | op "//" 16 | pushbytes base64(//8=) 17 | pushbytes b64(//8=) 18 | 19 | pushbytes base64(//8=) 20 | pushbytes b64(//8=) 21 | pushbytes "base64(//8=)" 22 | pushbytes "b64(//8=)" 23 | 24 | pushbytes base64 //8= 25 | pushbytes b64 //8= 26 | 27 | pushbytes base64 //8= 28 | pushbytes b64 //8= 29 | pushbytes "base64 //8=" 30 | pushbytes "b64 //8=" 31 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_call.approvals/test_readonly_call_with_error_with_new_client_missing_source_map.approved.txt: -------------------------------------------------------------------------------- 1 | Txn {txn} had error 'assert failed pc=743' at PC 743: 2 | 3 | Could not determine TEAL source line for the error as no approval source map was provided, to receive a trace of the 4 | error please provide an approval SourceMap. Either by: 5 | 1.Providing template_values when creating the ApplicationClient, so a SourceMap can be obtained automatically OR 6 | 2.Set approval_source_map from a previously compiled approval program OR 7 | 3.Import a previously exported source map using import_source_map -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_immutable_app_fails.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is not updatable but on_update=UpdateApp, will attempt to update app, update will most likely fail 6 | INFO: Updating SampleApp to 2.0 in {creator_account} account, with app id {app0} 7 | ERROR: LogicException: assert failed pc=140 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_immutable_app_cannot_determine_if_updatable.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (v1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=v1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: Cannot determine if App is updatable and on_update=UpdateApp, will attempt to update app 6 | INFO: Updating SampleApp to v1.1 in {creator_account} account, with app id {app0} 7 | ERROR: LogicError: assert failed pc=140 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_permanent_app_fails.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | ERROR: DeploymentFailedError: Schema break detected and on_schema_break=OnSchemaBreak.Fail, stopping deployment. If you want to try deleting and recreating the app then re-run with on_schema_break=OnSchemaBreak.ReplaceApp -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.Yes-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is updatable but on_update=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (2.0) in {creator_account} account. 7 | ERROR: LogicException: assert failed pc=153 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.No-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is not updatable and on_update=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (2.0) in {creator_account} account. 7 | ERROR: LogicException: assert failed pc=153 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.UpdateApp-Updatable.No-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is not updatable but on_update=UpdateApp, will attempt to update app, update will most likely fail 6 | INFO: Updating SampleApp to 2.0 in {creator_account} account, with app id {app0} 7 | ERROR: LogicException: assert failed pc=140 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.UpdateApp-Updatable.No-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is not updatable but on_update=UpdateApp, will attempt to update app, update will most likely fail 6 | INFO: Updating SampleApp to 2.0 in {creator_account} account, with app id {app0} 7 | ERROR: LogicException: assert failed pc=140 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_permanent_app_cannot_determine_if_deletable.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (v1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=v1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: Cannot determine if App is updatable and on_update=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (v1.0) with SampleApp (v1.1) in {creator_account} account. 7 | ERROR: LogicError: assert failed pc=153 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.No-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | ERROR: DeploymentFailedError: Schema break detected and on_schema_break=OnSchemaBreak.Fail, stopping deployment. If you want to try deleting and recreating the app then re-run with on_schema_break=OnSchemaBreak.ReplaceApp -------------------------------------------------------------------------------- /src/algokit_utils/_legacy_v2/application_specification.py: -------------------------------------------------------------------------------- 1 | from algokit_utils.applications.app_spec.arc32 import ( 2 | AppSpecStateDict, 3 | CallConfig, 4 | DefaultArgumentDict, 5 | DefaultArgumentType, 6 | MethodConfigDict, 7 | MethodHints, 8 | OnCompleteActionName, 9 | ) 10 | from algokit_utils.applications.app_spec.arc32 import Arc32Contract as ApplicationSpecification 11 | 12 | __all__ = [ 13 | "AppSpecStateDict", 14 | "ApplicationSpecification", 15 | "CallConfig", 16 | "DefaultArgumentDict", 17 | "DefaultArgumentType", 18 | "MethodConfigDict", 19 | "MethodHints", 20 | "OnCompleteActionName", 21 | ] 22 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.No-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | ERROR: DeploymentFailedError: Schema break detected and on_schema_break=OnSchemaBreak.Fail, stopping deployment. If you want to try deleting and recreating the app then re-run with on_schema_break=OnSchemaBreak.ReplaceApp -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.Yes-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | ERROR: DeploymentFailedError: Schema break detected and on_schema_break=OnSchemaBreak.Fail, stopping deployment. If you want to try deleting and recreating the app then re-run with on_schema_break=OnSchemaBreak.ReplaceApp -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.Fail-Updatable.Yes-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | ERROR: DeploymentFailedError: Schema break detected and on_schema_break=OnSchemaBreak.Fail, stopping deployment. If you want to try deleting and recreating the app then re-run with on_schema_break=OnSchemaBreak.ReplaceApp -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_permanent_app_on_update_equals_replace_app_fails_and_doesnt_create_2nd_app.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is not updatable and on_update=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (3.0) in {creator_account} account. 7 | ERROR: DeploymentFailedError: assert failed pc=153 -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Python Debugger: Current File", 6 | "type": "debugpy", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal" 10 | }, 11 | { 12 | "name": "Python: Debug Tests", 13 | "type": "debugpy", 14 | "request": "launch", 15 | "program": "${file}", 16 | "purpose": [ 17 | "debug-test" 18 | ], 19 | "console": "integratedTerminal", 20 | "justMyCode": false 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_permanent_app_and_on_schema_break_equals_replace_app_fails.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | WARNING: App is not deletable but on_schema_break=ReplaceApp, will attempt to delete app, delete will most likely fail 6 | INFO: Replacing SampleApp (1.0) with SampleApp (3.0) in {creator_account} account. 7 | ERROR: Deployment failed: assert failed pc=153 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.No-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | WARNING: App is not deletable but on_schema_break=ReplaceApp, will attempt to delete app, delete will most likely fail 6 | INFO: Replacing SampleApp (1.0) with SampleApp (3.0) in {creator_account} account. 7 | ERROR: LogicException: assert failed pc=153 -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.Yes-Deletable.No].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | WARNING: App is not deletable but on_schema_break=ReplaceApp, will attempt to delete app, delete will most likely fail 6 | INFO: Replacing SampleApp (1.0) with SampleApp (3.0) in {creator_account} account. 7 | ERROR: LogicException: assert failed pc=153 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F514 Feature Request" 3 | about: Suggestions for how we can improve the algorand platform. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Problem 10 | 11 | 12 | 13 | ## Solution 14 | 15 | 16 | 17 | ### Proposal 18 | 19 | 20 | 21 | ### Pros and Cons 22 | 23 | 24 | 25 | ## Dependencies 26 | 27 | 28 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.Yes-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is updatable but on_update=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (2.0) in {creator_account} account. 7 | INFO: SampleApp (2.0) deployed successfully, with app id {app1}. 8 | INFO: SampleApp (1.0) with app id {app0}, deleted successfully. -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_update[OnUpdate.ReplaceApp-Updatable.No-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is not updatable and on_update=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (2.0) in {creator_account} account. 7 | INFO: SampleApp (2.0) deployed successfully, with app id {app1}. 8 | INFO: SampleApp (1.0) with app id {app0}, deleted successfully. -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_app_with_existing_immutable_app_and_on_update_equals_replace_app_succeeds.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | INFO: Detected a TEAL update in app id {app0} 5 | WARNING: App is not updatable and on_update=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (2.0) in {creator_account} account. 7 | INFO: SampleApp (2.0) deployed successfully, with app id {app1}. 8 | INFO: SampleApp (1.0) with app id {app0}, deleted successfully. -------------------------------------------------------------------------------- /src/algokit_utils/protocols/account.py: -------------------------------------------------------------------------------- 1 | from typing import Protocol, runtime_checkable 2 | 3 | from algosdk.atomic_transaction_composer import TransactionSigner 4 | 5 | __all__ = ["TransactionSignerAccountProtocol"] 6 | 7 | 8 | @runtime_checkable 9 | class TransactionSignerAccountProtocol(Protocol): 10 | """An account that has a transaction signer. 11 | Implemented by SigningAccount, LogicSigAccount, MultiSigAccount and TransactionSignerAccount abstractions. 12 | """ 13 | 14 | @property 15 | def address(self) -> str: 16 | """The address of the account.""" 17 | ... 18 | 19 | @property 20 | def signer(self) -> TransactionSigner: 21 | """The transaction signer for the account.""" 22 | ... 23 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy.approvals/test_template_substitution.approved.txt: -------------------------------------------------------------------------------- 1 | 2 | test 123 // TMPL_INT 3 | test 123 4 | no change 5 | test 0x414243 // TMPL_STR 6 | 0x414243 7 | 0x414243 // TMPL_INT 8 | 0x414243 // foo // 9 | 0x414243 // bar 10 | test "TMPL_STR" // not replaced 11 | test "TMPL_STRING" // not replaced 12 | test TMPL_STRING // not replaced 13 | test TMPL_STRI // not replaced 14 | test 0x414243 123 123 0x414243 // TMPL_STR TMPL_INT TMPL_INT TMPL_STR 15 | test 123 0x414243 TMPL_STRING "TMPL_INT TMPL_STR TMPL_STRING" //TMPL_INT TMPL_STR TMPL_STRING 16 | test 123 123 TMPL_STRING TMPL_STRING TMPL_STRING 123 TMPL_STRING //keep 17 | 0x414243 0x414243 0x414243 18 | TMPL_STRING 19 | test NOTTMPL_STR // not replaced 20 | NOTTMPL_STR // not replaced 21 | 0x414243 // replaced -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.No-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | INFO: App is deletable and on_schema_break=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (3.0) in {creator_account} account. 7 | INFO: SampleApp (3.0) deployed successfully, with app id {app1}. 8 | INFO: SampleApp (1.0) with app id {app0}, deleted successfully. -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_with_schema_breaking_change[OnSchemaBreak.ReplaceApp-Updatable.Yes-Deletable.Yes].approved.txt: -------------------------------------------------------------------------------- 1 | INFO: SampleApp not found in {creator_account} account, deploying app. 2 | INFO: SampleApp (1.0) deployed successfully, with app id {app0}. 3 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1.0. 4 | WARNING: Detected a breaking app schema change: Global uints increased from 0 to 1 5 | INFO: App is deletable and on_schema_break=ReplaceApp, will attempt to create new app and delete old app 6 | INFO: Replacing SampleApp (1.0) with SampleApp (3.0) in {creator_account} account. 7 | INFO: SampleApp (3.0) deployed successfully, with app id {app1}. 8 | INFO: SampleApp (1.0) with app id {app0}, deleted successfully. -------------------------------------------------------------------------------- /tests/applications/_snapshots/test_app_manager.approvals/test_template_substitution.approved.txt: -------------------------------------------------------------------------------- 1 | 2 | test 123 // TMPL_INT 3 | test 123 4 | no change 5 | test 0x414243 // TMPL_STR 6 | 0x414243 7 | 0x414243 // TMPL_INT 8 | 0x414243 // foo // 9 | 0x414243 // bar 10 | test "TMPL_STR" // not replaced 11 | test "TMPL_STRING" // not replaced 12 | test TMPL_STRING // not replaced 13 | test TMPL_STRI // not replaced 14 | test 0x414243 123 123 0x414243 // TMPL_STR TMPL_INT TMPL_INT TMPL_STR 15 | test 123 0x414243 TMPL_STRING "TMPL_INT TMPL_STR TMPL_STRING" //TMPL_INT TMPL_STR TMPL_STRING 16 | test 123 123 TMPL_STRING TMPL_STRING TMPL_STRING 123 TMPL_STRING //keep 17 | 0x414243 0x414243 0x414243 18 | TMPL_STRING 19 | test NOTTMPL_STR // not replaced 20 | NOTTMPL_STR // not replaced 21 | 0x414243 // replaced -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from algokit_utils import ( 4 | DeploymentFailedError, 5 | get_next_version, 6 | ) 7 | 8 | 9 | @pytest.mark.parametrize( 10 | ("current", "expected_next"), 11 | [ 12 | ("1", "2"), 13 | ("v1", "v2"), 14 | ("v1-alpha", "v2-alpha"), 15 | ("1.0", "1.1"), 16 | ("v1.0", "v1.1"), 17 | ("v1.0-alpha", "v1.1-alpha"), 18 | ("1.0.0", "1.0.1"), 19 | ("v1.0.0", "v1.0.1"), 20 | ("v1.0.0-alpha", "v1.0.1-alpha"), 21 | ], 22 | ) 23 | def test_auto_version_increment(current: str, expected_next: str) -> None: 24 | value = get_next_version(current) 25 | assert value == expected_next 26 | 27 | 28 | def test_auto_version_increment_failure() -> None: 29 | with pytest.raises(DeploymentFailedError): 30 | get_next_version("teapot") 31 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /.github/workflows/check-docs.yaml: -------------------------------------------------------------------------------- 1 | name: Check Documentation 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | check-docs: 8 | runs-on: "ubuntu-latest" 9 | steps: 10 | - name: Checkout source code 11 | uses: actions/checkout@v4 12 | 13 | - name: Set up Python 3.12 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: "3.12" 17 | 18 | - name: Set up Poetry 19 | uses: ./.github/actions/setup-poetry 20 | 21 | - name: Install dependencies 22 | run: poetry install --no-interaction --no-root 23 | 24 | - name: Check docstrings are up to date 25 | run: poetry run poe docstrings-check 26 | 27 | - name: Check docs are up to date 28 | run: | 29 | poetry run poe docs-md-only 30 | git diff --exit-code ':!docs/markdown/autoapi/index.md' ':!docs/markdown/autoapi/algokit_utils/applications/app_factory/index.md' docs 31 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/protocols/account/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.protocols.account 2 | 3 | ## Classes 4 | 5 | | [`TransactionSignerAccountProtocol`](#algokit_utils.protocols.account.TransactionSignerAccountProtocol) | An account that has a transaction signer. | 6 | |-----------------------------------------------------------------------------------------------------------|---------------------------------------------| 7 | 8 | ## Module Contents 9 | 10 | ### *class* algokit_utils.protocols.account.TransactionSignerAccountProtocol 11 | 12 | Bases: `Protocol` 13 | 14 | An account that has a transaction signer. 15 | Implemented by SigningAccount, LogicSigAccount, MultiSigAccount and TransactionSignerAccount abstractions. 16 | 17 | #### *property* address *: str* 18 | 19 | The address of the account. 20 | 21 | #### *property* signer *: algosdk.atomic_transaction_composer.TransactionSigner* 22 | 23 | The transaction signer for the account. 24 | -------------------------------------------------------------------------------- /src/algokit_utils/_legacy_v2/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains common classes and methods that are reused in more than one file. 3 | """ 4 | 5 | import base64 6 | import typing 7 | 8 | from algosdk.source_map import SourceMap 9 | 10 | from algokit_utils._legacy_v2.deploy import strip_comments 11 | 12 | if typing.TYPE_CHECKING: 13 | from algosdk.v2client.algod import AlgodClient 14 | 15 | 16 | class Program: 17 | """A compiled TEAL program 18 | 19 | :param program: The TEAL program source code 20 | :param client: The AlgodClient instance to use for compiling the program 21 | """ 22 | 23 | def __init__(self, program: str, client: "AlgodClient"): 24 | self.teal = program 25 | result: dict = client.compile(strip_comments(self.teal), source_map=True) 26 | self.raw_binary = base64.b64decode(result["result"]) 27 | self.binary_hash: str = result["hash"] 28 | self.source_map = SourceMap(result["sourcemap"]) 29 | -------------------------------------------------------------------------------- /src/algokit_utils/asset.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | warnings.warn( 4 | """The legacy v2 asset module is deprecated and will be removed in a future version. 5 | 6 | Replacements for opt_in/opt_out functionality: 7 | 8 | 1. Using TransactionComposer: 9 | composer.add_asset_opt_in(AssetOptInParams( 10 | sender=account.address, 11 | asset_id=123 12 | )) 13 | composer.add_asset_opt_out(AssetOptOutParams( 14 | sender=account.address, 15 | asset_id=123, 16 | creator=creator_address 17 | )) 18 | 19 | 2. Using AlgorandClient: 20 | client.asset.opt_in(AssetOptInParams(...)) 21 | client.asset.opt_out(AssetOptOutParams(...)) 22 | 23 | 3. For bulk operations: 24 | client.asset.bulk_opt_in(account, [asset_ids]) 25 | client.asset.bulk_opt_out(account, [asset_ids]) 26 | 27 | Refer to AssetManager class from algokit_utils for more functionality.""", 28 | DeprecationWarning, 29 | stacklevel=2, 30 | ) 31 | 32 | from algokit_utils._legacy_v2.asset import * # noqa: F403, E402 33 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils 2 | 3 | AlgoKit Python Utilities - a set of utilities for building solutions on Algorand 4 | 5 | This module provides commonly used utilities and types at the root level for convenience. 6 | For more specific functionality, import directly from the relevant submodules: 7 | 8 | > from algokit_utils.accounts import KmdAccountManager 9 | > from algokit_utils.applications import AppClient 10 | > from algokit_utils.applications.app_spec import Arc52Contract 11 | > etc. 12 | 13 | ## Submodules 14 | 15 | * [algokit_utils.accounts](accounts/index.md) 16 | * [algokit_utils.algorand](algorand/index.md) 17 | * [algokit_utils.applications](applications/index.md) 18 | * [algokit_utils.assets](assets/index.md) 19 | * [algokit_utils.clients](clients/index.md) 20 | * [algokit_utils.config](config/index.md) 21 | * [algokit_utils.errors](errors/index.md) 22 | * [algokit_utils.models](models/index.md) 23 | * [algokit_utils.protocols](protocols/index.md) 24 | * [algokit_utils.transactions](transactions/index.md) 25 | -------------------------------------------------------------------------------- /src/algokit_utils/models/network.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | __all__ = [ 4 | "AlgoClientConfigs", 5 | "AlgoClientNetworkConfig", 6 | ] 7 | 8 | 9 | @dataclasses.dataclass 10 | class AlgoClientNetworkConfig: 11 | """Connection details for connecting to an {py:class}`algosdk.v2client.algod.AlgodClient` or 12 | {py:class}`algosdk.v2client.indexer.IndexerClient`""" 13 | 14 | server: str 15 | """URL for the service e.g. `http://localhost` or `https://testnet-api.algonode.cloud`""" 16 | token: str | None = None 17 | """API Token to authenticate with the service e.g '4001' or '8980'""" 18 | port: str | int | None = None 19 | 20 | def full_url(self) -> str: 21 | """Returns the full URL for the service""" 22 | return f"{self.server.rstrip('/')}{f':{self.port}' if self.port else ''}" 23 | 24 | 25 | @dataclasses.dataclass 26 | class AlgoClientConfigs: 27 | algod_config: AlgoClientNetworkConfig 28 | indexer_config: AlgoClientNetworkConfig | None 29 | kmd_config: AlgoClientNetworkConfig | None 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Algorand Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/algokit_utils/__init__.py: -------------------------------------------------------------------------------- 1 | """AlgoKit Python Utilities - a set of utilities for building solutions on Algorand 2 | 3 | This module provides commonly used utilities and types at the root level for convenience. 4 | For more specific functionality, import directly from the relevant submodules: 5 | 6 | from algokit_utils.accounts import KmdAccountManager 7 | from algokit_utils.applications import AppClient 8 | from algokit_utils.applications.app_spec import Arc52Contract 9 | etc. 10 | """ 11 | 12 | # Core types and utilities that are commonly used 13 | from algokit_utils.applications import * # noqa: F403 14 | from algokit_utils.assets import * # noqa: F403 15 | from algokit_utils.protocols import * # noqa: F403 16 | from algokit_utils.models import * # noqa: F403 17 | from algokit_utils.accounts import * # noqa: F403 18 | from algokit_utils.clients import * # noqa: F403 19 | from algokit_utils.transactions import * # noqa: F403 20 | from algokit_utils.errors import * # noqa: F403 21 | from algokit_utils.algorand import * # noqa: F403 22 | 23 | # Legacy types and utilities 24 | from algokit_utils._legacy_v2 import * # noqa: F403 25 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | # Local development environment variables 2 | # 3 | # Copy this file to .env (which is added to .gitignore so you can make local-only changes) 4 | # Changing the values in this file will change the configuration that is used when running this solution locally 5 | 6 | # Local sandbox config 7 | ALGOD_SERVER=http://localhost:4001 8 | ALGOD_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 9 | KMD_PORT=4002 10 | 11 | INDEXER_SERVER=http://localhost:8980 12 | INDEXER_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 13 | 14 | # 15 | 16 | # TestNet config 17 | 18 | # Minting creator account (non-local) 19 | #CREATOR_MNEMONIC={token} 20 | 21 | # Dispenser account (non-local) 22 | #DISPENSER_MNEMONIC={token} 23 | 24 | # AlgoNode - https://algonode.io/api/ 25 | # Useful service to target testnet and mainnet without having to run our own node 26 | # You'll need to fund the CREATOR_MNEMONIC and DISPENSER_MNEMONIC accounts via a TestNet dispenser 27 | #ALGOD_TOKEN= 28 | #ALGOD_SERVER=https://testnet-api.algonode.cloud/ 29 | #ALGOD_PORT=443 30 | #INDEXER_TOKEN= 31 | #INDEXER_SERVER=https://testnet-idx.algonode.cloud/ 32 | #INDEXER_PORT=443 33 | -------------------------------------------------------------------------------- /src/algokit_utils/applications/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | # NOTE: this is moved to a separate file to avoid circular imports 4 | 5 | 6 | class OnSchemaBreak(Enum): 7 | """Action to take if an Application's schema has breaking changes""" 8 | 9 | Fail = 0 10 | """Fail the deployment""" 11 | ReplaceApp = 2 12 | """Create a new Application and delete the old Application in a single transaction""" 13 | AppendApp = 3 14 | """Create a new Application""" 15 | 16 | 17 | class OnUpdate(Enum): 18 | """Action to take if an Application has been updated""" 19 | 20 | Fail = 0 21 | """Fail the deployment""" 22 | UpdateApp = 1 23 | """Update the Application with the new approval and clear programs""" 24 | ReplaceApp = 2 25 | """Create a new Application and delete the old Application in a single transaction""" 26 | AppendApp = 3 27 | """Create a new application""" 28 | 29 | 30 | class OperationPerformed(Enum): 31 | """Describes the actions taken during deployment""" 32 | 33 | Nothing = 0 34 | """An existing Application was found""" 35 | Create = 1 36 | """No existing Application was found, created a new Application""" 37 | Update = 2 38 | """An existing Application was found, but was out of date, updated to latest version""" 39 | Replace = 3 40 | """An existing Application was found, but was out of date, created a new Application and deleted the original""" 41 | -------------------------------------------------------------------------------- /tests/artifacts/hello_world/approval.teal: -------------------------------------------------------------------------------- 1 | #pragma version 10 2 | 3 | smart_contracts.hello_world.contract.HelloWorld.approval_program: 4 | intcblock 0 1 5 | callsub __puya_arc4_router__ 6 | return 7 | 8 | 9 | // smart_contracts.hello_world.contract.HelloWorld.__puya_arc4_router__() -> uint64: 10 | __puya_arc4_router__: 11 | proto 0 1 12 | txn NumAppArgs 13 | bz __puya_arc4_router___bare_routing@5 14 | pushbytes 0x02bece11 // method "hello(string)string" 15 | txna ApplicationArgs 0 16 | match __puya_arc4_router___hello_route@2 17 | intc_0 // 0 18 | retsub 19 | 20 | __puya_arc4_router___hello_route@2: 21 | txn OnCompletion 22 | ! 23 | assert // OnCompletion is NoOp 24 | txn ApplicationID 25 | assert // is not creating 26 | txna ApplicationArgs 1 27 | extract 2 0 28 | callsub hello 29 | dup 30 | len 31 | itob 32 | extract 6 2 33 | swap 34 | concat 35 | pushbytes 0x151f7c75 36 | swap 37 | concat 38 | log 39 | intc_1 // 1 40 | retsub 41 | 42 | __puya_arc4_router___bare_routing@5: 43 | txn OnCompletion 44 | bnz __puya_arc4_router___after_if_else@9 45 | txn ApplicationID 46 | ! 47 | assert // is creating 48 | intc_1 // 1 49 | retsub 50 | 51 | __puya_arc4_router___after_if_else@9: 52 | intc_0 // 0 53 | retsub 54 | 55 | 56 | // smart_contracts.hello_world.contract.HelloWorld.hello(name: bytes) -> bytes: 57 | hello: 58 | proto 1 1 59 | pushbytes "Hello, " 60 | frame_dig -1 61 | concat 62 | retsub 63 | -------------------------------------------------------------------------------- /.github/workflows/build-python.yaml: -------------------------------------------------------------------------------- 1 | name: Build, Test and Publish Python 2 | 3 | on: [workflow_call] 4 | 5 | jobs: 6 | build-python: 7 | strategy: 8 | matrix: 9 | os: ["ubuntu-latest"] 10 | python: ["3.10", "3.11", "3.12"] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - name: Checkout source code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Python ${{ matrix.python }} 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: ${{ matrix.python }} 20 | 21 | - name: Set up Poetry 22 | uses: ./.github/actions/setup-poetry 23 | 24 | - name: Install dependencies 25 | run: poetry install --no-interaction 26 | 27 | - name: pytest + coverage 28 | shell: bash 29 | run: | 30 | set -o pipefail 31 | pipx install algokit 32 | algokit localnet start 33 | poetry run pytest -n auto --junitxml=pytest-junit.xml --cov-report=term-missing:skip-covered --cov=src | tee pytest-coverage.txt 34 | algokit localnet stop 35 | 36 | - name: pytest coverage comment - using Python 3.10 on ubuntu-latest 37 | if: matrix.python == '3.10' && matrix.os == 'ubuntu-latest' 38 | uses: MishaKav/pytest-coverage-comment@main 39 | continue-on-error: true # forks fail to add a comment, so continue any way 40 | with: 41 | pytest-coverage-path: ./pytest-coverage.txt 42 | junitxml-path: ./pytest-junit.xml 43 | 44 | - name: Build Wheel 45 | run: poetry build --format wheel 46 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: ruff-format 5 | name: ruff-format 6 | description: "Run 'ruff format' for extremely fast Python formatting" 7 | entry: poetry run ruff format 8 | language: system 9 | types: [python] 10 | args: [] 11 | require_serial: true 12 | additional_dependencies: [] 13 | minimum_pre_commit_version: "2.9.2" 14 | files: "^(src|tests)/" 15 | - id: ruff 16 | name: ruff 17 | description: "Run 'ruff' for extremely fast Python linting" 18 | entry: poetry run ruff check 19 | language: system 20 | "types": [python] 21 | args: [--fix] 22 | require_serial: false 23 | additional_dependencies: [] 24 | minimum_pre_commit_version: "0" 25 | files: "^(src|tests)/" 26 | exclude: "^tests/artifacts/" 27 | - id: mypy 28 | name: mypy 29 | description: "`mypy` will check Python types for correctness" 30 | entry: poetry run mypy 31 | language: system 32 | types_or: [python, pyi] 33 | require_serial: true 34 | additional_dependencies: [] 35 | minimum_pre_commit_version: "2.9.2" 36 | files: "^(src|tests)/" 37 | exclude: "^tests/artifacts/" 38 | - id: docstrings-check 39 | name: docstrings-check 40 | description: "Check docstrings for correctness" 41 | entry: poetry run poe docstrings-check 42 | language: system 43 | types: [python] 44 | files: "^(src)/" 45 | -------------------------------------------------------------------------------- /src/algokit_utils/application_specification.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | 3 | from typing_extensions import deprecated 4 | 5 | warnings.warn( 6 | """The legacy v2 application_specification module is deprecated and will be removed in a future version. 7 | Use `from algokit_utils.applications.app_spec.arc32 import ...` to access Arc32 app spec instead. 8 | By default, the ARC52Contract is a recommended app spec to use, serving as a replacement 9 | for legacy 'ApplicationSpecification' class. 10 | To convert legacy app specs to ARC52, use `Arc56Contract.from_arc32`. 11 | """, 12 | DeprecationWarning, 13 | stacklevel=2, 14 | ) 15 | 16 | from algokit_utils.applications.app_spec.arc32 import ( # noqa: E402 # noqa: E402 17 | AppSpecStateDict, 18 | Arc32Contract, 19 | CallConfig, 20 | DefaultArgumentDict, 21 | DefaultArgumentType, 22 | MethodConfigDict, 23 | MethodHints, 24 | OnCompleteActionName, 25 | ) 26 | 27 | 28 | @deprecated( 29 | "Use `Arc32Contract` from algokit_utils.applications instead. Example:\n" 30 | "```python\n" 31 | "from algokit_utils.applications import Arc32Contract\n" 32 | "app_spec = Arc32Contract.from_json(app_spec_json)\n" 33 | "```" 34 | ) 35 | class ApplicationSpecification(Arc32Contract): 36 | """Deprecated class for ARC-0032 application specification""" 37 | 38 | 39 | __all__ = [ 40 | "AppSpecStateDict", 41 | "ApplicationSpecification", 42 | "CallConfig", 43 | "DefaultArgumentDict", 44 | "DefaultArgumentType", 45 | "MethodConfigDict", 46 | "MethodHints", 47 | "OnCompleteActionName", 48 | ] 49 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_network_clients.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import mock 3 | 4 | from algokit_utils import ( 5 | get_algod_client, 6 | get_algonode_config, 7 | get_default_localnet_config, 8 | get_indexer_client, 9 | ) 10 | 11 | DEFAULT_TOKEN = "a" * 64 12 | 13 | 14 | def test_localnet_algod() -> None: 15 | algod_client = get_algod_client(get_default_localnet_config("algod")) 16 | health_response = algod_client.health() 17 | assert health_response is None 18 | 19 | 20 | def test_localnet_indexer() -> None: 21 | indexer_client = get_indexer_client(get_default_localnet_config("indexer")) 22 | health_response = indexer_client.health() # type: ignore[no-untyped-call] 23 | assert isinstance(health_response, dict) 24 | 25 | 26 | @mock.patch.dict( 27 | os.environ, 28 | { 29 | "ALGOD_SERVER": "https://testnet-api.algonode.cloud", 30 | "ALGOD_PORT": "443", 31 | "ALGOD_TOKEN": DEFAULT_TOKEN, 32 | }, 33 | ) 34 | def test_environment_config() -> None: 35 | algod_client = get_algod_client() 36 | 37 | assert algod_client.algod_address == "https://testnet-api.algonode.cloud:443" 38 | 39 | 40 | def test_cloudnode_algod_headers() -> None: 41 | algod_client = get_algod_client(get_algonode_config("testnet", "algod", DEFAULT_TOKEN)) 42 | 43 | assert algod_client.headers == {"X-Algo-API-Token": DEFAULT_TOKEN} 44 | 45 | 46 | def test_cloudnode_indexer_headers() -> None: 47 | indexer_client = get_indexer_client(get_algonode_config("testnet", "indexer", DEFAULT_TOKEN)) 48 | 49 | assert indexer_client.headers == {"X-Indexer-API-Token": DEFAULT_TOKEN} 50 | -------------------------------------------------------------------------------- /.github/workflows/check-python.yaml: -------------------------------------------------------------------------------- 1 | name: Check Python Code 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | check-python: 8 | runs-on: "ubuntu-latest" 9 | steps: 10 | - name: Checkout source code 11 | uses: actions/checkout@v4 12 | 13 | - name: Set up Python 3.10 14 | uses: actions/setup-python@v5 15 | with: 16 | python-version: "3.10" 17 | 18 | - name: Set up Poetry 19 | uses: ./.github/actions/setup-poetry 20 | 21 | - name: Install dependencies 22 | run: poetry install --no-interaction --no-root 23 | 24 | - name: Audit with pip-audit 25 | run: | 26 | # audit non dev dependencies, no exclusions 27 | poetry export --without=dev > requirements.txt && poetry run pip-audit -r requirements.txt 28 | 29 | # audit all dependencies, with exclusions. 30 | # If a vulnerability is found in a dev dependency without an available fix, 31 | # it can be temporarily ignored by adding --ignore-vuln e.g. 32 | # --ignore-vuln "GHSA-hcpj-qp55-gfph" # GitPython vulnerability, dev only dependency 33 | poetry run pip-audit --ignore-vuln GHSA-4xh5-x5gv-qwph 34 | 35 | - name: Check formatting with Ruff 36 | run: | 37 | # stop the build if there are files that don't meet formatting requirements 38 | poetry run ruff format --check . 39 | 40 | - name: Check linting with Ruff 41 | run: | 42 | # stop the build if there are Python syntax errors or undefined names 43 | poetry run ruff check . 44 | 45 | - name: Check types with mypy 46 | run: poetry run mypy 47 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/models/network/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.models.network 2 | 3 | ## Classes 4 | 5 | | [`AlgoClientNetworkConfig`](#algokit_utils.models.network.AlgoClientNetworkConfig) | Connection details for connecting to an {py:class}\`algosdk.v2client.algod.AlgodClient\` or | 6 | |--------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| 7 | | [`AlgoClientConfigs`](#algokit_utils.models.network.AlgoClientConfigs) | | 8 | 9 | ## Module Contents 10 | 11 | ### *class* algokit_utils.models.network.AlgoClientNetworkConfig 12 | 13 | Connection details for connecting to an {py:class}\`algosdk.v2client.algod.AlgodClient\` or 14 | {py:class}\`algosdk.v2client.indexer.IndexerClient\` 15 | 16 | #### server *: str* 17 | 18 | URL for the service e.g. http://localhost or https://testnet-api.algonode.cloud 19 | 20 | #### token *: str | None* *= None* 21 | 22 | API Token to authenticate with the service e.g ‘4001’ or ‘8980’ 23 | 24 | #### port *: str | int | None* *= None* 25 | 26 | #### full_url() → str 27 | 28 | Returns the full URL for the service 29 | 30 | ### *class* algokit_utils.models.network.AlgoClientConfigs 31 | 32 | #### algod_config *: [AlgoClientNetworkConfig](#algokit_utils.models.network.AlgoClientNetworkConfig)* 33 | 34 | #### indexer_config *: [AlgoClientNetworkConfig](#algokit_utils.models.network.AlgoClientNetworkConfig) | None* 35 | 36 | #### kmd_config *: [AlgoClientNetworkConfig](#algokit_utils.models.network.AlgoClientNetworkConfig) | None* 37 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_delete.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | import pytest 4 | 5 | from algokit_utils import ( 6 | Account, 7 | ApplicationClient, 8 | ApplicationSpecification, 9 | LogicError, 10 | ) 11 | from legacy_v2_tests.conftest import check_output_stability 12 | 13 | if TYPE_CHECKING: 14 | from algosdk.v2client.algod import AlgodClient 15 | from algosdk.v2client.indexer import IndexerClient 16 | 17 | 18 | @pytest.fixture 19 | def client_fixture( 20 | algod_client: "AlgodClient", 21 | indexer_client: "IndexerClient", 22 | funded_account: Account, 23 | app_spec: ApplicationSpecification, 24 | ) -> ApplicationClient: 25 | client = ApplicationClient(algod_client, app_spec, creator=funded_account, indexer_client=indexer_client) 26 | client.create("create") 27 | return client 28 | 29 | 30 | def test_abi_delete(client_fixture: ApplicationClient) -> None: 31 | delete_response = client_fixture.delete("delete") 32 | 33 | assert delete_response.tx_id 34 | 35 | 36 | def test_bare_delete(client_fixture: ApplicationClient) -> None: 37 | delete_response = client_fixture.delete(call_abi_method=False) 38 | 39 | assert delete_response.tx_id 40 | 41 | 42 | def test_abi_delete_args(client_fixture: ApplicationClient) -> None: 43 | delete_response = client_fixture.delete("delete_args", check="Yes") 44 | 45 | assert delete_response.tx_id 46 | 47 | 48 | def test_abi_delete_args_fails(client_fixture: ApplicationClient) -> None: 49 | with pytest.raises(LogicError) as ex: 50 | client_fixture.delete("delete_args", check="No") 51 | 52 | check_output_stability(str(ex.value).replace(ex.value.transaction_id, "{txn}")) 53 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Literal, overload 3 | 4 | from algokit_utils.applications.app_manager import DELETABLE_TEMPLATE_NAME, UPDATABLE_TEMPLATE_NAME, AppManager 5 | from algokit_utils.applications.app_spec import Arc32Contract, Arc56Contract 6 | 7 | 8 | @overload 9 | def load_app_spec( 10 | path: Path, 11 | arc: Literal[32], 12 | *, 13 | updatable: bool | None = None, 14 | deletable: bool | None = None, 15 | template_values: dict | None = None, 16 | ) -> Arc32Contract: ... 17 | 18 | 19 | @overload 20 | def load_app_spec( 21 | path: Path, 22 | arc: Literal[56], 23 | *, 24 | updatable: bool | None = None, 25 | deletable: bool | None = None, 26 | template_values: dict | None = None, 27 | ) -> Arc56Contract: ... 28 | 29 | 30 | def load_app_spec( 31 | path: Path, 32 | arc: Literal[32, 56], 33 | *, 34 | updatable: bool | None = None, 35 | deletable: bool | None = None, 36 | template_values: dict | None = None, 37 | ) -> Arc32Contract | Arc56Contract: 38 | arc_class = Arc32Contract if arc == 32 else Arc56Contract 39 | spec = arc_class.from_json(path.read_text(encoding="utf-8")) 40 | 41 | template_variables = template_values or {} 42 | if updatable is not None: 43 | template_variables["UPDATABLE"] = int(updatable) 44 | 45 | if deletable is not None: 46 | template_variables["DELETABLE"] = int(deletable) 47 | 48 | if isinstance(spec, Arc32Contract): 49 | spec.approval_program = ( 50 | AppManager.replace_template_variables(spec.approval_program, template_variables) 51 | .replace(f"// {UPDATABLE_TEMPLATE_NAME}", "// updatable") 52 | .replace(f"// {DELETABLE_TEMPLATE_NAME}", "// deletable") 53 | ) 54 | return spec 55 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy_scenarios.approvals/test_deploy_templated_app_with_changing_parameters_succeeds.approved.txt: -------------------------------------------------------------------------------- 1 | INFO: Deploy V1 as updatable, deletable 2 | INFO: SampleApp not found in {creator_account} account, deploying app. 3 | INFO: SampleApp (1) deployed successfully, with app id {app0}. 4 | INFO: Called hello: Hello, call_1 5 | INFO: Deploy V2 as immutable, deletable 6 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=1. 7 | INFO: Detected a TEAL update in app id {app0} 8 | INFO: App is updatable and on_update=UpdateApp, will update app 9 | INFO: Updating SampleApp to 2 in {creator_account} account, with app id {app0} 10 | INFO: Called hello: Hello, call_2 11 | INFO: Attempt to deploy V3 as updatable, deletable, it will fail because V2 was immutable 12 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=2. 13 | INFO: Detected a TEAL update in app id {app0} 14 | WARNING: App is not updatable but on_update=UpdateApp, will attempt to update app, update will most likely fail 15 | INFO: Updating SampleApp to 3 in {creator_account} account, with app id {app0} 16 | ERROR: LogicException: assert failed pc=140 17 | INFO: Called hello: Hello, call_3 18 | INFO: 2nd Attempt to deploy V3 as updatable, deletable, it will succeed as on_update=OnUpdate.DeleteApp 19 | DEBUG: SampleApp found in {creator_account} account, with app id {app0}, version=2. 20 | INFO: Detected a TEAL update in app id {app0} 21 | WARNING: App is not updatable and on_update=ReplaceApp, will attempt to create new app and delete old app 22 | INFO: Replacing SampleApp (2) with SampleApp (4) in {creator_account} account. 23 | INFO: SampleApp (4) deployed successfully, with app id {app2}. 24 | INFO: SampleApp (2) with app id {app0}, deleted successfully. 25 | INFO: Called hello: Hello, call_4 26 | INFO: Called hello: Hello, call_5 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlgoKit Python Utilities 2 | 3 | A set of core Algorand utilities written in Python and released via PyPi that make it easier to build solutions on Algorand. 4 | This project is part of [AlgoKit](https://github.com/algorandfoundation/algokit-cli). 5 | 6 | The goal of this library is to provide intuitive, productive utility functions that make it easier, quicker and safer to build applications on Algorand. 7 | Largely these functions wrap the underlying Algorand SDK, but provide a higher level interface with sensible defaults and capabilities for common tasks. 8 | 9 | > **Note** 10 | > If you prefer TypeScript there's an equivalent [TypeScript utility library](https://github.com/algorandfoundation/algokit-utils-ts). 11 | 12 | [Install](https://github.com/algorandfoundation/algokit-utils-py#install) | [Documentation](https://algorandfoundation.github.io/algokit-utils-py) 13 | 14 | ## Install 15 | 16 | This library can be installed using pip, e.g.: 17 | 18 | ``` 19 | pip install algokit-utils 20 | ``` 21 | 22 | ## Migration from `v2.x` to `v3.x` 23 | 24 | Refer to the [v3 migration](./docs/source/v3-migration-guide.md) for more information on how to migrate to latest version of `algokit-utils-py`. 25 | 26 | ## Guiding principles 27 | 28 | This library follows the [Guiding Principles of AlgoKit](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md#guiding-principles). 29 | 30 | ## Contributing 31 | 32 | This is an open source project managed by the Algorand Foundation. 33 | See the [AlgoKit contributing page](https://github.com/algorandfoundation/algokit-cli/blob/main/CONTRIBUTING.MD) to learn about making improvements. 34 | 35 | To successfully run the tests in this repository you need to be running LocalNet via [AlgoKit](https://github.com/algorandfoundation/algokit-cli): 36 | 37 | ``` 38 | algokit localnet start 39 | ``` 40 | -------------------------------------------------------------------------------- /.github/actions/setup-poetry/action.yaml: -------------------------------------------------------------------------------- 1 | name: "Python Poetry Action" 2 | description: "An action to setup Poetry" 3 | runs: 4 | using: "composite" 5 | steps: 6 | # A workaround for pipx isn't installed on M1 runner. 7 | # We should remove it after this issue is resolved. 8 | # https://github.com/actions/runner-images/issues/9256 9 | - if: ${{ runner.os == 'macOS' && runner.arch == 'ARM64' }} 10 | run: | 11 | pip install poetry 12 | pip install poetry-plugin-export 13 | shell: bash 14 | 15 | # NOTE: Below commands currently causes a faulty behaviour in pipx where 16 | # preinstalled pipx on github worker has shared venv instantiated via python 3.10 17 | # however 2 of the above commands are supposed to reinstall pipx and use python version 18 | # specified in setup-python, however shared venv still uses 3.10 hence algokit fails on 19 | # pkgutil.ImpImporter module not found error. 20 | # To be approached as given until further clarified on corresponding issues on pipx repo. 21 | # ------ 22 | # pip install --user pipx 23 | # pipx ensurepath 24 | # ------ 25 | - if: ${{ runner.os != 'macOS' || runner.arch != 'ARM64' }} 26 | run: | 27 | pipx install poetry ${{ runner.os == 'macOS' && '--python "$Python_ROOT_DIR/bin/python"' || '' }} 28 | pipx inject poetry poetry-plugin-export 29 | shell: bash 30 | 31 | - name: Get full Python version 32 | id: full-python-version 33 | shell: bash 34 | run: echo "full_version=$(python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))')" >> $GITHUB_OUTPUT 35 | 36 | - name: Setup poetry cache 37 | uses: actions/cache@v4 38 | with: 39 | path: ./.venv 40 | key: venv-${{ hashFiles('poetry.lock') }}-${{ runner.os }}-${{ runner.arch }}-${{ steps.full-python-version.outputs.full_version }} 41 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // General - see also /.editorconfig 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": "explicit", 6 | "source.fixAll": "explicit" 7 | }, 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "files.exclude": { 10 | "**/.git": true, 11 | "**/.DS_Store": true, 12 | "**/Thumbs.db": true, 13 | ".mypy_cache": true, 14 | ".pytest_cache": true, 15 | ".ruff_cache": true, 16 | "**/__pycache__": true, 17 | ".idea": true 18 | }, 19 | // Python 20 | "platformSettings.autoLoad": true, 21 | "python.defaultInterpreterPath": "${workspaceFolder}/.venv", 22 | "python.analysis.extraPaths": [ 23 | "${workspaceFolder}/src" 24 | ], 25 | "[python]": { 26 | "editor.defaultFormatter": "charliermarsh.ruff" 27 | }, 28 | "python.analysis.exclude": [ 29 | "tests/artifacts/**" 30 | ], 31 | "python.analysis.typeCheckingMode": "basic", 32 | "ruff.enable": true, 33 | "ruff.lint.run": "onSave", 34 | "ruff.lint.args": [ 35 | "--config=pyproject.toml" 36 | ], 37 | "ruff.importStrategy": "fromEnvironment", 38 | "ruff.fixAll": true, //lint and fix all files in workspace 39 | "ruff.organizeImports": true, //organize imports on save 40 | "ruff.codeAction.disableRuleComment": { 41 | "enable": true 42 | }, 43 | "ruff.codeAction.fixViolation": { 44 | "enable": true 45 | }, 46 | "mypy.configFile": "pyproject.toml", 47 | // set to empty array to use config from project 48 | "mypy.targets": [], 49 | "mypy.runUsingActiveInterpreter": true, 50 | "python.testing.unittestEnabled": false, 51 | "python.testing.pytestEnabled": true, 52 | "emeraldwalk.runonsave": { 53 | "commands": [ 54 | { 55 | "match": "\\.py$", 56 | "cmd": "${workspaceFolder}/.venv/bin/ruff ${file} --fix" 57 | } 58 | ] 59 | }, 60 | "python.testing.pytestArgs": [ 61 | "." 62 | ], 63 | } 64 | -------------------------------------------------------------------------------- /src/algokit_utils/beta/_utils.py: -------------------------------------------------------------------------------- 1 | from typing import NoReturn 2 | 3 | 4 | def deprecated_import_error(old_path: str, new_path: str) -> NoReturn: 5 | """Helper to create consistent deprecation error messages""" 6 | raise ImportError( 7 | f"WARNING: The module '{old_path}' has been removed in algokit-utils v3. " 8 | f"Please update your imports to use '{new_path}' instead. " 9 | "See the migration guide for more details: " 10 | "https://github.com/algorandfoundation/algokit-utils-py/blob/main/docs/source/v3-migration-guide.md" 11 | ) 12 | 13 | 14 | def handle_getattr(name: str) -> NoReturn: 15 | param_mappings = { 16 | "ClientManager": "algokit_utils.ClientManager", 17 | "AlgorandClient": "algokit_utils.AlgorandClient", 18 | "AlgoSdkClients": "algokit_utils.AlgoSdkClients", 19 | "AccountManager": "algokit_utils.AccountManager", 20 | "PayParams": "algokit_utils.transactions.PaymentParams", 21 | "AlgokitComposer": "algokit_utils.TransactionComposer", 22 | "AssetCreateParams": "algokit_utils.transactions.AssetCreateParams", 23 | "AssetConfigParams": "algokit_utils.transactions.AssetConfigParams", 24 | "AssetFreezeParams": "algokit_utils.transactions.AssetFreezeParams", 25 | "AssetDestroyParams": "algokit_utils.transactions.AssetDestroyParams", 26 | "AssetTransferParams": "algokit_utils.transactions.AssetTransferParams", 27 | "AssetOptInParams": "algokit_utils.transactions.AssetOptInParams", 28 | "AppCallParams": "algokit_utils.transactions.AppCallParams", 29 | "MethodCallParams": "algokit_utils.transactions.MethodCallParams", 30 | "OnlineKeyRegParams": "algokit_utils.transactions.OnlineKeyRegistrationParams", 31 | } 32 | 33 | if name in param_mappings: 34 | deprecated_import_error(f"algokit_utils.beta.{name}", param_mappings[name]) 35 | 36 | raise AttributeError(f"module 'algokit_utils.beta' has no attribute '{name}'") 37 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_update.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | import pytest 4 | 5 | from algokit_utils import ( 6 | Account, 7 | ApplicationClient, 8 | ApplicationSpecification, 9 | LogicError, 10 | ) 11 | from legacy_v2_tests.conftest import check_output_stability 12 | 13 | if TYPE_CHECKING: 14 | from algosdk.v2client.algod import AlgodClient 15 | from algosdk.v2client.indexer import IndexerClient 16 | 17 | 18 | @pytest.fixture(scope="module") 19 | def client_fixture( 20 | algod_client: "AlgodClient", 21 | indexer_client: "IndexerClient", 22 | app_spec: ApplicationSpecification, 23 | funded_account: Account, 24 | ) -> ApplicationClient: 25 | client = ApplicationClient(algod_client, app_spec, creator=funded_account, indexer_client=indexer_client) 26 | client.create("create") 27 | return client 28 | 29 | 30 | def test_abi_update(client_fixture: ApplicationClient) -> None: 31 | update_response = client_fixture.update("update") 32 | 33 | assert update_response.tx_id 34 | assert client_fixture.call("hello", name="test").return_value == "Updated ABI, test" 35 | 36 | 37 | def test_bare_update(client_fixture: ApplicationClient) -> None: 38 | update_response = client_fixture.update(call_abi_method=False) 39 | 40 | assert update_response.tx_id 41 | assert client_fixture.call("hello", name="test").return_value == "Updated Bare, test" 42 | 43 | 44 | def test_abi_update_args(client_fixture: ApplicationClient) -> None: 45 | update_response = client_fixture.update("update_args", check="Yes") 46 | 47 | assert update_response.tx_id 48 | assert client_fixture.call("hello", name="test").return_value == "Updated Args, test" 49 | 50 | 51 | def test_abi_update_args_fails(client_fixture: ApplicationClient) -> None: 52 | with pytest.raises(LogicError) as ex: 53 | client_fixture.update("update_args", check="No") 54 | 55 | check_output_stability(str(ex.value).replace(ex.value.transaction_id, "{txn}")) 56 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_prepare.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from typing import TYPE_CHECKING 3 | 4 | from algosdk.atomic_transaction_composer import AccountTransactionSigner 5 | 6 | from algokit_utils import ( 7 | ApplicationClient, 8 | ApplicationSpecification, 9 | ) 10 | 11 | if TYPE_CHECKING: 12 | from algosdk.v2client.algod import AlgodClient 13 | 14 | 15 | def test_app_client_prepare_with_no_existing_or_new( 16 | algod_client: "AlgodClient", app_spec: ApplicationSpecification 17 | ) -> None: 18 | client = ApplicationClient(algod_client, app_spec) 19 | 20 | new_client = client.prepare() 21 | assert new_client.signer is None 22 | assert new_client.sender is None 23 | 24 | 25 | def test_app_client_prepare_with_existing_signer_sender( 26 | algod_client: "AlgodClient", app_spec: ApplicationSpecification 27 | ) -> None: 28 | signer = AccountTransactionSigner(base64.b64encode(b"a" * 64).decode("utf-8")) 29 | client = ApplicationClient(algod_client, app_spec, signer=signer, sender="a sender") 30 | 31 | new_client = client.prepare() 32 | assert isinstance(new_client.signer, AccountTransactionSigner) 33 | assert signer.private_key == new_client.signer.private_key 34 | assert client.sender == new_client.sender 35 | 36 | 37 | def test_app_client_prepare_with_new_sender(algod_client: "AlgodClient", app_spec: ApplicationSpecification) -> None: 38 | client = ApplicationClient(algod_client, app_spec) 39 | 40 | new_client = client.prepare(sender="new_sender") 41 | assert new_client.sender == "new_sender" 42 | 43 | 44 | def test_app_client_prepare_with_new_signer(algod_client: "AlgodClient", app_spec: ApplicationSpecification) -> None: 45 | signer = AccountTransactionSigner(base64.b64encode(b"a" * 64).decode("utf-8")) 46 | client = ApplicationClient(algod_client, app_spec) 47 | 48 | new_client = client.prepare(signer=signer) 49 | assert isinstance(new_client.signer, AccountTransactionSigner) 50 | assert new_client.signer.private_key == signer.private_key 51 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_opt_in.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | import pytest 4 | 5 | from algokit_utils import ( 6 | Account, 7 | ApplicationClient, 8 | ApplicationSpecification, 9 | LogicError, 10 | ) 11 | from legacy_v2_tests.conftest import check_output_stability, is_opted_in 12 | 13 | if TYPE_CHECKING: 14 | from algosdk.v2client.algod import AlgodClient 15 | from algosdk.v2client.indexer import IndexerClient 16 | 17 | 18 | @pytest.fixture 19 | def client_fixture( 20 | algod_client: "AlgodClient", 21 | indexer_client: "IndexerClient", 22 | app_spec: ApplicationSpecification, 23 | funded_account: Account, 24 | ) -> ApplicationClient: 25 | client = ApplicationClient(algod_client, app_spec, creator=funded_account, indexer_client=indexer_client) 26 | client.create("create") 27 | return client 28 | 29 | 30 | def test_abi_opt_in(client_fixture: ApplicationClient) -> None: 31 | opt_in_response = client_fixture.opt_in("opt_in") 32 | 33 | assert opt_in_response.tx_id 34 | assert client_fixture.call("get_last").return_value == "Opt In ABI" 35 | assert is_opted_in(client_fixture) 36 | 37 | 38 | def test_bare_opt_in(client_fixture: ApplicationClient) -> None: 39 | opt_in_response = client_fixture.opt_in(call_abi_method=False) 40 | 41 | assert opt_in_response.tx_id 42 | assert client_fixture.call("get_last").return_value == "Opt In Bare" 43 | assert is_opted_in(client_fixture) 44 | 45 | 46 | def test_abi_opt_in_args(client_fixture: ApplicationClient) -> None: 47 | update_response = client_fixture.opt_in("opt_in_args", check="Yes") 48 | 49 | assert update_response.tx_id 50 | assert client_fixture.call("get_last").return_value == "Opt In Args" 51 | assert is_opted_in(client_fixture) 52 | 53 | 54 | def test_abi_update_args_fails(client_fixture: ApplicationClient) -> None: 55 | with pytest.raises(LogicError) as ex: 56 | client_fixture.opt_in("opt_in_args", check="No") 57 | 58 | check_output_stability(str(ex.value).replace(ex.value.transaction_id, "{txn}")) 59 | assert not is_opted_in(client_fixture) 60 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_clear_state.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from typing import TYPE_CHECKING 3 | 4 | import pytest 5 | 6 | from algokit_utils import ( 7 | Account, 8 | ApplicationClient, 9 | ApplicationSpecification, 10 | ) 11 | from legacy_v2_tests.conftest import is_opted_in 12 | 13 | if TYPE_CHECKING: 14 | from algosdk.v2client.algod import AlgodClient 15 | from algosdk.v2client.indexer import IndexerClient 16 | 17 | 18 | @pytest.fixture 19 | def client_fixture( 20 | algod_client: "AlgodClient", 21 | indexer_client: "IndexerClient", 22 | app_spec: ApplicationSpecification, 23 | funded_account: Account, 24 | ) -> ApplicationClient: 25 | client = ApplicationClient(algod_client, app_spec, creator=funded_account, indexer_client=indexer_client) 26 | create_response = client.create("create") 27 | assert create_response.tx_id 28 | opt_in_response = client.opt_in("opt_in") 29 | assert opt_in_response.tx_id 30 | return client 31 | 32 | 33 | def test_clear_state(client_fixture: ApplicationClient) -> None: 34 | assert is_opted_in(client_fixture) 35 | 36 | close_out_response = client_fixture.clear_state() 37 | assert close_out_response.tx_id 38 | 39 | assert not is_opted_in(client_fixture) 40 | 41 | 42 | def test_clear_state_app_already_deleted(client_fixture: ApplicationClient) -> None: 43 | assert is_opted_in(client_fixture) 44 | 45 | client_fixture.delete("delete") 46 | assert is_opted_in(client_fixture) 47 | 48 | close_out_response = client_fixture.clear_state() 49 | assert close_out_response.tx_id 50 | 51 | assert not is_opted_in(client_fixture) 52 | 53 | 54 | def test_clear_state_app_args(client_fixture: ApplicationClient) -> None: 55 | assert is_opted_in(client_fixture) 56 | app_args = [b"test", b"data"] 57 | 58 | close_out_response = client_fixture.clear_state(app_args=app_args) 59 | assert close_out_response.tx_id 60 | 61 | tx_info = client_fixture.algod_client.pending_transaction_info(close_out_response.tx_id) 62 | assert isinstance(tx_info, dict) 63 | assert [base64.b64decode(x) for x in tx_info["txn"]["txn"]["apaa"]] == app_args 64 | -------------------------------------------------------------------------------- /src/algokit_utils/models/state.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from collections.abc import Mapping 3 | from dataclasses import dataclass 4 | from enum import IntEnum 5 | from typing import TypeAlias 6 | 7 | from algosdk.atomic_transaction_composer import AccountTransactionSigner 8 | from algosdk.box_reference import BoxReference as AlgosdkBoxReference 9 | 10 | __all__ = [ 11 | "BoxIdentifier", 12 | "BoxName", 13 | "BoxReference", 14 | "BoxValue", 15 | "DataTypeFlag", 16 | "TealTemplateParams", 17 | ] 18 | 19 | 20 | @dataclass(kw_only=True, frozen=True) 21 | class BoxName: 22 | """The name of the box""" 23 | 24 | name: str 25 | """The name of the box as a string. 26 | If the name can't be decoded from UTF-8, the string representation of the bytes is returned instead.""" 27 | name_raw: bytes 28 | """The name of the box as raw bytes""" 29 | name_base64: str 30 | """The name of the box as a base64 encoded string""" 31 | 32 | 33 | @dataclass(kw_only=True, frozen=True) 34 | class BoxValue: 35 | """The value of the box""" 36 | 37 | name: BoxName 38 | """The name of the box""" 39 | value: bytes 40 | """The value of the box as raw bytes""" 41 | 42 | 43 | class DataTypeFlag(IntEnum): 44 | BYTES = 1 45 | UINT = 2 46 | 47 | 48 | TealTemplateParams: TypeAlias = Mapping[str, str | int | bytes] | dict[str, str | int | bytes] 49 | 50 | 51 | BoxIdentifier: TypeAlias = str | bytes | AccountTransactionSigner 52 | 53 | 54 | class BoxReference(AlgosdkBoxReference): 55 | def __init__(self, app_id: int, name: bytes | str): 56 | super().__init__(app_index=app_id, name=self._b64_decode(name)) 57 | 58 | def __eq__(self, other: object) -> bool: 59 | if isinstance(other, (BoxReference | AlgosdkBoxReference)): 60 | return self.app_index == other.app_index and self.name == other.name 61 | return False 62 | 63 | def _b64_decode(self, value: str | bytes) -> bytes: 64 | if isinstance(value, str): 65 | try: 66 | return base64.b64decode(value) 67 | except Exception: 68 | return value.encode("utf-8") 69 | return value 70 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_deploy.py: -------------------------------------------------------------------------------- 1 | from algokit_utils import ( 2 | replace_template_variables, 3 | ) 4 | from algokit_utils._legacy_v2.deploy import strip_comments 5 | from legacy_v2_tests.conftest import check_output_stability 6 | 7 | 8 | def test_template_substitution() -> None: 9 | program = """ 10 | test TMPL_INT // TMPL_INT 11 | test TMPL_INT 12 | no change 13 | test TMPL_STR // TMPL_STR 14 | TMPL_STR 15 | TMPL_STR // TMPL_INT 16 | TMPL_STR // foo // 17 | TMPL_STR // bar 18 | test "TMPL_STR" // not replaced 19 | test "TMPL_STRING" // not replaced 20 | test TMPL_STRING // not replaced 21 | test TMPL_STRI // not replaced 22 | test TMPL_STR TMPL_INT TMPL_INT TMPL_STR // TMPL_STR TMPL_INT TMPL_INT TMPL_STR 23 | test TMPL_INT TMPL_STR TMPL_STRING "TMPL_INT TMPL_STR TMPL_STRING" //TMPL_INT TMPL_STR TMPL_STRING 24 | test TMPL_INT TMPL_INT TMPL_STRING TMPL_STRING TMPL_STRING TMPL_INT TMPL_STRING //keep 25 | TMPL_STR TMPL_STR TMPL_STR 26 | TMPL_STRING 27 | test NOTTMPL_STR // not replaced 28 | NOTTMPL_STR // not replaced 29 | TMPL_STR // replaced 30 | """ 31 | result = replace_template_variables(program, {"INT": 123, "STR": "ABC"}) 32 | check_output_stability(result) 33 | 34 | 35 | def test_comment_stripping() -> None: 36 | program = r""" 37 | //comment 38 | op arg //comment 39 | op "arg" //comment 40 | op "//" //comment 41 | op " //comment " //comment 42 | op "\" //" //comment 43 | op "// \" //" //comment 44 | op "" //comment 45 | // 46 | op 123 47 | op 123 // something 48 | op "" // more comments 49 | op "//" //op "//" 50 | op "//" 51 | pushbytes base64(//8=) 52 | pushbytes b64(//8=) 53 | 54 | pushbytes base64(//8=) // pushbytes base64(//8=) 55 | pushbytes b64(//8=) // pushbytes b64(//8=) 56 | pushbytes "base64(//8=)" // pushbytes "base64(//8=)" 57 | pushbytes "b64(//8=)" // pushbytes "b64(//8=)" 58 | 59 | pushbytes base64 //8= 60 | pushbytes b64 //8= 61 | 62 | pushbytes base64 //8= // pushbytes base64 //8= 63 | pushbytes b64 //8= // pushbytes b64 //8= 64 | pushbytes "base64 //8=" // pushbytes "base64 //8=" 65 | pushbytes "b64 //8=" // pushbytes "b64 //8=" 66 | 67 | """ 68 | result = strip_comments(program) 69 | check_output_stability(result) 70 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_resolve.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from algokit_utils import ( 4 | Account, 5 | ApplicationClient, 6 | DefaultArgumentDict, 7 | ) 8 | from legacy_v2_tests.conftest import read_spec 9 | 10 | if TYPE_CHECKING: 11 | from algosdk.v2client.algod import AlgodClient 12 | 13 | 14 | def test_resolve(algod_client: "AlgodClient", creator: Account) -> None: 15 | app_spec = read_spec("app_resolve.json") 16 | client_fixture = ApplicationClient(algod_client, app_spec, signer=creator) 17 | client_fixture.create() 18 | client_fixture.opt_in() 19 | 20 | int_default_argument: DefaultArgumentDict = {"source": "constant", "data": 1} 21 | assert client_fixture.resolve(int_default_argument) == 1 22 | 23 | string_default_argument: DefaultArgumentDict = {"source": "constant", "data": "stringy"} 24 | assert client_fixture.resolve(string_default_argument) == "stringy" 25 | 26 | global_state_int_default_argument: DefaultArgumentDict = { 27 | "source": "global-state", 28 | "data": "global_state_val_int", 29 | } 30 | assert client_fixture.resolve(global_state_int_default_argument) == 1 31 | 32 | global_state_byte_default_argument: DefaultArgumentDict = { 33 | "source": "global-state", 34 | "data": "global_state_val_byte", 35 | } 36 | assert client_fixture.resolve(global_state_byte_default_argument) == b"test" 37 | 38 | local_state_int_default_argument: DefaultArgumentDict = { 39 | "source": "local-state", 40 | "data": "acct_state_val_int", 41 | } 42 | acct_state_val_int_value = 2 # defined in TEAL 43 | assert client_fixture.resolve(local_state_int_default_argument) == acct_state_val_int_value 44 | 45 | local_state_byte_default_argument: DefaultArgumentDict = { 46 | "source": "local-state", 47 | "data": "acct_state_val_byte", 48 | } 49 | assert client_fixture.resolve(local_state_byte_default_argument) == b"local-test" 50 | 51 | method_default_argument: DefaultArgumentDict = { 52 | "source": "abi-method", 53 | "data": {"name": "dummy", "args": [], "returns": {"type": "string"}}, 54 | } 55 | assert client_fixture.resolve(method_default_argument) == "deadbeef" 56 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_deploy.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | import pytest 4 | 5 | from algokit_utils import ( 6 | ABICreateCallArgs, 7 | Account, 8 | ApplicationClient, 9 | ApplicationSpecification, 10 | TransferParameters, 11 | transfer, 12 | ) 13 | from legacy_v2_tests.conftest import get_unique_name, read_spec 14 | 15 | if TYPE_CHECKING: 16 | from algosdk.v2client.algod import AlgodClient 17 | from algosdk.v2client.indexer import IndexerClient 18 | 19 | 20 | @pytest.fixture 21 | def client_fixture( 22 | algod_client: "AlgodClient", 23 | indexer_client: "IndexerClient", 24 | funded_account: Account, 25 | ) -> ApplicationClient: 26 | app_spec = read_spec("app_client_test.json", deletable=True, updatable=True, template_values={"VERSION": 1}) 27 | return ApplicationClient( 28 | algod_client, app_spec, creator=funded_account, indexer_client=indexer_client, app_name=get_unique_name() 29 | ) 30 | 31 | 32 | def test_deploy_with_create(client_fixture: ApplicationClient, creator: Account) -> None: 33 | client_fixture.deploy( 34 | "v1", 35 | create_args=ABICreateCallArgs( 36 | method="create", 37 | ), 38 | ) 39 | 40 | transfer( 41 | client_fixture.algod_client, 42 | TransferParameters(from_account=creator, to_address=client_fixture.app_address, micro_algos=100_000), 43 | ) 44 | 45 | assert client_fixture.call("hello", name="test").return_value == "Hello ABI, test" 46 | 47 | 48 | def test_deploy_with_create_args(client_fixture: ApplicationClient, app_spec: ApplicationSpecification) -> None: 49 | create_args = next(m for m in app_spec.contract.methods if m.name == "create_args") 50 | client_fixture.deploy("v1", create_args=ABICreateCallArgs(method=create_args, args={"greeting": "deployed"})) 51 | 52 | assert client_fixture.call("hello", name="test").return_value == "deployed, test" 53 | 54 | 55 | def test_deploy_with_bare_create(client_fixture: ApplicationClient) -> None: 56 | client_fixture.deploy( 57 | "v1", 58 | create_args=ABICreateCallArgs( 59 | method=False, 60 | ), 61 | ) 62 | 63 | assert client_fixture.call("hello", name="test").return_value == "Hello Bare, test" 64 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/applications/enums/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.applications.enums 2 | 3 | ## Classes 4 | 5 | | [`OnSchemaBreak`](#algokit_utils.applications.enums.OnSchemaBreak) | Action to take if an Application's schema has breaking changes | 6 | |------------------------------------------------------------------------------|------------------------------------------------------------------| 7 | | [`OnUpdate`](#algokit_utils.applications.enums.OnUpdate) | Action to take if an Application has been updated | 8 | | [`OperationPerformed`](#algokit_utils.applications.enums.OperationPerformed) | Describes the actions taken during deployment | 9 | 10 | ## Module Contents 11 | 12 | ### *class* algokit_utils.applications.enums.OnSchemaBreak(\*args, \*\*kwds) 13 | 14 | Bases: `enum.Enum` 15 | 16 | Action to take if an Application’s schema has breaking changes 17 | 18 | #### Fail *= 0* 19 | 20 | Fail the deployment 21 | 22 | #### ReplaceApp *= 2* 23 | 24 | Create a new Application and delete the old Application in a single transaction 25 | 26 | #### AppendApp *= 3* 27 | 28 | Create a new Application 29 | 30 | ### *class* algokit_utils.applications.enums.OnUpdate(\*args, \*\*kwds) 31 | 32 | Bases: `enum.Enum` 33 | 34 | Action to take if an Application has been updated 35 | 36 | #### Fail *= 0* 37 | 38 | Fail the deployment 39 | 40 | #### UpdateApp *= 1* 41 | 42 | Update the Application with the new approval and clear programs 43 | 44 | #### ReplaceApp *= 2* 45 | 46 | Create a new Application and delete the old Application in a single transaction 47 | 48 | #### AppendApp *= 3* 49 | 50 | Create a new application 51 | 52 | ### *class* algokit_utils.applications.enums.OperationPerformed(\*args, \*\*kwds) 53 | 54 | Bases: `enum.Enum` 55 | 56 | Describes the actions taken during deployment 57 | 58 | #### Nothing *= 0* 59 | 60 | An existing Application was found 61 | 62 | #### Create *= 1* 63 | 64 | No existing Application was found, created a new Application 65 | 66 | #### Update *= 2* 67 | 68 | An existing Application was found, but was out of date, updated to latest version 69 | 70 | #### Replace *= 3* 71 | 72 | An existing Application was found, but was out of date, created a new Application and deleted the original 73 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_close_out.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | import pytest 4 | 5 | from algokit_utils import ( 6 | Account, 7 | ApplicationClient, 8 | ApplicationSpecification, 9 | LogicError, 10 | ) 11 | from legacy_v2_tests.conftest import check_output_stability, is_opted_in 12 | 13 | if TYPE_CHECKING: 14 | from algosdk.v2client.algod import AlgodClient 15 | from algosdk.v2client.indexer import IndexerClient 16 | 17 | 18 | @pytest.fixture 19 | def client_fixture( 20 | algod_client: "AlgodClient", 21 | indexer_client: "IndexerClient", 22 | app_spec: ApplicationSpecification, 23 | funded_account: Account, 24 | ) -> ApplicationClient: 25 | client = ApplicationClient(algod_client, app_spec, creator=funded_account, indexer_client=indexer_client) 26 | create_response = client.create("create") 27 | assert create_response.tx_id 28 | opt_in_response = client.opt_in("opt_in") 29 | assert opt_in_response.tx_id 30 | return client 31 | 32 | 33 | def test_abi_close_out(client_fixture: ApplicationClient) -> None: 34 | assert is_opted_in(client_fixture) 35 | 36 | close_out_response = client_fixture.close_out("close_out") 37 | assert close_out_response.tx_id 38 | 39 | assert not is_opted_in(client_fixture) 40 | 41 | 42 | def test_bare_close_out(client_fixture: ApplicationClient) -> None: 43 | assert is_opted_in(client_fixture) 44 | 45 | close_out_response = client_fixture.close_out(call_abi_method=False) 46 | assert close_out_response.tx_id 47 | 48 | assert not is_opted_in(client_fixture) 49 | 50 | 51 | def test_abi_close_out_args(client_fixture: ApplicationClient) -> None: 52 | assert is_opted_in(client_fixture) 53 | 54 | close_out_response = client_fixture.close_out("close_out_args", check="Yes") 55 | assert close_out_response.tx_id 56 | 57 | assert not is_opted_in(client_fixture) 58 | 59 | 60 | def test_abi_close_out_args_fails(client_fixture: ApplicationClient) -> None: 61 | assert is_opted_in(client_fixture) 62 | 63 | with pytest.raises(LogicError) as ex: 64 | client_fixture.close_out("close_out_args", check="No") 65 | 66 | check_output_stability(str(ex.value).replace(ex.value.transaction_id, "{txn}")) 67 | 68 | assert is_opted_in(client_fixture) 69 | -------------------------------------------------------------------------------- /tests/artifacts/testing_app_puya/contract.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from algopy import Application, ARC4Contract, Box, BoxMap, Bytes, arc4, op 4 | 5 | 6 | class DummyStruct(arc4.Struct): 7 | name: arc4.String 8 | id: arc4.UInt64 9 | 10 | 11 | class External(ARC4Contract): 12 | def __init__(self) -> None: 13 | self.box = Box(Bytes) 14 | 15 | @arc4.abimethod 16 | def set_box(self) -> None: 17 | self.box.value = Bytes(b"foo") 18 | 19 | 20 | class TestPuyaBoxes(ARC4Contract): 21 | def __init__(self) -> None: 22 | self.box_bytes = BoxMap(arc4.String, Bytes) 23 | self.box_bytes2 = BoxMap(Bytes, Bytes) 24 | self.box_str = BoxMap(arc4.String, arc4.String) 25 | self.box_int = BoxMap(arc4.String, arc4.UInt32) 26 | self.box_int512 = BoxMap(arc4.String, arc4.UInt512) 27 | self.box_static = BoxMap(arc4.String, arc4.StaticArray[arc4.Byte, Literal[4]]) 28 | self.external = Application(0) 29 | 30 | @arc4.abimethod 31 | def set_box_bytes(self, name: arc4.String, value: Bytes) -> None: 32 | self.box_bytes[name] = value 33 | 34 | @arc4.abimethod 35 | def set_box_str(self, name: arc4.String, value: arc4.String) -> None: 36 | self.box_str[name] = value 37 | 38 | @arc4.abimethod 39 | def set_box_int(self, name: arc4.String, value: arc4.UInt32) -> None: 40 | self.box_int[name] = value 41 | 42 | @arc4.abimethod 43 | def set_box_int512(self, name: arc4.String, value: arc4.UInt512) -> None: 44 | self.box_int512[name] = value 45 | 46 | @arc4.abimethod 47 | def set_box_static(self, name: arc4.String, value: arc4.StaticArray[arc4.Byte, Literal[4]]) -> None: 48 | self.box_static[name] = value.copy() 49 | 50 | @arc4.abimethod() 51 | def set_struct(self, name: arc4.String, value: DummyStruct) -> None: 52 | assert name.bytes == value.name.bytes, "Name must match id of struct" 53 | op.Box.put(name.bytes, value.bytes) 54 | 55 | @arc4.abimethod 56 | def bootstrap_external_app(self) -> Application: 57 | self.external = arc4.arc4_create(External).created_app 58 | return self.external 59 | 60 | @arc4.abimethod 61 | def set_external_box(self) -> None: 62 | arc4.abi_call( 63 | External.set_box, 64 | app_id=self.external, 65 | ) 66 | -------------------------------------------------------------------------------- /tests/artifacts/inner-fee/contract.py: -------------------------------------------------------------------------------- 1 | from algopy import ( 2 | ARC4Contract, 3 | BigUInt, 4 | Global, 5 | UInt64, 6 | arc4, 7 | ensure_budget, 8 | itxn, 9 | op, 10 | urange, 11 | ) 12 | 13 | 14 | class InnerFeeContract(ARC4Contract): 15 | @arc4.abimethod 16 | def burn_ops(self, op_budget: UInt64) -> None: 17 | # Uses approx 60 op budget per iteration 18 | count = op_budget // 60 19 | ensure_budget(op_budget) 20 | for i in urange(count): 21 | sqrt = op.bsqrt(BigUInt(i)) 22 | assert sqrt >= 0 # Prevent optimiser removing the sqrt 23 | 24 | @arc4.abimethod(readonly=True) 25 | def burn_ops_readonly(self, op_budget: UInt64) -> None: 26 | self.burn_ops(op_budget) 27 | 28 | @arc4.abimethod 29 | def no_op(self) -> None: 30 | pass 31 | 32 | @arc4.abimethod 33 | def send_x_inners_with_fees(self, app_id: UInt64, fees: arc4.DynamicArray[arc4.UInt64]) -> None: 34 | for fee in fees: 35 | arc4.abi_call("no_op", app_id=app_id, fee=fee.native) 36 | 37 | @arc4.abimethod 38 | def send_inners_with_fees( 39 | self, 40 | app_id_1: UInt64, 41 | app_id_2: UInt64, 42 | fees: arc4.Tuple[arc4.UInt64, arc4.UInt64, arc4.UInt64, arc4.UInt64, arc4.DynamicArray[arc4.UInt64]], 43 | ) -> None: 44 | arc4.abi_call("no_op", app_id=app_id_1, fee=fees[0].native) 45 | arc4.abi_call("no_op", app_id=app_id_1, fee=fees[1].native) 46 | itxn.Payment(amount=0, receiver=Global.current_application_address, fee=fees[2].native).submit() 47 | arc4.abi_call("send_x_inners_with_fees", app_id_2, fees[4], app_id=app_id_1, fee=fees[3].native) 48 | 49 | @arc4.abimethod 50 | def send_inners_with_fees_2( 51 | self, 52 | app_id_1: UInt64, 53 | app_id_2: UInt64, 54 | fees: arc4.Tuple[ 55 | arc4.UInt64, 56 | arc4.UInt64, 57 | arc4.DynamicArray[arc4.UInt64], 58 | arc4.UInt64, 59 | arc4.UInt64, 60 | arc4.DynamicArray[arc4.UInt64], 61 | ], 62 | ) -> None: 63 | arc4.abi_call("no_op", app_id=app_id_1, fee=fees[0].native) 64 | arc4.abi_call("send_x_inners_with_fees", app_id_2, fees[2], app_id=app_id_1, fee=fees[1].native) 65 | arc4.abi_call("no_op", app_id=app_id_1, fee=fees[3].native) 66 | arc4.abi_call("send_x_inners_with_fees", app_id_2, fees[5], app_id=app_id_1, fee=fees[4].native) 67 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/errors/logic_error/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.errors.logic_error 2 | 3 | ## Exceptions 4 | 5 | | [`LogicError`](#algokit_utils.errors.logic_error.LogicError) | Common base class for all non-exit exceptions. | 6 | |----------------------------------------------------------------|--------------------------------------------------| 7 | 8 | ## Classes 9 | 10 | | [`LogicErrorData`](#algokit_utils.errors.logic_error.LogicErrorData) | dict() -> new empty dictionary | 11 | |------------------------------------------------------------------------|----------------------------------| 12 | 13 | ## Functions 14 | 15 | | [`parse_logic_error`](#algokit_utils.errors.logic_error.parse_logic_error)(→ LogicErrorData | None) | | 16 | |-------------------------------------------------------------------------------------------------------|----| 17 | 18 | ## Module Contents 19 | 20 | ### *class* algokit_utils.errors.logic_error.LogicErrorData 21 | 22 | Bases: `TypedDict` 23 | 24 | dict() -> new empty dictionary 25 | dict(mapping) -> new dictionary initialized from a mapping object’s 26 | 27 | > (key, value) pairs 28 | 29 | dict(iterable) -> new dictionary initialized as if via: 30 | : d = {} 31 | for k, v in iterable: 32 |
33 | > d[k] = v 34 | 35 | dict( 36 | 37 | ``` 38 | ** 39 | ``` 40 | 41 | kwargs) -> new dictionary initialized with the name=value pairs 42 | : in the keyword argument list. For example: dict(one=1, two=2) 43 | 44 | #### transaction_id *: str* 45 | 46 | #### message *: str* 47 | 48 | #### pc *: int* 49 | 50 | ### algokit_utils.errors.logic_error.parse_logic_error(error_str: str) → [LogicErrorData](#algokit_utils.errors.logic_error.LogicErrorData) | None 51 | 52 | ### *exception* algokit_utils.errors.logic_error.LogicError(\*, logic_error_str: str, program: str, source_map: AlgoSourceMap | None, transaction_id: str, message: str, pc: int, logic_error: Exception | None = None, traces: list[[algokit_utils.models.simulate.SimulationTrace](../../models/simulate/index.md#algokit_utils.models.simulate.SimulationTrace)] | None = None, get_line_for_pc: collections.abc.Callable[[int], int | None] | None = None) 53 | 54 | Bases: `Exception` 55 | 56 | Common base class for all non-exit exceptions. 57 | 58 | #### logic_error *= None* 59 | 60 | #### logic_error_str 61 | 62 | #### source_map 63 | 64 | #### lines 65 | 66 | #### transaction_id 67 | 68 | #### message 69 | 70 | #### pc 71 | 72 | #### traces *= None* 73 | 74 | #### line_no 75 | 76 | #### trace(lines: int = 5) → str 77 | -------------------------------------------------------------------------------- /legacy_v2_tests/test_app_client_signer_sender.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import contextlib 3 | from typing import TYPE_CHECKING, Any 4 | 5 | import pytest 6 | from algosdk.atomic_transaction_composer import AccountTransactionSigner, TransactionSigner 7 | 8 | from algokit_utils import ( 9 | ApplicationClient, 10 | ApplicationSpecification, 11 | get_sender_from_signer, 12 | ) 13 | 14 | if TYPE_CHECKING: 15 | from algosdk import transaction 16 | from algosdk.transaction import GenericSignedTransaction 17 | from algosdk.v2client.algod import AlgodClient 18 | 19 | 20 | class CustomSigner(TransactionSigner): 21 | def sign_transactions( 22 | self, txn_group: list["transaction.Transaction"], indexes: list[int] 23 | ) -> list["GenericSignedTransaction"]: 24 | raise NotImplementedError 25 | 26 | 27 | fake_key = base64.b64encode(b"a" * 64).decode("utf8") 28 | 29 | 30 | @pytest.mark.parametrize("override_sender", ["override_sender", None]) 31 | @pytest.mark.parametrize("override_signer", [CustomSigner(), AccountTransactionSigner(fake_key), None]) 32 | @pytest.mark.parametrize("default_sender", ["default_sender", None]) 33 | @pytest.mark.parametrize("default_signer", [CustomSigner(), AccountTransactionSigner(fake_key), None]) 34 | def test_resolve_signer_sender( 35 | *, 36 | algod_client: "AlgodClient", 37 | app_spec: ApplicationSpecification, 38 | default_signer: TransactionSigner | None, 39 | default_sender: str | None, 40 | override_signer: TransactionSigner | None, 41 | override_sender: str | None, 42 | ) -> None: 43 | """Regression test against unexpected changes to signer/sender resolution in ApplicationClient""" 44 | app_client = ApplicationClient(algod_client, app_spec, signer=default_signer, sender=default_sender) 45 | 46 | expected_signer = override_signer or default_signer 47 | expected_sender = ( 48 | override_sender 49 | or get_sender_from_signer(override_signer) 50 | or default_sender 51 | or get_sender_from_signer(default_signer) 52 | ) 53 | 54 | ctx: Any 55 | if expected_signer is None: 56 | ctx = pytest.raises(ValueError, match="No signer provided") 57 | elif expected_sender is None: 58 | ctx = pytest.raises(ValueError, match="No sender provided") 59 | else: 60 | ctx = contextlib.nullcontext() 61 | 62 | with ctx: 63 | signer, sender = app_client.resolve_signer_sender(override_signer, override_sender) 64 | 65 | assert signer == expected_signer 66 | assert sender == expected_sender 67 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/models/state/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.models.state 2 | 3 | ## Attributes 4 | 5 | | [`TealTemplateParams`](#algokit_utils.models.state.TealTemplateParams) | | 6 | |--------------------------------------------------------------------------|----| 7 | | [`BoxIdentifier`](#algokit_utils.models.state.BoxIdentifier) | | 8 | 9 | ## Classes 10 | 11 | | [`BoxName`](#algokit_utils.models.state.BoxName) | The name of the box | 12 | |------------------------------------------------------------|-----------------------------------------------------------------------| 13 | | [`BoxValue`](#algokit_utils.models.state.BoxValue) | The value of the box | 14 | | [`DataTypeFlag`](#algokit_utils.models.state.DataTypeFlag) | Enum where members are also (and must be) ints | 15 | | [`BoxReference`](#algokit_utils.models.state.BoxReference) | Represents a box reference with a foreign app index and the box name. | 16 | 17 | ## Module Contents 18 | 19 | ### *class* algokit_utils.models.state.BoxName 20 | 21 | The name of the box 22 | 23 | #### name *: str* 24 | 25 | The name of the box as a string. 26 | If the name can’t be decoded from UTF-8, the string representation of the bytes is returned instead. 27 | 28 | #### name_raw *: bytes* 29 | 30 | The name of the box as raw bytes 31 | 32 | #### name_base64 *: str* 33 | 34 | The name of the box as a base64 encoded string 35 | 36 | ### *class* algokit_utils.models.state.BoxValue 37 | 38 | The value of the box 39 | 40 | #### name *: [BoxName](#algokit_utils.models.state.BoxName)* 41 | 42 | The name of the box 43 | 44 | #### value *: bytes* 45 | 46 | The value of the box as raw bytes 47 | 48 | ### *class* algokit_utils.models.state.DataTypeFlag 49 | 50 | Bases: `enum.IntEnum` 51 | 52 | Enum where members are also (and must be) ints 53 | 54 | #### BYTES *= 1* 55 | 56 | #### UINT *= 2* 57 | 58 | ### *type* algokit_utils.models.state.TealTemplateParams *= Mapping[str, str | int | bytes] | dict[str, str | int | bytes]* 59 | 60 | ### *type* algokit_utils.models.state.BoxIdentifier *= str | bytes | AccountTransactionSigner* 61 | 62 | ### *class* algokit_utils.models.state.BoxReference(app_id: int, name: bytes | str) 63 | 64 | Bases: `algosdk.box_reference.BoxReference` 65 | 66 | Represents a box reference with a foreign app index and the box name. 67 | 68 | Args: 69 | : app_index (int): index of the application in the foreign app array 70 | name (bytes): key for the box in bytes 71 | -------------------------------------------------------------------------------- /src/algokit_utils/models/application.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import TYPE_CHECKING 3 | 4 | import algosdk 5 | from algosdk.source_map import SourceMap 6 | 7 | if TYPE_CHECKING: 8 | pass 9 | 10 | __all__ = [ 11 | "AppCompilationResult", 12 | "AppInformation", 13 | "AppSourceMaps", 14 | "AppState", 15 | "CompiledTeal", 16 | ] 17 | 18 | 19 | @dataclass(kw_only=True, frozen=True) 20 | class AppState: 21 | key_raw: bytes 22 | """The key of the state as raw bytes""" 23 | key_base64: str 24 | """The key of the state""" 25 | value_raw: bytes | None 26 | """The value of the state as raw bytes""" 27 | value_base64: str | None 28 | """The value of the state as base64 encoded string""" 29 | value: str | int 30 | """The value of the state as a string or integer""" 31 | 32 | 33 | @dataclass(kw_only=True, frozen=True) 34 | class AppInformation: 35 | app_id: int 36 | """The ID of the application""" 37 | app_address: str 38 | """The address of the application""" 39 | approval_program: bytes 40 | """The approval program""" 41 | clear_state_program: bytes 42 | """The clear state program""" 43 | creator: str 44 | """The creator of the application""" 45 | global_state: dict[str, AppState] 46 | """The global state of the application""" 47 | local_ints: int 48 | """The number of local ints""" 49 | local_byte_slices: int 50 | """The number of local byte slices""" 51 | global_ints: int 52 | """The number of global ints""" 53 | global_byte_slices: int 54 | """The number of global byte slices""" 55 | extra_program_pages: int | None 56 | """The number of extra program pages""" 57 | 58 | 59 | @dataclass(kw_only=True, frozen=True) 60 | class CompiledTeal: 61 | """The compiled teal code""" 62 | 63 | teal: str 64 | """The teal code""" 65 | compiled: str 66 | """The compiled teal code""" 67 | compiled_hash: str 68 | """The compiled hash""" 69 | compiled_base64_to_bytes: bytes 70 | """The compiled base64 to bytes""" 71 | source_map: algosdk.source_map.SourceMap | None 72 | 73 | 74 | @dataclass(kw_only=True, frozen=True) 75 | class AppCompilationResult: 76 | """The compiled teal code""" 77 | 78 | compiled_approval: CompiledTeal 79 | """The compiled approval program""" 80 | compiled_clear: CompiledTeal 81 | """The compiled clear state program""" 82 | 83 | 84 | @dataclass(kw_only=True, frozen=True) 85 | class AppSourceMaps: 86 | """The source maps for the application""" 87 | 88 | approval_source_map: SourceMap | None = None 89 | """The source map for the approval program""" 90 | clear_source_map: SourceMap | None = None 91 | """The source map for the clear state program""" 92 | -------------------------------------------------------------------------------- /docs/source/capabilities/amount.md: -------------------------------------------------------------------------------- 1 | # Algo amount handling 2 | 3 | Algo amount handling is one of the core capabilities provided by AlgoKit Utils. It allows you to reliably and tersely specify amounts of microAlgo and Algo and safely convert between them. 4 | 5 | Any AlgoKit Utils function that needs an Algo amount will take an `AlgoAmount` object, which ensures that there is never any confusion about what value is being passed around. Whenever an AlgoKit Utils function calls into an underlying algosdk function, or if you need to take an `AlgoAmount` and pass it into an underlying algosdk function (per the {ref}`modularity principle `) you can safely and explicitly convert to microAlgo or Algo. 6 | 7 | To see some usage examples check out the automated tests. Alternatively, you can see the reference documentation for `AlgoAmount`. 8 | 9 | ## `AlgoAmount` 10 | 11 | The `AlgoAmount` class provides a safe wrapper around an underlying amount of microAlgo where any value entering or existing the `AlgoAmount` class must be explicitly stated to be in microAlgo or Algo. This makes it much safer to handle Algo amounts rather than passing them around as raw numbers where it's easy to make a (potentially costly!) mistake and not perform a conversion when one is needed (or perform one when it shouldn't be!). 12 | 13 | To import the AlgoAmount class you can access it via: 14 | 15 | ```python 16 | from algokit_utils import AlgoAmount 17 | ``` 18 | 19 | ### Creating an `AlgoAmount` 20 | 21 | There are a few ways to create an `AlgoAmount`: 22 | 23 | - Algo 24 | - Constructor: `AlgoAmount(algo=10)` 25 | - Static helper: `AlgoAmount.from_algo(10)` 26 | - microAlgo 27 | - Constructor: `AlgoAmount(micro_algo=10_000)` 28 | - Static helper: `AlgoAmount.from_micro_algo(10_000)` 29 | 30 | ### Extracting a value from `AlgoAmount` 31 | 32 | The `AlgoAmount` class has properties to return Algo and microAlgo: 33 | 34 | - `amount.algo` - Returns the value in Algo as a python `Decimal` object 35 | - `amount.micro_algo` - Returns the value in microAlgo as an integer 36 | 37 | `AlgoAmount` will coerce to an integer automatically (in microAlgo) when using `int(amount)`, which allows you to use `AlgoAmount` objects in comparison operations such as `<` and `>=` etc. 38 | 39 | You can also call `str(amount)` or use an `AlgoAmount` directly in string interpolation to convert it to a nice user-facing formatted amount expressed in microAlgo. 40 | 41 | ### Additional Features 42 | 43 | The `AlgoAmount` class supports arithmetic operations: 44 | 45 | - Addition: `amount1 + amount2` 46 | - Subtraction: `amount1 - amount2` 47 | - Comparison operations: `<`, `<=`, `>`, `>=`, `==`, `!=` 48 | 49 | Example: 50 | 51 | ```python 52 | amount1 = AlgoAmount(algo=1) 53 | amount2 = AlgoAmount(micro_algo=500_000) 54 | total = amount1 + amount2 # Results in 1.5 Algo 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/markdown/capabilities/amount.md: -------------------------------------------------------------------------------- 1 | # Algo amount handling 2 | 3 | Algo amount handling is one of the core capabilities provided by AlgoKit Utils. It allows you to reliably and tersely specify amounts of microAlgo and Algo and safely convert between them. 4 | 5 | Any AlgoKit Utils function that needs an Algo amount will take an `AlgoAmount` object, which ensures that there is never any confusion about what value is being passed around. Whenever an AlgoKit Utils function calls into an underlying algosdk function, or if you need to take an `AlgoAmount` and pass it into an underlying algosdk function (per the [modularity principle](../index.md#core-principles)) you can safely and explicitly convert to microAlgo or Algo. 6 | 7 | To see some usage examples check out the automated tests. Alternatively, you can see the reference documentation for `AlgoAmount`. 8 | 9 | ## `AlgoAmount` 10 | 11 | The `AlgoAmount` class provides a safe wrapper around an underlying amount of microAlgo where any value entering or existing the `AlgoAmount` class must be explicitly stated to be in microAlgo or Algo. This makes it much safer to handle Algo amounts rather than passing them around as raw numbers where it’s easy to make a (potentially costly!) mistake and not perform a conversion when one is needed (or perform one when it shouldn’t be!). 12 | 13 | To import the AlgoAmount class you can access it via: 14 | 15 | ```python 16 | from algokit_utils import AlgoAmount 17 | ``` 18 | 19 | ### Creating an `AlgoAmount` 20 | 21 | There are a few ways to create an `AlgoAmount`: 22 | 23 | - Algo 24 | - Constructor: `AlgoAmount(algo=10)` 25 | - Static helper: `AlgoAmount.from_algo(10)` 26 | - microAlgo 27 | - Constructor: `AlgoAmount(micro_algo=10_000)` 28 | - Static helper: `AlgoAmount.from_micro_algo(10_000)` 29 | 30 | ### Extracting a value from `AlgoAmount` 31 | 32 | The `AlgoAmount` class has properties to return Algo and microAlgo: 33 | 34 | - `amount.algo` - Returns the value in Algo as a python `Decimal` object 35 | - `amount.micro_algo` - Returns the value in microAlgo as an integer 36 | 37 | `AlgoAmount` will coerce to an integer automatically (in microAlgo) when using `int(amount)`, which allows you to use `AlgoAmount` objects in comparison operations such as `<` and `>=` etc. 38 | 39 | You can also call `str(amount)` or use an `AlgoAmount` directly in string interpolation to convert it to a nice user-facing formatted amount expressed in microAlgo. 40 | 41 | ### Additional Features 42 | 43 | The `AlgoAmount` class supports arithmetic operations: 44 | 45 | - Addition: `amount1 + amount2` 46 | - Subtraction: `amount1 - amount2` 47 | - Comparison operations: `<`, `<=`, `>`, `>=`, `==`, `!=` 48 | 49 | Example: 50 | 51 | ```python 52 | amount1 = AlgoAmount(algo=1) 53 | amount2 = AlgoAmount(micro_algo=500_000) 54 | total = amount1 + amount2 # Results in 1.5 Algo 55 | ``` 56 | -------------------------------------------------------------------------------- /tests/applications/test_app_manager.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from algokit_utils.algorand import AlgorandClient 4 | from algokit_utils.applications.app_manager import AppManager 5 | from algokit_utils.models.account import SigningAccount 6 | from algokit_utils.models.amount import AlgoAmount 7 | from tests.conftest import check_output_stability 8 | 9 | 10 | @pytest.fixture 11 | def algorand() -> AlgorandClient: 12 | return AlgorandClient.default_localnet() 13 | 14 | 15 | @pytest.fixture 16 | def funded_account(algorand: AlgorandClient) -> SigningAccount: 17 | new_account = algorand.account.random() 18 | dispenser = algorand.account.localnet_dispenser() 19 | algorand.account.ensure_funded( 20 | new_account, dispenser, AlgoAmount.from_algo(100), min_funding_increment=AlgoAmount.from_algo(1) 21 | ) 22 | algorand.set_signer(sender=new_account.address, signer=new_account.signer) 23 | return new_account 24 | 25 | 26 | def test_template_substitution() -> None: 27 | program = """ 28 | test TMPL_INT // TMPL_INT 29 | test TMPL_INT 30 | no change 31 | test TMPL_STR // TMPL_STR 32 | TMPL_STR 33 | TMPL_STR // TMPL_INT 34 | TMPL_STR // foo // 35 | TMPL_STR // bar 36 | test "TMPL_STR" // not replaced 37 | test "TMPL_STRING" // not replaced 38 | test TMPL_STRING // not replaced 39 | test TMPL_STRI // not replaced 40 | test TMPL_STR TMPL_INT TMPL_INT TMPL_STR // TMPL_STR TMPL_INT TMPL_INT TMPL_STR 41 | test TMPL_INT TMPL_STR TMPL_STRING "TMPL_INT TMPL_STR TMPL_STRING" //TMPL_INT TMPL_STR TMPL_STRING 42 | test TMPL_INT TMPL_INT TMPL_STRING TMPL_STRING TMPL_STRING TMPL_INT TMPL_STRING //keep 43 | TMPL_STR TMPL_STR TMPL_STR 44 | TMPL_STRING 45 | test NOTTMPL_STR // not replaced 46 | NOTTMPL_STR // not replaced 47 | TMPL_STR // replaced 48 | """ 49 | result = AppManager.replace_template_variables(program, {"INT": 123, "STR": "ABC"}) 50 | check_output_stability(result) 51 | 52 | 53 | def test_comment_stripping() -> None: 54 | program = r""" 55 | //comment 56 | op arg //comment 57 | op "arg" //comment 58 | op "//" //comment 59 | op " //comment " //comment 60 | op "\" //" //comment 61 | op "// \" //" //comment 62 | op "" //comment 63 | // 64 | op 123 65 | op 123 // something 66 | op "" // more comments 67 | op "//" //op "//" 68 | op "//" 69 | pushbytes base64(//8=) 70 | pushbytes b64(//8=) 71 | 72 | pushbytes base64(//8=) // pushbytes base64(//8=) 73 | pushbytes b64(//8=) // pushbytes b64(//8=) 74 | pushbytes "base64(//8=)" // pushbytes "base64(//8=)" 75 | pushbytes "b64(//8=)" // pushbytes "b64(//8=)" 76 | 77 | pushbytes base64 //8= 78 | pushbytes b64 //8= 79 | 80 | pushbytes base64 //8= // pushbytes base64 //8= 81 | pushbytes b64 //8= // pushbytes b64 //8= 82 | pushbytes "base64 //8=" // pushbytes "base64 //8=" 83 | pushbytes "b64 //8=" // pushbytes "b64 //8=" 84 | 85 | """ 86 | result = AppManager.strip_teal_comments(program) 87 | check_output_stability(result) 88 | -------------------------------------------------------------------------------- /tests/artifacts/resource-packer/ExternalAppV8.arc32.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": { 3 | "dummy()void": { 4 | "call_config": { 5 | "no_op": "CALL" 6 | } 7 | }, 8 | "createApplication()void": { 9 | "call_config": { 10 | "no_op": "CREATE" 11 | } 12 | } 13 | }, 14 | "bare_call_config": { 15 | "no_op": "NEVER", 16 | "opt_in": "NEVER", 17 | "close_out": "NEVER", 18 | "update_application": "NEVER", 19 | "delete_application": "NEVER" 20 | }, 21 | "schema": { 22 | "local": { 23 | "declared": {}, 24 | "reserved": {} 25 | }, 26 | "global": { 27 | "declared": {}, 28 | "reserved": {} 29 | } 30 | }, 31 | "state": { 32 | "global": { 33 | "num_byte_slices": 0, 34 | "num_uints": 0 35 | }, 36 | "local": { 37 | "num_byte_slices": 0, 38 | "num_uints": 0 39 | } 40 | }, 41 | "source": { 42 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDkKCi8vIFRoaXMgVEVBTCB3YXMgZ2VuZXJhdGVkIGJ5IFRFQUxTY3JpcHQgdjAuNjMuMAovLyBodHRwczovL2dpdGh1Yi5jb20vYWxnb3JhbmRmb3VuZGF0aW9uL1RFQUxTY3JpcHQKCi8vIFRoaXMgY29udHJhY3QgaXMgY29tcGxpYW50IHdpdGggYW5kL29yIGltcGxlbWVudHMgdGhlIGZvbGxvd2luZyBBUkNzOiBbIEFSQzQgXQoKLy8gVGhlIGZvbGxvd2luZyB0ZW4gbGluZXMgb2YgVEVBTCBoYW5kbGUgaW5pdGlhbCBwcm9ncmFtIGZsb3cKLy8gVGhpcyBwYXR0ZXJuIGlzIHVzZWQgdG8gbWFrZSBpdCBlYXN5IGZvciBhbnlvbmUgdG8gcGFyc2UgdGhlIHN0YXJ0IG9mIHRoZSBwcm9ncmFtIGFuZCBkZXRlcm1pbmUgaWYgYSBzcGVjaWZpYyBhY3Rpb24gaXMgYWxsb3dlZAovLyBIZXJlLCBhY3Rpb24gcmVmZXJzIHRvIHRoZSBPbkNvbXBsZXRlIGluIGNvbWJpbmF0aW9uIHdpdGggd2hldGhlciB0aGUgYXBwIGlzIGJlaW5nIGNyZWF0ZWQgb3IgY2FsbGVkCi8vIEV2ZXJ5IHBvc3NpYmxlIGFjdGlvbiBmb3IgdGhpcyBjb250cmFjdCBpcyByZXByZXNlbnRlZCBpbiB0aGUgc3dpdGNoIHN0YXRlbWVudAovLyBJZiB0aGUgYWN0aW9uIGlzIG5vdCBpbXBsbWVudGVkIGluIHRoZSBjb250cmFjdCwgaXRzIHJlcHNlY3RpdmUgYnJhbmNoIHdpbGwgYmUgIk5PVF9JTVBMTUVOVEVEIiB3aGljaCBqdXN0IGNvbnRhaW5zICJlcnIiCnR4biBBcHBsaWNhdGlvbklECmludCAwCj4KaW50IDYKKgp0eG4gT25Db21wbGV0aW9uCisKc3dpdGNoIGNyZWF0ZV9Ob09wIE5PVF9JTVBMRU1FTlRFRCBOT1RfSU1QTEVNRU5URUQgTk9UX0lNUExFTUVOVEVEIE5PVF9JTVBMRU1FTlRFRCBOT1RfSU1QTEVNRU5URUQgY2FsbF9Ob09wCgpOT1RfSU1QTEVNRU5URUQ6CgllcnIKCi8vIGR1bW15KCl2b2lkCmFiaV9yb3V0ZV9kdW1teToKCS8vIGV4ZWN1dGUgZHVtbXkoKXZvaWQKCWNhbGxzdWIgZHVtbXkKCWludCAxCglyZXR1cm4KCmR1bW15OgoJcHJvdG8gMCAwCglyZXRzdWIKCmFiaV9yb3V0ZV9jcmVhdGVBcHBsaWNhdGlvbjoKCWludCAxCglyZXR1cm4KCmNyZWF0ZV9Ob09wOgoJbWV0aG9kICJjcmVhdGVBcHBsaWNhdGlvbigpdm9pZCIKCXR4bmEgQXBwbGljYXRpb25BcmdzIDAKCW1hdGNoIGFiaV9yb3V0ZV9jcmVhdGVBcHBsaWNhdGlvbgoJZXJyCgpjYWxsX05vT3A6CgltZXRob2QgImR1bW15KCl2b2lkIgoJdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAoJbWF0Y2ggYWJpX3JvdXRlX2R1bW15CgllcnI=", 43 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDk=" 44 | }, 45 | "contract": { 46 | "name": "ExternalAppV8", 47 | "desc": "", 48 | "methods": [ 49 | { 50 | "name": "dummy", 51 | "args": [], 52 | "desc": "", 53 | "returns": { 54 | "type": "void", 55 | "desc": "" 56 | } 57 | }, 58 | { 59 | "name": "createApplication", 60 | "desc": "", 61 | "returns": { 62 | "type": "void", 63 | "desc": "" 64 | }, 65 | "args": [] 66 | } 67 | ] 68 | } 69 | } -------------------------------------------------------------------------------- /legacy_v2_tests/test_dispenser_api_client.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from pytest_httpx import HTTPXMock 5 | 6 | from algokit_utils.dispenser_api import ( 7 | DISPENSER_ASSETS, 8 | DispenserApiConfig, 9 | DispenserAssetName, 10 | TestNetDispenserApiClient, 11 | ) 12 | 13 | 14 | class TestDispenserApiTestnetClient: 15 | def test_fund_account_with_algos_with_auth_token(self, httpx_mock: HTTPXMock) -> None: 16 | mock_response = {"txID": "dummy_tx_id", "amount": 1} 17 | httpx_mock.add_response( 18 | url=f"{DispenserApiConfig.BASE_URL}/fund/{DispenserAssetName.ALGO}", 19 | method="POST", 20 | json=mock_response, 21 | ) 22 | dispenser_client = TestNetDispenserApiClient(auth_token="dummy_auth_token") 23 | address = "dummy_address" 24 | amount = 1 25 | asset_id = DispenserAssetName.ALGO 26 | response = dispenser_client.fund(address, amount, asset_id) 27 | assert response.tx_id == "dummy_tx_id" 28 | assert response.amount == 1 29 | 30 | def test_register_refund_with_auth_token(self, httpx_mock: HTTPXMock) -> None: 31 | httpx_mock.add_response( 32 | url=f"{DispenserApiConfig.BASE_URL}/refund", 33 | method="POST", 34 | json={}, 35 | ) 36 | dispenser_client = TestNetDispenserApiClient(auth_token="dummy_auth_token") 37 | refund_txn_id = "dummy_txn_id" 38 | dispenser_client.refund(refund_txn_id) 39 | assert len(httpx_mock.get_requests()) == 1 40 | request = httpx_mock.get_requests()[0] 41 | assert request.method == "POST" 42 | assert request.url.path == "/refund" 43 | assert request.headers["Authorization"] == f"Bearer {dispenser_client.auth_token}" 44 | assert json.loads(request.read().decode()) == {"refundTransactionID": refund_txn_id} 45 | 46 | def test_limit_with_auth_token(self, httpx_mock: HTTPXMock) -> None: 47 | amount = 10000000 48 | mock_response = {"amount": amount} 49 | httpx_mock.add_response( 50 | url=f"{DispenserApiConfig.BASE_URL}/fund/{DISPENSER_ASSETS[DispenserAssetName.ALGO].asset_id}/limit", 51 | method="GET", 52 | json=mock_response, 53 | ) 54 | dispenser_client = TestNetDispenserApiClient("dummy_auth_token") 55 | address = "dummy_address" 56 | response = dispenser_client.get_limit(address) 57 | assert response.amount == amount 58 | 59 | def test_dispenser_api_init(self) -> None: 60 | with pytest.raises( 61 | Exception, 62 | match="Can't init AlgoKit TestNet Dispenser API client because neither environment variable", 63 | ): 64 | TestNetDispenserApiClient() 65 | 66 | def test_dispenser_api_init_with_ci_(self, monkeypatch: pytest.MonkeyPatch) -> None: 67 | monkeypatch.setenv("ALGOKIT_DISPENSER_ACCESS_TOKEN", "test_value") 68 | 69 | client = TestNetDispenserApiClient() 70 | assert client.auth_token == "test_value" 71 | 72 | def test_dispenser_api_init_with_ci_and_arg(self, monkeypatch: pytest.MonkeyPatch) -> None: 73 | monkeypatch.setenv("ALGOKIT_DISPENSER_ACCESS_TOKEN", "test_value") 74 | 75 | client = TestNetDispenserApiClient("test_value_2") 76 | assert client.auth_token == "test_value_2" 77 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/index.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | This page contains auto-generated API reference documentation [1](#f1). 4 | 5 | * [algokit_utils](algokit_utils/index.md) 6 | * [algokit_utils.accounts](algokit_utils/accounts/index.md) 7 | * [algokit_utils.accounts.account_manager](algokit_utils/accounts/account_manager/index.md) 8 | * [algokit_utils.accounts.kmd_account_manager](algokit_utils/accounts/kmd_account_manager/index.md) 9 | * [algokit_utils.algorand](algokit_utils/algorand/index.md) 10 | * [algokit_utils.applications](algokit_utils/applications/index.md) 11 | * [algokit_utils.applications.abi](algokit_utils/applications/abi/index.md) 12 | * [algokit_utils.applications.app_client](algokit_utils/applications/app_client/index.md) 13 | * [algokit_utils.applications.app_deployer](algokit_utils/applications/app_deployer/index.md) 14 | * [algokit_utils.applications.app_factory](algokit_utils/applications/app_factory/index.md) 15 | * [algokit_utils.applications.app_manager](algokit_utils/applications/app_manager/index.md) 16 | * [algokit_utils.applications.app_spec](algokit_utils/applications/app_spec/index.md) 17 | * [algokit_utils.applications.app_spec.arc32](algokit_utils/applications/app_spec/arc32/index.md) 18 | * [algokit_utils.applications.app_spec.arc56](algokit_utils/applications/app_spec/arc56/index.md) 19 | * [algokit_utils.applications.enums](algokit_utils/applications/enums/index.md) 20 | * [algokit_utils.assets](algokit_utils/assets/index.md) 21 | * [algokit_utils.assets.asset_manager](algokit_utils/assets/asset_manager/index.md) 22 | * [algokit_utils.clients](algokit_utils/clients/index.md) 23 | * [algokit_utils.clients.client_manager](algokit_utils/clients/client_manager/index.md) 24 | * [algokit_utils.clients.dispenser_api_client](algokit_utils/clients/dispenser_api_client/index.md) 25 | * [algokit_utils.config](algokit_utils/config/index.md) 26 | * [algokit_utils.errors](algokit_utils/errors/index.md) 27 | * [algokit_utils.errors.logic_error](algokit_utils/errors/logic_error/index.md) 28 | * [algokit_utils.models](algokit_utils/models/index.md) 29 | * [algokit_utils.models.account](algokit_utils/models/account/index.md) 30 | * [algokit_utils.models.amount](algokit_utils/models/amount/index.md) 31 | * [algokit_utils.models.application](algokit_utils/models/application/index.md) 32 | * [algokit_utils.models.network](algokit_utils/models/network/index.md) 33 | * [algokit_utils.models.simulate](algokit_utils/models/simulate/index.md) 34 | * [algokit_utils.models.state](algokit_utils/models/state/index.md) 35 | * [algokit_utils.models.transaction](algokit_utils/models/transaction/index.md) 36 | * [algokit_utils.protocols](algokit_utils/protocols/index.md) 37 | * [algokit_utils.protocols.account](algokit_utils/protocols/account/index.md) 38 | * [algokit_utils.protocols.typed_clients](algokit_utils/protocols/typed_clients/index.md) 39 | * [algokit_utils.transactions](algokit_utils/transactions/index.md) 40 | * [algokit_utils.transactions.transaction_composer](algokit_utils/transactions/transaction_composer/index.md) 41 | * [algokit_utils.transactions.transaction_creator](algokit_utils/transactions/transaction_creator/index.md) 42 | * [algokit_utils.transactions.transaction_sender](algokit_utils/transactions/transaction_sender/index.md) 43 | 44 | * **[1]** Created with [sphinx-autoapi](https://github.com/readthedocs/sphinx-autoapi) 45 | -------------------------------------------------------------------------------- /src/algokit_utils/models/transaction.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Literal, TypedDict, TypeVar 2 | 3 | import algosdk 4 | 5 | __all__ = [ 6 | "Arc2TransactionNote", 7 | "BaseArc2Note", 8 | "JsonFormatArc2Note", 9 | "SendParams", 10 | "StringFormatArc2Note", 11 | "TransactionNote", 12 | "TransactionNoteData", 13 | "TransactionWrapper", 14 | ] 15 | 16 | 17 | # Define specific types for different formats 18 | class BaseArc2Note(TypedDict): 19 | """Base ARC-0002 transaction note structure""" 20 | 21 | dapp_name: str 22 | 23 | 24 | class StringFormatArc2Note(BaseArc2Note): 25 | """ARC-0002 note for string-based formats (m/b/u)""" 26 | 27 | format: Literal["m", "b", "u"] 28 | data: str 29 | 30 | 31 | class JsonFormatArc2Note(BaseArc2Note): 32 | """ARC-0002 note for JSON format""" 33 | 34 | format: Literal["j"] 35 | data: str | dict[str, Any] | list[Any] | int | None 36 | 37 | 38 | # Combined type for all valid ARC-0002 notes 39 | # See: https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md 40 | Arc2TransactionNote = StringFormatArc2Note | JsonFormatArc2Note 41 | 42 | TransactionNoteData = str | None | int | list[Any] | dict[str, Any] 43 | TransactionNote = bytes | TransactionNoteData | Arc2TransactionNote 44 | 45 | TxnTypeT = TypeVar("TxnTypeT", bound=algosdk.transaction.Transaction) 46 | 47 | 48 | class TransactionWrapper: 49 | """Wrapper around algosdk.transaction.Transaction with optional property validators""" 50 | 51 | def __init__(self, transaction: algosdk.transaction.Transaction) -> None: 52 | self._raw = transaction 53 | 54 | @property 55 | def raw(self) -> algosdk.transaction.Transaction: 56 | return self._raw 57 | 58 | @property 59 | def payment(self) -> algosdk.transaction.PaymentTxn: 60 | return self._return_if_type( 61 | algosdk.transaction.PaymentTxn, 62 | ) 63 | 64 | @property 65 | def keyreg(self) -> algosdk.transaction.KeyregTxn: 66 | return self._return_if_type(algosdk.transaction.KeyregTxn) 67 | 68 | @property 69 | def asset_config(self) -> algosdk.transaction.AssetConfigTxn: 70 | return self._return_if_type(algosdk.transaction.AssetConfigTxn) 71 | 72 | @property 73 | def asset_transfer(self) -> algosdk.transaction.AssetTransferTxn: 74 | return self._return_if_type(algosdk.transaction.AssetTransferTxn) 75 | 76 | @property 77 | def asset_freeze(self) -> algosdk.transaction.AssetFreezeTxn: 78 | return self._return_if_type(algosdk.transaction.AssetFreezeTxn) 79 | 80 | @property 81 | def application_call(self) -> algosdk.transaction.ApplicationCallTxn: 82 | return self._return_if_type(algosdk.transaction.ApplicationCallTxn) 83 | 84 | @property 85 | def state_proof(self) -> algosdk.transaction.StateProofTxn: 86 | return self._return_if_type(algosdk.transaction.StateProofTxn) 87 | 88 | def _return_if_type(self, txn_type: type[TxnTypeT]) -> TxnTypeT: 89 | if isinstance(self._raw, txn_type): 90 | return self._raw 91 | raise ValueError(f"Transaction is not of type {txn_type.__name__}") 92 | 93 | 94 | class SendParams(TypedDict, total=False): 95 | """Parameters for sending a transaction""" 96 | 97 | max_rounds_to_wait: int | None 98 | suppress_log: bool | None 99 | populate_app_call_resources: bool | None 100 | cover_app_call_inner_transaction_fees: bool | None 101 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Delivery of Python package 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - "docs/**" 9 | - ".github/**" 10 | workflow_dispatch: 11 | inputs: 12 | production_release: 13 | description: "Production release?" 14 | type: boolean 15 | required: true 16 | default: true 17 | 18 | concurrency: release 19 | 20 | permissions: 21 | contents: write 22 | packages: read 23 | 24 | jobs: 25 | ci-check-python: 26 | name: Check Python 27 | uses: ./.github/workflows/check-python.yaml 28 | 29 | ci-check-docs: 30 | name: Check Documentation 31 | uses: ./.github/workflows/check-docs.yaml 32 | 33 | ci-build-python: 34 | name: Build Python 35 | uses: ./.github/workflows/build-python.yaml 36 | needs: [ci-check-python, ci-check-docs] 37 | 38 | release: 39 | name: Release Library 40 | needs: ci-build-python 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - name: Generate bot token 45 | uses: actions/create-github-app-token@v1 46 | id: app_token 47 | with: 48 | app-id: ${{ secrets.BOT_ID }} 49 | private-key: ${{ secrets.BOT_SK }} 50 | 51 | - uses: actions/checkout@v4 52 | with: 53 | # Fetch entire repository history so we can determine version number from it 54 | fetch-depth: 0 55 | token: ${{ steps.app_token.outputs.token }} 56 | 57 | - name: Set up Python 58 | uses: actions/setup-python@v5 59 | with: 60 | python-version: "3.10" 61 | 62 | - name: Set up Poetry 63 | uses: ./.github/actions/setup-poetry 64 | 65 | - name: Install dependencies 66 | run: poetry install --no-interaction --no-root 67 | 68 | - name: Get branch name 69 | shell: bash 70 | run: echo "branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT 71 | id: get_branch 72 | 73 | - name: Set Git user as GitHub actions 74 | run: git config --global user.email "179917785+engineering-ci[bot]@users.noreply.github.com" && git config --global user.name "engineering-ci[bot]" 75 | 76 | - name: Create Continuous Deployment - Beta (non-prod) 77 | if: steps.get_branch.outputs.branch == 'main' && !inputs.production_release 78 | run: | 79 | poetry run semantic-release \ 80 | -v DEBUG \ 81 | --prerelease \ 82 | --define=branch=main \ 83 | --define=upload_to_repository=true \ 84 | publish 85 | gh release edit --prerelease "v$(poetry run semantic-release print-version --current)" 86 | env: 87 | GH_TOKEN: ${{ steps.app_token.outputs.token }} 88 | REPOSITORY_USERNAME: __token__ 89 | REPOSITORY_PASSWORD: ${{ secrets.PYPI_API_KEY }} 90 | 91 | - name: Create Continuous Deployment - Production 92 | if: steps.get_branch.outputs.branch == 'main' && inputs.production_release 93 | run: | 94 | poetry run semantic-release \ 95 | -v DEBUG \ 96 | --define=version_source="commit" \ 97 | --define=patch_without_tag=true \ 98 | --define=upload_to_repository=true \ 99 | --define=branch=main \ 100 | publish 101 | env: 102 | GH_TOKEN: ${{ steps.app_token.outputs.token }} 103 | REPOSITORY_USERNAME: __token__ 104 | REPOSITORY_PASSWORD: ${{ secrets.PYPI_API_KEY }} 105 | -------------------------------------------------------------------------------- /tests/models/test_algo_amount.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | import pytest 4 | 5 | from algokit_utils.models.amount import ALGORAND_MIN_TX_FEE, AlgoAmount, algo, micro_algo, transaction_fees 6 | 7 | 8 | def test_initialization() -> None: 9 | # Test valid initialization formats 10 | assert AlgoAmount(micro_algo=500_000).micro_algo == 500_000 11 | assert AlgoAmount(algo=1).micro_algo == 1_000_000 12 | assert AlgoAmount(algo=Decimal("0.5")).micro_algo == 500_000 13 | 14 | # Test decimal precision 15 | assert AlgoAmount(algo=Decimal("0.000001")).micro_algo == 1 16 | assert AlgoAmount(algo=Decimal("123.456789")).micro_algo == 123_456_789 17 | 18 | 19 | def test_from_methods() -> None: 20 | assert AlgoAmount.from_micro_algo(500_000).micro_algo == 500_000 21 | assert AlgoAmount.from_micro_algo(250_000).micro_algo == 250_000 22 | assert AlgoAmount.from_algo(2).micro_algo == 2_000_000 23 | assert AlgoAmount.from_algo(Decimal("0.75")).micro_algo == 750_000 24 | 25 | 26 | def test_properties() -> None: 27 | amount = AlgoAmount.from_micro_algo(1_234_567) 28 | assert amount.micro_algo == 1_234_567 29 | assert amount.algo == Decimal("1.234567") 30 | 31 | 32 | def test_arithmetic_operations() -> None: 33 | a = AlgoAmount.from_algo(5) 34 | b = AlgoAmount.from_algo(3) 35 | 36 | # Addition 37 | assert (a + b).micro_algo == 8_000_000 38 | a += b 39 | assert a.micro_algo == 8_000_000 40 | 41 | # Subtraction 42 | assert (a - b).micro_algo == 5_000_000 43 | a -= b 44 | assert a.micro_algo == 5_000_000 45 | 46 | # Right operations 47 | assert (AlgoAmount.from_micro_algo(1000) + a).micro_algo == 5_001_000 48 | assert (AlgoAmount.from_algo(10) - a).micro_algo == 5_000_000 49 | 50 | 51 | def test_comparison_operators() -> None: 52 | base = AlgoAmount.from_algo(5) 53 | same = AlgoAmount.from_algo(5) 54 | larger = AlgoAmount.from_algo(10) 55 | 56 | assert base == same 57 | assert base != larger 58 | assert base < larger 59 | assert larger > base 60 | assert base <= same 61 | assert larger >= base 62 | 63 | # Test int comparison 64 | assert base == 5_000_000 65 | assert base < 6_000_000 66 | assert base > 4_000_000 67 | 68 | 69 | def test_edge_cases() -> None: 70 | # Zero value 71 | zero = AlgoAmount.from_micro_algo(0) 72 | assert zero.micro_algo == 0 73 | assert zero.algo == 0 74 | 75 | # Very large values 76 | large = AlgoAmount.from_algo(Decimal("1e9")) 77 | assert large.micro_algo == 1e9 * 1e6 78 | 79 | # Decimal precision limits 80 | precise = AlgoAmount(algo=Decimal("0.123456789")) 81 | assert precise.micro_algo == 123_456 82 | 83 | 84 | def test_string_representation() -> None: 85 | assert str(AlgoAmount.from_micro_algo(1_000_000)) == "1,000,000 µALGO" 86 | assert str(AlgoAmount.from_algo(Decimal("2.5"))) == "2,500,000 µALGO" 87 | 88 | 89 | def test_type_safety() -> None: 90 | with pytest.raises(TypeError, match="Unsupported operand type"): 91 | # int is not AlgoAmount 92 | AlgoAmount.from_algo(5) + 1000 # type: ignore # noqa: PGH003 93 | 94 | with pytest.raises(TypeError, match="Unsupported operand type"): 95 | AlgoAmount.from_algo(5) - "invalid" # type: ignore # noqa: PGH003 96 | 97 | 98 | def test_helper_functions() -> None: 99 | assert algo(1).micro_algo == 1_000_000 100 | assert micro_algo(1_000_000).micro_algo == 1_000_000 101 | assert ALGORAND_MIN_TX_FEE.micro_algo == 1_000 102 | assert transaction_fees(1).micro_algo == 1_000 103 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/models/application/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.models.application 2 | 3 | ## Classes 4 | 5 | | [`AppState`](#algokit_utils.models.application.AppState) | | 6 | |----------------------------------------------------------------------------------|-------------------------------------| 7 | | [`AppInformation`](#algokit_utils.models.application.AppInformation) | | 8 | | [`CompiledTeal`](#algokit_utils.models.application.CompiledTeal) | The compiled teal code | 9 | | [`AppCompilationResult`](#algokit_utils.models.application.AppCompilationResult) | The compiled teal code | 10 | | [`AppSourceMaps`](#algokit_utils.models.application.AppSourceMaps) | The source maps for the application | 11 | 12 | ## Module Contents 13 | 14 | ### *class* algokit_utils.models.application.AppState 15 | 16 | #### key_raw *: bytes* 17 | 18 | The key of the state as raw bytes 19 | 20 | #### key_base64 *: str* 21 | 22 | The key of the state 23 | 24 | #### value_raw *: bytes | None* 25 | 26 | The value of the state as raw bytes 27 | 28 | #### value_base64 *: str | None* 29 | 30 | The value of the state as base64 encoded string 31 | 32 | #### value *: str | int* 33 | 34 | The value of the state as a string or integer 35 | 36 | ### *class* algokit_utils.models.application.AppInformation 37 | 38 | #### app_id *: int* 39 | 40 | The ID of the application 41 | 42 | #### app_address *: str* 43 | 44 | The address of the application 45 | 46 | #### approval_program *: bytes* 47 | 48 | The approval program 49 | 50 | #### clear_state_program *: bytes* 51 | 52 | The clear state program 53 | 54 | #### creator *: str* 55 | 56 | The creator of the application 57 | 58 | #### global_state *: dict[str, [AppState](#algokit_utils.models.application.AppState)]* 59 | 60 | The global state of the application 61 | 62 | #### local_ints *: int* 63 | 64 | The number of local ints 65 | 66 | #### local_byte_slices *: int* 67 | 68 | The number of local byte slices 69 | 70 | #### global_ints *: int* 71 | 72 | The number of global ints 73 | 74 | #### global_byte_slices *: int* 75 | 76 | The number of global byte slices 77 | 78 | #### extra_program_pages *: int | None* 79 | 80 | The number of extra program pages 81 | 82 | ### *class* algokit_utils.models.application.CompiledTeal 83 | 84 | The compiled teal code 85 | 86 | #### teal *: str* 87 | 88 | The teal code 89 | 90 | #### compiled *: str* 91 | 92 | The compiled teal code 93 | 94 | #### compiled_hash *: str* 95 | 96 | The compiled hash 97 | 98 | #### compiled_base64_to_bytes *: bytes* 99 | 100 | The compiled base64 to bytes 101 | 102 | #### source_map *: algosdk.source_map.SourceMap | None* 103 | 104 | ### *class* algokit_utils.models.application.AppCompilationResult 105 | 106 | The compiled teal code 107 | 108 | #### compiled_approval *: [CompiledTeal](#algokit_utils.models.application.CompiledTeal)* 109 | 110 | The compiled approval program 111 | 112 | #### compiled_clear *: [CompiledTeal](#algokit_utils.models.application.CompiledTeal)* 113 | 114 | The compiled clear state program 115 | 116 | ### *class* algokit_utils.models.application.AppSourceMaps 117 | 118 | The source maps for the application 119 | 120 | #### approval_source_map *: algosdk.source_map.SourceMap | None* *= None* 121 | 122 | The source map for the approval program 123 | 124 | #### clear_source_map *: algosdk.source_map.SourceMap | None* *= None* 125 | 126 | The source map for the clear state program 127 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/models/transaction/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.models.transaction 2 | 3 | ## Attributes 4 | 5 | | [`Arc2TransactionNote`](#algokit_utils.models.transaction.Arc2TransactionNote) | | 6 | |----------------------------------------------------------------------------------|----| 7 | | [`TransactionNoteData`](#algokit_utils.models.transaction.TransactionNoteData) | | 8 | | [`TransactionNote`](#algokit_utils.models.transaction.TransactionNote) | | 9 | 10 | ## Classes 11 | 12 | | [`BaseArc2Note`](#algokit_utils.models.transaction.BaseArc2Note) | Base ARC-0002 transaction note structure | 13 | |----------------------------------------------------------------------------------|----------------------------------------------------------------------------------| 14 | | [`StringFormatArc2Note`](#algokit_utils.models.transaction.StringFormatArc2Note) | ARC-0002 note for string-based formats (m/b/u) | 15 | | [`JsonFormatArc2Note`](#algokit_utils.models.transaction.JsonFormatArc2Note) | ARC-0002 note for JSON format | 16 | | [`TransactionWrapper`](#algokit_utils.models.transaction.TransactionWrapper) | Wrapper around algosdk.transaction.Transaction with optional property validators | 17 | | [`SendParams`](#algokit_utils.models.transaction.SendParams) | Parameters for sending a transaction | 18 | 19 | ## Module Contents 20 | 21 | ### *class* algokit_utils.models.transaction.BaseArc2Note 22 | 23 | Bases: `TypedDict` 24 | 25 | Base ARC-0002 transaction note structure 26 | 27 | #### dapp_name *: str* 28 | 29 | ### *class* algokit_utils.models.transaction.StringFormatArc2Note 30 | 31 | Bases: [`BaseArc2Note`](#algokit_utils.models.transaction.BaseArc2Note) 32 | 33 | ARC-0002 note for string-based formats (m/b/u) 34 | 35 | #### format *: Literal['m', 'b', 'u']* 36 | 37 | #### data *: str* 38 | 39 | ### *class* algokit_utils.models.transaction.JsonFormatArc2Note 40 | 41 | Bases: [`BaseArc2Note`](#algokit_utils.models.transaction.BaseArc2Note) 42 | 43 | ARC-0002 note for JSON format 44 | 45 | #### format *: Literal['j']* 46 | 47 | #### data *: str | dict[str, Any] | list[Any] | int | None* 48 | 49 | ### algokit_utils.models.transaction.Arc2TransactionNote 50 | 51 | ### algokit_utils.models.transaction.TransactionNoteData 52 | 53 | ### algokit_utils.models.transaction.TransactionNote 54 | 55 | ### *class* algokit_utils.models.transaction.TransactionWrapper(transaction: algosdk.transaction.Transaction) 56 | 57 | Wrapper around algosdk.transaction.Transaction with optional property validators 58 | 59 | #### *property* raw *: algosdk.transaction.Transaction* 60 | 61 | #### *property* payment *: algosdk.transaction.PaymentTxn* 62 | 63 | #### *property* keyreg *: algosdk.transaction.KeyregTxn* 64 | 65 | #### *property* asset_config *: algosdk.transaction.AssetConfigTxn* 66 | 67 | #### *property* asset_transfer *: algosdk.transaction.AssetTransferTxn* 68 | 69 | #### *property* asset_freeze *: algosdk.transaction.AssetFreezeTxn* 70 | 71 | #### *property* application_call *: algosdk.transaction.ApplicationCallTxn* 72 | 73 | #### *property* state_proof *: algosdk.transaction.StateProofTxn* 74 | 75 | ### *class* algokit_utils.models.transaction.SendParams 76 | 77 | Bases: `TypedDict` 78 | 79 | Parameters for sending a transaction 80 | 81 | #### max_rounds_to_wait *: int | None* 82 | 83 | #### suppress_log *: bool | None* 84 | 85 | #### populate_app_call_resources *: bool | None* 86 | 87 | #### cover_app_call_inner_transaction_fees *: bool | None* 88 | -------------------------------------------------------------------------------- /docs/markdown/capabilities/dispenser-client.md: -------------------------------------------------------------------------------- 1 | # TestNet Dispenser Client 2 | 3 | The TestNet Dispenser Client is a utility for interacting with the AlgoKit TestNet Dispenser API. It provides methods to fund an account, register a refund for a transaction, and get the current limit for an account. 4 | 5 | ## Creating a Dispenser Client 6 | 7 | To create a Dispenser Client, you need to provide an authorization token. This can be done in two ways: 8 | 9 | 1. Pass the token directly to the client constructor as `auth_token`. 10 | 2. Set the token as an environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN` (see [docs](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md#login) on how to obtain the token). 11 | 12 | If both methods are used, the constructor argument takes precedence. 13 | 14 | ```python 15 | import algokit_utils 16 | 17 | # With auth token 18 | dispenser = algorand.client.get_testnet_dispenser( 19 | auth_token="your_auth_token", 20 | ) 21 | 22 | # With auth token and timeout 23 | dispenser = algorand.client.get_testnet_dispenser( 24 | auth_token="your_auth_token", 25 | request_timeout=2, # seconds 26 | ) 27 | 28 | # From environment variables 29 | # i.e. os.environ['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' 30 | dispenser = algorand.client.get_testnet_dispenser_from_environment() 31 | 32 | # Alternatively, you can construct it directly 33 | from algokit_utils import TestNetDispenserApiClient 34 | 35 | # Using constructor argument 36 | client = TestNetDispenserApiClient(auth_token="your_auth_token") 37 | 38 | # Using environment variable 39 | import os 40 | os.environ['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' 41 | client = TestNetDispenserApiClient() 42 | ``` 43 | 44 | ## Funding an Account 45 | 46 | To fund an account with Algo from the dispenser API, use the `fund` method. This method requires the receiver’s address and the amount to be funded. 47 | 48 | ```python 49 | response = dispenser.fund( 50 | receiver="RECEIVER_ADDRESS", 51 | amount=1000, # Amount in microAlgos 52 | ) 53 | ``` 54 | 55 | The `fund` method returns a `DispenserFundResponse` object, which contains the transaction ID (`tx_id`) and the amount funded. 56 | 57 | ## Registering a Refund 58 | 59 | To register a refund for a transaction with the dispenser API, use the `refund` method. This method requires the transaction ID of the refund transaction. 60 | 61 | ```python 62 | dispenser.refund("transaction_id") 63 | ``` 64 | 65 | > Keep in mind, to perform a refund you need to perform a payment transaction yourself first by sending funds back to TestNet Dispenser, then you can invoke this refund endpoint and pass the txn_id of your refund txn. You can obtain dispenser address by inspecting the sender field of any issued fund transaction initiated via [fund](). 66 | 67 | ## Getting Current Limit 68 | 69 | To get the current limit for an account with Algo from the dispenser API, use the `get_limit` method. 70 | 71 | ```python 72 | response = dispenser.get_limit() 73 | ``` 74 | 75 | The `get_limit` method returns a `DispenserLimitResponse` object, which contains the current limit amount. 76 | 77 | ## Error Handling 78 | 79 | If an error occurs while making a request to the dispenser API, an exception will be raised with a message indicating the type of error. Refer to [Error Handling docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md#error-handling) for details on how you can handle each individual error `code`. 80 | 81 | Here’s an example of handling errors: 82 | 83 | ```python 84 | try: 85 | response = dispenser.fund( 86 | receiver="RECEIVER_ADDRESS", 87 | amount=1000, 88 | ) 89 | except Exception as e: 90 | print(f"Error occurred: {str(e)}") 91 | ``` 92 | -------------------------------------------------------------------------------- /docs/source/capabilities/dispenser-client.md: -------------------------------------------------------------------------------- 1 | # TestNet Dispenser Client 2 | 3 | The TestNet Dispenser Client is a utility for interacting with the AlgoKit TestNet Dispenser API. It provides methods to fund an account, register a refund for a transaction, and get the current limit for an account. 4 | 5 | ## Creating a Dispenser Client 6 | 7 | To create a Dispenser Client, you need to provide an authorization token. This can be done in two ways: 8 | 9 | 1. Pass the token directly to the client constructor as `auth_token`. 10 | 2. Set the token as an environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN` (see [docs](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md#login) on how to obtain the token). 11 | 12 | If both methods are used, the constructor argument takes precedence. 13 | 14 | ```python 15 | import algokit_utils 16 | 17 | # With auth token 18 | dispenser = algorand.client.get_testnet_dispenser( 19 | auth_token="your_auth_token", 20 | ) 21 | 22 | # With auth token and timeout 23 | dispenser = algorand.client.get_testnet_dispenser( 24 | auth_token="your_auth_token", 25 | request_timeout=2, # seconds 26 | ) 27 | 28 | # From environment variables 29 | # i.e. os.environ['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' 30 | dispenser = algorand.client.get_testnet_dispenser_from_environment() 31 | 32 | # Alternatively, you can construct it directly 33 | from algokit_utils import TestNetDispenserApiClient 34 | 35 | # Using constructor argument 36 | client = TestNetDispenserApiClient(auth_token="your_auth_token") 37 | 38 | # Using environment variable 39 | import os 40 | os.environ['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' 41 | client = TestNetDispenserApiClient() 42 | ``` 43 | 44 | ## Funding an Account 45 | 46 | To fund an account with Algo from the dispenser API, use the `fund` method. This method requires the receiver's address and the amount to be funded. 47 | 48 | ```python 49 | response = dispenser.fund( 50 | receiver="RECEIVER_ADDRESS", 51 | amount=1000, # Amount in microAlgos 52 | ) 53 | ``` 54 | 55 | The `fund` method returns a `DispenserFundResponse` object, which contains the transaction ID (`tx_id`) and the amount funded. 56 | 57 | ## Registering a Refund 58 | 59 | To register a refund for a transaction with the dispenser API, use the `refund` method. This method requires the transaction ID of the refund transaction. 60 | 61 | ```python 62 | dispenser.refund("transaction_id") 63 | ``` 64 | 65 | > Keep in mind, to perform a refund you need to perform a payment transaction yourself first by sending funds back to TestNet Dispenser, then you can invoke this refund endpoint and pass the txn_id of your refund txn. You can obtain dispenser address by inspecting the sender field of any issued fund transaction initiated via [fund](#funding-an-account). 66 | 67 | ## Getting Current Limit 68 | 69 | To get the current limit for an account with Algo from the dispenser API, use the `get_limit` method. 70 | 71 | ```python 72 | response = dispenser.get_limit() 73 | ``` 74 | 75 | The `get_limit` method returns a `DispenserLimitResponse` object, which contains the current limit amount. 76 | 77 | ## Error Handling 78 | 79 | If an error occurs while making a request to the dispenser API, an exception will be raised with a message indicating the type of error. Refer to [Error Handling docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md#error-handling) for details on how you can handle each individual error `code`. 80 | 81 | Here's an example of handling errors: 82 | 83 | ```python 84 | try: 85 | response = dispenser.fund( 86 | receiver="RECEIVER_ADDRESS", 87 | amount=1000, 88 | ) 89 | except Exception as e: 90 | print(f"Error occurred: {str(e)}") 91 | ``` 92 | -------------------------------------------------------------------------------- /src/algokit_utils/protocols/typed_clients.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar 4 | 5 | from algosdk.atomic_transaction_composer import TransactionSigner 6 | from algosdk.source_map import SourceMap 7 | from typing_extensions import Self 8 | 9 | from algokit_utils.models import SendParams 10 | 11 | if TYPE_CHECKING: 12 | from algokit_utils.algorand import AlgorandClient 13 | from algokit_utils.applications.app_client import ( 14 | AppClientBareCallCreateParams, 15 | AppClientBareCallParams, 16 | AppClientCompilationParams, 17 | BaseAppClientMethodCallParams, 18 | ) 19 | from algokit_utils.applications.app_deployer import ( 20 | ApplicationLookup, 21 | OnSchemaBreak, 22 | OnUpdate, 23 | ) 24 | from algokit_utils.applications.app_factory import AppFactoryDeployResult 25 | 26 | __all__ = [ 27 | "TypedAppClientProtocol", 28 | "TypedAppFactoryProtocol", 29 | ] 30 | 31 | 32 | class TypedAppClientProtocol(Protocol): 33 | @classmethod 34 | def from_creator_and_name( 35 | cls, 36 | *, 37 | creator_address: str, 38 | app_name: str, 39 | default_sender: str | None = None, 40 | default_signer: TransactionSigner | None = None, 41 | ignore_cache: bool | None = None, 42 | app_lookup_cache: ApplicationLookup | None = None, 43 | algorand: AlgorandClient, 44 | ) -> Self: ... 45 | 46 | @classmethod 47 | def from_network( 48 | cls, 49 | *, 50 | app_name: str | None = None, 51 | default_sender: str | None = None, 52 | default_signer: TransactionSigner | None = None, 53 | approval_source_map: SourceMap | None = None, 54 | clear_source_map: SourceMap | None = None, 55 | algorand: AlgorandClient, 56 | ) -> Self: ... 57 | 58 | def __init__( 59 | self, 60 | *, 61 | app_id: int, 62 | app_name: str | None = None, 63 | default_sender: str | None = None, 64 | default_signer: TransactionSigner | None = None, 65 | algorand: AlgorandClient, 66 | approval_source_map: SourceMap | None = None, 67 | clear_source_map: SourceMap | None = None, 68 | ) -> None: ... 69 | 70 | 71 | CreateParamsT = TypeVar( # noqa: PLC0105 72 | "CreateParamsT", 73 | bound="BaseAppClientMethodCallParams | AppClientBareCallCreateParams | None", 74 | contravariant=True, 75 | ) 76 | UpdateParamsT = TypeVar( # noqa: PLC0105 77 | "UpdateParamsT", 78 | bound="BaseAppClientMethodCallParams | AppClientBareCallParams | None", 79 | contravariant=True, 80 | ) 81 | DeleteParamsT = TypeVar( # noqa: PLC0105 82 | "DeleteParamsT", 83 | bound="BaseAppClientMethodCallParams | AppClientBareCallParams | None", 84 | contravariant=True, 85 | ) 86 | 87 | 88 | class TypedAppFactoryProtocol(Protocol, Generic[CreateParamsT, UpdateParamsT, DeleteParamsT]): 89 | def __init__( 90 | self, 91 | algorand: AlgorandClient, 92 | **kwargs: Any, 93 | ) -> None: ... 94 | 95 | def deploy( 96 | self, 97 | *, 98 | on_update: OnUpdate | None = None, 99 | on_schema_break: OnSchemaBreak | None = None, 100 | create_params: CreateParamsT | None = None, 101 | update_params: UpdateParamsT | None = None, 102 | delete_params: DeleteParamsT | None = None, 103 | existing_deployments: ApplicationLookup | None = None, 104 | ignore_cache: bool = False, 105 | app_name: str | None = None, 106 | send_params: SendParams | None = None, 107 | compilation_params: AppClientCompilationParams | None = None, 108 | ) -> tuple[TypedAppClientProtocol, AppFactoryDeployResult]: ... 109 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | pytest-coverage.txt 54 | pytest-junit.xml 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # poetry 100 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 101 | # This is especially recommended for binary packages to ensure reproducibility, and is more 102 | # commonly ignored for libraries. 103 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 104 | #poetry.lock 105 | 106 | # pdm 107 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 108 | #pdm.lock 109 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 110 | # in version control. 111 | # https://pdm.fming.dev/#use-with-ide 112 | .pdm.toml 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Ruff (linter) 155 | .ruff_cache/ 156 | 157 | # Cython debug symbols 158 | cython_debug/ 159 | 160 | # PyCharm 161 | .idea/ 162 | !.idea/runConfigurations 163 | 164 | # macOS 165 | .DS_Store 166 | 167 | #Sphinx 168 | .doctrees/ 169 | # ignore auto-generated sources 170 | /docs/source/apidocs 171 | 172 | !docs/html 173 | 174 | # Received approval test files 175 | *.received.* 176 | -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/accounts/kmd_account_manager/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.accounts.kmd_account_manager 2 | 3 | ## Classes 4 | 5 | | [`KmdAccount`](#algokit_utils.accounts.kmd_account_manager.KmdAccount) | Account retrieved from KMD with signing capabilities, extending base Account. | 6 | |--------------------------------------------------------------------------------------|---------------------------------------------------------------------------------| 7 | | [`KmdAccountManager`](#algokit_utils.accounts.kmd_account_manager.KmdAccountManager) | Provides abstractions over KMD that makes it easier to get and manage accounts. | 8 | 9 | ## Module Contents 10 | 11 | ### *class* algokit_utils.accounts.kmd_account_manager.KmdAccount(private_key: str, address: str | None = None) 12 | 13 | Bases: [`algokit_utils.models.account.SigningAccount`](../../models/account/index.md#algokit_utils.models.account.SigningAccount) 14 | 15 | Account retrieved from KMD with signing capabilities, extending base Account. 16 | 17 | Provides an account implementation that can be used to sign transactions using keys stored in KMD. 18 | 19 | * **Parameters:** 20 | * **private_key** – Base64 encoded private key 21 | * **address** – Optional address override for rekeyed accounts, defaults to None 22 | 23 | ### *class* algokit_utils.accounts.kmd_account_manager.KmdAccountManager(client_manager: [algokit_utils.clients.client_manager.ClientManager](../../clients/client_manager/index.md#algokit_utils.clients.client_manager.ClientManager)) 24 | 25 | Provides abstractions over KMD that makes it easier to get and manage accounts. 26 | 27 | #### kmd() → algosdk.kmd.KMDClient 28 | 29 | Returns the KMD client, initializing it if needed. 30 | 31 | * **Raises:** 32 | **Exception** – If KMD client is not configured and not running against LocalNet 33 | * **Returns:** 34 | The KMD client 35 | 36 | #### get_wallet_account(wallet_name: str, predicate: collections.abc.Callable[[dict[str, Any]], bool] | None = None, sender: str | None = None) → [KmdAccount](#algokit_utils.accounts.kmd_account_manager.KmdAccount) | None 37 | 38 | Returns an Algorand signing account with private key loaded from the given KMD wallet. 39 | 40 | Retrieves an account from a KMD wallet that matches the given predicate, or a random account 41 | if no predicate is provided. 42 | 43 | * **Parameters:** 44 | * **wallet_name** – The name of the wallet to retrieve an account from 45 | * **predicate** – Optional filter to use to find the account (otherwise gets a random account from the wallet) 46 | * **sender** – Optional sender address to use this signer for (aka a rekeyed account) 47 | * **Returns:** 48 | The signing account or None if no matching wallet or account was found 49 | 50 | #### get_or_create_wallet_account(name: str, fund_with: [algokit_utils.models.amount.AlgoAmount](../../models/amount/index.md#algokit_utils.models.amount.AlgoAmount) | None = None) → [KmdAccount](#algokit_utils.accounts.kmd_account_manager.KmdAccount) 51 | 52 | Gets or creates a funded account in a KMD wallet of the given name. 53 | 54 | Provides idempotent access to accounts from LocalNet without specifying the private key. 55 | 56 | * **Parameters:** 57 | * **name** – The name of the wallet to retrieve / create 58 | * **fund_with** – The number of Algos to fund the account with when created 59 | * **Returns:** 60 | An Algorand account with private key loaded 61 | 62 | #### get_localnet_dispenser_account() → [KmdAccount](#algokit_utils.accounts.kmd_account_manager.KmdAccount) 63 | 64 | Returns an Algorand account with private key loaded for the default LocalNet dispenser account. 65 | 66 | Retrieves the default funded account from LocalNet that can be used to fund other accounts. 67 | 68 | * **Raises:** 69 | **Exception** – If not running against LocalNet or dispenser account not found 70 | * **Returns:** 71 | The default LocalNet dispenser account 72 | -------------------------------------------------------------------------------- /tests/artifacts/resource-packer/InnerBoxApp.arc32.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": { 3 | "createEmptyBox()void": { 4 | "call_config": { 5 | "no_op": "CALL" 6 | } 7 | }, 8 | "createApplication()void": { 9 | "call_config": { 10 | "no_op": "CREATE" 11 | } 12 | } 13 | }, 14 | "bare_call_config": { 15 | "no_op": "NEVER", 16 | "opt_in": "NEVER", 17 | "close_out": "NEVER", 18 | "update_application": "NEVER", 19 | "delete_application": "NEVER" 20 | }, 21 | "schema": { 22 | "local": { 23 | "declared": {}, 24 | "reserved": {} 25 | }, 26 | "global": { 27 | "declared": {}, 28 | "reserved": {} 29 | } 30 | }, 31 | "state": { 32 | "global": { 33 | "num_byte_slices": 0, 34 | "num_uints": 0 35 | }, 36 | "local": { 37 | "num_byte_slices": 0, 38 | "num_uints": 0 39 | } 40 | }, 41 | "source": { 42 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCmludGNibG9jayAxCgovLyBUaGlzIFRFQUwgd2FzIGdlbmVyYXRlZCBieSBURUFMU2NyaXB0IHYwLjEwNi4zCi8vIGh0dHBzOi8vZ2l0aHViLmNvbS9hbGdvcmFuZGZvdW5kYXRpb24vVEVBTFNjcmlwdAoKLy8gVGhpcyBjb250cmFjdCBpcyBjb21wbGlhbnQgd2l0aCBhbmQvb3IgaW1wbGVtZW50cyB0aGUgZm9sbG93aW5nIEFSQ3M6IFsgQVJDNCBdCgovLyBUaGUgZm9sbG93aW5nIHRlbiBsaW5lcyBvZiBURUFMIGhhbmRsZSBpbml0aWFsIHByb2dyYW0gZmxvdwovLyBUaGlzIHBhdHRlcm4gaXMgdXNlZCB0byBtYWtlIGl0IGVhc3kgZm9yIGFueW9uZSB0byBwYXJzZSB0aGUgc3RhcnQgb2YgdGhlIHByb2dyYW0gYW5kIGRldGVybWluZSBpZiBhIHNwZWNpZmljIGFjdGlvbiBpcyBhbGxvd2VkCi8vIEhlcmUsIGFjdGlvbiByZWZlcnMgdG8gdGhlIE9uQ29tcGxldGUgaW4gY29tYmluYXRpb24gd2l0aCB3aGV0aGVyIHRoZSBhcHAgaXMgYmVpbmcgY3JlYXRlZCBvciBjYWxsZWQKLy8gRXZlcnkgcG9zc2libGUgYWN0aW9uIGZvciB0aGlzIGNvbnRyYWN0IGlzIHJlcHJlc2VudGVkIGluIHRoZSBzd2l0Y2ggc3RhdGVtZW50Ci8vIElmIHRoZSBhY3Rpb24gaXMgbm90IGltcGxlbWVudGVkIGluIHRoZSBjb250cmFjdCwgaXRzIHJlc3BlY3RpdmUgYnJhbmNoIHdpbGwgYmUgIipOT1RfSU1QTEVNRU5URUQiIHdoaWNoIGp1c3QgY29udGFpbnMgImVyciIKdHhuIEFwcGxpY2F0aW9uSUQKIQpwdXNoaW50IDYKKgp0eG4gT25Db21wbGV0aW9uCisKc3dpdGNoICpjYWxsX05vT3AgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpjcmVhdGVfTm9PcCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQgKk5PVF9JTVBMRU1FTlRFRCAqTk9UX0lNUExFTUVOVEVEICpOT1RfSU1QTEVNRU5URUQKCipOT1RfSU1QTEVNRU5URUQ6CgkvLyBUaGUgcmVxdWVzdGVkIGFjdGlvbiBpcyBub3QgaW1wbGVtZW50ZWQgaW4gdGhpcyBjb250cmFjdC4gQXJlIHlvdSB1c2luZyB0aGUgY29ycmVjdCBPbkNvbXBsZXRlPyBEaWQgeW91IHNldCB5b3VyIGFwcCBJRD8KCWVycgoKLy8gY3JlYXRlRW1wdHlCb3goKXZvaWQKKmFiaV9yb3V0ZV9jcmVhdGVFbXB0eUJveDoKCS8vIGV4ZWN1dGUgY3JlYXRlRW1wdHlCb3goKXZvaWQKCWNhbGxzdWIgY3JlYXRlRW1wdHlCb3gKCWludGMgMCAvLyAxCglyZXR1cm4KCi8vIGNyZWF0ZUVtcHR5Qm94KCk6IHZvaWQKY3JlYXRlRW1wdHlCb3g6Cglwcm90byAwIDAKCgkvLyB0ZXN0cy9leGFtcGxlLWNvbnRyYWN0cy9yZXNvdXJjZS1wYWNrZXIvcmVzb3VyY2UtcGFja2VyLmFsZ28udHM6NjEKCS8vIHRoaXMuZW1wdHlCb3guY3JlYXRlKCkKCXB1c2hieXRlcyAweDY1NmQ3MDc0Nzk0MjZmNzggLy8gImVtcHR5Qm94IgoJcHVzaGludCAwCglib3hfY3JlYXRlCglwb3AKCXJldHN1YgoKKmFiaV9yb3V0ZV9jcmVhdGVBcHBsaWNhdGlvbjoKCWludGMgMCAvLyAxCglyZXR1cm4KCipjcmVhdGVfTm9PcDoKCXB1c2hieXRlcyAweGI4NDQ3YjM2IC8vIG1ldGhvZCAiY3JlYXRlQXBwbGljYXRpb24oKXZvaWQiCgl0eG5hIEFwcGxpY2F0aW9uQXJncyAwCgltYXRjaCAqYWJpX3JvdXRlX2NyZWF0ZUFwcGxpY2F0aW9uCgoJLy8gdGhpcyBjb250cmFjdCBkb2VzIG5vdCBpbXBsZW1lbnQgdGhlIGdpdmVuIEFCSSBtZXRob2QgZm9yIGNyZWF0ZSBOb09wCgllcnIKCipjYWxsX05vT3A6CglwdXNoYnl0ZXMgMHhhNjhiZDI5NyAvLyBtZXRob2QgImNyZWF0ZUVtcHR5Qm94KCl2b2lkIgoJdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMAoJbWF0Y2ggKmFiaV9yb3V0ZV9jcmVhdGVFbXB0eUJveAoKCS8vIHRoaXMgY29udHJhY3QgZG9lcyBub3QgaW1wbGVtZW50IHRoZSBnaXZlbiBBQkkgbWV0aG9kIGZvciBjYWxsIE5vT3AKCWVycg==", 43 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDEw" 44 | }, 45 | "contract": { 46 | "name": "InnerBoxApp", 47 | "desc": "", 48 | "methods": [ 49 | { 50 | "name": "createEmptyBox", 51 | "args": [], 52 | "returns": { 53 | "type": "void" 54 | } 55 | }, 56 | { 57 | "name": "createApplication", 58 | "args": [], 59 | "returns": { 60 | "type": "void" 61 | } 62 | } 63 | ] 64 | } 65 | } -------------------------------------------------------------------------------- /tests/accounts/test_account_manager.py: -------------------------------------------------------------------------------- 1 | import algosdk 2 | import pytest 3 | 4 | from algokit_utils import SigningAccount 5 | from algokit_utils.algorand import AlgorandClient 6 | from algokit_utils.models.amount import AlgoAmount 7 | from tests.conftest import get_unique_name 8 | 9 | 10 | @pytest.fixture 11 | def algorand() -> AlgorandClient: 12 | return AlgorandClient.default_localnet() 13 | 14 | 15 | @pytest.fixture 16 | def funded_account(algorand: AlgorandClient) -> SigningAccount: 17 | new_account = algorand.account.random() 18 | dispenser = algorand.account.localnet_dispenser() 19 | algorand.account.ensure_funded( 20 | new_account, dispenser, AlgoAmount.from_algo(100), min_funding_increment=AlgoAmount.from_algo(1) 21 | ) 22 | algorand.set_signer(sender=new_account.address, signer=new_account.signer) 23 | return new_account 24 | 25 | 26 | def test_new_account_is_retrieved_and_funded(algorand: AlgorandClient) -> None: 27 | # Act 28 | account_name = get_unique_name() 29 | account = algorand.account.from_environment(account_name) 30 | 31 | # Assert 32 | account_info = algorand.account.get_information(account.address) 33 | assert account_info.amount > 0 34 | 35 | 36 | def test_same_account_is_subsequently_retrieved(algorand: AlgorandClient) -> None: 37 | # Arrange 38 | account_name = get_unique_name() 39 | 40 | # Act 41 | account1 = algorand.account.from_environment(account_name) 42 | account2 = algorand.account.from_environment(account_name) 43 | 44 | # Assert - accounts should be different objects but with same underlying keys 45 | assert account1 is not account2 46 | assert account1.address == account2.address 47 | assert account1.private_key == account2.private_key 48 | 49 | 50 | def test_environment_is_used_in_preference_to_kmd(algorand: AlgorandClient, monkeypatch: pytest.MonkeyPatch) -> None: 51 | # Arrange 52 | account_name = get_unique_name() 53 | account1 = algorand.account.from_environment(account_name) 54 | 55 | # Set up environment variable for second account 56 | env_account_name = "TEST_ACCOUNT" 57 | monkeypatch.setenv(f"{env_account_name}_MNEMONIC", algosdk.mnemonic.from_private_key(account1.private_key)) 58 | 59 | # Act 60 | account2 = algorand.account.from_environment(env_account_name) 61 | 62 | # Assert - accounts should be different objects but with same underlying keys 63 | assert account1 is not account2 64 | assert account1.address == account2.address 65 | assert account1.private_key == account2.private_key 66 | 67 | 68 | def test_random_account_creation(algorand: AlgorandClient) -> None: 69 | # Act 70 | account = algorand.account.random() 71 | 72 | # Assert 73 | assert account.address 74 | assert account.private_key 75 | assert len(account.public_key) == 32 76 | 77 | 78 | def test_ensure_funded_from_environment(algorand: AlgorandClient) -> None: 79 | # Arrange 80 | account = algorand.account.random() 81 | min_balance = AlgoAmount.from_algo(1) 82 | 83 | # Act 84 | result = algorand.account.ensure_funded_from_environment( 85 | account_to_fund=account.address, 86 | min_spending_balance=min_balance, 87 | ) 88 | 89 | # Assert 90 | assert result is not None 91 | assert result.amount_funded is not None 92 | account_info = algorand.account.get_information(account.address) 93 | assert account_info.amount_without_pending_rewards >= min_balance.micro_algo 94 | 95 | 96 | def test_get_account_information(algorand: AlgorandClient) -> None: 97 | # Arrange 98 | account = algorand.account.random() 99 | 100 | # Act 101 | info = algorand.account.get_information(account.address) 102 | 103 | # Assert 104 | assert info.amount is not None 105 | assert info.min_balance is not None 106 | assert info.address is not None 107 | assert info.address == account.address 108 | -------------------------------------------------------------------------------- /legacy_v2_tests/app_v1.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": { 3 | "update()void": { 4 | "call_config": { 5 | "update_application": "CALL" 6 | } 7 | }, 8 | "delete()void": { 9 | "call_config": { 10 | "delete_application": "CALL" 11 | } 12 | }, 13 | "hello(string)string": { 14 | "read_only": true, 15 | "call_config": { 16 | "no_op": "CALL" 17 | } 18 | } 19 | }, 20 | "source": { 21 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQp0eG4gTnVtQXBwQXJncwppbnRjXzAgLy8gMAo9PQpibnogbWFpbl9sOAp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweGEwZTgxODcyIC8vICJ1cGRhdGUoKXZvaWQiCj09CmJueiBtYWluX2w3CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4MjQzNzhkM2MgLy8gImRlbGV0ZSgpdm9pZCIKPT0KYm56IG1haW5fbDYKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMApwdXNoYnl0ZXMgMHgwMmJlY2UxMSAvLyAiaGVsbG8oc3RyaW5nKXN0cmluZyIKPT0KYm56IG1haW5fbDUKZXJyCm1haW5fbDU6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CnR4bmEgQXBwbGljYXRpb25BcmdzIDEKY2FsbHN1YiBoZWxsb18yCnN0b3JlIDAKcHVzaGJ5dGVzIDB4MTUxZjdjNzUgLy8gMHgxNTFmN2M3NQpsb2FkIDAKY29uY2F0CmxvZwppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sNjoKdHhuIE9uQ29tcGxldGlvbgpwdXNoaW50IDUgLy8gRGVsZXRlQXBwbGljYXRpb24KPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgZGVsZXRlXzEKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDc6CnR4biBPbkNvbXBsZXRpb24KcHVzaGludCA0IC8vIFVwZGF0ZUFwcGxpY2F0aW9uCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIHVwZGF0ZV8wCmludGNfMSAvLyAxCnJldHVybgptYWluX2w4Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CmJueiBtYWluX2wxMAplcnIKbWFpbl9sMTA6CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCj09CmFzc2VydAppbnRjXzEgLy8gMQpyZXR1cm4KCi8vIHVwZGF0ZQp1cGRhdGVfMDoKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX1VQREFUQUJMRSAvLyBUTVBMX1VQREFUQUJMRQovLyBDaGVjayBhcHAgaXMgdXBkYXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGRlbGV0ZQpkZWxldGVfMToKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX0RFTEVUQUJMRSAvLyBUTVBMX0RFTEVUQUJMRQovLyBDaGVjayBhcHAgaXMgZGVsZXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGhlbGxvCmhlbGxvXzI6CnByb3RvIDEgMQpwdXNoYnl0ZXMgMHggLy8gIiIKcHVzaGJ5dGVzIDB4NDg2NTZjNmM2ZjJjMjAgLy8gIkhlbGxvLCAiCmZyYW1lX2RpZyAtMQpleHRyYWN0IDIgMApjb25jYXQKZnJhbWVfYnVyeSAwCmZyYW1lX2RpZyAwCmxlbgppdG9iCmV4dHJhY3QgNiAwCmZyYW1lX2RpZyAwCmNvbmNhdApmcmFtZV9idXJ5IDAKcmV0c3Vi", 22 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" 23 | }, 24 | "state": { 25 | "global": { 26 | "num_byte_slices": 0, 27 | "num_uints": 0 28 | }, 29 | "local": { 30 | "num_byte_slices": 0, 31 | "num_uints": 0 32 | } 33 | }, 34 | "schema": { 35 | "global": { 36 | "declared": {}, 37 | "reserved": {} 38 | }, 39 | "local": { 40 | "declared": {}, 41 | "reserved": {} 42 | } 43 | }, 44 | "contract": { 45 | "name": "SampleApp", 46 | "methods": [ 47 | { 48 | "name": "update", 49 | "args": [], 50 | "returns": { 51 | "type": "void" 52 | } 53 | }, 54 | { 55 | "name": "delete", 56 | "args": [], 57 | "returns": { 58 | "type": "void" 59 | } 60 | }, 61 | { 62 | "name": "hello", 63 | "args": [ 64 | { 65 | "type": "string", 66 | "name": "name" 67 | } 68 | ], 69 | "returns": { 70 | "type": "string" 71 | } 72 | } 73 | ], 74 | "networks": {} 75 | }, 76 | "bare_call_config": { 77 | "no_op": "CREATE" 78 | } 79 | } -------------------------------------------------------------------------------- /legacy_v2_tests/app_v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": { 3 | "update()void": { 4 | "call_config": { 5 | "update_application": "CALL" 6 | } 7 | }, 8 | "delete()void": { 9 | "call_config": { 10 | "delete_application": "CALL" 11 | } 12 | }, 13 | "hello(string)string": { 14 | "read_only": true, 15 | "call_config": { 16 | "no_op": "CALL" 17 | } 18 | } 19 | }, 20 | "source": { 21 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQp0eG4gTnVtQXBwQXJncwppbnRjXzAgLy8gMAo9PQpibnogbWFpbl9sOAp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweGEwZTgxODcyIC8vICJ1cGRhdGUoKXZvaWQiCj09CmJueiBtYWluX2w3CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4MjQzNzhkM2MgLy8gImRlbGV0ZSgpdm9pZCIKPT0KYm56IG1haW5fbDYKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMApwdXNoYnl0ZXMgMHgwMmJlY2UxMSAvLyAiaGVsbG8oc3RyaW5nKXN0cmluZyIKPT0KYm56IG1haW5fbDUKZXJyCm1haW5fbDU6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CnR4bmEgQXBwbGljYXRpb25BcmdzIDEKY2FsbHN1YiBoZWxsb18yCnN0b3JlIDAKcHVzaGJ5dGVzIDB4MTUxZjdjNzUgLy8gMHgxNTFmN2M3NQpsb2FkIDAKY29uY2F0CmxvZwppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sNjoKdHhuIE9uQ29tcGxldGlvbgpwdXNoaW50IDUgLy8gRGVsZXRlQXBwbGljYXRpb24KPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgZGVsZXRlXzEKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDc6CnR4biBPbkNvbXBsZXRpb24KcHVzaGludCA0IC8vIFVwZGF0ZUFwcGxpY2F0aW9uCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIHVwZGF0ZV8wCmludGNfMSAvLyAxCnJldHVybgptYWluX2w4Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CmJueiBtYWluX2wxMAplcnIKbWFpbl9sMTA6CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCj09CmFzc2VydAppbnRjXzEgLy8gMQpyZXR1cm4KCi8vIHVwZGF0ZQp1cGRhdGVfMDoKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX1VQREFUQUJMRSAvLyBUTVBMX1VQREFUQUJMRQovLyBDaGVjayBhcHAgaXMgdXBkYXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGRlbGV0ZQpkZWxldGVfMToKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX0RFTEVUQUJMRSAvLyBUTVBMX0RFTEVUQUJMRQovLyBDaGVjayBhcHAgaXMgZGVsZXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGhlbGxvCmhlbGxvXzI6CnByb3RvIDEgMQpwdXNoYnl0ZXMgMHggLy8gIiIKcHVzaGJ5dGVzIDB4NDc3MjY1NjU3NDY5NmU2NzczMmMyMCAvLyAiR3JlZXRpbmdzLCAiCmZyYW1lX2RpZyAtMQpleHRyYWN0IDIgMApjb25jYXQKZnJhbWVfYnVyeSAwCmZyYW1lX2RpZyAwCmxlbgppdG9iCmV4dHJhY3QgNiAwCmZyYW1lX2RpZyAwCmNvbmNhdApmcmFtZV9idXJ5IDAKcmV0c3Vi", 22 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" 23 | }, 24 | "state": { 25 | "global": { 26 | "num_byte_slices": 0, 27 | "num_uints": 0 28 | }, 29 | "local": { 30 | "num_byte_slices": 0, 31 | "num_uints": 0 32 | } 33 | }, 34 | "schema": { 35 | "global": { 36 | "declared": {}, 37 | "reserved": {} 38 | }, 39 | "local": { 40 | "declared": {}, 41 | "reserved": {} 42 | } 43 | }, 44 | "contract": { 45 | "name": "SampleApp", 46 | "methods": [ 47 | { 48 | "name": "update", 49 | "args": [], 50 | "returns": { 51 | "type": "void" 52 | } 53 | }, 54 | { 55 | "name": "delete", 56 | "args": [], 57 | "returns": { 58 | "type": "void" 59 | } 60 | }, 61 | { 62 | "name": "hello", 63 | "args": [ 64 | { 65 | "type": "string", 66 | "name": "name" 67 | } 68 | ], 69 | "returns": { 70 | "type": "string" 71 | } 72 | } 73 | ], 74 | "networks": {} 75 | }, 76 | "bare_call_config": { 77 | "no_op": "CREATE" 78 | } 79 | } -------------------------------------------------------------------------------- /tests/artifacts/hello_world/app_spec.arc32.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": { 3 | "hello(string)string": { 4 | "call_config": { 5 | "no_op": "CALL" 6 | } 7 | } 8 | }, 9 | "source": { 10 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5hcHByb3ZhbF9wcm9ncmFtOgogICAgaW50Y2Jsb2NrIDAgMQogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmhlbGxvX3dvcmxkLmNvbnRyYWN0LkhlbGxvV29ybGQuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHByb3RvIDAgMQogICAgdHhuIE51bUFwcEFyZ3MKICAgIGJ6IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A1CiAgICBwdXNoYnl0ZXMgMHgwMmJlY2UxMSAvLyBtZXRob2QgImhlbGxvKHN0cmluZylzdHJpbmciCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyCiAgICBpbnRjXzAgLy8gMAogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjYKICAgIC8vIEBhYmltZXRob2QoKQogICAgdHhuIE9uQ29tcGxldGlvbgogICAgIQogICAgYXNzZXJ0IC8vIE9uQ29tcGxldGlvbiBpcyBOb09wCiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgYXNzZXJ0IC8vIGlzIG5vdCBjcmVhdGluZwogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDEKICAgIGV4dHJhY3QgMiAwCiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NgogICAgLy8gQGFiaW1ldGhvZCgpCiAgICBjYWxsc3ViIGhlbGxvCiAgICBkdXAKICAgIGxlbgogICAgaXRvYgogICAgZXh0cmFjdCA2IDIKICAgIHN3YXAKICAgIGNvbmNhdAogICAgcHVzaGJ5dGVzIDB4MTUxZjdjNzUKICAgIHN3YXAKICAgIGNvbmNhdAogICAgbG9nCiAgICBpbnRjXzEgLy8gMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANToKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9oZWxsb193b3JsZC9jb250cmFjdC5weTo1CiAgICAvLyBjbGFzcyBIZWxsb1dvcmxkKEFSQzRDb250cmFjdCk6CiAgICB0eG4gT25Db21wbGV0aW9uCiAgICBibnogX19wdXlhX2FyYzRfcm91dGVyX19fYWZ0ZXJfaWZfZWxzZUA5CiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgIQogICAgYXNzZXJ0IC8vIGlzIGNyZWF0aW5nCiAgICBpbnRjXzEgLy8gMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDk6CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NQogICAgLy8gY2xhc3MgSGVsbG9Xb3JsZChBUkM0Q29udHJhY3QpOgogICAgaW50Y18wIC8vIDAKICAgIHJldHN1YgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5oZWxsb193b3JsZC5jb250cmFjdC5IZWxsb1dvcmxkLmhlbGxvKG5hbWU6IGJ5dGVzKSAtPiBieXRlczoKaGVsbG86CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6Ni03CiAgICAvLyBAYWJpbWV0aG9kKCkKICAgIC8vIGRlZiBoZWxsbyhzZWxmLCBuYW1lOiBTdHJpbmcpIC0+IFN0cmluZzoKICAgIHByb3RvIDEgMQogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjgKICAgIC8vIHJldHVybiAiSGVsbG8yLCAiICsgbmFtZQogICAgcHVzaGJ5dGVzICJIZWxsbzIsICIKICAgIGZyYW1lX2RpZyAtMQogICAgY29uY2F0CiAgICByZXRzdWIK", 11 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgcHVzaGludCAxIC8vIDEKICAgIHJldHVybgo=" 12 | }, 13 | "state": { 14 | "global": { 15 | "num_byte_slices": 0, 16 | "num_uints": 0 17 | }, 18 | "local": { 19 | "num_byte_slices": 0, 20 | "num_uints": 0 21 | } 22 | }, 23 | "schema": { 24 | "global": { 25 | "declared": {}, 26 | "reserved": {} 27 | }, 28 | "local": { 29 | "declared": {}, 30 | "reserved": {} 31 | } 32 | }, 33 | "contract": { 34 | "name": "HelloWorld", 35 | "methods": [ 36 | { 37 | "name": "hello", 38 | "args": [ 39 | { 40 | "type": "string", 41 | "name": "name" 42 | } 43 | ], 44 | "readonly": false, 45 | "returns": { 46 | "type": "string" 47 | } 48 | } 49 | ], 50 | "networks": {} 51 | }, 52 | "bare_call_config": { 53 | "no_op": "CREATE" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/applications/_snapshots/test_arc56.approvals/test_arc56_from_arc32_json.approved.txt: -------------------------------------------------------------------------------- 1 | { 2 | "arcs": [], 3 | "bareActions": { 4 | "call": [], 5 | "create": [ 6 | "NoOp" 7 | ] 8 | }, 9 | "methods": [ 10 | { 11 | "actions": { 12 | "call": [ 13 | "NoOp" 14 | ], 15 | "create": [] 16 | }, 17 | "args": [ 18 | { 19 | "type": "string", 20 | "name": "name" 21 | } 22 | ], 23 | "name": "hello", 24 | "returns": { 25 | "type": "string" 26 | }, 27 | "events": [] 28 | } 29 | ], 30 | "name": "HelloWorld", 31 | "state": { 32 | "keys": { 33 | "box": {}, 34 | "global": {}, 35 | "local": {} 36 | }, 37 | "maps": { 38 | "box": {}, 39 | "global": {}, 40 | "local": {} 41 | }, 42 | "schema": { 43 | "global": { 44 | "bytes": 0, 45 | "ints": 0 46 | }, 47 | "local": { 48 | "bytes": 0, 49 | "ints": 0 50 | } 51 | } 52 | }, 53 | "structs": {}, 54 | "source": { 55 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5hcHByb3ZhbF9wcm9ncmFtOgogICAgaW50Y2Jsb2NrIDAgMQogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmhlbGxvX3dvcmxkLmNvbnRyYWN0LkhlbGxvV29ybGQuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHByb3RvIDAgMQogICAgdHhuIE51bUFwcEFyZ3MKICAgIGJ6IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A1CiAgICBwdXNoYnl0ZXMgMHgwMmJlY2UxMSAvLyBtZXRob2QgImhlbGxvKHN0cmluZylzdHJpbmciCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyCiAgICBpbnRjXzAgLy8gMAogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjYKICAgIC8vIEBhYmltZXRob2QoKQogICAgdHhuIE9uQ29tcGxldGlvbgogICAgIQogICAgYXNzZXJ0IC8vIE9uQ29tcGxldGlvbiBpcyBOb09wCiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgYXNzZXJ0IC8vIGlzIG5vdCBjcmVhdGluZwogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDEKICAgIGV4dHJhY3QgMiAwCiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NgogICAgLy8gQGFiaW1ldGhvZCgpCiAgICBjYWxsc3ViIGhlbGxvCiAgICBkdXAKICAgIGxlbgogICAgaXRvYgogICAgZXh0cmFjdCA2IDIKICAgIHN3YXAKICAgIGNvbmNhdAogICAgcHVzaGJ5dGVzIDB4MTUxZjdjNzUKICAgIHN3YXAKICAgIGNvbmNhdAogICAgbG9nCiAgICBpbnRjXzEgLy8gMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANToKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9oZWxsb193b3JsZC9jb250cmFjdC5weTo1CiAgICAvLyBjbGFzcyBIZWxsb1dvcmxkKEFSQzRDb250cmFjdCk6CiAgICB0eG4gT25Db21wbGV0aW9uCiAgICBibnogX19wdXlhX2FyYzRfcm91dGVyX19fYWZ0ZXJfaWZfZWxzZUA5CiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgIQogICAgYXNzZXJ0IC8vIGlzIGNyZWF0aW5nCiAgICBpbnRjXzEgLy8gMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDk6CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NQogICAgLy8gY2xhc3MgSGVsbG9Xb3JsZChBUkM0Q29udHJhY3QpOgogICAgaW50Y18wIC8vIDAKICAgIHJldHN1YgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5oZWxsb193b3JsZC5jb250cmFjdC5IZWxsb1dvcmxkLmhlbGxvKG5hbWU6IGJ5dGVzKSAtPiBieXRlczoKaGVsbG86CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6Ni03CiAgICAvLyBAYWJpbWV0aG9kKCkKICAgIC8vIGRlZiBoZWxsbyhzZWxmLCBuYW1lOiBTdHJpbmcpIC0+IFN0cmluZzoKICAgIHByb3RvIDEgMQogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjgKICAgIC8vIHJldHVybiAiSGVsbG8yLCAiICsgbmFtZQogICAgcHVzaGJ5dGVzICJIZWxsbzIsICIKICAgIGZyYW1lX2RpZyAtMQogICAgY29uY2F0CiAgICByZXRzdWIK", 56 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgcHVzaGludCAxIC8vIDEKICAgIHJldHVybgo=" 57 | } 58 | } -------------------------------------------------------------------------------- /tests/applications/_snapshots/test_arc56.approvals/test_arc56_from_arc32_instance.approved.txt: -------------------------------------------------------------------------------- 1 | { 2 | "arcs": [], 3 | "bareActions": { 4 | "call": [], 5 | "create": [ 6 | "NoOp" 7 | ] 8 | }, 9 | "methods": [ 10 | { 11 | "actions": { 12 | "call": [ 13 | "NoOp" 14 | ], 15 | "create": [] 16 | }, 17 | "args": [ 18 | { 19 | "type": "string", 20 | "name": "name" 21 | } 22 | ], 23 | "name": "hello", 24 | "returns": { 25 | "type": "string" 26 | }, 27 | "events": [] 28 | } 29 | ], 30 | "name": "HelloWorld", 31 | "state": { 32 | "keys": { 33 | "box": {}, 34 | "global": {}, 35 | "local": {} 36 | }, 37 | "maps": { 38 | "box": {}, 39 | "global": {}, 40 | "local": {} 41 | }, 42 | "schema": { 43 | "global": { 44 | "bytes": 0, 45 | "ints": 0 46 | }, 47 | "local": { 48 | "bytes": 0, 49 | "ints": 0 50 | } 51 | } 52 | }, 53 | "structs": {}, 54 | "source": { 55 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5hcHByb3ZhbF9wcm9ncmFtOgogICAgaW50Y2Jsb2NrIDAgMQogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmhlbGxvX3dvcmxkLmNvbnRyYWN0LkhlbGxvV29ybGQuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHByb3RvIDAgMQogICAgdHhuIE51bUFwcEFyZ3MKICAgIGJ6IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A1CiAgICBwdXNoYnl0ZXMgMHgwMmJlY2UxMSAvLyBtZXRob2QgImhlbGxvKHN0cmluZylzdHJpbmciCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyCiAgICBpbnRjXzAgLy8gMAogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjYKICAgIC8vIEBhYmltZXRob2QoKQogICAgdHhuIE9uQ29tcGxldGlvbgogICAgIQogICAgYXNzZXJ0IC8vIE9uQ29tcGxldGlvbiBpcyBOb09wCiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgYXNzZXJ0IC8vIGlzIG5vdCBjcmVhdGluZwogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHR4bmEgQXBwbGljYXRpb25BcmdzIDEKICAgIGV4dHJhY3QgMiAwCiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NgogICAgLy8gQGFiaW1ldGhvZCgpCiAgICBjYWxsc3ViIGhlbGxvCiAgICBkdXAKICAgIGxlbgogICAgaXRvYgogICAgZXh0cmFjdCA2IDIKICAgIHN3YXAKICAgIGNvbmNhdAogICAgcHVzaGJ5dGVzIDB4MTUxZjdjNzUKICAgIHN3YXAKICAgIGNvbmNhdAogICAgbG9nCiAgICBpbnRjXzEgLy8gMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19iYXJlX3JvdXRpbmdANToKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9oZWxsb193b3JsZC9jb250cmFjdC5weTo1CiAgICAvLyBjbGFzcyBIZWxsb1dvcmxkKEFSQzRDb250cmFjdCk6CiAgICB0eG4gT25Db21wbGV0aW9uCiAgICBibnogX19wdXlhX2FyYzRfcm91dGVyX19fYWZ0ZXJfaWZfZWxzZUA5CiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgIQogICAgYXNzZXJ0IC8vIGlzIGNyZWF0aW5nCiAgICBpbnRjXzEgLy8gMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDk6CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NQogICAgLy8gY2xhc3MgSGVsbG9Xb3JsZChBUkM0Q29udHJhY3QpOgogICAgaW50Y18wIC8vIDAKICAgIHJldHN1YgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5oZWxsb193b3JsZC5jb250cmFjdC5IZWxsb1dvcmxkLmhlbGxvKG5hbWU6IGJ5dGVzKSAtPiBieXRlczoKaGVsbG86CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6Ni03CiAgICAvLyBAYWJpbWV0aG9kKCkKICAgIC8vIGRlZiBoZWxsbyhzZWxmLCBuYW1lOiBTdHJpbmcpIC0+IFN0cmluZzoKICAgIHByb3RvIDEgMQogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjgKICAgIC8vIHJldHVybiAiSGVsbG8yLCAiICsgbmFtZQogICAgcHVzaGJ5dGVzICJIZWxsbzIsICIKICAgIGZyYW1lX2RpZyAtMQogICAgY29uY2F0CiAgICByZXRzdWI=", 56 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgcHVzaGludCAxIC8vIDEKICAgIHJldHVybgo=" 57 | } 58 | } -------------------------------------------------------------------------------- /docs/markdown/autoapi/algokit_utils/models/amount/index.md: -------------------------------------------------------------------------------- 1 | # algokit_utils.models.amount 2 | 3 | ## Attributes 4 | 5 | | [`ALGORAND_MIN_TX_FEE`](#algokit_utils.models.amount.ALGORAND_MIN_TX_FEE) | | 6 | |-----------------------------------------------------------------------------|----| 7 | 8 | ## Classes 9 | 10 | | [`AlgoAmount`](#algokit_utils.models.amount.AlgoAmount) | Wrapper class to ensure safe, explicit conversion between µAlgo, Algo and numbers. | 11 | |-----------------------------------------------------------|--------------------------------------------------------------------------------------| 12 | 13 | ## Functions 14 | 15 | | [`algo`](#algokit_utils.models.amount.algo)(→ AlgoAmount) | Create an AlgoAmount object representing the given number of Algo. | 16 | |-----------------------------------------------------------------------------------|--------------------------------------------------------------------------| 17 | | [`micro_algo`](#algokit_utils.models.amount.micro_algo)(→ AlgoAmount) | Create an AlgoAmount object representing the given number of µAlgo. | 18 | | [`transaction_fees`](#algokit_utils.models.amount.transaction_fees)(→ AlgoAmount) | Calculate the total transaction fees for a given number of transactions. | 19 | 20 | ## Module Contents 21 | 22 | ### *class* algokit_utils.models.amount.AlgoAmount(\*, micro_algo: int) 23 | 24 | ### *class* algokit_utils.models.amount.AlgoAmount(\*, algo: int | decimal.Decimal) 25 | 26 | Wrapper class to ensure safe, explicit conversion between µAlgo, Algo and numbers. 27 | 28 | * **Example:** 29 | ```python 30 | amount = AlgoAmount(algo=1) 31 | amount = AlgoAmount.from_algo(1) 32 | amount = AlgoAmount(micro_algo=1_000_000) 33 | amount = AlgoAmount.from_micro_algo(1_000_000) 34 | ``` 35 | 36 | #### *property* micro_algo *: int* 37 | 38 | Return the amount as a number in µAlgo. 39 | 40 | * **Returns:** 41 | The amount in µAlgo. 42 | 43 | #### *property* algo *: decimal.Decimal* 44 | 45 | Return the amount as a number in Algo. 46 | 47 | * **Returns:** 48 | The amount in Algo. 49 | 50 | #### *static* from_algo(amount: int | decimal.Decimal) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) 51 | 52 | Create an AlgoAmount object representing the given number of Algo. 53 | 54 | * **Parameters:** 55 | **amount** – The amount in Algo. 56 | * **Returns:** 57 | An AlgoAmount instance. 58 | * **Example:** 59 | ```python 60 | amount = AlgoAmount.from_algo(1) 61 | ``` 62 | 63 | #### *static* from_micro_algo(amount: int) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) 64 | 65 | Create an AlgoAmount object representing the given number of µAlgo. 66 | 67 | * **Parameters:** 68 | **amount** – The amount in µAlgo. 69 | * **Returns:** 70 | An AlgoAmount instance. 71 | * **Example:** 72 | ```python 73 | amount = AlgoAmount.from_micro_algo(1_000_000) 74 | ``` 75 | 76 | ### algokit_utils.models.amount.algo(algo: int) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) 77 | 78 | Create an AlgoAmount object representing the given number of Algo. 79 | 80 | * **Parameters:** 81 | **algo** – The number of Algo to create an AlgoAmount object for. 82 | * **Returns:** 83 | An AlgoAmount object representing the given number of Algo. 84 | 85 | ### algokit_utils.models.amount.micro_algo(micro_algo: int) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) 86 | 87 | Create an AlgoAmount object representing the given number of µAlgo. 88 | 89 | * **Parameters:** 90 | **micro_algo** – The number of µAlgo to create an AlgoAmount object for. 91 | * **Returns:** 92 | An AlgoAmount object representing the given number of µAlgo. 93 | 94 | ### algokit_utils.models.amount.ALGORAND_MIN_TX_FEE 95 | 96 | ### algokit_utils.models.amount.transaction_fees(number_of_transactions: int) → [AlgoAmount](#algokit_utils.models.amount.AlgoAmount) 97 | 98 | Calculate the total transaction fees for a given number of transactions. 99 | 100 | * **Parameters:** 101 | **number_of_transactions** – The number of transactions to calculate the fees for. 102 | * **Returns:** 103 | The total transaction fees. 104 | --------------------------------------------------------------------------------