diff --git a/examples/production_python_react/.github/workflows/production-python-react-contracts-cd.yaml b/examples/production_python_react/.github/workflows/production-python-react-contracts-cd.yaml
index d846590..b9e5ed3 100644
--- a/examples/production_python_react/.github/workflows/production-python-react-contracts-cd.yaml
+++ b/examples/production_python_react/.github/workflows/production-python-react-contracts-cd.yaml
@@ -13,7 +13,9 @@ jobs:
uses: actions/checkout@v4
- name: Install poetry
- run: pipx install poetry
+ run: |
+ pipx install poetry
+ pipx inject poetry poetry-plugin-export
- name: Set up Python 3.12
uses: actions/setup-python@v5
diff --git a/examples/production_python_react/.github/workflows/production-python-react-contracts-ci.yaml b/examples/production_python_react/.github/workflows/production-python-react-contracts-ci.yaml
index 67097ff..c2e0fce 100644
--- a/examples/production_python_react/.github/workflows/production-python-react-contracts-ci.yaml
+++ b/examples/production_python_react/.github/workflows/production-python-react-contracts-ci.yaml
@@ -11,7 +11,9 @@ jobs:
uses: actions/checkout@v4
- name: Install poetry
- run: pipx install poetry
+ run: |
+ pipx install poetry
+ pipx inject poetry poetry-plugin-export
- name: Set up Python 3.12
uses: actions/setup-python@v5
diff --git a/examples/production_python_react/projects/production_python_react-contracts/.algokit.toml b/examples/production_python_react/projects/production_python_react-contracts/.algokit.toml
index 172e5dd..febb96a 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/.algokit.toml
+++ b/examples/production_python_react/projects/production_python_react-contracts/.algokit.toml
@@ -34,7 +34,7 @@ test = { commands = [
], description = 'Run smart contract tests' }
audit = { commands = [
'poetry run pip-audit',
-], description = 'Audit with pip-audit' }
+], description = 'Audit with pip-audit. NOTE: If used with poetry >v2, make sure to install `poetry-plugin-export` as per https://github.com/python-poetry/poetry-plugin-export#installation.' }
lint = { commands = [
'poetry run black --check --diff .',
'poetry run ruff check .',
diff --git a/examples/production_python_react/projects/production_python_react-contracts/.algokit/.copier-answers.yml b/examples/production_python_react/projects/production_python_react-contracts/.algokit/.copier-answers.yml
index 25a6bfd..ce34938 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/.algokit/.copier-answers.yml
+++ b/examples/production_python_react/projects/production_python_react-contracts/.algokit/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
-_commit: 1.4.4
+_commit: 1.5.0
_src_path: gh:algorandfoundation/algokit-python-template
author_email: None
author_name: None
diff --git a/examples/production_python_react/projects/production_python_react-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 b/examples/production_python_react/projects/production_python_react-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2
index eb726b9..a2ff46d 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2
+++ b/examples/production_python_react/projects/production_python_react-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2
@@ -1,35 +1,44 @@
import logging
import algokit_utils
-from algosdk.v2client.algod import AlgodClient
-from algosdk.v2client.indexer import IndexerClient
logger = logging.getLogger(__name__)
# define deployment behaviour based on supplied app spec
-def deploy(
- algod_client: AlgodClient,
- indexer_client: IndexerClient,
- app_spec: algokit_utils.ApplicationSpecification,
- deployer: algokit_utils.Account,
-) -> None:
+def deploy() -> None:
from smart_contracts.artifacts.{{ contract_name }}.{{ contract_name }}_client import (
- {{ contract_name.split('_')|map('capitalize')|join }}Client,
+ {{ contract_name.split('_')|map('capitalize')|join }}Factory,
+ HelloArgs,
)
- app_client = {{ contract_name.split('_')|map('capitalize')|join }}Client(
- algod_client,
- creator=deployer,
- indexer_client=indexer_client,
+ algorand = algokit_utils.AlgorandClient.from_environment()
+ deployer_ = algorand.account.from_environment("DEPLOYER")
+
+ factory = algorand.client.get_typed_app_factory(
+ {{ contract_name.split('_')|map('capitalize')|join }}Factory, default_sender=deployer_.address
)
- app_client.deploy(
- on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
+
+ app_client, result = factory.deploy(
on_update=algokit_utils.OnUpdate.AppendApp,
+ on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
)
+
+ if result.operation_performed in [
+ algokit_utils.OperationPerformed.Create,
+ algokit_utils.OperationPerformed.Replace,
+ ]:
+ algorand.send.payment(
+ algokit_utils.PaymentParams(
+ amount=algokit_utils.AlgoAmount(algo=1),
+ sender=deployer_.address,
+ receiver=app_client.app_address,
+ )
+ )
+
name = "world"
- response = app_client.hello(name=name)
+ response = app_client.send.hello(args=HelloArgs(name=name))
logger.info(
- f"Called hello on {app_spec.contract.name} ({app_client.app_id}) "
- f"with name={name}, received: {response.return_value}"
+ f"Called hello on {app_client.app_name} ({app_client.app_id}) "
+ f"with name={name}, received: {response.abi_return}"
)
diff --git a/examples/production_python_react/projects/production_python_react-contracts/.vscode/settings.json b/examples/production_python_react/projects/production_python_react-contracts/.vscode/settings.json
index 2130dbb..1e7e3c0 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/.vscode/settings.json
+++ b/examples/production_python_react/projects/production_python_react-contracts/.vscode/settings.json
@@ -13,6 +13,7 @@
},
// Python
+ "python.analysis.autoImportCompletions": true,
"python.analysis.extraPaths": ["${workspaceFolder}/smart_contracts"],
"python.analysis.diagnosticSeverityOverrides": {
"reportMissingModuleSource": "none"
diff --git a/examples/production_python_react/projects/production_python_react-contracts/pyproject.toml b/examples/production_python_react/projects/production_python_react-contracts/pyproject.toml
index 55957d5..31add8d 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/pyproject.toml
+++ b/examples/production_python_react/projects/production_python_react-contracts/pyproject.toml
@@ -7,16 +7,16 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
-algokit-utils = "^2.4.0"
+algokit-utils = "^3.0.0"
python-dotenv = "^1.0.0"
algorand-python = "^2.0.0"
algorand-python-testing = "^0.4.0"
[tool.poetry.group.dev.dependencies]
-algokit-client-generator = "^1.1.3"
+algokit-client-generator = "^2.0.0"
black = {extras = ["d"], version = "*"}
-ruff = "^0.1.6"
-mypy = "1.11.0"
+ruff = "^0.9.4"
+mypy = "^1"
pytest = "*"
pytest-cov = "*"
pip-audit = "*"
@@ -28,14 +28,10 @@ build-backend = "poetry.core.masonry.api"
[tool.ruff]
line-length = 120
-select = ["E", "F", "ANN", "UP", "N", "C4", "B", "A", "YTT", "W", "FBT", "Q", "RUF", "I"]
-ignore = [
- "ANN101", # no type for self
- "ANN102", # no type for cls
-]
-unfixable = ["B", "RUF"]
+lint.select = ["E", "F", "ANN", "UP", "N", "C4", "B", "A", "YTT", "W", "FBT", "Q", "RUF", "I"]
+lint.unfixable = ["B", "RUF"]
-[tool.ruff.flake8-annotations]
+[tool.ruff.lint.flake8-annotations]
allow-star-arg-any = true
suppress-none-returning = true
diff --git a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/__main__.py b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/__main__.py
index 9c691ba..6bb9664 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/__main__.py
+++ b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/__main__.py
@@ -1,35 +1,173 @@
+import dataclasses
+import importlib
import logging
+import subprocess
import sys
+from collections.abc import Callable
from pathlib import Path
+from shutil import rmtree
+from algokit_utils.config import config
from dotenv import load_dotenv
-from smart_contracts._helpers.build import build
-from smart_contracts._helpers.config import contracts
-from smart_contracts._helpers.deploy import deploy
-
-# Uncomment the following lines to enable auto generation of AVM Debugger compliant sourcemap and simulation trace file.
+# Set trace_all to True to capture all transactions, defaults to capturing traces only on failure
# Learn more about using AlgoKit AVM Debugger to debug your TEAL source codes and inspect various kinds of
# Algorand transactions in atomic groups -> https://github.com/algorandfoundation/algokit-avm-vscode-debugger
-# from algokit_utils.config import config
-# config.configure(debug=True, trace_all=True)
+config.configure(debug=True, trace_all=False)
+
+# Set up logging and load environment variables.
logging.basicConfig(
level=logging.DEBUG, format="%(asctime)s %(levelname)-10s: %(message)s"
)
logger = logging.getLogger(__name__)
logger.info("Loading .env")
-# For manual script execution (bypassing `algokit project deploy`) with a custom .env,
-# modify `load_dotenv()` accordingly. For example, `load_dotenv('.env.localnet')`.
load_dotenv()
+
+# Determine the root path based on this file's location.
root_path = Path(__file__).parent
+# ----------------------- Contract Configuration ----------------------- #
+
+
+@dataclasses.dataclass
+class SmartContract:
+ path: Path
+ name: str
+ deploy: Callable[[], None] | None = None
+
+
+def import_contract(folder: Path) -> Path:
+ """Imports the contract from a folder if it exists."""
+ contract_path = folder / "contract.py"
+ if contract_path.exists():
+ return contract_path
+ else:
+ raise Exception(f"Contract not found in {folder}")
+
+
+def import_deploy_if_exists(folder: Path) -> Callable[[], None] | None:
+ """Imports the deploy function from a folder if it exists."""
+ try:
+ module_name = f"{folder.parent.name}.{folder.name}.deploy_config"
+ deploy_module = importlib.import_module(module_name)
+ return deploy_module.deploy # type: ignore[no-any-return, misc]
+ except ImportError:
+ return None
+
+
+def has_contract_file(directory: Path) -> bool:
+ """Checks whether the directory contains a contract.py file."""
+ return (directory / "contract.py").exists()
+
+
+# Use the current directory (root_path) as the base for contract folders and exclude
+# folders that start with '_' (internal helpers).
+contracts: list[SmartContract] = [
+ SmartContract(
+ path=import_contract(folder),
+ name=folder.name,
+ deploy=import_deploy_if_exists(folder),
+ )
+ for folder in root_path.iterdir()
+ if folder.is_dir() and has_contract_file(folder) and not folder.name.startswith("_")
+]
+
+# -------------------------- Build Logic -------------------------- #
+
+deployment_extension = "py"
+
+
+def _get_output_path(output_dir: Path, deployment_extension: str) -> Path:
+ """Constructs the output path for the generated client file."""
+ return output_dir / Path(
+ "{contract_name}"
+ + ("_client" if deployment_extension == "py" else "Client")
+ + f".{deployment_extension}"
+ )
+
+
+def build(output_dir: Path, contract_path: Path) -> Path:
+ """
+ Builds the contract by exporting (compiling) its source and generating a client.
+ If the output directory already exists, it is cleared.
+ """
+ output_dir = output_dir.resolve()
+ if output_dir.exists():
+ rmtree(output_dir)
+ output_dir.mkdir(exist_ok=True, parents=True)
+ logger.info(f"Exporting {contract_path} to {output_dir}")
+
+ build_result = subprocess.run(
+ [
+ "algokit",
+ "--no-color",
+ "compile",
+ "python",
+ str(contract_path.resolve()),
+ f"--out-dir={output_dir}",
+ "--no-output-arc32",
+ "--output-arc56",
+ "--output-source-map",
+ ],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ )
+ if build_result.returncode:
+ raise Exception(f"Could not build contract:\n{build_result.stdout}")
+
+ # Look for arc56.json files and generate the client based on them.
+ app_spec_file_names: list[str] = [
+ file.name for file in output_dir.glob("*.arc56.json")
+ ]
+
+ client_file: str | None = None
+ if not app_spec_file_names:
+ logger.warning(
+ "No '*.arc56.json' file found (likely a logic signature being compiled). Skipping client generation."
+ )
+ else:
+ for file_name in app_spec_file_names:
+ client_file = file_name
+ print(file_name)
+ generate_result = subprocess.run(
+ [
+ "algokit",
+ "generate",
+ "client",
+ str(output_dir),
+ "--output",
+ str(_get_output_path(output_dir, deployment_extension)),
+ ],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ )
+ if generate_result.returncode:
+ if "No such command" in generate_result.stdout:
+ raise Exception(
+ "Could not generate typed client, requires AlgoKit 2.0.0 or later. Please update AlgoKit"
+ )
+ else:
+ raise Exception(
+ f"Could not generate typed client:\n{generate_result.stdout}"
+ )
+ if client_file:
+ return output_dir / client_file
+ return output_dir
+
+
+# --------------------------- Main Logic --------------------------- #
+
def main(action: str, contract_name: str | None = None) -> None:
+ """Main entry point to build and/or deploy smart contracts."""
artifact_path = root_path / "artifacts"
-
- # Filter contracts if a specific contract name is provided
+ # Filter contracts based on an optional specific contract name.
filtered_contracts = [
- c for c in contracts if contract_name is None or c.name == contract_name
+ contract
+ for contract in contracts
+ if contract_name is None or contract.name == contract_name
]
match action:
@@ -44,23 +182,24 @@ def main(action: str, contract_name: str | None = None) -> None:
(
file.name
for file in output_dir.iterdir()
- if file.is_file() and file.suffixes == [".arc32", ".json"]
+ if file.is_file() and file.suffixes == [".arc56", ".json"]
),
None,
)
if app_spec_file_name is None:
- raise Exception("Could not deploy app, .arc32.json file not found")
- app_spec_path = output_dir / app_spec_file_name
+ raise Exception("Could not deploy app, .arc56.json file not found")
if contract.deploy:
logger.info(f"Deploying app {contract.name}")
- deploy(app_spec_path, contract.deploy)
+ contract.deploy()
case "all":
for contract in filtered_contracts:
logger.info(f"Building app at {contract.path}")
- app_spec_path = build(artifact_path / contract.name, contract.path)
+ build(artifact_path / contract.name, contract.path)
if contract.deploy:
- logger.info(f"Deploying {contract.path.name}")
- deploy(app_spec_path, contract.deploy)
+ logger.info(f"Deploying {contract.name}")
+ contract.deploy()
+ case _:
+ logger.error(f"Unknown action: {action}")
if __name__ == "__main__":
diff --git a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/__init__.py b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/build.py b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/build.py
deleted file mode 100644
index 3461154..0000000
--- a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/build.py
+++ /dev/null
@@ -1,76 +0,0 @@
-import logging
-import subprocess
-from pathlib import Path
-from shutil import rmtree
-
-logger = logging.getLogger(__name__)
-deployment_extension = "py"
-
-
-def _get_output_path(output_dir: Path, deployment_extension: str) -> Path:
- return output_dir / Path(
- "{contract_name}"
- + ("_client" if deployment_extension == "py" else "Client")
- + f".{deployment_extension}"
- )
-
-
-def build(output_dir: Path, contract_path: Path) -> Path:
- output_dir = output_dir.resolve()
- if output_dir.exists():
- rmtree(output_dir)
- output_dir.mkdir(exist_ok=True, parents=True)
- logger.info(f"Exporting {contract_path} to {output_dir}")
-
- build_result = subprocess.run(
- [
- "algokit",
- "--no-color",
- "compile",
- "python",
- contract_path.absolute(),
- f"--out-dir={output_dir}",
- "--output-arc32",
- "--output-source-map",
- ],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- )
- if build_result.returncode:
- raise Exception(f"Could not build contract:\n{build_result.stdout}")
-
- app_spec_file_names = [file.name for file in output_dir.glob("*.arc32.json")]
- app_spec_file_name = None
- for app_spec_file_name in app_spec_file_names:
- if app_spec_file_name is None:
- logger.warning(
- "No '*.arc32.json' file found (likely a logic signature being compiled). Skipping client generation."
- )
- continue
- print(app_spec_file_name)
- generate_result = subprocess.run(
- [
- "algokit",
- "generate",
- "client",
- output_dir,
- "--output",
- _get_output_path(output_dir, deployment_extension),
- ],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- )
- if generate_result.returncode:
- if "No such command" in generate_result.stdout:
- raise Exception(
- "Could not generate typed client, requires AlgoKit 2.0.0 or "
- "later. Please update AlgoKit"
- )
- else:
- raise Exception(
- f"Could not generate typed client:\n{generate_result.stdout}"
- )
-
- return output_dir / app_spec_file_name if app_spec_file_name else output_dir
diff --git a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/config.py b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/config.py
deleted file mode 100644
index 8f3ca93..0000000
--- a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/config.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import dataclasses
-import importlib
-from collections.abc import Callable
-from pathlib import Path
-
-from algokit_utils import Account, ApplicationSpecification
-from algosdk.v2client.algod import AlgodClient
-from algosdk.v2client.indexer import IndexerClient
-
-
-@dataclasses.dataclass
-class SmartContract:
- path: Path
- name: str
- deploy: (
- Callable[[AlgodClient, IndexerClient, ApplicationSpecification, Account], None]
- | None
- ) = None
-
-
-def import_contract(folder: Path) -> Path:
- """Imports the contract from a folder if it exists."""
- contract_path = folder / "contract.py"
- if contract_path.exists():
- return contract_path
- else:
- raise Exception(f"Contract not found in {folder}")
-
-
-def import_deploy_if_exists(
- folder: Path,
-) -> (
- Callable[[AlgodClient, IndexerClient, ApplicationSpecification, Account], None]
- | None
-):
- """Imports the deploy function from a folder if it exists."""
- try:
- deploy_module = importlib.import_module(
- f"{folder.parent.name}.{folder.name}.deploy_config"
- )
- return deploy_module.deploy # type: ignore
- except ImportError:
- return None
-
-
-def has_contract_file(directory: Path) -> bool:
- """Checks whether the directory contains contract.py file."""
- return (directory / "contract.py").exists()
-
-
-# define contracts to build and/or deploy
-base_dir = Path("smart_contracts")
-contracts = [
- SmartContract(
- path=import_contract(folder),
- name=folder.name,
- deploy=import_deploy_if_exists(folder),
- )
- for folder in base_dir.iterdir()
- if folder.is_dir() and has_contract_file(folder)
-]
diff --git a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/deploy.py b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/deploy.py
deleted file mode 100644
index 10185a9..0000000
--- a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/_helpers/deploy.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# mypy: disable-error-code="no-untyped-call, misc"
-
-
-import logging
-from collections.abc import Callable
-from pathlib import Path
-
-from algokit_utils import (
- Account,
- ApplicationSpecification,
- EnsureBalanceParameters,
- ensure_funded,
- get_account,
- get_algod_client,
- get_indexer_client,
-)
-from algosdk.util import algos_to_microalgos
-from algosdk.v2client.algod import AlgodClient
-from algosdk.v2client.indexer import IndexerClient
-
-logger = logging.getLogger(__name__)
-
-
-def deploy(
- app_spec_path: Path,
- deploy_callback: Callable[
- [AlgodClient, IndexerClient, ApplicationSpecification, Account], None
- ],
- deployer_initial_funds: int = 2,
-) -> None:
- # get clients
- # by default client configuration is loaded from environment variables
- algod_client = get_algod_client()
- indexer_client = get_indexer_client()
-
- # get app spec
- app_spec = ApplicationSpecification.from_json(app_spec_path.read_text())
-
- # get deployer account by name
- deployer = get_account(algod_client, "DEPLOYER", fund_with_algos=0)
-
- minimum_funds_micro_algos = algos_to_microalgos(deployer_initial_funds)
- ensure_funded(
- algod_client,
- EnsureBalanceParameters(
- account_to_fund=deployer,
- min_spending_balance_micro_algos=minimum_funds_micro_algos,
- min_funding_increment_micro_algos=minimum_funds_micro_algos,
- ),
- )
-
- # use provided callback to deploy the app
- deploy_callback(algod_client, indexer_client, app_spec, deployer)
diff --git a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/hello_world/deploy_config.py b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/hello_world/deploy_config.py
index 36dda16..364edf6 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/hello_world/deploy_config.py
+++ b/examples/production_python_react/projects/production_python_react-contracts/smart_contracts/hello_world/deploy_config.py
@@ -1,36 +1,44 @@
import logging
import algokit_utils
-from algosdk.v2client.algod import AlgodClient
-from algosdk.v2client.indexer import IndexerClient
logger = logging.getLogger(__name__)
# define deployment behaviour based on supplied app spec
-def deploy(
- algod_client: AlgodClient,
- indexer_client: IndexerClient,
- app_spec: algokit_utils.ApplicationSpecification,
- deployer: algokit_utils.Account,
-) -> None:
+def deploy() -> None:
from smart_contracts.artifacts.hello_world.hello_world_client import (
- HelloWorldClient,
+ HelloArgs,
+ HelloWorldFactory,
)
- app_client = HelloWorldClient(
- algod_client,
- creator=deployer,
- indexer_client=indexer_client,
+ algorand = algokit_utils.AlgorandClient.from_environment()
+ deployer_ = algorand.account.from_environment("DEPLOYER")
+
+ factory = algorand.client.get_typed_app_factory(
+ HelloWorldFactory, default_sender=deployer_.address
)
- app_client.deploy(
- on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
+ app_client, result = factory.deploy(
on_update=algokit_utils.OnUpdate.AppendApp,
+ on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
)
+
+ if result.operation_performed in [
+ algokit_utils.OperationPerformed.Create,
+ algokit_utils.OperationPerformed.Replace,
+ ]:
+ algorand.send.payment(
+ algokit_utils.PaymentParams(
+ amount=algokit_utils.AlgoAmount(algo=1),
+ sender=deployer_.address,
+ receiver=app_client.app_address,
+ )
+ )
+
name = "world"
- response = app_client.hello(name=name)
+ response = app_client.send.hello(args=HelloArgs(name=name))
logger.info(
- f"Called hello on {app_spec.contract.name} ({app_client.app_id}) "
- f"with name={name}, received: {response.return_value}"
+ f"Called hello on {app_client.app_name} ({app_client.app_id}) "
+ f"with name={name}, received: {response.abi_return}"
)
diff --git a/examples/production_python_react/projects/production_python_react-contracts/tests/conftest.py b/examples/production_python_react/projects/production_python_react-contracts/tests/conftest.py
index aec2485..c15f645 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/tests/conftest.py
+++ b/examples/production_python_react/projects/production_python_react-contracts/tests/conftest.py
@@ -1,11 +1,6 @@
import pytest
-from algokit_utils import (
- get_algod_client,
- get_default_localnet_config,
- get_indexer_client,
-)
-from algosdk.v2client.algod import AlgodClient
-from algosdk.v2client.indexer import IndexerClient
+from algokit_utils import AlgorandClient
+from algokit_utils.config import config
# Uncomment if you want to load network specific or generic .env file
# @pytest.fixture(autouse=True, scope="session")
@@ -13,14 +8,13 @@
# env_path = Path(__file__).parent.parent / ".env"
# load_dotenv(env_path)
-
-@pytest.fixture(scope="session")
-def algod_client() -> AlgodClient:
- # by default we are using localnet algod
- client = get_algod_client(get_default_localnet_config("algod"))
- return client
+config.configure(
+ debug=True,
+ # trace_all=True, # uncomment to trace all transactions
+)
@pytest.fixture(scope="session")
-def indexer_client() -> IndexerClient:
- return get_indexer_client(get_default_localnet_config("indexer"))
+def algorand_client() -> AlgorandClient:
+ # by default we are using localnet algod
+ return AlgorandClient.from_environment()
diff --git a/examples/production_python_react/projects/production_python_react-contracts/tests/hello_world_client_test.py b/examples/production_python_react/projects/production_python_react-contracts/tests/hello_world_client_test.py
index 1c8ebff..6a9c447 100644
--- a/examples/production_python_react/projects/production_python_react-contracts/tests/hello_world_client_test.py
+++ b/examples/production_python_react/projects/production_python_react-contracts/tests/hello_world_client_test.py
@@ -1,29 +1,35 @@
import algokit_utils
import pytest
-from algokit_utils import get_localnet_default_account
-from algokit_utils.config import config
-from algosdk.v2client.algod import AlgodClient
-from algosdk.v2client.indexer import IndexerClient
-
-from smart_contracts.artifacts.hello_world.hello_world_client import HelloWorldClient
+from algokit_utils import (
+ AlgoAmount,
+ AlgorandClient,
+ SigningAccount,
+)
+
+from smart_contracts.artifacts.hello_world.hello_world_client import (
+ HelloWorldClient,
+ HelloWorldFactory,
+)
+
+
+@pytest.fixture()
+def deployer(algorand_client: AlgorandClient) -> SigningAccount:
+ account = algorand_client.account.from_environment("DEPLOYER")
+ algorand_client.account.ensure_funded_from_environment(
+ account_to_fund=account.address, min_spending_balance=AlgoAmount.from_algo(10)
+ )
+ return account
-@pytest.fixture(scope="session")
+@pytest.fixture()
def hello_world_client(
- algod_client: AlgodClient, indexer_client: IndexerClient
+ algorand_client: AlgorandClient, deployer: SigningAccount
) -> HelloWorldClient:
- config.configure(
- debug=True,
- # trace_all=True,
- )
-
- client = HelloWorldClient(
- algod_client,
- creator=get_localnet_default_account(algod_client),
- indexer_client=indexer_client,
+ factory = algorand_client.client.get_typed_app_factory(
+ HelloWorldFactory, default_sender=deployer.address
)
- client.deploy(
+ client, _ = factory.deploy(
on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
on_update=algokit_utils.OnUpdate.AppendApp,
)
@@ -31,18 +37,19 @@ def hello_world_client(
def test_says_hello(hello_world_client: HelloWorldClient) -> None:
- result = hello_world_client.hello(name="World")
-
- assert result.return_value == "Hello, World"
+ result = hello_world_client.send.hello(args=("World",))
+ assert result.abi_return == "Hello, World"
def test_simulate_says_hello_with_correct_budget_consumed(
- hello_world_client: HelloWorldClient, algod_client: AlgodClient
+ hello_world_client: HelloWorldClient,
) -> None:
result = (
- hello_world_client.compose().hello(name="World").hello(name="Jane").simulate()
+ hello_world_client.new_group()
+ .hello(args=("World",))
+ .hello(args=("Jane",))
+ .simulate()
)
-
- assert result.abi_results[0].return_value == "Hello, World"
- assert result.abi_results[1].return_value == "Hello, Jane"
+ assert result.returns[0].value == "Hello, World"
+ assert result.returns[1].value == "Hello, Jane"
assert result.simulate_response["txn-groups"][0]["app-budget-consumed"] < 100
diff --git a/examples/production_python_react/projects/production_python_react-frontend/.algokit/.copier-answers.yml b/examples/production_python_react/projects/production_python_react-frontend/.algokit/.copier-answers.yml
index 1c668c5..3283fc8 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/.algokit/.copier-answers.yml
+++ b/examples/production_python_react/projects/production_python_react-frontend/.algokit/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
-_commit: 1.0.12
+_commit: 1.0.13
_src_path: gh:algorandfoundation/algokit-react-frontend-template
author_email: None
author_name: None
diff --git a/examples/production_python_react/projects/production_python_react-frontend/.env.template b/examples/production_python_react/projects/production_python_react-frontend/.env.template
index e05d499..eb4b229 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/.env.template
+++ b/examples/production_python_react/projects/production_python_react-frontend/.env.template
@@ -9,7 +9,7 @@ VITE_ENVIRONMENT=local
VITE_ALGOD_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
VITE_ALGOD_SERVER=http://localhost
VITE_ALGOD_PORT=4001
-VITE_ALGOD_NETWORK=""
+VITE_ALGOD_NETWORK=localnet
# Indexer
VITE_INDEXER_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
diff --git a/examples/production_python_react/projects/production_python_react-frontend/package.json b/examples/production_python_react/projects/production_python_react-frontend/package.json
index 8601ca3..fdbb66a 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/package.json
+++ b/examples/production_python_react/projects/production_python_react-frontend/package.json
@@ -12,7 +12,7 @@
"npm": ">=9.0"
},
"devDependencies": {
- "@algorandfoundation/algokit-client-generator": "^4.0.0",
+ "@algorandfoundation/algokit-client-generator": "^4.0.6",
"@types/node": "^18.17.14",
"@types/react": "^18.2.11",
"@types/react-dom": "^18.2.4",
@@ -35,12 +35,12 @@
"vite-plugin-node-polyfills": "^0.22.0"
},
"dependencies": {
- "@algorandfoundation/algokit-utils": "^7.0.0",
- "@blockshake/defly-connect": "1.1.6",
- "@daffiwallet/connect": "^1.0.3",
- "@perawallet/connect": "1.3.5",
- "@txnlab/use-wallet": "^2.8.2",
- "algosdk": ">=2.9.0 <3.0",
+ "@algorandfoundation/algokit-utils": "^8.1.0",
+ "@blockshake/defly-connect": "^1.2.1",
+ "@perawallet/connect": "^1.4.1",
+ "@txnlab/use-wallet": "^4.0.0",
+ "@txnlab/use-wallet-react": "^4.0.0",
+ "algosdk": "^3.0.0",
"daisyui": "^4.0.0",
"notistack": "^3.0.1",
"react": "^18.2.0",
diff --git a/examples/production_python_react/projects/production_python_react-frontend/src/App.tsx b/examples/production_python_react/projects/production_python_react-frontend/src/App.tsx
index 58feddf..f346006 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/src/App.tsx
+++ b/examples/production_python_react/projects/production_python_react-frontend/src/App.tsx
@@ -1,33 +1,26 @@
-import { DeflyWalletConnect } from '@blockshake/defly-connect'
-import { DaffiWalletConnect } from '@daffiwallet/connect'
-import { PeraWalletConnect } from '@perawallet/connect'
-import { PROVIDER_ID, ProvidersArray, WalletProvider, useInitializeProviders } from '@txnlab/use-wallet'
-import algosdk from 'algosdk'
+import { SupportedWallet, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react'
import { SnackbarProvider } from 'notistack'
import Home from './Home'
import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
-let providersArray: ProvidersArray
-if (import.meta.env.VITE_ALGOD_NETWORK === '') {
+let supportedWallets: SupportedWallet[]
+if (import.meta.env.VITE_ALGOD_NETWORK === 'localnet') {
const kmdConfig = getKmdConfigFromViteEnvironment()
- providersArray = [
+ supportedWallets = [
{
- id: PROVIDER_ID.KMD,
- clientOptions: {
- wallet: kmdConfig.wallet,
- password: kmdConfig.password,
- host: kmdConfig.server,
+ id: WalletId.KMD,
+ options: {
+ baseServer: kmdConfig.server,
token: String(kmdConfig.token),
port: String(kmdConfig.port),
},
},
]
} else {
- providersArray = [
- { id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect },
- { id: PROVIDER_ID.PERA, clientStatic: PeraWalletConnect },
- { id: PROVIDER_ID.DAFFI, clientStatic: DaffiWalletConnect },
- { id: PROVIDER_ID.EXODUS },
+ supportedWallets = [
+ { id: WalletId.DEFLY },
+ { id: WalletId.PERA },
+ { id: WalletId.EXODUS },
// If you are interested in WalletConnect v2 provider
// refer to https://github.com/TxnLab/use-wallet for detailed integration instructions
]
@@ -36,20 +29,26 @@ if (import.meta.env.VITE_ALGOD_NETWORK === '') {
export default function App() {
const algodConfig = getAlgodConfigFromViteEnvironment()
- const walletProviders = useInitializeProviders({
- providers: providersArray,
- nodeConfig: {
- network: algodConfig.network,
- nodeServer: algodConfig.server,
- nodePort: String(algodConfig.port),
- nodeToken: String(algodConfig.token),
+ const walletManager = new WalletManager({
+ wallets: supportedWallets,
+ defaultNetwork: algodConfig.network,
+ networks: {
+ [algodConfig.network]: {
+ algod: {
+ baseServer: algodConfig.server,
+ port: algodConfig.port,
+ token: String(algodConfig.token),
+ },
+ },
+ },
+ options: {
+ resetNetwork: true,
},
- algosdkStatic: algosdk,
})
return (
-
+
diff --git a/examples/production_python_react/projects/production_python_react-frontend/src/Home.tsx b/examples/production_python_react/projects/production_python_react-frontend/src/Home.tsx
index 192f3b9..68313ee 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/src/Home.tsx
+++ b/examples/production_python_react/projects/production_python_react-frontend/src/Home.tsx
@@ -1,5 +1,5 @@
// src/components/Home.tsx
-import { useWallet } from '@txnlab/use-wallet'
+import { useWallet } from '@txnlab/use-wallet-react'
import React, { useState } from 'react'
import ConnectWallet from './components/ConnectWallet'
import Transact from './components/Transact'
diff --git a/examples/production_python_react/projects/production_python_react-frontend/src/components/Account.tsx b/examples/production_python_react/projects/production_python_react-frontend/src/components/Account.tsx
index 8324312..081ce47 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/src/components/Account.tsx
+++ b/examples/production_python_react/projects/production_python_react-frontend/src/components/Account.tsx
@@ -1,4 +1,4 @@
-import { useWallet } from '@txnlab/use-wallet'
+import { useWallet } from '@txnlab/use-wallet-react'
import { useMemo } from 'react'
import { ellipseAddress } from '../utils/ellipseAddress'
import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs'
diff --git a/examples/production_python_react/projects/production_python_react-frontend/src/components/AppCalls.tsx b/examples/production_python_react/projects/production_python_react-frontend/src/components/AppCalls.tsx
index 1667fbc..1268994 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/src/components/AppCalls.tsx
+++ b/examples/production_python_react/projects/production_python_react-frontend/src/components/AppCalls.tsx
@@ -1,4 +1,4 @@
-import { useWallet } from '@txnlab/use-wallet'
+import { useWallet } from '@txnlab/use-wallet-react'
import { useSnackbar } from 'notistack'
import { useState } from 'react'
import { HelloWorldFactory } from '../contracts/HelloWorld'
@@ -15,7 +15,7 @@ const AppCalls = ({ openModal, setModalState }: AppCallsInterface) => {
const [loading, setLoading] = useState(false)
const [contractInput, setContractInput] = useState('')
const { enqueueSnackbar } = useSnackbar()
- const { signer, activeAddress } = useWallet()
+ const { transactionSigner, activeAddress } = useWallet()
const algodConfig = getAlgodConfigFromViteEnvironment()
const indexerConfig = getIndexerConfigFromViteEnvironment()
@@ -23,7 +23,7 @@ const AppCalls = ({ openModal, setModalState }: AppCallsInterface) => {
algodConfig,
indexerConfig,
})
- algorand.setDefaultSigner(signer)
+ algorand.setDefaultSigner(transactionSigner)
const sendAppCall = async () => {
setLoading(true)
@@ -34,7 +34,7 @@ const AppCalls = ({ openModal, setModalState }: AppCallsInterface) => {
// Given the simplicity of the starter contract, we are deploying it on the frontend
// for demonstration purposes.
const factory = new HelloWorldFactory({
- defaultSender: activeAddress,
+ defaultSender: activeAddress ?? undefined,
algorand,
})
const deployResult = await factory
diff --git a/examples/production_python_react/projects/production_python_react-frontend/src/components/ConnectWallet.tsx b/examples/production_python_react/projects/production_python_react-frontend/src/components/ConnectWallet.tsx
index c4225bc..8ff01dc 100644
--- a/examples/production_python_react/projects/production_python_react-frontend/src/components/ConnectWallet.tsx
+++ b/examples/production_python_react/projects/production_python_react-frontend/src/components/ConnectWallet.tsx
@@ -1,4 +1,4 @@
-import { Provider, useWallet } from '@txnlab/use-wallet'
+import { useWallet, Wallet, WalletId } from '@txnlab/use-wallet-react'
import Account from './Account'
interface ConnectWalletInterface {
@@ -7,9 +7,9 @@ interface ConnectWalletInterface {
}
const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
- const { providers, activeAddress } = useWallet()
+ const { wallets, activeAddress } = useWallet()
- const isKmd = (provider: Provider) => provider.metadata.name.toLowerCase() === 'kmd'
+ const isKmd = (wallet: Wallet) => wallet.id === WalletId.KMD
return (