diff --git a/template_content/.algokit/generators/create_contract/smart_contracts/{% raw %}{{ contract_name }}{% endraw %}/{% if deployment_language == 'typescript' %}deploy-config.ts.j2{% endif %}.jinja b/template_content/.algokit/generators/create_contract/smart_contracts/{% raw %}{{ contract_name }}{% endraw %}/{% if deployment_language == 'typescript' %}deploy-config.ts.j2{% endif %}.jinja index 08a15408..dddaad11 100644 --- a/template_content/.algokit/generators/create_contract/smart_contracts/{% raw %}{{ contract_name }}{% endraw %}/{% if deployment_language == 'typescript' %}deploy-config.ts.j2{% endif %}.jinja +++ b/template_content/.algokit/generators/create_contract/smart_contracts/{% raw %}{{ contract_name }}{% endraw %}/{% if deployment_language == 'typescript' %}deploy-config.ts.j2{% endif %}.jinja @@ -8,10 +8,7 @@ export async function deploy() { const algod = algokit.getAlgoClient() const indexer = algokit.getAlgoIndexerClient() - const deployer = await algokit.getAccount( - { config: algokit.getAccountConfigFromEnvironment('DEPLOYER'), fundWith: algokit.algos(3) }, - algod, - ) + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) await algokit.ensureFunded( { accountToFund: deployer, @@ -20,7 +17,6 @@ export async function deploy() { }, algod, ) - const isMainNet = await algokit.isMainNet(algod) const appClient = new {{ contract_name.split('_')|map('capitalize')|join }}Client( { resolveBy: 'creatorAndName', @@ -37,6 +33,7 @@ export async function deploy() { onUpdate: 'append', }) {% elif preset_name == 'production' %} + const isMainNet = await algokit.isMainNet(algod) const app = await appClient.deploy({ allowDelete: !isMainNet, allowUpdate: !isMainNet, diff --git a/template_content/smart_contracts/{{ contract_name }}/{% if deployment_language == 'typescript' %}deploy-config.ts{% endif %}.jinja b/template_content/smart_contracts/{{ contract_name }}/{% if deployment_language == 'typescript' %}deploy-config.ts{% endif %}.jinja index abd1b188..325fb24a 100644 --- a/template_content/smart_contracts/{{ contract_name }}/{% if deployment_language == 'typescript' %}deploy-config.ts{% endif %}.jinja +++ b/template_content/smart_contracts/{{ contract_name }}/{% if deployment_language == 'typescript' %}deploy-config.ts{% endif %}.jinja @@ -7,10 +7,7 @@ export async function deploy() { const algod = algokit.getAlgoClient() const indexer = algokit.getAlgoIndexerClient() - const deployer = await algokit.getAccount( - { config: algokit.getAccountConfigFromEnvironment('DEPLOYER'), fundWith: algokit.algos(3) }, - algod, - ) + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) await algokit.ensureFunded( { accountToFund: deployer, @@ -19,7 +16,6 @@ export async function deploy() { }, algod, ) - const isMainNet = await algokit.isMainNet(algod) const appClient = new {{ contract_name.split('_')|map('capitalize')|join }}Client( { resolveBy: 'creatorAndName', @@ -36,6 +32,7 @@ export async function deploy() { onUpdate: 'append', }) {% elif preset_name == 'production' %} + const isMainNet = await algokit.isMainNet(algod) const app = await appClient.deploy({ allowDelete: !isMainNet, allowUpdate: !isMainNet, diff --git a/template_content/{% if deployment_language == 'typescript' %}package.json{% endif %}.jinja b/template_content/{% if deployment_language == 'typescript' %}package.json{% endif %}.jinja index 228b5dac..927b682e 100644 --- a/template_content/{% if deployment_language == 'typescript' %}package.json{% endif %}.jinja +++ b/template_content/{% if deployment_language == 'typescript' %}package.json{% endif %}.jinja @@ -8,8 +8,12 @@ "deploy:ci": "ts-node --transpile-only -r dotenv/config smart_contracts/index.ts", "format": "prettier --write ." }, + "engines": { + "node": ">=18.0" + }, "dependencies": { - "@algorandfoundation/algokit-utils": "^2.3.1" + "@algorandfoundation/algokit-utils": "^3.0.0", + "algosdk": "^2.5.0" }, "devDependencies": { "dotenv": "^16.0.3", diff --git a/tests/test_generators.py b/tests/test_generators.py index ec6d7117..4996189b 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -179,7 +179,14 @@ def test_smart_contract_generator_default_starter_preset( ) -> None: test_name = f"test_smart_contract_generator_default_starter_preset_{language}" - response = run_init(working_dir, test_name) + response = run_init( + working_dir, + test_name, + answers={ + "preset_name": "starter", + "deployment_language": language, + }, + ) assert response.returncode == 0, response.stdout response = run_generator( @@ -203,7 +210,14 @@ def test_smart_contract_generator_default_production_preset( ) -> None: test_name = f"test_smart_contract_generator_default_production_preset_{language}" - response = run_init(working_dir, test_name, answers={"preset_name": "production"}) + response = run_init( + working_dir, + test_name, + answers={ + "preset_name": "production", + "deployment_language": language, + }, + ) assert response.returncode == 0, response.stdout response = run_generator( diff --git a/tests_generated/test_deployment_language-typescript/package.json b/tests_generated/test_deployment_language-typescript/package.json index 228b5dac..927b682e 100644 --- a/tests_generated/test_deployment_language-typescript/package.json +++ b/tests_generated/test_deployment_language-typescript/package.json @@ -8,8 +8,12 @@ "deploy:ci": "ts-node --transpile-only -r dotenv/config smart_contracts/index.ts", "format": "prettier --write ." }, + "engines": { + "node": ">=18.0" + }, "dependencies": { - "@algorandfoundation/algokit-utils": "^2.3.1" + "@algorandfoundation/algokit-utils": "^3.0.0", + "algosdk": "^2.5.0" }, "devDependencies": { "dotenv": "^16.0.3", diff --git a/tests_generated/test_deployment_language-typescript/smart_contracts/hello_world/deploy-config.ts b/tests_generated/test_deployment_language-typescript/smart_contracts/hello_world/deploy-config.ts index 9eb348c3..527fe2c6 100644 --- a/tests_generated/test_deployment_language-typescript/smart_contracts/hello_world/deploy-config.ts +++ b/tests_generated/test_deployment_language-typescript/smart_contracts/hello_world/deploy-config.ts @@ -7,10 +7,7 @@ export async function deploy() { const algod = algokit.getAlgoClient() const indexer = algokit.getAlgoIndexerClient() - const deployer = await algokit.getAccount( - { config: algokit.getAccountConfigFromEnvironment('DEPLOYER'), fundWith: algokit.algos(3) }, - algod, - ) + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) await algokit.ensureFunded( { accountToFund: deployer, @@ -19,7 +16,6 @@ export async function deploy() { }, algod, ) - const isMainNet = await algokit.isMainNet(algod) const appClient = new HelloWorldClient( { resolveBy: 'creatorAndName', diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit.toml b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit.toml index 6ffa3b4c..17861919 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit.toml +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit.toml @@ -2,7 +2,7 @@ min_version = "v1.4.0" [deploy] -command = "poetry run python -m smart_contracts deploy" +command = "npm run deploy:ci" environment_secrets = [ "DEPLOYER_MNEMONIC", ] diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy-config.ts.j2 b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy-config.ts.j2 new file mode 100644 index 00000000..325fb24a --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy-config.ts.j2 @@ -0,0 +1,59 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { {{ contract_name.split('_')|map('capitalize')|join }}Client } from '../artifacts/{{ contract_name }}/client' + +// Below is a showcase of various deployment options you can use in TypeScript Client +export async function deploy() { + console.log('=== Deploying {{ contract_name.split('_')|map('capitalize')|join }} ===') + + const algod = algokit.getAlgoClient() + const indexer = algokit.getAlgoIndexerClient() + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) + await algokit.ensureFunded( + { + accountToFund: deployer, + minSpendingBalance: algokit.algos(2), + minFundingIncrement: algokit.algos(2), + }, + algod, + ) + const appClient = new {{ contract_name.split('_')|map('capitalize')|join }}Client( + { + resolveBy: 'creatorAndName', + findExistingUsing: indexer, + sender: deployer, + creatorAddress: deployer.addr, + }, + algod, + ) + + {%- if preset_name == 'starter' %} + const app = await appClient.deploy({ + onSchemaBreak: 'append', + onUpdate: 'append', + }) + {% elif preset_name == 'production' %} + const isMainNet = await algokit.isMainNet(algod) + const app = await appClient.deploy({ + allowDelete: !isMainNet, + allowUpdate: !isMainNet, + onSchemaBreak: isMainNet ? 'append' : 'replace', + onUpdate: isMainNet ? 'append' : 'update', + }) + {% endif %} + + // If app was just created fund the app account + if (['create', 'replace'].includes(app.operationPerformed)) { + algokit.transferAlgos( + { + amount: algokit.algos(1), + from: deployer, + to: app.appAddress, + }, + algod, + ) + } + + const method = 'hello' + const response = await appClient.hello({ name: 'world' }) + console.log(`Called ${method} on ${app.name} (${app.appId}) with name = world, received: ${response.return}`) +} diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 deleted file mode 100644 index bb169ac2..00000000 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 +++ /dev/null @@ -1,54 +0,0 @@ -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: - from smart_contracts.artifacts.{{ contract_name }}.client import ( - {{ contract_name.split('_')|map('capitalize')|join }}Client, - ) - - app_client = {{ contract_name.split('_')|map('capitalize')|join }}Client( - algod_client, - creator=deployer, - indexer_client=indexer_client, - ) - - - {%- if preset_name == 'starter' %} - app_client.deploy( - on_schema_break=algokit_utils.OnSchemaBreak.AppendApp, - on_update=algokit_utils.OnUpdate.AppendApp, - ) - {%- elif preset_name == 'production' %} - is_mainnet = algokit_utils.is_mainnet(algod_client) - app_client.deploy( - on_schema_break=( - algokit_utils.OnSchemaBreak.AppendApp - if is_mainnet - else algokit_utils.OnSchemaBreak.ReplaceApp - ), - on_update=algokit_utils.OnUpdate.AppendApp - if is_mainnet - else algokit_utils.OnUpdate.UpdateApp, - allow_delete=not is_mainnet, - allow_update=not is_mainnet, - ) - {%- endif %} - - name = "world" - response = app_client.hello(name=name) - logger.info( - f"Called hello on {app_spec.contract.name} ({app_client.app_id}) " - f"with name={name}, received: {response.return_value}" - ) diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.copier-answers.yml b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.copier-answers.yml index d6d5f914..803705b1 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.copier-answers.yml +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.copier-answers.yml @@ -7,7 +7,7 @@ algod_token: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa author_email: None author_name: None contract_name: hello_world -deployment_language: python +deployment_language: typescript ide_vscode: true indexer_port: 8980 indexer_server: http://localhost diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.github/workflows/checks.yaml b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.github/workflows/checks.yaml index c03719d7..92be59ee 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.github/workflows/checks.yaml +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.github/workflows/checks.yaml @@ -76,4 +76,4 @@ jobs: git diff --exit-code --minimal ./smart_contracts/artifacts || (echo "::error ::Smart contract artifacts have changed, ensure committed artifacts are up to date" && exit 1); - name: Run deployer against LocalNet - run: poetry run python -m smart_contracts deploy + run: npm run --prefix smart_contracts deploy:ci diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.prettierignore b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.prettierignore new file mode 100644 index 00000000..dbda6ae9 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.prettierignore @@ -0,0 +1,12 @@ +# don't ever format node_modules +node_modules +# don't lint format output (make sure it's set to your correct build folder name) +dist +build +# don't format nyc coverage output +coverage +# don't format generated types +**/generated/types.d.ts +**/generated/types.ts +# don't format ide files +.idea diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.prettierrc.js b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.prettierrc.js new file mode 100644 index 00000000..c484d0e1 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.prettierrc.js @@ -0,0 +1,10 @@ +module.exports = { + singleQuote: true, + jsxSingleQuote: false, + semi: false, + tabWidth: 2, + trailingComma: 'all', + printWidth: 120, + endOfLine: 'lf', + arrowParens: 'always', +} diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/extensions.json b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/extensions.json index 2a407ed8..8ddc6c01 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/extensions.json +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/extensions.json @@ -3,6 +3,7 @@ "ms-python.python", "charliermarsh.ruff", "matangover.mypy", + "esbenp.prettier-vscode", "bungcip.better-toml", "editorconfig.editorconfig" ] diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/launch.json b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/launch.json index 8a39d265..4ccd8e12 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/launch.json +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/launch.json @@ -3,20 +3,25 @@ "configurations": [ { "name": "Build & Deploy Beaker application", - "type": "python", + "type": "node", "request": "launch", - "module": "smart_contracts", - "cwd": "${workspaceFolder}", - "preLaunchTask": "Start AlgoKit LocalNet", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "deploy"], + "cwd": "${workspaceFolder}/smart_contracts", + "console": "integratedTerminal", + "skipFiles": ["/**", "node_modules/**"], + "preLaunchTask": "Build Beaker application (+ LocalNet)", "envFile": "${workspaceFolder}/.env.localnet" }, { "name": "Deploy Built Beaker application", - "type": "python", + "type": "node", "request": "launch", - "module": "smart_contracts", - "args": ["deploy"], - "cwd": "${workspaceFolder}", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "deploy"], + "cwd": "${workspaceFolder}/smart_contracts", + "console": "integratedTerminal", + "skipFiles": ["/**", "node_modules/**"], "envFile": "${workspaceFolder}/.env.localnet" }, { diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/settings.json b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/settings.json index 411a4e10..77e84c88 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/settings.json +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/.vscode/settings.json @@ -12,6 +12,9 @@ ".idea": true }, + // TypeScript + "editor.defaultFormatter": "esbenp.prettier-vscode", + // Python "python.analysis.extraPaths": ["${workspaceFolder}/smart_contracts"], "python.defaultInterpreterPath": "${workspaceFolder}/.venv", diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/README.md b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/README.md index b14e7277..15d34b63 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/README.md +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/README.md @@ -15,6 +15,8 @@ This project has been generated using AlgoKit. See below for default getting sta - Run `poetry install` in the root directory, which will set up a `.venv` folder with a Python virtual environment and also install all Python dependencies - Copy `.env.template` to `.env` - Run `algokit localnet start` to start a local Algorand network in Docker. If you are using VS Code launch configurations provided by the template, this will be done automatically for you. + - Run `npm install` to install NPM packages + 3. Open the project and start debugging / developing via: - VS Code 1. Open the repository root in VS Code @@ -46,9 +48,9 @@ This project uses [GitHub Actions](https://docs.github.com/en/actions/learn-gith #### Setting up GitHub for CI/CD workflow and TestNet deployment 1. Every time you have a change to your smart contract, and when you first initialize the project you need to [build the contract](#initial-setup) and then commit the `smart_contracts/artifacts` folder so the [output stability](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/articles/output_stability.md) tests pass - 2. Decide what values you want to use for the `allow_update`, `allow_delete` and the `on_schema_break`, `on_update` parameters specified in [`contract.py`](./smart_contracts/hello_world/contract.py). - When deploying to LocalNet these values are both set to allow update and replacement of the app for convenience. But for non-LocalNet networks - the defaults are more conservative. + 2. Decide what values you want to use for the `allowUpdate` and `allowDelete` parameters specified in [`deploy-config.ts`](./smart_contracts/hello_world/deploy-config.ts). + When deploying to LocalNet these values are both set to `true` for convenience. But for non-LocalNet networks + they are more conservative and use `false` These default values will allow the smart contract to be deployed initially, but will not allow the app to be updated or deleted if is changed and the build will instead fail. To help you decide it may be helpful to read the [AlgoKit Utils app deployment documentation](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/docs/capabilities/app-deploy.md) or the [AlgoKit smart contract deployment architecture](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md#upgradeable-and-deletable-contracts). 3. Create a [Github Environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#creating-an-environment) named `Test`. @@ -88,12 +90,16 @@ This project makes use of Python to build Algorand smart contracts. The followin - [AlgoKit](https://github.com/algorandfoundation/algokit-cli) - One-stop shop tool for developers building on the Algorand network; [docs](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md), [intro tutorial](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/tutorials/intro.md) - [Beaker](https://github.com/algorand-devrel/beaker) - Smart contract development framework for PyTeal; [docs](https://beaker.algo.xyz), [examples](https://github.com/algorand-devrel/beaker/tree/master/examples) - [PyTEAL](https://github.com/algorand/pyteal) - Python language binding for Algorand smart contracts; [docs](https://pyteal.readthedocs.io/en/stable/) -- [AlgoKit Utils](https://github.com/algorandfoundation/algokit-utils-py) - A set of core Algorand utilities that make it easier to build solutions on Algorand. +- [AlgoKit Utils](https://github.com/algorandfoundation/algokit-utils-ts) - A set of core Algorand utilities that make it easier to build solutions on Algorand. - [Poetry](https://python-poetry.org/): Python packaging and dependency management.- [Black](https://github.com/psf/black): A Python code formatter.- [Ruff](https://github.com/charliermarsh/ruff): An extremely fast Python linter. - [mypy](https://mypy-lang.org/): Static type checker. - [pytest](https://docs.pytest.org/): Automated testing. - [pip-audit](https://pypi.org/project/pip-audit/): Tool for scanning Python environments for packages with known vulnerabilities. - [pre-commit](https://pre-commit.com/): A framework for managing and maintaining multi-language pre-commit hooks, to enable pre-commit you need to run `pre-commit install` in the root of the repository. This will install the pre-commit hooks and run them against modified files when committing. If any of the hooks fail, the commit will be aborted. To run the hooks on all files, use `pre-commit run --all-files`. +- [npm](https://www.npmjs.com/): Node.js package manager +- [TypeScript](https://www.typescriptlang.org/): Strongly typed programming language that builds on JavaScript +- [ts-node-dev](https://github.com/wclr/ts-node-dev): TypeScript development execution environment + It has also been configured to have a productive dev experience out of the box in [VS Code](https://code.visualstudio.com/), see the [.vscode](./.vscode) folder. diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/package.json b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/package.json new file mode 100644 index 00000000..927b682e --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/package.json @@ -0,0 +1,24 @@ +{ + "name": "smart_contracts", + "version": "1.0.0", + "description": "Smart contract deployer", + "main": "index.ts", + "scripts": { + "deploy": "ts-node-dev --transpile-only --watch .env -r dotenv/config smart_contracts/index.ts", + "deploy:ci": "ts-node --transpile-only -r dotenv/config smart_contracts/index.ts", + "format": "prettier --write ." + }, + "engines": { + "node": ">=18.0" + }, + "dependencies": { + "@algorandfoundation/algokit-utils": "^3.0.0", + "algosdk": "^2.5.0" + }, + "devDependencies": { + "dotenv": "^16.0.3", + "prettier": "^2.8.4", + "ts-node-dev": "^2.0.0", + "typescript": "^4.9.5" + } +} diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/README.md b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/README.md index 0f64fe3d..3f9c4ccd 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/README.md +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/README.md @@ -3,7 +3,8 @@ By the default the template creates a single `HelloWorld` contract under hello_world folder in the `smart_contracts` directory. To add a new contract: 1. From the root of the repository execute `algokit generate smart-contract`. This will create a new starter smart contract and deployment configuration file under `{your_contract_name}` subfolder under `smart_contracts` directory. -2. Each contract potentially has different creation parameters and deployment steps. Hence, you need to define your deployment logic in `deploy_config.py`file. +2. Each contract potentially has different creation parameters and deployment steps. Hence, you need to define your deployment logic in `deploy-config.ts`file. 3. `config.py` file will automatically build all contracts under `smart_contracts` directory. If you want to build specific contracts manually, modify the default code provided by the template in `config.py` file. +4. Since you are generating a TypeScript client, you also need to reference your contract deployment logic in `index.ts` file. However, similar to config.py, by default, `index.ts` will auto import all TypeScript deployment files under `smart_contracts` directory. If you want to manually import specific contracts, modify the default code provided by the template in `index.ts` file. > Please note, above is just a suggested convention tailored for the base configuration and structure of this template. Default code supplied by the template in `config.py` and `index.ts` (if using ts clients) files are tailored for the suggested convention. You are free to modify the structure and naming conventions as you see fit. diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/__main__.py b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/__main__.py index 3ef8a27d..0026e40f 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/__main__.py +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/__main__.py @@ -6,7 +6,6 @@ from smart_contracts.config import contracts from smart_contracts.helpers.build import build -from smart_contracts.helpers.deploy import deploy logging.basicConfig( level=logging.DEBUG, format="%(asctime)s %(levelname)-10s: %(message)s" @@ -24,23 +23,10 @@ def main(action: str) -> None: for contract in contracts: logger.info(f"Building app {contract.app.name}") build(artifact_path / contract.app.name, contract.app) - case "deploy": - for contract in contracts: - logger.info(f"Deploying app {contract.app.name}") - app_spec_path = artifact_path / contract.app.name / "application.json" - if contract.deploy: - deploy(app_spec_path, contract.deploy) - case "all": - for contract in contracts: - logger.info(f"Building app {contract.app.name}") - app_spec_path = build(artifact_path / contract.app.name, contract.app) - logger.info(f"Deploying {contract.app.name}") - if contract.deploy: - deploy(app_spec_path, contract.deploy) if __name__ == "__main__": if len(sys.argv) > 1: main(sys.argv[1]) else: - main("all") + main("build") diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/cool_contract/deploy-config.ts b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/cool_contract/deploy-config.ts new file mode 100644 index 00000000..d51faf47 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/cool_contract/deploy-config.ts @@ -0,0 +1,52 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { CoolContractClient } from '../artifacts/cool_contract/client' + +// Below is a showcase of various deployment options you can use in TypeScript Client +export async function deploy() { + console.log('=== Deploying CoolContract ===') + + const algod = algokit.getAlgoClient() + const indexer = algokit.getAlgoIndexerClient() + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) + await algokit.ensureFunded( + { + accountToFund: deployer, + minSpendingBalance: algokit.algos(2), + minFundingIncrement: algokit.algos(2), + }, + algod, + ) + const appClient = new CoolContractClient( + { + resolveBy: 'creatorAndName', + findExistingUsing: indexer, + sender: deployer, + creatorAddress: deployer.addr, + }, + algod, + ) + const isMainNet = await algokit.isMainNet(algod) + const app = await appClient.deploy({ + allowDelete: !isMainNet, + allowUpdate: !isMainNet, + onSchemaBreak: isMainNet ? 'append' : 'replace', + onUpdate: isMainNet ? 'append' : 'update', + }) + + + // If app was just created fund the app account + if (['create', 'replace'].includes(app.operationPerformed)) { + algokit.transferAlgos( + { + amount: algokit.algos(1), + from: deployer, + to: app.appAddress, + }, + algod, + ) + } + + const method = 'hello' + const response = await appClient.hello({ name: 'world' }) + console.log(`Called ${method} on ${app.name} (${app.appId}) with name = world, received: ${response.return}`) +} diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/cool_contract/deploy_config.py b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/cool_contract/deploy_config.py deleted file mode 100644 index 964de9a0..00000000 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/cool_contract/deploy_config.py +++ /dev/null @@ -1,45 +0,0 @@ -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: - from smart_contracts.artifacts.cool_contract.client import ( - CoolContractClient, - ) - - app_client = CoolContractClient( - algod_client, - creator=deployer, - indexer_client=indexer_client, - ) - is_mainnet = algokit_utils.is_mainnet(algod_client) - app_client.deploy( - on_schema_break=( - algokit_utils.OnSchemaBreak.AppendApp - if is_mainnet - else algokit_utils.OnSchemaBreak.ReplaceApp - ), - on_update=algokit_utils.OnUpdate.AppendApp - if is_mainnet - else algokit_utils.OnUpdate.UpdateApp, - allow_delete=not is_mainnet, - allow_update=not is_mainnet, - ) - - name = "world" - response = app_client.hello(name=name) - logger.info( - f"Called hello on {app_spec.contract.name} ({app_client.app_id}) " - f"with name={name}, received: {response.return_value}" - ) diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/hello_world/deploy-config.ts b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/hello_world/deploy-config.ts new file mode 100644 index 00000000..dab537c5 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/hello_world/deploy-config.ts @@ -0,0 +1,52 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { HelloWorldClient } from '../artifacts/hello_world/client' + +// Below is a showcase of various deployment options you can use in TypeScript Client +export async function deploy() { + console.log('=== Deploying HelloWorld ===') + + const algod = algokit.getAlgoClient() + const indexer = algokit.getAlgoIndexerClient() + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) + await algokit.ensureFunded( + { + accountToFund: deployer, + minSpendingBalance: algokit.algos(2), + minFundingIncrement: algokit.algos(2), + }, + algod, + ) + const appClient = new HelloWorldClient( + { + resolveBy: 'creatorAndName', + findExistingUsing: indexer, + sender: deployer, + creatorAddress: deployer.addr, + }, + algod, + ) + const isMainNet = await algokit.isMainNet(algod) + const app = await appClient.deploy({ + allowDelete: !isMainNet, + allowUpdate: !isMainNet, + onSchemaBreak: isMainNet ? 'append' : 'replace', + onUpdate: isMainNet ? 'append' : 'update', + }) + + + // If app was just created fund the app account + if (['create', 'replace'].includes(app.operationPerformed)) { + algokit.transferAlgos( + { + amount: algokit.algos(1), + from: deployer, + to: app.appAddress, + }, + algod, + ) + } + + const method = 'hello' + const response = await appClient.hello({ name: 'world' }) + console.log(`Called ${method} on ${app.name} (${app.appId}) with name = world, received: ${response.return}`) +} diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/hello_world/deploy_config.py b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/hello_world/deploy_config.py deleted file mode 100644 index 7ed7ad6c..00000000 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/hello_world/deploy_config.py +++ /dev/null @@ -1,45 +0,0 @@ -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: - from smart_contracts.artifacts.hello_world.client import ( - HelloWorldClient, - ) - - app_client = HelloWorldClient( - algod_client, - creator=deployer, - indexer_client=indexer_client, - ) - is_mainnet = algokit_utils.is_mainnet(algod_client) - app_client.deploy( - on_schema_break=( - algokit_utils.OnSchemaBreak.AppendApp - if is_mainnet - else algokit_utils.OnSchemaBreak.ReplaceApp - ), - on_update=algokit_utils.OnUpdate.AppendApp - if is_mainnet - else algokit_utils.OnUpdate.UpdateApp, - allow_delete=not is_mainnet, - allow_update=not is_mainnet, - ) - - name = "world" - response = app_client.hello(name=name) - logger.info( - f"Called hello on {app_spec.contract.name} ({app_client.app_id}) " - f"with name={name}, received: {response.return_value}" - ) diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/helpers/build.py b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/helpers/build.py index 201e5d66..20d6d6d6 100644 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/helpers/build.py +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/helpers/build.py @@ -6,7 +6,7 @@ import beaker logger = logging.getLogger(__name__) -deployment_extension = "py" +deployment_extension = "ts" def build(output_dir: Path, app: beaker.Application) -> Path: diff --git a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/helpers/deploy.py b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/helpers/deploy.py deleted file mode 100644 index 08367a3a..00000000 --- a/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/helpers/deploy.py +++ /dev/null @@ -1,50 +0,0 @@ -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/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/index.ts b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/index.ts new file mode 100644 index 00000000..4f18d4df --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_production_preset_typescript/smart_contracts/index.ts @@ -0,0 +1,42 @@ +import * as fs from 'fs' +import * as path from 'path' +import { consoleLogger } from '@algorandfoundation/algokit-utils/types/logging' +import * as algokit from '@algorandfoundation/algokit-utils' + +algokit.Config.configure({ + logger: consoleLogger, +}) + +// base directory +const baseDir = path.resolve(__dirname) + +// function to validate and dynamically import a module +async function importDeployerIfExists(dir: string) { + const deployerPath = path.resolve(dir, 'deploy-config') + if (fs.existsSync(deployerPath + '.ts') || fs.existsSync(deployerPath + '.js')) { + const deployer = await import(deployerPath) + return deployer.deploy + } +} + +// get a list of all deployers from the subdirectories +async function getDeployers() { + const directories = fs.readdirSync(baseDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => path.resolve(baseDir, dirent.name)) + + return Promise.all(directories.map(importDeployerIfExists)) +} + +// execute all the deployers +(async () => { + const contractDeployers = (await getDeployers()).filter(Boolean) + + for (const deployer of contractDeployers) { + try { + await deployer() + } catch (e) { + console.error(e) + } + } +})() diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit.toml b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit.toml index 6ffa3b4c..17861919 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit.toml +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit.toml @@ -2,7 +2,7 @@ min_version = "v1.4.0" [deploy] -command = "poetry run python -m smart_contracts deploy" +command = "npm run deploy:ci" environment_secrets = [ "DEPLOYER_MNEMONIC", ] diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy-config.ts.j2 b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy-config.ts.j2 new file mode 100644 index 00000000..325fb24a --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy-config.ts.j2 @@ -0,0 +1,59 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { {{ contract_name.split('_')|map('capitalize')|join }}Client } from '../artifacts/{{ contract_name }}/client' + +// Below is a showcase of various deployment options you can use in TypeScript Client +export async function deploy() { + console.log('=== Deploying {{ contract_name.split('_')|map('capitalize')|join }} ===') + + const algod = algokit.getAlgoClient() + const indexer = algokit.getAlgoIndexerClient() + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) + await algokit.ensureFunded( + { + accountToFund: deployer, + minSpendingBalance: algokit.algos(2), + minFundingIncrement: algokit.algos(2), + }, + algod, + ) + const appClient = new {{ contract_name.split('_')|map('capitalize')|join }}Client( + { + resolveBy: 'creatorAndName', + findExistingUsing: indexer, + sender: deployer, + creatorAddress: deployer.addr, + }, + algod, + ) + + {%- if preset_name == 'starter' %} + const app = await appClient.deploy({ + onSchemaBreak: 'append', + onUpdate: 'append', + }) + {% elif preset_name == 'production' %} + const isMainNet = await algokit.isMainNet(algod) + const app = await appClient.deploy({ + allowDelete: !isMainNet, + allowUpdate: !isMainNet, + onSchemaBreak: isMainNet ? 'append' : 'replace', + onUpdate: isMainNet ? 'append' : 'update', + }) + {% endif %} + + // If app was just created fund the app account + if (['create', 'replace'].includes(app.operationPerformed)) { + algokit.transferAlgos( + { + amount: algokit.algos(1), + from: deployer, + to: app.appAddress, + }, + algod, + ) + } + + const method = 'hello' + const response = await appClient.hello({ name: 'world' }) + console.log(`Called ${method} on ${app.name} (${app.appId}) with name = world, received: ${response.return}`) +} diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 deleted file mode 100644 index bb169ac2..00000000 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 +++ /dev/null @@ -1,54 +0,0 @@ -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: - from smart_contracts.artifacts.{{ contract_name }}.client import ( - {{ contract_name.split('_')|map('capitalize')|join }}Client, - ) - - app_client = {{ contract_name.split('_')|map('capitalize')|join }}Client( - algod_client, - creator=deployer, - indexer_client=indexer_client, - ) - - - {%- if preset_name == 'starter' %} - app_client.deploy( - on_schema_break=algokit_utils.OnSchemaBreak.AppendApp, - on_update=algokit_utils.OnUpdate.AppendApp, - ) - {%- elif preset_name == 'production' %} - is_mainnet = algokit_utils.is_mainnet(algod_client) - app_client.deploy( - on_schema_break=( - algokit_utils.OnSchemaBreak.AppendApp - if is_mainnet - else algokit_utils.OnSchemaBreak.ReplaceApp - ), - on_update=algokit_utils.OnUpdate.AppendApp - if is_mainnet - else algokit_utils.OnUpdate.UpdateApp, - allow_delete=not is_mainnet, - allow_update=not is_mainnet, - ) - {%- endif %} - - name = "world" - response = app_client.hello(name=name) - logger.info( - f"Called hello on {app_spec.contract.name} ({app_client.app_id}) " - f"with name={name}, received: {response.return_value}" - ) diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.copier-answers.yml b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.copier-answers.yml index 0e495070..3d37db08 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.copier-answers.yml +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.copier-answers.yml @@ -7,7 +7,7 @@ algod_token: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa author_email: None author_name: None contract_name: hello_world -deployment_language: python +deployment_language: typescript ide_vscode: true indexer_port: 8980 indexer_server: http://localhost diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.prettierignore b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.prettierignore new file mode 100644 index 00000000..dbda6ae9 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.prettierignore @@ -0,0 +1,12 @@ +# don't ever format node_modules +node_modules +# don't lint format output (make sure it's set to your correct build folder name) +dist +build +# don't format nyc coverage output +coverage +# don't format generated types +**/generated/types.d.ts +**/generated/types.ts +# don't format ide files +.idea diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.prettierrc.js b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.prettierrc.js new file mode 100644 index 00000000..c484d0e1 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.prettierrc.js @@ -0,0 +1,10 @@ +module.exports = { + singleQuote: true, + jsxSingleQuote: false, + semi: false, + tabWidth: 2, + trailingComma: 'all', + printWidth: 120, + endOfLine: 'lf', + arrowParens: 'always', +} diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/extensions.json b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/extensions.json index 78fafc41..bd33e620 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/extensions.json +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/extensions.json @@ -1,6 +1,7 @@ { "recommendations": [ "ms-python.python", + "esbenp.prettier-vscode", "bungcip.better-toml", "editorconfig.editorconfig" ] diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/launch.json b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/launch.json index 8a39d265..4ccd8e12 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/launch.json +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/launch.json @@ -3,20 +3,25 @@ "configurations": [ { "name": "Build & Deploy Beaker application", - "type": "python", + "type": "node", "request": "launch", - "module": "smart_contracts", - "cwd": "${workspaceFolder}", - "preLaunchTask": "Start AlgoKit LocalNet", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "deploy"], + "cwd": "${workspaceFolder}/smart_contracts", + "console": "integratedTerminal", + "skipFiles": ["/**", "node_modules/**"], + "preLaunchTask": "Build Beaker application (+ LocalNet)", "envFile": "${workspaceFolder}/.env.localnet" }, { "name": "Deploy Built Beaker application", - "type": "python", + "type": "node", "request": "launch", - "module": "smart_contracts", - "args": ["deploy"], - "cwd": "${workspaceFolder}", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "deploy"], + "cwd": "${workspaceFolder}/smart_contracts", + "console": "integratedTerminal", + "skipFiles": ["/**", "node_modules/**"], "envFile": "${workspaceFolder}/.env.localnet" }, { diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/settings.json b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/settings.json index cee77726..b80fe85d 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/settings.json +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/.vscode/settings.json @@ -12,6 +12,9 @@ ".idea": true }, + // TypeScript + "editor.defaultFormatter": "esbenp.prettier-vscode", + // Python "python.analysis.extraPaths": ["${workspaceFolder}/smart_contracts"], "python.defaultInterpreterPath": "${workspaceFolder}/.venv", diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/README.md b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/README.md index 248ca07c..6f4d1cc8 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/README.md +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/README.md @@ -15,6 +15,8 @@ This project has been generated using AlgoKit. See below for default getting sta - Run `poetry install` in the root directory, which will set up a `.venv` folder with a Python virtual environment and also install all Python dependencies - Copy `.env.template` to `.env` - Run `algokit localnet start` to start a local Algorand network in Docker. If you are using VS Code launch configurations provided by the template, this will be done automatically for you. + - Run `npm install` to install NPM packages + 3. Open the project and start debugging / developing via: - VS Code 1. Open the repository root in VS Code @@ -49,9 +51,13 @@ This project makes use of Python to build Algorand smart contracts. The followin - [AlgoKit](https://github.com/algorandfoundation/algokit-cli) - One-stop shop tool for developers building on the Algorand network; [docs](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md), [intro tutorial](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/tutorials/intro.md) - [Beaker](https://github.com/algorand-devrel/beaker) - Smart contract development framework for PyTeal; [docs](https://beaker.algo.xyz), [examples](https://github.com/algorand-devrel/beaker/tree/master/examples) - [PyTEAL](https://github.com/algorand/pyteal) - Python language binding for Algorand smart contracts; [docs](https://pyteal.readthedocs.io/en/stable/) -- [AlgoKit Utils](https://github.com/algorandfoundation/algokit-utils-py) - A set of core Algorand utilities that make it easier to build solutions on Algorand. +- [AlgoKit Utils](https://github.com/algorandfoundation/algokit-utils-ts) - A set of core Algorand utilities that make it easier to build solutions on Algorand. - [Poetry](https://python-poetry.org/): Python packaging and dependency management.- [Black](https://github.com/psf/black): A Python code formatter. - [pytest](https://docs.pytest.org/): Automated testing. - [pip-audit](https://pypi.org/project/pip-audit/): Tool for scanning Python environments for packages with known vulnerabilities. +- [npm](https://www.npmjs.com/): Node.js package manager +- [TypeScript](https://www.typescriptlang.org/): Strongly typed programming language that builds on JavaScript +- [ts-node-dev](https://github.com/wclr/ts-node-dev): TypeScript development execution environment + It has also been configured to have a productive dev experience out of the box in [VS Code](https://code.visualstudio.com/), see the [.vscode](./.vscode) folder. diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/package.json b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/package.json new file mode 100644 index 00000000..927b682e --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/package.json @@ -0,0 +1,24 @@ +{ + "name": "smart_contracts", + "version": "1.0.0", + "description": "Smart contract deployer", + "main": "index.ts", + "scripts": { + "deploy": "ts-node-dev --transpile-only --watch .env -r dotenv/config smart_contracts/index.ts", + "deploy:ci": "ts-node --transpile-only -r dotenv/config smart_contracts/index.ts", + "format": "prettier --write ." + }, + "engines": { + "node": ">=18.0" + }, + "dependencies": { + "@algorandfoundation/algokit-utils": "^3.0.0", + "algosdk": "^2.5.0" + }, + "devDependencies": { + "dotenv": "^16.0.3", + "prettier": "^2.8.4", + "ts-node-dev": "^2.0.0", + "typescript": "^4.9.5" + } +} diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/README.md b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/README.md index 0f64fe3d..3f9c4ccd 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/README.md +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/README.md @@ -3,7 +3,8 @@ By the default the template creates a single `HelloWorld` contract under hello_world folder in the `smart_contracts` directory. To add a new contract: 1. From the root of the repository execute `algokit generate smart-contract`. This will create a new starter smart contract and deployment configuration file under `{your_contract_name}` subfolder under `smart_contracts` directory. -2. Each contract potentially has different creation parameters and deployment steps. Hence, you need to define your deployment logic in `deploy_config.py`file. +2. Each contract potentially has different creation parameters and deployment steps. Hence, you need to define your deployment logic in `deploy-config.ts`file. 3. `config.py` file will automatically build all contracts under `smart_contracts` directory. If you want to build specific contracts manually, modify the default code provided by the template in `config.py` file. +4. Since you are generating a TypeScript client, you also need to reference your contract deployment logic in `index.ts` file. However, similar to config.py, by default, `index.ts` will auto import all TypeScript deployment files under `smart_contracts` directory. If you want to manually import specific contracts, modify the default code provided by the template in `index.ts` file. > Please note, above is just a suggested convention tailored for the base configuration and structure of this template. Default code supplied by the template in `config.py` and `index.ts` (if using ts clients) files are tailored for the suggested convention. You are free to modify the structure and naming conventions as you see fit. diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/__main__.py b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/__main__.py index 3ef8a27d..0026e40f 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/__main__.py +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/__main__.py @@ -6,7 +6,6 @@ from smart_contracts.config import contracts from smart_contracts.helpers.build import build -from smart_contracts.helpers.deploy import deploy logging.basicConfig( level=logging.DEBUG, format="%(asctime)s %(levelname)-10s: %(message)s" @@ -24,23 +23,10 @@ def main(action: str) -> None: for contract in contracts: logger.info(f"Building app {contract.app.name}") build(artifact_path / contract.app.name, contract.app) - case "deploy": - for contract in contracts: - logger.info(f"Deploying app {contract.app.name}") - app_spec_path = artifact_path / contract.app.name / "application.json" - if contract.deploy: - deploy(app_spec_path, contract.deploy) - case "all": - for contract in contracts: - logger.info(f"Building app {contract.app.name}") - app_spec_path = build(artifact_path / contract.app.name, contract.app) - logger.info(f"Deploying {contract.app.name}") - if contract.deploy: - deploy(app_spec_path, contract.deploy) if __name__ == "__main__": if len(sys.argv) > 1: main(sys.argv[1]) else: - main("all") + main("build") diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/cool_contract/deploy-config.ts b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/cool_contract/deploy-config.ts new file mode 100644 index 00000000..4e8229f9 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/cool_contract/deploy-config.ts @@ -0,0 +1,49 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { CoolContractClient } from '../artifacts/cool_contract/client' + +// Below is a showcase of various deployment options you can use in TypeScript Client +export async function deploy() { + console.log('=== Deploying CoolContract ===') + + const algod = algokit.getAlgoClient() + const indexer = algokit.getAlgoIndexerClient() + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) + await algokit.ensureFunded( + { + accountToFund: deployer, + minSpendingBalance: algokit.algos(2), + minFundingIncrement: algokit.algos(2), + }, + algod, + ) + const appClient = new CoolContractClient( + { + resolveBy: 'creatorAndName', + findExistingUsing: indexer, + sender: deployer, + creatorAddress: deployer.addr, + }, + algod, + ) + const app = await appClient.deploy({ + onSchemaBreak: 'append', + onUpdate: 'append', + }) + + + // If app was just created fund the app account + if (['create', 'replace'].includes(app.operationPerformed)) { + algokit.transferAlgos( + { + amount: algokit.algos(1), + from: deployer, + to: app.appAddress, + }, + algod, + ) + } + + const method = 'hello' + const response = await appClient.hello({ name: 'world' }) + console.log(`Called ${method} on ${app.name} (${app.appId}) with name = world, received: ${response.return}`) +} diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/cool_contract/deploy_config.py b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/cool_contract/deploy_config.py deleted file mode 100644 index d33f65bc..00000000 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/cool_contract/deploy_config.py +++ /dev/null @@ -1,36 +0,0 @@ -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: - from smart_contracts.artifacts.cool_contract.client import ( - CoolContractClient, - ) - - app_client = CoolContractClient( - algod_client, - creator=deployer, - indexer_client=indexer_client, - ) - app_client.deploy( - on_schema_break=algokit_utils.OnSchemaBreak.AppendApp, - on_update=algokit_utils.OnUpdate.AppendApp, - ) - - name = "world" - response = app_client.hello(name=name) - logger.info( - f"Called hello on {app_spec.contract.name} ({app_client.app_id}) " - f"with name={name}, received: {response.return_value}" - ) diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/hello_world/deploy-config.ts b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/hello_world/deploy-config.ts new file mode 100644 index 00000000..527fe2c6 --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/hello_world/deploy-config.ts @@ -0,0 +1,49 @@ +import * as algokit from '@algorandfoundation/algokit-utils' +import { HelloWorldClient } from '../artifacts/hello_world/client' + +// Below is a showcase of various deployment options you can use in TypeScript Client +export async function deploy() { + console.log('=== Deploying HelloWorld ===') + + const algod = algokit.getAlgoClient() + const indexer = algokit.getAlgoIndexerClient() + const deployer = await algokit.mnemonicAccountFromEnvironment({ name: 'DEPLOYER', fundWith: algokit.algos(3) }, algod) + await algokit.ensureFunded( + { + accountToFund: deployer, + minSpendingBalance: algokit.algos(2), + minFundingIncrement: algokit.algos(2), + }, + algod, + ) + const appClient = new HelloWorldClient( + { + resolveBy: 'creatorAndName', + findExistingUsing: indexer, + sender: deployer, + creatorAddress: deployer.addr, + }, + algod, + ) + const app = await appClient.deploy({ + onSchemaBreak: 'append', + onUpdate: 'append', + }) + + + // If app was just created fund the app account + if (['create', 'replace'].includes(app.operationPerformed)) { + algokit.transferAlgos( + { + amount: algokit.algos(1), + from: deployer, + to: app.appAddress, + }, + algod, + ) + } + + const method = 'hello' + const response = await appClient.hello({ name: 'world' }) + console.log(`Called ${method} on ${app.name} (${app.appId}) with name = world, received: ${response.return}`) +} diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/hello_world/deploy_config.py b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/hello_world/deploy_config.py deleted file mode 100644 index 4afdc60e..00000000 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/hello_world/deploy_config.py +++ /dev/null @@ -1,36 +0,0 @@ -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: - from smart_contracts.artifacts.hello_world.client import ( - HelloWorldClient, - ) - - app_client = HelloWorldClient( - algod_client, - creator=deployer, - indexer_client=indexer_client, - ) - app_client.deploy( - on_schema_break=algokit_utils.OnSchemaBreak.AppendApp, - on_update=algokit_utils.OnUpdate.AppendApp, - ) - - name = "world" - response = app_client.hello(name=name) - logger.info( - f"Called hello on {app_spec.contract.name} ({app_client.app_id}) " - f"with name={name}, received: {response.return_value}" - ) diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/helpers/build.py b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/helpers/build.py index 201e5d66..20d6d6d6 100644 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/helpers/build.py +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/helpers/build.py @@ -6,7 +6,7 @@ import beaker logger = logging.getLogger(__name__) -deployment_extension = "py" +deployment_extension = "ts" def build(output_dir: Path, app: beaker.Application) -> Path: diff --git a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/helpers/deploy.py b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/helpers/deploy.py deleted file mode 100644 index 08367a3a..00000000 --- a/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/helpers/deploy.py +++ /dev/null @@ -1,50 +0,0 @@ -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/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/index.ts b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/index.ts new file mode 100644 index 00000000..4f18d4df --- /dev/null +++ b/tests_generated/test_smart_contract_generator_default_starter_preset_typescript/smart_contracts/index.ts @@ -0,0 +1,42 @@ +import * as fs from 'fs' +import * as path from 'path' +import { consoleLogger } from '@algorandfoundation/algokit-utils/types/logging' +import * as algokit from '@algorandfoundation/algokit-utils' + +algokit.Config.configure({ + logger: consoleLogger, +}) + +// base directory +const baseDir = path.resolve(__dirname) + +// function to validate and dynamically import a module +async function importDeployerIfExists(dir: string) { + const deployerPath = path.resolve(dir, 'deploy-config') + if (fs.existsSync(deployerPath + '.ts') || fs.existsSync(deployerPath + '.js')) { + const deployer = await import(deployerPath) + return deployer.deploy + } +} + +// get a list of all deployers from the subdirectories +async function getDeployers() { + const directories = fs.readdirSync(baseDir, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => path.resolve(baseDir, dirent.name)) + + return Promise.all(directories.map(importDeployerIfExists)) +} + +// execute all the deployers +(async () => { + const contractDeployers = (await getDeployers()).filter(Boolean) + + for (const deployer of contractDeployers) { + try { + await deployer() + } catch (e) { + console.error(e) + } + } +})()