From 7344544284f604bc73928ceb882738351514ba51 Mon Sep 17 00:00:00 2001 From: Al Date: Wed, 19 Feb 2025 19:53:48 +0100 Subject: [PATCH] chore: algokit utils v3 migration (#611) * chore: algokit utils v3 migration * chore: temporarily set portability test algokit init against pr branch * chore: tmp disable audit * chore: restore tests against main branch --- .github/actions/setup-poetry/action.yaml | 2 + .github/workflows/check-python.yaml | 11 +- poetry.lock | 209 ++++++++--------------- pyproject.toml | 10 +- src/algokit/__main__.py | 1 - src/algokit/cli/tasks/assets.py | 30 +++- src/algokit/cli/tasks/mint.py | 14 +- src/algokit/cli/tasks/transfer.py | 56 +++--- src/algokit/cli/tasks/utils.py | 51 +++--- src/algokit/core/project/deploy.py | 10 +- src/algokit/core/tasks/mint/mint.py | 6 +- src/algokit/core/utils.py | 27 ++- tests/tasks/conftest.py | 4 +- tests/tasks/test_asset.py | 39 ++++- tests/tasks/test_transfer.py | 98 ++++++++++- 15 files changed, 326 insertions(+), 242 deletions(-) diff --git a/.github/actions/setup-poetry/action.yaml b/.github/actions/setup-poetry/action.yaml index 9b26c6078..e74b2eefc 100644 --- a/.github/actions/setup-poetry/action.yaml +++ b/.github/actions/setup-poetry/action.yaml @@ -9,6 +9,7 @@ runs: - if: ${{ runner.os == 'macOS' && runner.arch == 'ARM64' }} run: | pip install poetry + pip install poetry-plugin-export shell: bash - if: ${{ runner.os != 'macOS' || runner.arch != 'ARM64' }} @@ -16,6 +17,7 @@ runs: pip install --user pipx pipx ensurepath pipx install poetry ${{ runner.os == 'macOS' && '--python "$Python_ROOT_DIR/bin/python"' || '' }} + pipx inject poetry poetry-plugin-export shell: bash - name: Get full Python version diff --git a/.github/workflows/check-python.yaml b/.github/workflows/check-python.yaml index 5d82519ab..3544c9685 100644 --- a/.github/workflows/check-python.yaml +++ b/.github/workflows/check-python.yaml @@ -24,12 +24,17 @@ jobs: - name: Audit with pip-audit run: | # audit non dev dependencies, no exclusions - poetry export --without=dev > requirements.txt && poetry run pip-audit -r requirements.txt - + poetry export --without=dev > requirements.txt + poetry run pip-audit -r requirements.txt --ignore-vuln 'GHSA-79v4-65xg-pq4g' # audit all dependencies, with exclusions. # If a vulnerability is found in a dev dependency without an available fix, # it can be temporarily ignored by adding --ignore-vuln e.g. - poetry run pip-audit + # TODO: decide on `GHSA-79v4-65xg-pq4g`, see https://osv.dev/vulnerability/GHSA-79v4-65xg-pq4g + # The vulnerability is not applicable to the cli case, the only abstraciton leveraged is `RSAPublicKey` in + # vendored src/algokit/core/_vendor/auth0/authentication/token_verifier.py that was added to remove dependency + # on auth0 package that caused many adhoc transitive dependency errors in cli. As a result, consequent cryptography + # vulnerabilities need to be a) verified for applicability to cli case and ignored if not applicable or b) fixed by + # updating the vendored file to use the latest version of `cryptography` that has the fix. - name: Check formatting with Ruff run: | diff --git a/poetry.lock b/poetry.lock index ccde5272e..f472a3df9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "alabaster" @@ -13,18 +13,18 @@ files = [ [[package]] name = "algokit-utils" -version = "2.3.1" +version = "3.0.1" description = "Utilities for Algorand development for use by AlgoKit" optional = false python-versions = "<4.0,>=3.10" files = [ - {file = "algokit_utils-2.3.1-py3-none-any.whl", hash = "sha256:a3bbbbe3cc9eb04a343b762a8dab9970232080facc8ec3fc74f8d3d942315ec1"}, + {file = "algokit_utils-3.0.1-py3-none-any.whl", hash = "sha256:b93bc7ed000f3aeb2ea83b470c7545b4c78a9898a202e3da1f15b6b678bc5db7"}, ] [package.dependencies] -deprecated = ">=1.2.14,<2.0.0" httpx = ">=0.23.1,<0.24.0" py-algorand-sdk = ">=2.4.0,<3.0.0" +typing-extensions = ">=4.6.0" [[package]] name = "allpairspy" @@ -569,38 +569,38 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "43.0.1" +version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, - {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, - {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, - {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, - {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, - {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, - {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] @@ -613,7 +613,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -649,23 +649,6 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] -[[package]] -name = "deprecated" -version = "1.2.14" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] - [[package]] name = "distlib" version = "0.3.8" @@ -2092,19 +2075,19 @@ setuptools = ">=42.0.0" [[package]] name = "pyjwt" -version = "2.9.0" +version = "2.8.0" description = "JSON Web Token implementation in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, - {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, + {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, + {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] @@ -2232,6 +2215,25 @@ pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytest-sugar" +version = "1.0.0" +description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." +optional = false +python-versions = "*" +files = [ + {file = "pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a"}, + {file = "pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd"}, +] + +[package.dependencies] +packaging = ">=21.3" +pytest = ">=6.2.0" +termcolor = ">=2.1.0" + +[package.extras] +dev = ["black", "flake8", "pre-commit"] + [[package]] name = "pytest-xdist" version = "3.6.1" @@ -2833,6 +2835,20 @@ sphinx = "*" unify = "*" yapf = "*" +[[package]] +name = "termcolor" +version = "2.5.0" +description = "ANSI color formatting for output in terminal" +optional = false +python-versions = ">=3.9" +files = [ + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "toml" version = "0.10.2" @@ -3033,85 +3049,6 @@ files = [ [package.extras] test = ["pytest (>=6.0.0)", "setuptools (>=65)"] -[[package]] -name = "wrapt" -version = "1.16.0" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.6" -files = [ - {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, - {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, - {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, - {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, - {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, - {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, - {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, - {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, - {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, - {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, - {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, - {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, - {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, - {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, - {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, - {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, - {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, - {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, - {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, - {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, - {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, - {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, - {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, - {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, - {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, - {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, - {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, - {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, - {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, - {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, - {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, - {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, - {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, - {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, - {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, - {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, - {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, -] - [[package]] name = "yapf" version = "0.40.2" @@ -3150,4 +3087,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.14" -content-hash = "840963bc632603993284f2794a74fdead782e8c244f89182d2a3e3f034611c0b" +content-hash = "745e3c8f88e795e75acc93716727f4cd3423accd286d771d15015e546c357a3b" diff --git a/pyproject.toml b/pyproject.toml index 2d3487ce4..f7ebdabb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,9 +18,12 @@ tomli = { version = "^2.0.1", python = "<3.11" } python-dotenv = "^1.0.0" mslex = "^1.1.0" keyring = "25.2.1" -pyjwt = "^2.8.0" -cryptography = "^43.0.1" # pyjwt has a weak dependency on cryptography -algokit-utils = "^2.3.0" +# pyjwt is locked to version ^2.8.0 because its explicitly +# vendored from auth0 repo, to reduce depedency on auth0 package that caused many adhoc transitive dependency errors in cli +# see header in src/algokit/core/_vendor/auth0/authentication/token_verifier.py +pyjwt = "^2.8.0" +cryptography = "^43.0.1" # pyjwt has a weak dependency on cryptography and explicitly requires it in the vendored file, hence the lock +algokit-utils = "^3.0.0" multiformats = "0.3.1" multiformats_config = "0.3.1" # pinned this to be in lockstep with multiformats jsondiff = "^2.0.0" @@ -44,6 +47,7 @@ sphinxnotes-markdown-builder = "^0.5.6" poethepoet = ">=0.17.1,<0.27.0" gfm-toc = "^0.0.7" pytest-xdist = "^3.4.0" +pytest-sugar = "^1.0.0" [build-system] requires = ["poetry-core"] diff --git a/src/algokit/__main__.py b/src/algokit/__main__.py index 91df6dec1..c0f3b299e 100644 --- a/src/algokit/__main__.py +++ b/src/algokit/__main__.py @@ -2,6 +2,5 @@ from algokit.cli import algokit -# Required to support full feature parity when running in binary execution mode freeze_support() algokit() diff --git a/src/algokit/cli/tasks/assets.py b/src/algokit/cli/tasks/assets.py index cc03d730f..c68f803a5 100644 --- a/src/algokit/cli/tasks/assets.py +++ b/src/algokit/cli/tasks/assets.py @@ -1,7 +1,6 @@ import logging import click -from algokit_utils import opt_in, opt_out from algosdk import error from algosdk.v2client.algod import AlgodClient @@ -14,6 +13,7 @@ validate_account_balance_to_opt_in, validate_address, ) +from algokit.core.utils import get_algorand_client_for_network logger = logging.getLogger(__name__) @@ -55,19 +55,24 @@ def opt_in_command(asset_ids: tuple[int], account: str, network: AlgorandNetwork opt_in_account = get_account_with_private_key(account) validate_address(opt_in_account.address) algod_client = load_algod_client(network) + algorand = get_algorand_client_for_network(network) validate_account_balance_to_opt_in(algod_client, opt_in_account, len(asset_ids_list)) try: click.echo("Performing opt-in. This may take a few seconds...") - response = opt_in(algod_client=algod_client, account=opt_in_account, asset_ids=asset_ids_list) + response = algorand.asset.bulk_opt_in( + account=opt_in_account.address, + asset_ids=asset_ids_list, + signer=opt_in_account.signer, + ) click.echo("Successfully performed opt-in.") if len(response) > 1: account_url = get_explorer_url(opt_in_account.address, network, ExplorerEntityType.ADDRESS) click.echo(f"Check latest transactions on your account at: {account_url}") else: - for asset_id, txn_id in response.items(): - explorer_url = get_explorer_url(txn_id, network, ExplorerEntityType.ASSET) - click.echo(f"Check opt-in status for asset {asset_id} at: {explorer_url}") + for asset_opt_int_result in response: + explorer_url = get_explorer_url(asset_opt_int_result.transaction_id, network, ExplorerEntityType.ASSET) + click.echo(f"Check opt-in status for asset {asset_opt_int_result.asset_id} at: {explorer_url}") except error.AlgodHTTPError as err: raise click.ClickException(str(err)) from err except ValueError as err: @@ -106,6 +111,7 @@ def opt_out_command(*, asset_ids: tuple[int], account: str, network: AlgorandNet opt_out_account = get_account_with_private_key(account) validate_address(opt_out_account.address) algod_client = load_algod_client(network) + algorand = get_algorand_client_for_network(network) asset_ids_list = [] try: asset_ids_list = _get_zero_balanced_assets( @@ -119,15 +125,21 @@ def opt_out_command(*, asset_ids: tuple[int], account: str, network: AlgorandNet raise click.ClickException("No assets to opt-out of.") click.echo("Performing opt-out. This may take a few seconds...") - response = opt_out(algod_client=algod_client, account=opt_out_account, asset_ids=asset_ids_list) + response = algorand.asset.bulk_opt_out( + account=opt_out_account.address, + asset_ids=asset_ids_list, + signer=opt_out_account.signer, + ) click.echo("Successfully performed opt-out.") if len(response) > 1: account_url = get_explorer_url(opt_out_account.address, network, ExplorerEntityType.ADDRESS) click.echo(f"Check latest transactions on your account at: {account_url}") else: - asset_id, txn_id = response.popitem() - transaction_url = get_explorer_url(txn_id, network, ExplorerEntityType.TRANSACTION) - click.echo(f"Check opt-in status for asset {asset_id} at: {transaction_url}") + asset_opt_out_result = response[0] + transaction_url = get_explorer_url( + asset_opt_out_result.transaction_id, network, ExplorerEntityType.TRANSACTION + ) + click.echo(f"Check opt-in status for asset {asset_opt_out_result.asset_id} at: {transaction_url}") except error.AlgodHTTPError as err: raise click.ClickException(str(err)) from err except ConnectionRefusedError as err: diff --git a/src/algokit/cli/tasks/mint.py b/src/algokit/cli/tasks/mint.py index 0c101b8ad..8bee91750 100644 --- a/src/algokit/cli/tasks/mint.py +++ b/src/algokit/cli/tasks/mint.py @@ -1,12 +1,12 @@ import json import logging import math +from decimal import Decimal from pathlib import Path import click -from algokit_utils import Account +from algokit_utils import AlgoAmount, SigningAccount from algosdk.error import AlgodHTTPError -from algosdk.util import algos_to_microalgos from algokit.cli.common.constants import AlgorandNetwork, ExplorerEntityType from algokit.cli.common.utils import get_explorer_url @@ -31,7 +31,7 @@ MAX_UNIT_NAME_BYTE_LENGTH = 8 MAX_ASSET_NAME_BYTE_LENGTH = 32 -ASSET_MINTING_MBR = 0.2 # Algos, 0.1 for base account, 0.1 for asset creation +ASSET_MINTING_MBR = Decimal(0.2) # Algos, 0.1 for base account, 0.1 for asset creation def _validate_supply(total: int, decimals: int) -> None: @@ -115,7 +115,7 @@ def _get_and_validate_asset_name(context: click.Context, param: click.Parameter, ) -def _get_creator_account(_: click.Context, __: click.Parameter, value: str) -> Account: +def _get_creator_account(_: click.Context, __: click.Parameter, value: str) -> SigningAccount: """ Validate the creator account by checking if it is a valid Algorand address. @@ -124,7 +124,7 @@ def _get_creator_account(_: click.Context, __: click.Parameter, value: str) -> A value (str): The value of the parameter. Returns: - Account: An account object with the address and private key. + SigningAccount: An account object with the address and private key. """ try: return get_account_with_private_key(value) @@ -284,7 +284,7 @@ def _validate_supply_for_nft(context: click.Context, _: click.Parameter, value: ) def mint( # noqa: PLR0913 *, - creator: Account, + creator: SigningAccount, asset_name: str, unit_name: str, total: int, @@ -304,7 +304,7 @@ def mint( # noqa: PLR0913 client, creator, 0, - algos_to_microalgos(ASSET_MINTING_MBR), # type: ignore[no-untyped-call] + AlgoAmount.from_algo(ASSET_MINTING_MBR).micro_algo, ) token_metadata = TokenMetadata.from_json_file(token_metadata_path, asset_name, decimals) diff --git a/src/algokit/cli/tasks/transfer.py b/src/algokit/cli/tasks/transfer.py index da50d5f45..a8d971bc9 100644 --- a/src/algokit/cli/tasks/transfer.py +++ b/src/algokit/cli/tasks/transfer.py @@ -1,15 +1,7 @@ import logging -from typing import TYPE_CHECKING import click -from algokit_utils import ( - TransferAssetParameters, - TransferParameters, - transfer_asset, -) -from algokit_utils import ( - transfer as transfer_algos, -) +from algokit_utils import AlgoAmount, AssetTransferParams, PaymentParams, SendAtomicTransactionComposerResults from algokit.cli.common.constants import AlgorandNetwork, ExplorerEntityType from algokit.cli.common.utils import get_explorer_url @@ -21,9 +13,7 @@ validate_address, validate_balance, ) - -if TYPE_CHECKING: - from algosdk.transaction import AssetTransferTxn, PaymentTxn +from algokit.core.utils import get_algorand_client_for_network logger = logging.getLogger(__name__) @@ -95,29 +85,39 @@ def transfer( # noqa: PLR0913 validate_balance(algod_client, receiver_address, asset_id) # Transfer algos or assets depending on asset_id - txn_response: PaymentTxn | AssetTransferTxn | None = None + txn_response: SendAtomicTransactionComposerResults | None = None + algorand = get_algorand_client_for_network(network) try: if asset_id == 0: - txn_response = transfer_algos( - algod_client, - TransferParameters(to_address=receiver_address, from_account=sender_account, micro_algos=amount), + txn_response = ( + algorand.new_group() + .add_payment( + PaymentParams( + sender=sender_account.address, + receiver=receiver_address, + amount=AlgoAmount(micro_algo=amount), + signer=sender_account.signer, + ) + ) + .send() ) else: - txn_response = transfer_asset( - algod_client, - TransferAssetParameters( - from_account=sender_account, - to_address=receiver_address, - amount=amount, - asset_id=asset_id, - ), + txn_response = ( + algorand.new_group() + .add_asset_transfer( + AssetTransferParams( + sender=sender_account.address, + receiver=receiver_address, + amount=amount, + asset_id=asset_id, + signer=sender_account.signer, + ), + ) + .send() ) - if not txn_response: - raise click.ClickException("Failed to perform transfer") - txn_url = get_explorer_url( - identifier=txn_response.get_txid(), # type: ignore[no-untyped-call] + identifier=txn_response.tx_ids[0], network=network, entity_type=ExplorerEntityType.TRANSACTION, ) diff --git a/src/algokit/cli/tasks/utils.py b/src/algokit/cli/tasks/utils.py index 65dbb33dc..e563cc104 100644 --- a/src/algokit/cli/tasks/utils.py +++ b/src/algokit/cli/tasks/utils.py @@ -5,19 +5,14 @@ import stat import sys from collections.abc import Callable +from decimal import Decimal from functools import wraps from typing import Any import algosdk import algosdk.encoding import click -from algokit_utils import ( - Account, - get_algod_client, - get_algonode_config, - get_default_localnet_config, -) -from algosdk.util import algos_to_microalgos, microalgos_to_algos +from algokit_utils import AlgoAmount, ClientManager, SigningAccount from algokit.cli.common.constants import AlgorandNetwork from algokit.core.tasks.wallet import get_alias @@ -29,7 +24,7 @@ def _validate_asset_balance(account_info: dict, asset_id: int, decimals: int, am asset_record = next((asset for asset in account_info.get("assets", []) if asset["asset-id"] == asset_id), None) if not asset_record: - raise click.ClickException("Account is not opted into the asset") + raise click.ClickException("SigningAccount is not opted into the asset") if amount > 0 and asset_record["amount"] < amount: required = amount / 10**decimals @@ -41,10 +36,10 @@ def _validate_asset_balance(account_info: dict, asset_id: int, decimals: int, am def _validate_algo_balance(account_info: dict, amount: int) -> None: if account_info.get("amount", 0) < amount: - required = microalgos_to_algos(amount) # type: ignore[no-untyped-call] - available = microalgos_to_algos(account_info.get("amount", 0)) # type: ignore[no-untyped-call] + required = AlgoAmount.from_micro_algo(amount) + available = AlgoAmount.from_micro_algo(account_info.get("amount", 0)) raise click.ClickException( - f"Insufficient Algos balance in account, required: {required} Algos, available: {available} Algos" + f"Insufficient Algos balance in account, required: {required.algo} Algos, available: {available.algo} Algos" ) @@ -94,12 +89,12 @@ def load_algod_client(network: AlgorandNetwork) -> algosdk.v2client.algod.AlgodC """ config_mapping = { - AlgorandNetwork.LOCALNET: get_default_localnet_config("algod"), - AlgorandNetwork.TESTNET: get_algonode_config("testnet", "algod", ""), - AlgorandNetwork.MAINNET: get_algonode_config("mainnet", "algod", ""), + AlgorandNetwork.LOCALNET: ClientManager.get_default_localnet_config("algod"), + AlgorandNetwork.TESTNET: ClientManager.get_algonode_config("testnet", "algod"), + AlgorandNetwork.MAINNET: ClientManager.get_algonode_config("mainnet", "algod"), } try: - return get_algod_client(config_mapping[network]) + return ClientManager.get_algod_client(config_mapping[network]) except KeyError as err: raise click.ClickException("Invalid network") from err @@ -137,7 +132,7 @@ def get_asset_decimals(asset_id: int, algod_client: algosdk.v2client.algod.Algod def validate_balance( - algod_client: algosdk.v2client.algod.AlgodClient, account: Account | str, asset_id: int, amount: int = 0 + algod_client: algosdk.v2client.algod.AlgodClient, account: SigningAccount | str, asset_id: int, amount: int = 0 ) -> None: """ Validates the balance of an account before an operation. @@ -145,14 +140,14 @@ def validate_balance( Args: algod_client (algosdk.v2client.algod.AlgodClient): The AlgodClient object for interacting with the Algorand blockchain. - account (Account | str): The account object. + account (SigningAccount | str): The account object. asset_id (int): The ID of the asset to be checked (0 for Algos). amount (int): The amount of Algos or asset for the operation. Defaults to 0 implying opt-in check only. Raises: click.ClickException: If any validation check fails. """ - address = account.address if isinstance(account, Account) else account + address = account.address if isinstance(account, SigningAccount) else account account_info = algod_client.account_info(address) if not isinstance(account_info, dict): @@ -183,7 +178,7 @@ def validate_address(address: str) -> None: raise click.ClickException(f"`{address}` is an invalid account address") -def get_account_with_private_key(address: str) -> Account: +def get_account_with_private_key(address: str) -> SigningAccount: """ Retrieves an account object with the private key based on the provided address. @@ -191,7 +186,7 @@ def get_account_with_private_key(address: str) -> Account: address (str): The address for which to retrieve the account object. Returns: - Account: An account object with the address and private key. + SigningAccount: An account object with the address and private key. Raises: click.ClickException: If the address is not valid or if the alias does not exist or does not have a private key. @@ -202,7 +197,7 @@ def get_account_with_private_key(address: str) -> Account: try: validate_address(parsed_address) pk = get_private_key_from_mnemonic() - return Account(address=parsed_address, private_key=pk) + return SigningAccount(address=parsed_address, private_key=pk) except click.ClickException as ex: alias_data = get_alias(parsed_address) @@ -211,7 +206,7 @@ def get_account_with_private_key(address: str) -> Account: if not alias_data.private_key: raise click.ClickException(f"Alias `{parsed_address}` does not have a private key.") from ex - return Account(address=alias_data.address, private_key=alias_data.private_key) + return SigningAccount(address=alias_data.address, private_key=alias_data.private_key) def get_address(address: str) -> str: @@ -263,7 +258,7 @@ def stdin_has_content() -> bool: def validate_account_balance_to_opt_in( - algod_client: algosdk.v2client.algod.AlgodClient, account: Account, num_assets: int + algod_client: algosdk.v2client.algod.AlgodClient, account: SigningAccount, num_assets: int ) -> None: """ Validates the balance of an account before opt in operation. @@ -272,24 +267,24 @@ def validate_account_balance_to_opt_in( Args: algod_client (algosdk.v2client.algod.AlgodClient): The AlgodClient object for interacting with the Algorand blockchain. - account (Account | str): The account object. + account (SigningAccount | str): The account object. num_assets (int): The number of the assets for opt in (0 for Algos). Raises: click.ClickException: If there is an insufficient fund in the account or account is not valid. """ - address = account.address if isinstance(account, Account) else account + address = account.address if isinstance(account, SigningAccount) else account account_info = algod_client.account_info(address) if not isinstance(account_info, dict): raise click.ClickException("Invalid account info response") - required_microalgos = num_assets * algos_to_microalgos(0.1) # type: ignore[no-untyped-call] + required_microalgos = num_assets * AlgoAmount.from_algo(Decimal(0.1)).micro_algo available_microalgos = account_info.get("amount", 0) if available_microalgos < required_microalgos: - required_algo = microalgos_to_algos(required_microalgos) # type: ignore[no-untyped-call] - available_algos = microalgos_to_algos(available_microalgos) # type: ignore[no-untyped-call] + required_algo = AlgoAmount.from_micro_algo(required_microalgos).algo + available_algos = AlgoAmount.from_micro_algo(available_microalgos).algo raise click.ClickException( f"Insufficient Algos balance in account to opt in, required: {required_algo} Algos, available:" f" {available_algos} Algos" diff --git a/src/algokit/core/project/deploy.py b/src/algokit/core/project/deploy.py index 5eeeeb358..f74db49b7 100644 --- a/src/algokit/core/project/deploy.py +++ b/src/algokit/core/project/deploy.py @@ -4,7 +4,7 @@ import click import dotenv -from algokit_utils import get_algonode_config +from algokit_utils import ClientManager from algokit.core.conf import ALGOKIT_CONFIG, get_algokit_config from algokit.core.sandbox import ( @@ -26,10 +26,10 @@ class _KnownEnvironments: TESTNET = "testnet" -DEFAULT_MAINNET_ALGOD_SERVER = get_algonode_config("mainnet", config="algod", token="").server -DEFAULT_TESTNET_ALGOD_SERVER = get_algonode_config("testnet", config="algod", token="").server -DEFAULT_MAINNET_INDEXER_SERVER = get_algonode_config("mainnet", config="indexer", token="").server -DEFAULT_TESTNET_INDEXER_SERVER = get_algonode_config("testnet", config="indexer", token="").server +DEFAULT_MAINNET_ALGOD_SERVER = ClientManager.get_algonode_config("mainnet", "algod").server +DEFAULT_TESTNET_ALGOD_SERVER = ClientManager.get_algonode_config("testnet", "algod").server +DEFAULT_MAINNET_INDEXER_SERVER = ClientManager.get_algonode_config("mainnet", "indexer").server +DEFAULT_TESTNET_INDEXER_SERVER = ClientManager.get_algonode_config("testnet", "indexer").server _ENVIRONMENT_CONFIG: dict[str, dict[str, str | None]] = { diff --git a/src/algokit/core/tasks/mint/mint.py b/src/algokit/core/tasks/mint/mint.py index d30acd1cb..979dc3b9e 100644 --- a/src/algokit/core/tasks/mint/mint.py +++ b/src/algokit/core/tasks/mint/mint.py @@ -7,7 +7,7 @@ import re from dataclasses import asdict -from algokit_utils import Account +from algokit_utils import SigningAccount from algosdk import encoding, transaction from algosdk.transaction import wait_for_confirmation from algosdk.v2client import algod @@ -145,7 +145,7 @@ def mint_token( # noqa: PLR0913 *, client: algod.AlgodClient, jwt: str, - creator_account: Account, + creator_account: SigningAccount, unit_name: str, total: int, token_metadata: TokenMetadata, @@ -158,7 +158,7 @@ def mint_token( # noqa: PLR0913 Args: client (algod.AlgodClient): An instance of the `algod.AlgodClient` class representing the Algorand node. jwt (str): The JWT for accessing the Piñata API. - creator_account (Account): An instance of the `Account` class representing the account that + creator_account (SigningAccount): An instance of the `SigningAccount` class representing the account that will create the token. asset_name (str): A string representing the name of the token. unit_name (str): A string representing the unit name of the token. diff --git a/src/algokit/core/utils.py b/src/algokit/core/utils.py index 8de1d8162..119ef59bf 100644 --- a/src/algokit/core/utils.py +++ b/src/algokit/core/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import platform import re @@ -6,18 +8,24 @@ import sys import threading import time -from collections.abc import Callable, Iterator +from functools import cache from itertools import cycle from os import environ from pathlib import Path from shutil import which -from typing import Any +from typing import TYPE_CHECKING, Any import click import dotenv +from algokit_utils import AlgorandClient from algokit.core import proc +if TYPE_CHECKING: + from collections.abc import Callable, Iterator + + from algokit.cli.common.constants import AlgorandNetwork + CLEAR_LINE = "\033[K" SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] @@ -275,3 +283,18 @@ def alphanumeric_sort_key(s: str) -> list[int | str]: For instance, ensures that "name_digit_1" comes before "name_digit_2". """ return [int(text) if text.isdigit() else text.lower() for text in re.split("([0-9]+)", s)] + + +@cache +def get_algorand_client_for_network(network: AlgorandNetwork) -> AlgorandClient: + from algokit.cli.common.constants import AlgorandNetwork + + match network: + case AlgorandNetwork.MAINNET: + return AlgorandClient.mainnet() + case AlgorandNetwork.TESTNET: + return AlgorandClient.testnet() + case AlgorandNetwork.LOCALNET: + return AlgorandClient.default_localnet() + case _: + raise ValueError(f"Unsupported network: {network}") diff --git a/tests/tasks/conftest.py b/tests/tasks/conftest.py index 093ece2ba..5ffeacbbd 100644 --- a/tests/tasks/conftest.py +++ b/tests/tasks/conftest.py @@ -1,4 +1,4 @@ -from algokit_utils import Account +from algokit_utils import SigningAccount from algosdk import transaction DUMMY_SUGGESTED_PARAMS = transaction.SuggestedParams( # type: ignore[no-untyped-call] @@ -11,7 +11,7 @@ flat_fee=True, consensus_version="https://github.com/algorandfoundation/specs/tree/abd3d4823c6f77349fc04c3af7b1e99fe4df699f", ) -DUMMY_ACCOUNT = Account( +DUMMY_ACCOUNT = SigningAccount( private_key="iLsfFiRDwi0ijFdvdyO1PGkYxooOanbJSgpJ4pPKjKZluk70pvuPX4dYD1Jir85uZP+AImM/8SBmdPRpBSTFAg==", address="MW5E55FG7OHV7B2YB5JGFL6ONZSP7ABCMM77CIDGOT2GSBJEYUBOF3UYKA", ) diff --git a/tests/tasks/test_asset.py b/tests/tasks/test_asset.py index a26bb1a8a..076da2724 100644 --- a/tests/tasks/test_asset.py +++ b/tests/tasks/test_asset.py @@ -1,6 +1,7 @@ import json from algokit.core.tasks.wallet import WALLET_ALIASES_KEYRING_USERNAME +from algokit_utils import BulkAssetOptInOutResult from algosdk import account, mnemonic from pytest_mock import MockerFixture @@ -34,7 +35,11 @@ def test_opt_in_invalid_network() -> None: def test_opt_in_to_assets_from_account_address_successful(mocker: MockerFixture) -> None: - mocker.patch("algokit.cli.tasks.assets.opt_in", return_value={123: "dummy_txn_id"}) + algorand_mock = mocker.MagicMock() + algorand_mock.asset.bulk_opt_in.return_value = [ + BulkAssetOptInOutResult(asset_id=123, transaction_id="dummy_txn_id") + ] + algorand_mock = mocker.patch("algokit.cli.tasks.assets.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.assets.validate_address") mocker.patch("algokit.cli.tasks.assets.validate_account_balance_to_opt_in") dummy_account_pk, dummy_account_address = _generate_account() @@ -49,7 +54,11 @@ def test_opt_in_to_assets_from_account_address_successful(mocker: MockerFixture) def test_opt_in_of_assets_from_account_alias_successful(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None: - mocker.patch("algokit.cli.tasks.assets.opt_in", return_value={123: "dummy_txn_id"}) + algorand_mock = mocker.MagicMock() + algorand_mock.asset.bulk_opt_in.return_value = [ + BulkAssetOptInOutResult(asset_id=123, transaction_id="dummy_txn_id") + ] + algorand_mock = mocker.patch("algokit.cli.tasks.assets.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.assets.validate_address") mocker.patch("algokit.cli.tasks.assets.validate_account_balance_to_opt_in") dummy_account_pk, dummy_account_address = _generate_account() @@ -70,7 +79,9 @@ def test_opt_in_of_assets_from_account_alias_successful(mocker: MockerFixture, m def test_opt_in_to_assets_from_account_address_failed(mocker: MockerFixture) -> None: - mocker.patch("algokit.cli.tasks.assets.opt_in", side_effect=Exception("dummy error")) + algorand_mock = mocker.MagicMock() + algorand_mock.asset.bulk_opt_in.side_effect = Exception("dummy error") + algorand_mock = mocker.patch("algokit.cli.tasks.assets.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.assets.validate_address") mocker.patch("algokit.cli.tasks.assets.validate_account_balance_to_opt_in") dummy_account_pk, dummy_account_address = _generate_account() @@ -101,7 +112,11 @@ def test_opt_out_invalid_network() -> None: def test_opt_out_of_assets_from_account_address_successful(mocker: MockerFixture) -> None: - mocker.patch("algokit.cli.tasks.assets.opt_out", return_value={123: "dummy_txn_id"}) + algorand_mock = mocker.MagicMock() + algorand_mock.asset.bulk_opt_out.return_value = [ + BulkAssetOptInOutResult(asset_id=123, transaction_id="dummy_txn_id") + ] + algorand_mock = mocker.patch("algokit.cli.tasks.assets.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.assets.validate_address") dummy_account_pk, dummy_account_address = _generate_account() asset_id = 123 @@ -117,8 +132,12 @@ def test_opt_out_of_assets_from_account_address_successful(mocker: MockerFixture def test_opt_out_of_all_assets_from_account_address_successful(mocker: MockerFixture) -> None: dummy_account_info = {"assets": [{"asset-id": 1, "amount": 0}]} mocker.patch("algokit.cli.tasks.assets.get_account_info", return_value=dummy_account_info) + algorand_mock = mocker.MagicMock() + algorand_mock.asset.bulk_opt_out.return_value = [ + BulkAssetOptInOutResult(asset_id=123, transaction_id="dummy_txn_id") + ] + algorand_mock = mocker.patch("algokit.cli.tasks.assets.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.assets.validate_address") - mocker.patch("algokit.cli.tasks.assets.opt_out", return_value={123: "dummy_txn_id"}) dummy_account_pk, dummy_account_address = _generate_account() result = invoke( f"task opt-out -a {dummy_account_address} --network localnet --all", @@ -130,7 +149,11 @@ def test_opt_out_of_all_assets_from_account_address_successful(mocker: MockerFix def test_opt_out_of_assets_from_account_alias_successful(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None: - mocker.patch("algokit.cli.tasks.assets.opt_out", return_value={123: "dummy_txn_id"}) + algorand_mock = mocker.MagicMock() + algorand_mock.asset.bulk_opt_out.return_value = [ + BulkAssetOptInOutResult(asset_id=123, transaction_id="dummy_txn_id") + ] + algorand_mock = mocker.patch("algokit.cli.tasks.assets.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.assets.validate_address") dummy_account_pk, dummy_account_address = _generate_account() @@ -150,7 +173,9 @@ def test_opt_out_of_assets_from_account_alias_successful(mocker: MockerFixture, def test_opt_out_assets_from_account_address_failed(mocker: MockerFixture) -> None: - mocker.patch("algokit.cli.tasks.assets.opt_out", side_effect=Exception("dummy error")) + algorand_mock = mocker.MagicMock() + algorand_mock.asset.bulk_opt_out.side_effect = Exception("dummy error") + algorand_mock = mocker.patch("algokit.cli.tasks.assets.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.assets.validate_address") dummy_account_pk, dummy_account_address = _generate_account() asset_id = 123 diff --git a/tests/tasks/test_transfer.py b/tests/tasks/test_transfer.py index 5802dc023..64e11139c 100644 --- a/tests/tasks/test_transfer.py +++ b/tests/tasks/test_transfer.py @@ -2,6 +2,7 @@ import pytest from algokit.core.tasks.wallet import WALLET_ALIASES_KEYRING_USERNAME +from algokit_utils import SendAtomicTransactionComposerResults from algosdk import account, mnemonic from pytest_mock import MockerFixture @@ -77,7 +78,18 @@ def test_transfer_no_amount() -> None: def test_transfer_algo_from_address_successful(mocker: MockerFixture) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_algos", return_value=TransactionMock()) + algorand_mock = mocker.MagicMock() + composer_mock = mocker.MagicMock() + composer_mock.add_payment.return_value = composer_mock + composer_mock.send.return_value = SendAtomicTransactionComposerResults( + group_id="dummy_group_id", + confirmations=[], + tx_ids=["dummy_txid"], + transactions=[], + returns=[], + ) + algorand_mock.new_group.return_value = composer_mock + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account() @@ -96,7 +108,18 @@ def test_transfer_algo_from_address_successful(mocker: MockerFixture) -> None: def test_transfer_algo_from_alias_successful(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_algos", return_value=TransactionMock()) + algorand_mock = mocker.MagicMock() + composer_mock = mocker.MagicMock() + composer_mock.add_payment.return_value = composer_mock + composer_mock.send.return_value = SendAtomicTransactionComposerResults( + group_id="dummy_group_id", + confirmations=[], + tx_ids=["dummy_txid"], + transactions=[], + returns=[], + ) + algorand_mock.new_group.return_value = composer_mock + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account() @@ -121,7 +144,18 @@ def test_transfer_algo_from_alias_successful(mocker: MockerFixture, mock_keyring def test_transfer_asset_from_address_successful(mocker: MockerFixture) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_asset", return_value=TransactionMock()) + algorand_mock = mocker.MagicMock() + composer_mock = mocker.MagicMock() + composer_mock.add_asset_transfer.return_value = composer_mock + composer_mock.send.return_value = SendAtomicTransactionComposerResults( + group_id="dummy_group_id", + confirmations=[], + tx_ids=["dummy_txid"], + transactions=[], + returns=[], + ) + algorand_mock.new_group.return_value = composer_mock + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account() @@ -140,7 +174,18 @@ def test_transfer_asset_from_address_successful(mocker: MockerFixture) -> None: def test_transfer_asset_from_address_to_alias_successful(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_asset", return_value=TransactionMock()) + algorand_mock = mocker.MagicMock() + composer_mock = mocker.MagicMock() + composer_mock.add_asset_transfer.return_value = composer_mock + composer_mock.send.return_value = SendAtomicTransactionComposerResults( + group_id="dummy_group_id", + confirmations=[], + tx_ids=["dummy_txid"], + transactions=[], + returns=[], + ) + algorand_mock.new_group.return_value = composer_mock + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account() @@ -165,7 +210,18 @@ def test_transfer_asset_from_address_to_alias_successful(mocker: MockerFixture, def test_transfer_asset_from_alias_successful(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_asset", return_value=TransactionMock()) + algorand_mock = mocker.MagicMock() + composer_mock = mocker.MagicMock() + composer_mock.add_asset_transfer.return_value = composer_mock + composer_mock.send.return_value = SendAtomicTransactionComposerResults( + group_id="dummy_group_id", + confirmations=[], + tx_ids=["dummy_txid"], + transactions=[], + returns=[], + ) + algorand_mock.new_group.return_value = composer_mock + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account() @@ -190,7 +246,11 @@ def test_transfer_asset_from_alias_successful(mocker: MockerFixture, mock_keyrin def test_transfer_failed(mocker: MockerFixture, mock_keyring: dict[str, str]) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_algos", side_effect=Exception("dummy error")) + algorand_mock = mocker.MagicMock() + algorand_mock.new_group.return_value = mocker.MagicMock( + add_payment=mocker.MagicMock(side_effect=Exception("dummy error")) + ) + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account() @@ -215,7 +275,18 @@ def test_transfer_failed(mocker: MockerFixture, mock_keyring: dict[str, str]) -> def test_transfer_on_testnet(mocker: MockerFixture) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_algos", return_value=TransactionMock()) + algorand_mock = mocker.MagicMock() + composer_mock = mocker.MagicMock() + composer_mock.add_payment.return_value = composer_mock + composer_mock.send.return_value = SendAtomicTransactionComposerResults( + group_id="dummy_group_id", + confirmations=[], + tx_ids=["dummy_txid"], + transactions=[], + returns=[], + ) + algorand_mock.new_group.return_value = composer_mock + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account() @@ -234,7 +305,18 @@ def test_transfer_on_testnet(mocker: MockerFixture) -> None: def test_transfer_on_mainnet(mocker: MockerFixture) -> None: # Arrange - mocker.patch("algokit.cli.tasks.transfer.transfer_algos", return_value=TransactionMock()) + algorand_mock = mocker.MagicMock() + composer_mock = mocker.MagicMock() + composer_mock.add_payment.return_value = composer_mock + composer_mock.send.return_value = SendAtomicTransactionComposerResults( + group_id="dummy_group_id", + confirmations=[], + tx_ids=["dummy_txid"], + transactions=[], + returns=[], + ) + algorand_mock.new_group.return_value = composer_mock + mocker.patch("algokit.cli.tasks.transfer.get_algorand_client_for_network", return_value=algorand_mock) mocker.patch("algokit.cli.tasks.transfer.validate_address") mocker.patch("algokit.cli.tasks.transfer.validate_balance") dummy_sender_pk, dummy_sender_address = _generate_account()