25 | )
26 | }
27 |
28 | export default Account
29 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.algokit/sources/sources.avm.json:
--------------------------------------------------------------------------------
1 | {"txn-group-sources": [{"sourcemap-location": "/home/giorgio/PycharmProjects/py-dm-beginner-en/projects/py-dm-beginner-en/.algokit/sources/DigitalMarketplace/approval.teal.tok.map", "hash": "On6BhIXZMcEjtrx60WRzxk3MW+9EJUrAJG4eQPZU2QQ="}, {"sourcemap-location": "/home/giorgio/PycharmProjects/py-dm-beginner-en/projects/py-dm-beginner-en/.algokit/sources/DigitalMarketplace/clear.teal.tok.map", "hash": "L4QyXhQzhDuFZFVmibpP6yWtYSDtIlwwj2Cr/ydTiBY="}, {"sourcemap-location": "/Users/joe/git/algorand-bootcamp/py-dm-beginner-en/projects/py-dm-beginner-en/.algokit/sources/DigitalMarketplace/approval.teal.tok.map", "hash": "ygKaB8AlLOu6/+ceEBI1FW9632hqiNUZolBKeUq0Nvg="}, {"sourcemap-location": "/Users/joe/git/algorand-bootcamp/py-dm-beginner-en/projects/py-dm-beginner-en/.algokit/sources/DigitalMarketplace/clear.teal.tok.map", "hash": "L4QyXhQzhDuFZFVmibpP6yWtYSDtIlwwj2Cr/ydTiBY="}, {"sourcemap-location": "/Users/joe/git/algorand-bootcamp/py-dm-beginner-en/projects/py-dm-beginner-en/.algokit/sources/DigitalMarketplace/approval.teal.tok.map", "hash": "WyeByLzbHCN5FtrzUq/MUO2D046uWJVCFLYWClnR270="}]}
--------------------------------------------------------------------------------
/projects/frontend/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Algorand Foundation
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Build & Deploy contracts",
6 | "type": "python",
7 | "request": "launch",
8 | "module": "smart_contracts",
9 | "cwd": "${workspaceFolder}",
10 | "preLaunchTask": "Start AlgoKit LocalNet",
11 | "envFile": "${workspaceFolder}/.env.localnet"
12 | },
13 | {
14 | "name": "Deploy contracts",
15 | "type": "python",
16 | "request": "launch",
17 | "module": "smart_contracts",
18 | "args": ["deploy"],
19 | "cwd": "${workspaceFolder}",
20 | "envFile": "${workspaceFolder}/.env.localnet"
21 | },
22 | {
23 | "name": "Build contracts",
24 | "type": "python",
25 | "request": "launch",
26 | "module": "smart_contracts",
27 | "args": ["build"],
28 | "cwd": "${workspaceFolder}"
29 | },
30 | {
31 | "type": "avm",
32 | "request": "launch",
33 | "name": "Debug TEAL via AlgoKit AVM Debugger",
34 | "simulateTraceFile": "${workspaceFolder}/${command:PickSimulateTraceFile}",
35 | "stopOnEntry": true
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/smart_contracts/README.md:
--------------------------------------------------------------------------------
1 | ## How to add new smart contracts?
2 |
3 | By the default the template creates a single `HelloWorld` contract under digital_marketplace folder in the `smart_contracts` directory. To add a new contract:
4 |
5 | 1. From the root of the project (`../`) 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.
6 | 2. Each contract potentially has different creation parameters and deployment steps. Hence, you need to define your deployment logic in `deploy_config.py`file.
7 | 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.
8 |
9 | > 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.
10 |
--------------------------------------------------------------------------------
/projects/frontend/src/contracts/README.md:
--------------------------------------------------------------------------------
1 | ## How to connect my web app with Algorand smart contracts?
2 |
3 | The following folder is reserved for the Algorand Application Clients. The clients are used to interact with instances of Algorand Smart Contracts (ASC1s) deployed on-chain.
4 |
5 | To integrate this react frontend template with your smart contracts codebase, perform the following steps:
6 |
7 | 1. Generate the typed client using `algokit generate client -l typescript -o {path/to/this/folder}`
8 | 2. The generated typescript client should be ready to be imported and used in this react frontend template, making it a full fledged dApp.
9 |
10 | ### FAQ
11 |
12 | - **How to interact with the smart contract?**
13 | - The generated client provides a set of functions that can be used to interact with the ABI (Application Binary Interface) compliant Algorand smart contract. For example, if the smart contract has a function called `hello`, the generated client will have a function called `hello` that can be used to interact with the smart contract. Refer to a [full-stack end-to-end starter template](https://github.com/algorandfoundation/algokit-fullstack-template) for a reference example on invoking and interacting with typescript typed clients generated.
14 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import algokit_utils
4 | from algosdk.v2client.algod import AlgodClient
5 | from algosdk.v2client.indexer import IndexerClient
6 |
7 | logger = logging.getLogger(__name__)
8 |
9 |
10 | # define deployment behaviour based on supplied app spec
11 | def deploy(
12 | algod_client: AlgodClient,
13 | indexer_client: IndexerClient,
14 | app_spec: algokit_utils.ApplicationSpecification,
15 | deployer: algokit_utils.Account,
16 | ) -> None:
17 | from smart_contracts.artifacts.digital_marketplace.client import (
18 | DigitalMarketplaceClient,
19 | )
20 |
21 | app_client = DigitalMarketplaceClient(
22 | algod_client,
23 | creator=deployer,
24 | indexer_client=indexer_client,
25 | )
26 | app_client.deploy(
27 | on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
28 | on_update=algokit_utils.OnUpdate.AppendApp,
29 | )
30 | name = "world"
31 | response = app_client.hello(name=name)
32 | logger.info(
33 | f"Called hello on {app_spec.contract.name} ({app_client.app_id}) "
34 | f"with name={name}, received: {response.return_value}"
35 | )
36 |
--------------------------------------------------------------------------------
/projects/frontend/src/interfaces/network.ts:
--------------------------------------------------------------------------------
1 | import { AlgoClientConfig } from '@algorandfoundation/algokit-utils/types/network-client'
2 | import type { TokenHeader } from 'algosdk/dist/types/client/urlTokenBaseHTTPClient'
3 |
4 | export interface AlgoViteClientConfig extends AlgoClientConfig {
5 | /** Base URL of the server e.g. http://localhost, https://testnet-api.algonode.cloud/, etc. */
6 | server: string
7 | /** The port to use e.g. 4001, 443, etc. */
8 | port: string | number
9 | /** The token to use for API authentication (or undefined if none needed) - can be a string, or an object with the header key => value */
10 | token: string | TokenHeader
11 | /** String representing current Algorand Network type (testnet/mainnet and etc) */
12 | network: string
13 | }
14 |
15 | export interface AlgoViteKMDConfig extends AlgoClientConfig {
16 | /** Base URL of the server e.g. http://localhost, https://testnet-api.algonode.cloud/, etc. */
17 | server: string
18 | /** The port to use e.g. 4001, 443, etc. */
19 | port: string | number
20 | /** The token to use for API authentication (or undefined if none needed) - can be a string, or an object with the header key => value */
21 | token: string | TokenHeader
22 | /** KMD wallet name */
23 | wallet: string
24 | /** KMD wallet password */
25 | password: string
26 | }
27 |
--------------------------------------------------------------------------------
/projects/frontend/.github/workflows/checks.yaml:
--------------------------------------------------------------------------------
1 | name: Check code base
2 |
3 | on:
4 | workflow_call:
5 | inputs:
6 | run-build:
7 | required: false
8 | type: boolean
9 | default: false
10 | push:
11 | branches:
12 | - main
13 |
14 | jobs:
15 | checks:
16 | runs-on: 'ubuntu-latest'
17 | steps:
18 | - name: Check out repository
19 | uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 |
23 | - name: Setup node
24 | uses: actions/setup-node@v3
25 | with:
26 | node-version: 18
27 |
28 | - name: Install dependencies
29 | run: npm ci
30 |
31 | - name: Run linters
32 | run: npm run lint
33 |
34 | - name: Run unit tests
35 | run: npm run test
36 |
37 | - name: Create placeholder .env file
38 | if: ${{ inputs.run-build }}
39 | uses: makerxstudio/shared-config/.github/actions/env-to-placeholders@main
40 | with:
41 | env-output-path: './.env'
42 | env-template-path: './.env.template'
43 | env-variable-prefix: VITE_
44 |
45 | - name: Build
46 | if: ${{ inputs.run-build }}
47 | run: npm run build
48 |
49 | - name: Archive
50 | if: ${{ inputs.run-build }}
51 | uses: actions/upload-artifact@v4
52 | with:
53 | name: dist
54 | path: dist/
55 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "py-dm-beginner-en"
3 | version = "0.1.0"
4 | description = "Algorand smart contracts"
5 | authors = ["CiottiGiorgio "]
6 | readme = "README.md"
7 |
8 | [tool.poetry.dependencies]
9 | python = "^3.12"
10 | python-dotenv = "^1.0.0"
11 | algorand-python = "^1.0.0"
12 | algokit-utils = {git = "https://github.com/algorandfoundation/algokit-utils-py", rev = "feat/algorand_client"}
13 |
14 | [tool.poetry.group.dev.dependencies]
15 | black = {extras = ["d"], version = "*"}
16 | ruff = "^0.1.6"
17 | mypy = "*"
18 | pytest = "*"
19 | pytest-cov = "*"
20 | pip-audit = "*"
21 | pre-commit = "*"
22 | puyapy = "^0.7.1"
23 |
24 | [build-system]
25 | requires = ["poetry-core"]
26 | build-backend = "poetry.core.masonry.api"
27 |
28 | [tool.ruff]
29 | line-length = 120
30 | select = ["E", "F", "ANN", "UP", "N", "C4", "B", "A", "YTT", "W", "FBT", "Q", "RUF", "I"]
31 | ignore = [
32 | "ANN101", # no type for self
33 | "ANN102", # no type for cls
34 | ]
35 | unfixable = ["B", "RUF"]
36 |
37 | [tool.ruff.flake8-annotations]
38 | allow-star-arg-any = true
39 | suppress-none-returning = true
40 |
41 | [tool.pytest.ini_options]
42 | pythonpath = ["smart_contracts", "tests"]
43 |
44 | [tool.mypy]
45 | files = "smart_contracts/"
46 | python_version = "3.12"
47 | check_untyped_defs = true
48 | warn_redundant_casts = true
49 | warn_unused_ignores = true
50 | allow_untyped_defs = false
51 | strict_equality = true
52 |
--------------------------------------------------------------------------------
/.github/workflows/py-dm-beginner-en-cd.yaml:
--------------------------------------------------------------------------------
1 | name: Release py-dm-beginner-en
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | deploy-testnet:
8 | runs-on: "ubuntu-latest"
9 |
10 | environment: contract-testnet
11 | steps:
12 | - name: Checkout source code
13 | uses: actions/checkout@v4
14 |
15 | - name: Install poetry
16 | run: pipx install poetry
17 |
18 | - name: Set up Python 3.12
19 | uses: actions/setup-python@v5
20 | with:
21 | python-version: "3.12"
22 | cache: "poetry"
23 |
24 | - name: Install algokit
25 | run: pipx install git+https://github.com/algorandfoundation/algokit-cli@feat/command_orchestration
26 |
27 | - name: Bootstrap dependencies
28 | run: algokit bootstrap all --project-name 'py-dm-beginner-en'
29 |
30 | - name: Configure git
31 | shell: bash
32 | run: |
33 | # set git user and email as test invoke git
34 | git config --global user.email "actions@github.com" && git config --global user.name "github-actions"
35 |
36 | - name: Deploy to testnet
37 | run: algokit deploy testnet --project-name 'py-dm-beginner-en'
38 | env:
39 | # This is the account that becomes the creator of the contract
40 | DEPLOYER_MNEMONIC: ${{ secrets.DEPLOYER_MNEMONIC }}
41 | # The dispenser account is used to ensure the deployer account is funded
42 | DISPENSER_MNEMONIC: ${{ secrets.DISPENSER_MNEMONIC }}
43 |
--------------------------------------------------------------------------------
/projects/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
21 | React App
22 |
23 |
24 |
25 |
26 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/projects/frontend/tests/example.spec.ts:
--------------------------------------------------------------------------------
1 | import { randomAccount } from '@algorandfoundation/algokit-utils'
2 | import { expect, test } from '@playwright/test'
3 |
4 | test.beforeEach(async ({ page }) => {
5 | await page.goto('http://localhost:5173/')
6 | })
7 |
8 | test('has title', async ({ page }) => {
9 | // Expect a title "to contain" a substring.
10 | await expect(page).toHaveTitle('AlgoKit React Template')
11 | })
12 |
13 | test('get started link', async ({ page }) => {
14 | await expect(page.getByTestId('getting-started')).toHaveText('Getting started')
15 | })
16 |
17 | test('authentication and dummy payment transaction', async ({ page }) => {
18 | page.on('dialog', async (dialog) => {
19 | dialog.message() === 'KMD password' ? await dialog.accept() : await dialog.dismiss()
20 | })
21 |
22 | // 1. Must be able to connect to a KMD wallet provider
23 | await page.getByTestId('connect-wallet').click()
24 | await page.getByTestId('kmd-connect').click()
25 | await page.getByTestId('close-wallet-modal').click()
26 |
27 | // 2. Must be able to send a dummy payment transaction
28 | await page.getByTestId('transactions-demo').click()
29 |
30 | const dummyAccount = randomAccount()
31 | await page.getByTestId('receiver-address').fill(dummyAccount.addr)
32 | await page.getByTestId('send-algo').click()
33 |
34 | // 3. Must be able to see a notification that the transaction was sent
35 | const notification = await page.getByText('Transaction sent:')
36 | await notification.waitFor()
37 | expect(notification).toBeTruthy()
38 | })
39 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: local
3 | hooks:
4 |
5 | - id: black
6 | name: black
7 | description: "Black: The uncompromising Python code formatter"
8 | entry: poetry run black
9 | language: system
10 | minimum_pre_commit_version: 2.9.2
11 | require_serial: true
12 | types_or: [ python, pyi ]
13 |
14 |
15 | - id: ruff
16 | name: ruff
17 | description: "Run 'ruff' for extremely fast Python linting"
18 | entry: poetry run ruff
19 | language: system
20 | types: [ python ]
21 | args: [ --fix ]
22 | require_serial: false
23 | additional_dependencies: [ ]
24 | minimum_pre_commit_version: '0'
25 | files: '^(src|tests)/'
26 |
27 |
28 | - id: mypy
29 | name: mypy
30 | description: '`mypy` will check Python types for correctness'
31 | entry: poetry run mypy
32 | language: system
33 | types_or: [ python, pyi ]
34 | require_serial: true
35 | additional_dependencies: [ ]
36 | minimum_pre_commit_version: '2.9.2'
37 | files: '^(src|tests)/'
38 |
39 | - id: tealer
40 | name: tealer
41 | description: "Run AlgoKit `Tealer` for TEAL static analysis"
42 | entry: algokit
43 | language: system
44 | args: [task, analyze, ".algokit", "--recursive", "--force"]
45 | require_serial: false
46 | additional_dependencies: []
47 | minimum_pre_commit_version: "0"
48 | files: '^.*\.teal$'
49 |
--------------------------------------------------------------------------------
/projects/frontend/src/utils/network/getAlgoClientConfigs.ts:
--------------------------------------------------------------------------------
1 | import { AlgoViteClientConfig, AlgoViteKMDConfig } from '../../interfaces/network'
2 |
3 | export function getAlgodConfigFromViteEnvironment(): AlgoViteClientConfig {
4 | if (!import.meta.env.VITE_ALGOD_SERVER) {
5 | throw new Error('Attempt to get default algod configuration without specifying VITE_ALGOD_SERVER in the environment variables')
6 | }
7 |
8 | return {
9 | server: import.meta.env.VITE_ALGOD_SERVER,
10 | port: import.meta.env.VITE_ALGOD_PORT,
11 | token: import.meta.env.VITE_ALGOD_TOKEN,
12 | network: import.meta.env.VITE_ALGOD_NETWORK,
13 | }
14 | }
15 |
16 | export function getIndexerConfigFromViteEnvironment(): AlgoViteClientConfig {
17 | if (!import.meta.env.VITE_INDEXER_SERVER) {
18 | throw new Error('Attempt to get default algod configuration without specifying VITE_INDEXER_SERVER in the environment variables')
19 | }
20 |
21 | return {
22 | server: import.meta.env.VITE_INDEXER_SERVER,
23 | port: import.meta.env.VITE_INDEXER_PORT,
24 | token: import.meta.env.VITE_INDEXER_TOKEN,
25 | network: import.meta.env.VITE_ALGOD_NETWORK,
26 | }
27 | }
28 |
29 | export function getKmdConfigFromViteEnvironment(): AlgoViteKMDConfig {
30 | if (!import.meta.env.VITE_KMD_SERVER) {
31 | throw new Error('Attempt to get default kmd configuration without specifying VITE_KMD_SERVER in the environment variables')
32 | }
33 |
34 | return {
35 | server: import.meta.env.VITE_KMD_SERVER,
36 | port: import.meta.env.VITE_KMD_PORT,
37 | token: import.meta.env.VITE_KMD_TOKEN,
38 | wallet: import.meta.env.VITE_KMD_WALLET,
39 | password: import.meta.env.VITE_KMD_PASSWORD,
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/projects/frontend/src/components/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode } from 'react'
2 |
3 | interface ErrorBoundaryProps {
4 | children: ReactNode
5 | }
6 |
7 | interface ErrorBoundaryState {
8 | hasError: boolean
9 | error: Error | null
10 | }
11 |
12 | class ErrorBoundary extends React.Component {
13 | constructor(props: ErrorBoundaryProps) {
14 | super(props)
15 | this.state = { hasError: false, error: null }
16 | }
17 |
18 | static getDerivedStateFromError(error: Error): ErrorBoundaryState {
19 | // Update state so the next render will show the fallback UI.
20 | return { hasError: true, error: error }
21 | }
22 |
23 | render(): ReactNode {
24 | if (this.state.hasError) {
25 | // You can render any custom fallback UI
26 | return (
27 |
28 |
29 |
30 |
Error occured
31 |
32 | {this.state.error?.message.includes('Attempt to get default algod configuration')
33 | ? 'Please make sure to set up your environment variables correctly. Create a .env file based on .env.template and fill in the required values. This controls the network and credentials for connections with Algod and Indexer.'
34 | : this.state.error?.message}
35 |
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | return this.props.children
43 | }
44 | }
45 |
46 | export default ErrorBoundary
47 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/smart_contracts/helpers/deploy.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from collections.abc import Callable
3 | from pathlib import Path
4 |
5 | from algokit_utils import (
6 | Account,
7 | ApplicationSpecification,
8 | EnsureBalanceParameters,
9 | ensure_funded,
10 | get_account,
11 | get_algod_client,
12 | get_indexer_client,
13 | )
14 | from algosdk.util import algos_to_microalgos
15 | from algosdk.v2client.algod import AlgodClient
16 | from algosdk.v2client.indexer import IndexerClient
17 |
18 | logger = logging.getLogger(__name__)
19 |
20 |
21 | def deploy(
22 | app_spec_path: Path,
23 | deploy_callback: Callable[
24 | [AlgodClient, IndexerClient, ApplicationSpecification, Account], None
25 | ],
26 | deployer_initial_funds: int = 2,
27 | ) -> None:
28 | # get clients
29 | # by default client configuration is loaded from environment variables
30 | algod_client = get_algod_client()
31 | indexer_client = get_indexer_client()
32 |
33 | # get app spec
34 | app_spec = ApplicationSpecification.from_json(app_spec_path.read_text())
35 |
36 | # get deployer account by name
37 | deployer = get_account(algod_client, "DEPLOYER", fund_with_algos=0)
38 |
39 | minimum_funds_micro_algos = algos_to_microalgos(deployer_initial_funds)
40 | ensure_funded(
41 | algod_client,
42 | EnsureBalanceParameters(
43 | account_to_fund=deployer,
44 | min_spending_balance_micro_algos=minimum_funds_micro_algos,
45 | min_funding_increment_micro_algos=minimum_funds_micro_algos,
46 | ),
47 | )
48 |
49 | # use provided callback to deploy the app
50 | deploy_callback(algod_client, indexer_client, app_spec, deployer)
51 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // General - see also /.editorconfig
3 | "editor.formatOnSave": true,
4 | "files.exclude": {
5 | "**/.git": true,
6 | "**/.DS_Store": true,
7 | "**/Thumbs.db": true,
8 | ".mypy_cache": true,
9 | ".pytest_cache": true,
10 | ".ruff_cache": true,
11 | "**/__pycache__": true,
12 | ".idea": true
13 | },
14 |
15 | // Python
16 | "python.analysis.extraPaths": ["${workspaceFolder}/smart_contracts"],
17 | "python.defaultInterpreterPath": "${workspaceFolder}/.venv",
18 | "[python]": {
19 | "editor.codeActionsOnSave": {
20 | "source.fixAll": "explicit",
21 | // Prevent default import sorting from running; Ruff will sort imports for us anyway
22 | "source.organizeImports": "never"
23 | },
24 | "editor.defaultFormatter": "ms-python.black-formatter",
25 | },
26 | "black-formatter.args": ["--config=pyproject.toml"],
27 | "python.testing.pytestEnabled": true,
28 | "ruff.enable": true,
29 | "ruff.lint.run": "onSave",
30 | "ruff.lint.args": ["--config=pyproject.toml"],
31 | "ruff.importStrategy": "fromEnvironment",
32 | "ruff.fixAll": true, //lint and fix all files in workspace
33 | "ruff.organizeImports": true, //organize imports on save
34 | "ruff.codeAction.disableRuleComment": {
35 | "enable": true
36 | },
37 | "ruff.codeAction.fixViolation": {
38 | "enable": true
39 | },
40 | "python.analysis.typeCheckingMode": "off",
41 | "mypy.configFile": "pyproject.toml",
42 | // set to empty array to use config from project
43 | "mypy.targets": [],
44 | "mypy.runUsingActiveInterpreter": true,
45 |
46 | // On Windows, if execution policy is set to Signed (default) then it won't be able to activate the venv
47 | // so instead let's set it to RemoteSigned for VS Code terminal
48 | "terminal.integrated.shellArgs.windows": ["-ExecutionPolicy", "RemoteSigned"],
49 | }
50 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/smart_contracts/config.py:
--------------------------------------------------------------------------------
1 | import dataclasses
2 | import importlib
3 | from collections.abc import Callable
4 | from pathlib import Path
5 |
6 | from algokit_utils import Account, ApplicationSpecification
7 | from algosdk.v2client.algod import AlgodClient
8 | from algosdk.v2client.indexer import IndexerClient
9 |
10 |
11 | @dataclasses.dataclass
12 | class SmartContract:
13 | path: Path
14 | name: str
15 | deploy: (
16 | Callable[[AlgodClient, IndexerClient, ApplicationSpecification, Account], None]
17 | | None
18 | ) = None
19 |
20 |
21 | def import_contract(folder: Path) -> Path:
22 | """Imports the contract from a folder if it exists."""
23 | contract_path = folder / "contract.py"
24 | if contract_path.exists():
25 | return contract_path
26 | else:
27 | raise Exception(f"Contract not found in {folder}")
28 |
29 |
30 | def import_deploy_if_exists(
31 | folder: Path,
32 | ) -> (
33 | Callable[[AlgodClient, IndexerClient, ApplicationSpecification, Account], None]
34 | | None
35 | ):
36 | """Imports the deploy function from a folder if it exists."""
37 | try:
38 | deploy_module = importlib.import_module(
39 | f"{folder.parent.name}.{folder.name}.deploy_config"
40 | )
41 | return deploy_module.deploy
42 | except ImportError:
43 | return None
44 |
45 |
46 | def has_contract_file(directory: Path) -> bool:
47 | """Checks whether the directory contains contract.py file."""
48 | return (directory / "contract.py").exists()
49 |
50 |
51 | # define contracts to build and/or deploy
52 | base_dir = Path("smart_contracts")
53 | contracts = [
54 | SmartContract(
55 | path=import_contract(folder),
56 | name=folder.name,
57 | deploy=import_deploy_if_exists(folder),
58 | )
59 | for folder in base_dir.iterdir()
60 | if folder.is_dir() and has_contract_file(folder)
61 | ]
62 |
--------------------------------------------------------------------------------
/projects/frontend/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "type": "msedge",
5 | "request": "launch",
6 | "name": "Run (Edge)",
7 | "url": "http://localhost:5173",
8 | "webRoot": "${workspaceFolder}",
9 | "presentation": {
10 | "hidden": false,
11 | "group": "2. Web"
12 | }
13 | },
14 | {
15 | "type": "chrome",
16 | "request": "launch",
17 | "name": "Run (Chrome)",
18 | "url": "http://localhost:5173",
19 | "webRoot": "${workspaceFolder}",
20 | "presentation": {
21 | "hidden": false,
22 | "group": "2. Web"
23 | }
24 | },
25 | {
26 | "type": "firefox",
27 | "request": "launch",
28 | "name": "Run (Firefox)",
29 | "url": "http://localhost:5173",
30 | "webRoot": "${workspaceFolder}",
31 | "presentation": {
32 | "hidden": false,
33 | "group": "2. Web"
34 | }
35 | },
36 | {
37 | "name": "Run dApp",
38 | "type": "node",
39 | "request": "launch",
40 | "runtimeExecutable": "npm",
41 | "runtimeArgs": ["run", "dev"],
42 | "cwd": "${workspaceRoot}",
43 | "console": "integratedTerminal",
44 | "skipFiles": ["/**", "node_modules/**"],
45 | "presentation": {
46 | "hidden": false,
47 | "group": "1. Run Project",
48 | "order": 1
49 | }
50 | },
51 | {
52 | "name": "Run dApp (+ LocalNet)",
53 | "type": "node",
54 | "request": "launch",
55 | "runtimeExecutable": "npm",
56 | "runtimeArgs": ["run", "dev"],
57 | "cwd": "${workspaceRoot}",
58 | "console": "integratedTerminal",
59 | "skipFiles": ["/**", "node_modules/**"],
60 | "preLaunchTask": "Start AlgoKit LocalNet",
61 | "presentation": {
62 | "hidden": false,
63 | "group": "1. Run Project",
64 | "order": 1
65 | }
66 | }
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/projects/frontend/.env.template:
--------------------------------------------------------------------------------
1 | # ======================
2 | # LocalNet configuration
3 | # uncomment below to use
4 | # ======================
5 |
6 | VITE_ENVIRONMENT=local
7 |
8 | # Algod
9 | VITE_ALGOD_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
10 | VITE_ALGOD_SERVER=http://localhost
11 | VITE_ALGOD_PORT=4001
12 | VITE_ALGOD_NETWORK=""
13 |
14 | # Indexer
15 | VITE_INDEXER_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
16 | VITE_INDEXER_SERVER=http://localhost
17 | VITE_INDEXER_PORT=8980
18 |
19 | # KMD
20 | # Please note:
21 | # 1. This is only needed for LocalNet since
22 | # by default KMD provider is ignored on other networks.
23 | # 2. AlgoKit LocalNet starts with a single wallet called 'unencrypted-default-wallet',
24 | # with heaps of tokens available for testing.
25 | VITE_KMD_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
26 | VITE_KMD_SERVER=http://localhost
27 | VITE_KMD_PORT=4002
28 | VITE_KMD_WALLET="unencrypted-default-wallet"
29 | VITE_KMD_PASSWORD=""
30 |
31 | # # ======================
32 | # # TestNet configuration:
33 | # # uncomment below to use
34 | # # ======================
35 |
36 | # VITE_ENVIRONMENT=local
37 |
38 | # # Algod
39 | # VITE_ALGOD_TOKEN=""
40 | # VITE_ALGOD_SERVER="https://testnet-api.algonode.cloud"
41 | # VITE_ALGOD_PORT=""
42 | # VITE_ALGOD_NETWORK="testnet"
43 |
44 | # # Indexer
45 | # VITE_INDEXER_TOKEN=""
46 | # VITE_INDEXER_SERVER="https://testnet-idx.algonode.cloud"
47 | # VITE_INDEXER_PORT=""
48 |
49 |
50 | # # ======================
51 | # # MainNet configuration:
52 | # # uncomment below to use
53 | # # ======================
54 |
55 | # VITE_ENVIRONMENT=production
56 |
57 | # # Algod
58 | # VITE_ALGOD_TOKEN=""
59 | # VITE_ALGOD_SERVER="https://mainnet-api.algonode.cloud"
60 | # VITE_ALGOD_PORT=""
61 | # VITE_ALGOD_NETWORK="mainnet"
62 |
63 | # # Indexer
64 | # VITE_INDEXER_TOKEN=""
65 | # VITE_INDEXER_SERVER="https://mainnet-idx.algonode.cloud"
66 | # VITE_INDEXER_PORT=""
67 |
68 |
--------------------------------------------------------------------------------
/projects/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
4 | "module": "ES2022" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
5 | "declaration": true /* Generates corresponding '.d.ts' file. */,
6 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
7 | "sourceMap": true /* Generates corresponding '.map' file. */,
8 | "strict": true /* Enable all strict type-checking options. */,
9 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
10 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
11 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
12 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
13 | "emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
14 | "skipLibCheck": true /* Skip type checking of declaration files. */,
15 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
16 | "allowJs": false,
17 | "allowSyntheticDefaultImports": true,
18 | "moduleResolution": "Node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "noEmit": true,
22 | "jsx": "react-jsx",
23 | "outDir": "./dist/"
24 | },
25 | "include": [
26 | "src/**/*.ts",
27 | "src/**/*.tsx",
28 | "vite.config.js",
29 | "src/utils/ellipseAddress.spec.tsx",
30 | "src/utils/ellipseAddress.spec.tsx",
31 | "src/main.tsx",
32 | ],
33 | "references": [
34 | {
35 | "path": "./tsconfig.node.json"
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/projects/frontend/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths-ignore:
8 | - "docs/**"
9 | - "**.md"
10 | - ".vscode/**"
11 | - ".idea/**"
12 |
13 | permissions:
14 | contents: read
15 | packages: read
16 |
17 | jobs:
18 | lint-and-build:
19 | name: CI dApp
20 | uses: ./.github/workflows/checks.yaml
21 |
22 | deploy:
23 | runs-on: ubuntu-latest
24 | name: Deploy to Netlify
25 | environment: Prod
26 | concurrency: "${{ github.workflow }}-prod"
27 | needs:
28 | - lint-and-build
29 | steps:
30 | - name: Checkout code
31 | uses: actions/checkout@v2
32 |
33 | - name: Download build artifacts
34 | uses: actions/download-artifact@v4
35 | with:
36 | name: dist
37 | path: dist
38 |
39 | - name: Replace template vars
40 | uses: makerxstudio/shared-config/.github/actions/placeholder-transforms@main
41 | with:
42 | app-artifact-path: './dist'
43 | static-site-transforms: |-
44 | VITE_ALGOD_TOKEN:${{ secrets.VITE_ALGOD_TOKEN }}
45 | VITE_ALGOD_SERVER:${{ vars.VITE_ALGOD_SERVER }}
46 | VITE_ALGOD_PORT:${{ vars.VITE_ALGOD_PORT }}
47 | VITE_ALGOD_NETWORK:${{ vars.VITE_ALGOD_NETWORK }}
48 | VITE_INDEXER_SERVER:${{ vars.VITE_INDEXER_SERVER }}
49 | VITE_INDEXER_PORT:${{ vars.VITE_INDEXER_PORT }}
50 | VITE_INDEXER_TOKEN:${{ secrets.VITE_INDEXER_TOKEN }}
51 | VITE_ENVIRONMENT:${{ vars.VITE_ENVIRONMENT }}
52 |
53 | - name: Install netlify cli
54 | run: npm i netlify-cli
55 |
56 | - name: Publish to netlify
57 | run: netlify deploy --prod --dir "dist"
58 | env:
59 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
60 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
61 | # Set your netlify project env variables on your github repository (see README for more info)
62 |
63 |
--------------------------------------------------------------------------------
/.github/workflows/py-dm-beginner-en-ci.yaml:
--------------------------------------------------------------------------------
1 | name: Validate py-dm-beginner-en
2 |
3 | on:
4 | workflow_call:
5 |
6 | jobs:
7 | validate:
8 | runs-on: "ubuntu-latest"
9 | steps:
10 | - name: Checkout source code
11 | uses: actions/checkout@v4
12 |
13 | - name: Install poetry
14 | run: pipx install poetry
15 |
16 | - name: Set up Python 3.12
17 | uses: actions/setup-python@v5
18 | with:
19 | python-version: "3.12"
20 | cache: "poetry"
21 |
22 | - name: Install algokit
23 | run: pipx install git+https://github.com/algorandfoundation/algokit-cli@feat/command_orchestration
24 |
25 | - name: Start LocalNet
26 | run: algokit localnet start
27 |
28 | - name: Bootstrap dependencies
29 | run: algokit bootstrap all --project-name 'py-dm-beginner-en'
30 |
31 | - name: Configure git
32 | shell: bash
33 | run: |
34 | # set git user and email as test invoke git
35 | git config --global user.email "actions@github.com" && git config --global user.name "github-actions"
36 |
37 | - name: Audit python dependencies
38 | run: algokit project run audit --project-name 'py-dm-beginner-en'
39 |
40 | - name: Lint and format python dependencies
41 | run: algokit project run lint --project-name 'py-dm-beginner-en'
42 |
43 | - name: Run tests
44 | shell: bash
45 | run: |
46 | set -o pipefail
47 | algokit project run test --project-name 'py-dm-beginner-en'
48 |
49 | - name: Build smart contracts
50 | run: algokit project run build --project-name 'py-dm-beginner-en'
51 |
52 | - name: Scan TEAL files for issues
53 | run: algokit project run audit-teal --project-name 'py-dm-beginner-en'
54 |
55 | - name: Check output stability of the smart contracts
56 | run: algokit project run ci-teal-diff --project-name 'py-dm-beginner-en'
57 |
58 | - name: Run deployer against LocalNet
59 | run: algokit project deploy localnet --project-name 'py-dm-beginner-en'
60 |
--------------------------------------------------------------------------------
/projects/frontend/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { DeflyWalletConnect } from '@blockshake/defly-connect'
2 | import { DaffiWalletConnect } from '@daffiwallet/connect'
3 | import { PeraWalletConnect } from '@perawallet/connect'
4 | import { PROVIDER_ID, ProvidersArray, WalletProvider, useInitializeProviders } from '@txnlab/use-wallet'
5 | import algosdk from 'algosdk'
6 | import { SnackbarProvider } from 'notistack'
7 | import Home from './Home'
8 | import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
9 |
10 | let providersArray: ProvidersArray
11 | if (import.meta.env.VITE_ALGOD_NETWORK === '') {
12 | const kmdConfig = getKmdConfigFromViteEnvironment()
13 | providersArray = [
14 | {
15 | id: PROVIDER_ID.KMD,
16 | clientOptions: {
17 | wallet: kmdConfig.wallet,
18 | password: kmdConfig.password,
19 | host: kmdConfig.server,
20 | token: String(kmdConfig.token),
21 | port: String(kmdConfig.port),
22 | },
23 | },
24 | ]
25 | } else {
26 | providersArray = [
27 | { id: PROVIDER_ID.DEFLY, clientStatic: DeflyWalletConnect },
28 | { id: PROVIDER_ID.PERA, clientStatic: PeraWalletConnect },
29 | { id: PROVIDER_ID.DAFFI, clientStatic: DaffiWalletConnect },
30 | { id: PROVIDER_ID.EXODUS },
31 | // If you are interested in WalletConnect v2 provider
32 | // refer to https://github.com/TxnLab/use-wallet for detailed integration instructions
33 | ]
34 | }
35 |
36 | export default function App() {
37 | const algodConfig = getAlgodConfigFromViteEnvironment()
38 |
39 | const walletProviders = useInitializeProviders({
40 | providers: providersArray,
41 | nodeConfig: {
42 | network: algodConfig.network,
43 | nodeServer: algodConfig.server,
44 | nodePort: String(algodConfig.port),
45 | nodeToken: String(algodConfig.token),
46 | },
47 | algosdkStatic: algosdk,
48 | })
49 |
50 | return (
51 |
52 |
53 |
54 |
55 |
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/smart_contracts/helpers/build.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import subprocess
3 | from pathlib import Path
4 | from shutil import rmtree
5 |
6 | from smart_contracts.helpers.util import find_app_spec_file
7 |
8 | logger = logging.getLogger(__name__)
9 | deployment_extension = "py"
10 |
11 |
12 | def build(output_dir: Path, contract_path: Path) -> Path:
13 | output_dir = output_dir.resolve()
14 | if output_dir.exists():
15 | rmtree(output_dir)
16 | output_dir.mkdir(exist_ok=True, parents=True)
17 | logger.info(f"Exporting {contract_path} to {output_dir}")
18 |
19 | build_result = subprocess.run(
20 | [
21 | "poetry",
22 | "run",
23 | "puyapy",
24 | contract_path.absolute(),
25 | f"--out-dir={output_dir}",
26 | "--output-arc32",
27 | ],
28 | stdout=subprocess.PIPE,
29 | stderr=subprocess.STDOUT,
30 | text=True,
31 | )
32 | if build_result.returncode:
33 | raise Exception(f"Could not build contract:\n{build_result.stdout}")
34 |
35 | app_spec_file_name = find_app_spec_file(output_dir)
36 | if app_spec_file_name is None:
37 | raise Exception("Could not generate typed client, .arc32.json file not found")
38 |
39 | generate_result = subprocess.run(
40 | [
41 | "algokit",
42 | "generate",
43 | "client",
44 | output_dir / app_spec_file_name,
45 | "--output",
46 | output_dir / f"client.{deployment_extension}",
47 | ],
48 | stdout=subprocess.PIPE,
49 | stderr=subprocess.STDOUT,
50 | text=True,
51 | )
52 | if generate_result.returncode:
53 | if "No such command" in generate_result.stdout:
54 | raise Exception(
55 | "Could not generate typed client, requires AlgoKit 1.1 or "
56 | "later. Please update AlgoKit"
57 | )
58 | else:
59 | raise Exception(
60 | f"Could not generate typed client:\n{generate_result.stdout}"
61 | )
62 | return output_dir / app_spec_file_name
63 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "Build contracts",
6 | "command": "${workspaceFolder}/.venv/bin/python",
7 | "windows": {
8 | "command": "${workspaceFolder}/.venv/Scripts/python.exe"
9 | },
10 | "args": ["-m", "smart_contracts", "build"],
11 | "options": {
12 | "cwd": "${workspaceFolder}"
13 | },
14 | "group": {
15 | "kind": "build",
16 | "isDefault": true
17 | },
18 | "problemMatcher": []
19 | },
20 | {
21 | "label": "Build contracts (+ LocalNet)",
22 | "command": "${workspaceFolder}/.venv/bin/python",
23 | "windows": {
24 | "command": "${workspaceFolder}/.venv/Scripts/python.exe"
25 | },
26 | "args": ["-m", "smart_contracts", "build"],
27 | "options": {
28 | "cwd": "${workspaceFolder}"
29 | },
30 | "dependsOn": "Start AlgoKit LocalNet",
31 | "problemMatcher": []
32 | },
33 | {
34 | "label": "Start AlgoKit LocalNet",
35 | "command": "algokit",
36 | "args": ["localnet", "start"],
37 | "type": "shell",
38 | "options": {
39 | "cwd": "${workspaceFolder}"
40 | },
41 | "problemMatcher": []
42 | },
43 | {
44 | "label": "Stop AlgoKit LocalNet",
45 | "command": "algokit",
46 | "args": ["localnet", "stop"],
47 | "type": "shell",
48 | "options": {
49 | "cwd": "${workspaceFolder}"
50 | },
51 | "problemMatcher": []
52 | },
53 | {
54 | "label": "Reset AlgoKit LocalNet",
55 | "command": "algokit",
56 | "args": ["localnet", "reset"],
57 | "type": "shell",
58 | "options": {
59 | "cwd": "${workspaceFolder}"
60 | },
61 | "problemMatcher": []
62 | },
63 | {
64 | "label": "Analyze TEAL contracts with AlgoKit Tealer integration",
65 | "command": "algokit",
66 | "args": [
67 | "task",
68 | "analyze",
69 | "${workspaceFolder}/.algokit",
70 | "--recursive",
71 | "--force"
72 | ],
73 | "options": {
74 | "cwd": "${workspaceFolder}"
75 | },
76 | "problemMatcher": []
77 | }
78 | ]
79 | }
80 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.algokit.toml:
--------------------------------------------------------------------------------
1 | [algokit]
2 | min_version = "v1.12.1"
3 |
4 | [generate.smart_contract]
5 | description = "Adds new smart contract to existing project"
6 | path = ".algokit/generators/create_contract"
7 |
8 | [project]
9 | type = 'contract'
10 | name = 'py-dm-beginner-en'
11 | artifacts = 'smart_contracts/artifacts'
12 |
13 | [project.deploy]
14 | command = "poetry run python -m smart_contracts deploy"
15 | environment_secrets = [
16 | "DEPLOYER_MNEMONIC",
17 | "DISPENSER_MNEMONIC",
18 | ]
19 |
20 | [project.deploy.localnet]
21 | environment_secrets = []
22 |
23 | [project.run]
24 | # Commands intented for use locally and in CI
25 | build = { commands = [
26 | 'poetry run python -m smart_contracts build',
27 | ], description = 'Build all smart contracts in the project' }
28 | test = { commands = [
29 | 'poetry run pytest',
30 | ], description = 'Run smart contract tests' }
31 | audit = { commands = [
32 | 'poetry export --without=dev -o requirements.txt',
33 | 'poetry run pip-audit -r requirements.txt',
34 | ], description = 'Audit with pip-audit' }
35 | lint = { commands = [
36 | 'poetry run black --check .',
37 | 'poetry run ruff .',
38 | 'poetry run mypy',
39 | ], description = 'Perform linting' }
40 | lint-fix = { commands = [
41 | 'poetry run black .',
42 | 'poetry run ruff . --fix',
43 | ], description = 'Perform linting fix' }
44 | audit-teal = { commands = [
45 | # 🚨 IMPORTANT 🚨: For strict TEAL validation, remove --exclude statements. The default starter contract is not for production. Ensure thorough testing and adherence to best practices in smart contract development. This is not a replacement for a professional audit.
46 | 'algokit task analyze smart_contracts/artifacts --recursive --force --exclude rekey-to --exclude is-updatable --exclude missing-fee-check --exclude is-deletable --exclude can-close-asset --exclude can-close-account --exclude unprotected-deletable --exclude unprotected-updatable',
47 | ], description = 'Audit TEAL files' }
48 |
49 | # Commands intented for CI only, prefixed with `ci-` by convention
50 | ci-teal-diff = { commands = [
51 | 'git add -N ./smart_contracts/artifacts',
52 | 'git diff --exit-code --minimal ./smart_contracts/artifacts',
53 | ], description = 'Check TEAL files for differences' }
54 |
--------------------------------------------------------------------------------
/projects/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "author": {
5 | "name": "Joe Polny",
6 | "email": "joepolny@gmail.com"
7 | },
8 | "private": true,
9 | "type": "module",
10 | "engines": {
11 | "node": ">=18.0"
12 | },
13 | "devDependencies": {
14 | "@playwright/test": "^1.35.0",
15 | "@types/jest": "29.5.2",
16 | "@types/node": "^18.17.14",
17 | "@types/react": "^18.2.11",
18 | "@types/react-dom": "^18.2.4",
19 | "@typescript-eslint/eslint-plugin": "^6.5.0",
20 | "@typescript-eslint/parser": "^6.5.0",
21 | "@vitejs/plugin-react": "^4.2.1",
22 | "autoprefixer": "^10.4.14",
23 | "eslint": "^8.42.0",
24 | "eslint-config-prettier": "^8.8.0",
25 | "eslint-plugin-prettier": "^5.0.0",
26 | "playwright": "^1.35.0",
27 | "postcss": "^8.4.24",
28 | "tailwindcss": "3.3.2",
29 | "ts-jest": "^29.1.1",
30 | "ts-node": "^10.9.1",
31 | "typescript": "^5.1.6",
32 | "vite": "^5.0.0"
33 | },
34 | "dependencies": {
35 | "@algorandfoundation/algokit-utils": "^6.0.0-beta.4",
36 | "@blockshake/defly-connect": "^1.1.6",
37 | "@daffiwallet/connect": "^1.0.3",
38 | "@perawallet/connect": "^1.3.1",
39 | "@txnlab/use-wallet": "^2.4.0",
40 | "@walletconnect/modal-sign-html": "^2.6.1",
41 | "algosdk": "^2.7.0",
42 | "daisyui": "^4.0.0",
43 | "notistack": "^3.0.1",
44 | "react": "^18.2.0",
45 | "react-dom": "^18.2.0",
46 | "tslib": "^2.6.2"
47 | },
48 | "scripts": {
49 | "dev": "vite",
50 | "build": "tsc && vite build",
51 | "test": "jest --coverage --passWithNoTests",
52 | "playwright:test": "playwright test",
53 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
54 | "lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix",
55 | "preview": "vite preview"
56 | },
57 | "eslintConfig": {
58 | "extends": [
59 | "react-app/jest",
60 | "react-app"
61 | ]
62 | },
63 | "browserslist": {
64 | "production": [
65 | ">0.2%",
66 | "not dead",
67 | "not op_mini all"
68 | ],
69 | "development": [
70 | "last 1 chrome version",
71 | "last 1 firefox version",
72 | "last 1 safari version"
73 | ]
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/projects/frontend/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, devices } from '@playwright/test'
2 |
3 | /**
4 | * Read environment variables from file.
5 | * https://github.com/motdotla/dotenv
6 | */
7 | // require('dotenv').config();
8 |
9 | /**
10 | * See https://playwright.dev/docs/test-configuration.
11 | */
12 | export default defineConfig({
13 | testDir: './tests',
14 | /* Run tests in files in parallel */
15 | fullyParallel: true,
16 | /* Fail the build on CI if you accidentally left test.only in the source code. */
17 | forbidOnly: !!process.env.CI,
18 | /* Retry on CI only */
19 | retries: process.env.CI ? 2 : 0,
20 | /* Opt out of parallel tests on CI. */
21 | workers: process.env.CI ? 1 : undefined,
22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
23 | reporter: 'html',
24 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
25 | use: {
26 | /* Base URL to use in actions like `await page.goto('/')`. */
27 | // baseURL: 'http://127.0.0.1:3000',
28 |
29 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
30 | trace: 'on-first-retry',
31 | testIdAttribute: 'data-test-id',
32 | },
33 |
34 | /* Configure projects for major browsers */
35 | projects: [
36 | {
37 | name: 'chromium',
38 | use: { ...devices['Desktop Chrome'] },
39 | },
40 |
41 | {
42 | name: 'firefox',
43 | use: { ...devices['Desktop Firefox'] },
44 | },
45 |
46 | /* Test against mobile viewports. */
47 | // {
48 | // name: 'Mobile Chrome',
49 | // use: { ...devices['Pixel 5'] },
50 | // },
51 | // {
52 | // name: 'Mobile Safari',
53 | // use: { ...devices['iPhone 12'] },
54 | // },
55 |
56 | /* Test against branded browsers. */
57 | // {
58 | // name: 'Microsoft Edge',
59 | // use: { ...devices['Desktop Edge'], channel: 'msedge' },
60 | // },
61 | // {
62 | // name: 'Google Chrome',
63 | // use: { ..devices['Desktop Chrome'], channel: 'chrome' },
64 | // },
65 | ],
66 |
67 | /* Run your local dev server before starting the tests */
68 | webServer: {
69 | command: 'npm run dev',
70 | url: 'http://localhost:5173',
71 | reuseExistingServer: !process.env.CI,
72 | },
73 | })
74 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/smart_contracts/__main__.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import sys
3 | from pathlib import Path
4 |
5 | from dotenv import load_dotenv
6 |
7 | from smart_contracts.config import contracts
8 | from smart_contracts.helpers.build import build
9 | from smart_contracts.helpers.deploy import deploy
10 | from smart_contracts.helpers.util import find_app_spec_file
11 |
12 | # Uncomment the following lines to enable auto generation of AVM Debugger compliant sourcemap and simulation trace file.
13 | # Learn more about using AlgoKit AVM Debugger to debug your TEAL source codes and inspect various kinds of
14 | # Algorand transactions in atomic groups -> https://github.com/algorandfoundation/algokit-avm-vscode-debugger
15 | # from algokit_utils.config import config
16 | # config.configure(debug=True, trace_all=True)
17 | logging.basicConfig(
18 | level=logging.DEBUG, format="%(asctime)s %(levelname)-10s: %(message)s"
19 | )
20 | logger = logging.getLogger(__name__)
21 | logger.info("Loading .env")
22 | load_dotenv()
23 | root_path = Path(__file__).parent
24 |
25 |
26 | def main(action: str) -> None:
27 | artifact_path = root_path / "artifacts"
28 | match action:
29 | case "build":
30 | for contract in contracts:
31 | logger.info(f"Building app at {contract.path}")
32 | build(artifact_path / contract.name, contract.path)
33 | case "deploy":
34 | for contract in contracts:
35 | logger.info(f"Deploying app {contract.name}")
36 | output_dir = artifact_path / contract.name
37 | app_spec_file_name = find_app_spec_file(output_dir)
38 | if app_spec_file_name is None:
39 | raise Exception("Could not deploy app, .arc32.json file not found")
40 | app_spec_path = output_dir / app_spec_file_name
41 | if contract.deploy:
42 | deploy(app_spec_path, contract.deploy)
43 | case "all":
44 | for contract in contracts:
45 | logger.info(f"Building app at {contract.path}")
46 | app_spec_path = build(artifact_path / contract.name, contract.path)
47 | logger.info(f"Deploying {contract.path.name}")
48 | if contract.deploy:
49 | deploy(app_spec_path, contract.deploy)
50 |
51 |
52 | if __name__ == "__main__":
53 | if len(sys.argv) > 1:
54 | main(sys.argv[1])
55 | else:
56 | main("all")
57 |
--------------------------------------------------------------------------------
/projects/frontend/src/methods.ts:
--------------------------------------------------------------------------------
1 | import * as algokit from '@algorandfoundation/algokit-utils'
2 | import { DigitalMarketplaceClient } from './contracts/DigitalMarketplace'
3 |
4 | /**
5 | * Create the application and opt it into the desired asset
6 | */
7 | export function create(
8 | algorand: algokit.AlgorandClient,
9 | dmClient: DigitalMarketplaceClient,
10 | sender: string,
11 | unitaryPrice: bigint,
12 | quantity: bigint,
13 | assetBeingSold: bigint,
14 | setAppId: (id: number) => void,
15 | ) {
16 | return async () => {
17 | let assetId = assetBeingSold
18 |
19 | if (assetId === 0n) {
20 | const assetCreate = await algorand.send.assetCreate({
21 | sender,
22 | total: quantity,
23 | })
24 |
25 | assetId = BigInt(assetCreate.confirmation.assetIndex!)
26 | }
27 |
28 | const createResult = await dmClient.create.createApplication({ assetId, unitaryPrice })
29 |
30 | const mbrTxn = await algorand.transactions.payment({
31 | sender,
32 | receiver: createResult.appAddress,
33 | amount: algokit.algos(0.1 + 0.1),
34 | extraFee: algokit.algos(0.001),
35 | })
36 |
37 | await dmClient.optInToAsset({ mbrPay: mbrTxn })
38 |
39 | await algorand.send.assetTransfer({
40 | assetId,
41 | sender,
42 | receiver: createResult.appAddress,
43 | amount: quantity,
44 | })
45 |
46 | setAppId(Number(createResult.appId))
47 | }
48 | }
49 |
50 | export function buy(
51 | algorand: algokit.AlgorandClient,
52 | dmClient: DigitalMarketplaceClient,
53 | sender: string,
54 | appAddress: string,
55 | quantity: bigint,
56 | unitaryPrice: bigint,
57 | setUnitsLeft: React.Dispatch>,
58 | ) {
59 | return async () => {
60 | const buyerTxn = await algorand.transactions.payment({
61 | sender,
62 | receiver: appAddress,
63 | amount: algokit.microAlgos(Number(quantity * unitaryPrice)),
64 | extraFee: algokit.algos(0.001),
65 | })
66 |
67 | await dmClient.buy({
68 | buyerTxn,
69 | quantity,
70 | })
71 |
72 | const state = await dmClient.getGlobalState()
73 | const info = await algorand.account.getAssetInformation(appAddress, state.assetId!.asBigInt())
74 | setUnitsLeft(info.balance)
75 | }
76 | }
77 |
78 | export function deleteApp(dmClient: DigitalMarketplaceClient, setAppId: (id: number) => void) {
79 | return async () => {
80 | await dmClient.delete.deleteApplication({}, { sendParams: { fee: algokit.algos(0.003) } })
81 | setAppId(0)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/projects/frontend/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/projects/frontend/src/components/ConnectWallet.tsx:
--------------------------------------------------------------------------------
1 | import { Provider, useWallet } from '@txnlab/use-wallet'
2 | import Account from './Account'
3 |
4 | interface ConnectWalletInterface {
5 | openModal: boolean
6 | closeModal: () => void
7 | }
8 |
9 | const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
10 | const { providers, activeAddress } = useWallet()
11 |
12 | const isKmd = (provider: Provider) => provider.metadata.name.toLowerCase() === 'kmd'
13 |
14 | return (
15 |
84 | )
85 | }
86 | export default ConnectWallet
87 |
--------------------------------------------------------------------------------
/projects/frontend/src/components/Transact.tsx:
--------------------------------------------------------------------------------
1 | import * as algokit from '@algorandfoundation/algokit-utils'
2 | import { useWallet } from '@txnlab/use-wallet'
3 | import algosdk from 'algosdk'
4 | import { useSnackbar } from 'notistack'
5 | import { useState } from 'react'
6 | import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs'
7 |
8 | interface TransactInterface {
9 | openModal: boolean
10 | setModalState: (value: boolean) => void
11 | }
12 |
13 | const Transact = ({ openModal, setModalState }: TransactInterface) => {
14 | const [loading, setLoading] = useState(false)
15 | const [receiverAddress, setReceiverAddress] = useState('')
16 |
17 | const algodConfig = getAlgodConfigFromViteEnvironment()
18 | const algodClient = algokit.getAlgoClient({
19 | server: algodConfig.server,
20 | port: algodConfig.port,
21 | token: algodConfig.token,
22 | })
23 |
24 | const { enqueueSnackbar } = useSnackbar()
25 |
26 | const { signer, activeAddress, signTransactions, sendTransactions } = useWallet()
27 |
28 | const handleSubmitAlgo = async () => {
29 | setLoading(true)
30 |
31 | if (!signer || !activeAddress) {
32 | enqueueSnackbar('Please connect wallet first', { variant: 'warning' })
33 | return
34 | }
35 |
36 | const suggestedParams = await algodClient.getTransactionParams().do()
37 |
38 | const transaction = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
39 | from: activeAddress,
40 | to: receiverAddress,
41 | amount: 1e6,
42 | suggestedParams,
43 | })
44 |
45 | const encodedTransaction = algosdk.encodeUnsignedTransaction(transaction)
46 |
47 | const signedTransactions = await signTransactions([encodedTransaction])
48 |
49 | const waitRoundsToConfirm = 4
50 |
51 | try {
52 | enqueueSnackbar('Sending transaction...', { variant: 'info' })
53 | const { id } = await sendTransactions(signedTransactions, waitRoundsToConfirm)
54 | enqueueSnackbar(`Transaction sent: ${id}`, { variant: 'success' })
55 | setReceiverAddress('')
56 | } catch (e) {
57 | enqueueSnackbar('Failed to send transaction', { variant: 'error' })
58 | }
59 |
60 | setLoading(false)
61 | }
62 |
63 | return (
64 |
92 | )
93 | }
94 |
95 | export default Transact
96 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Ruff (linter)
153 | .ruff_cache/
154 |
155 | # Cython debug symbols
156 | cython_debug/
157 |
158 | # PyCharm
159 | .idea/
160 | !.idea/runConfigurations
161 |
162 | # macOS
163 | .DS_Store
164 |
165 | # Received approval test files
166 | *.received.*
167 |
168 | # NPM
169 | node_modules
170 |
171 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 | coverage/
54 |
55 | # Translations
56 | *.mo
57 | *.pot
58 |
59 | # Django stuff:
60 | *.log
61 | local_settings.py
62 | db.sqlite3
63 | db.sqlite3-journal
64 |
65 | # Flask stuff:
66 | instance/
67 | .webassets-cache
68 |
69 | # Scrapy stuff:
70 | .scrapy
71 |
72 | # Sphinx documentation
73 | docs/_build/
74 |
75 | # PyBuilder
76 | .pybuilder/
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # IPython
83 | profile_default/
84 | ipython_config.py
85 |
86 | # pyenv
87 | # For a library or package, you might want to ignore these files since the code is
88 | # intended to run in multiple environments; otherwise, check them in:
89 | # .python-version
90 |
91 | # pipenv
92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
95 | # install all needed dependencies.
96 | #Pipfile.lock
97 |
98 | # poetry
99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
100 | # This is especially recommended for binary packages to ensure reproducibility, and is more
101 | # commonly ignored for libraries.
102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
103 | #poetry.lock
104 |
105 | # pdm
106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
107 | #pdm.lock
108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
109 | # in version control.
110 | # https://pdm.fming.dev/#use-with-ide
111 | .pdm.toml
112 |
113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
114 | __pypackages__/
115 |
116 | # Celery stuff
117 | celerybeat-schedule
118 | celerybeat.pid
119 |
120 | # SageMath parsed files
121 | *.sage.py
122 |
123 | # Environments
124 | .env
125 | .venv
126 | env/
127 | .env.*
128 | !.env.*.template
129 | !.env.template
130 | venv/
131 | ENV/
132 | env.bak/
133 | venv.bak/
134 |
135 | # Spyder project settings
136 | .spyderproject
137 | .spyproject
138 |
139 | # Rope project settings
140 | .ropeproject
141 |
142 | # mkdocs documentation
143 | /site
144 |
145 | # mypy
146 | .mypy_cache/
147 | .dmypy.json
148 | dmypy.json
149 |
150 | # Pyre type checker
151 | .pyre/
152 |
153 | # pytype static type analyzer
154 | .pytype/
155 |
156 | # Ruff (linter)
157 | .ruff_cache/
158 |
159 | # Cython debug symbols
160 | cython_debug/
161 |
162 | # PyCharm
163 | .idea
164 | !.idea/
165 | .idea/*
166 | !.idea/runConfigurations/
167 |
168 | # macOS
169 | .DS_Store
170 |
171 | # Received approval test files
172 | *.received.*
173 |
174 | # NPM
175 | node_modules
176 |
177 | # AlgoKit
178 | debug_traces/
179 |
180 | .algokit/static-analysis/tealer/
181 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/.tours/getting-started-with-your-algokit-project.tour:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://aka.ms/codetour-schema",
3 | "title": "Getting Started with Your AlgoKit Project",
4 | "steps": [
5 | {
6 | "file": "README.md",
7 | "description": "Welcome to your brand new AlgoKit template-based project. In this tour, we will guide you through the main features and capabilities included in the template.",
8 | "line": 3
9 | },
10 | {
11 | "file": "README.md",
12 | "description": "Start by ensuring you have followed the setup of pre-requisites.",
13 | "line": 9
14 | },
15 | {
16 | "file": "smart_contracts/__main__.py",
17 | "description": "This is the main entry point for building your smart contracts. The default template includes a starter 'Hello World' contract that is deployed via the `algokit-utils` package (either `ts` or `py`, depending on your choice). To create a new smart contract, you can use the [`algokit generate`](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/generate.md) command and invoke a pre-bundled generator template by running `algokit generate smart-contract`. This action will create a new folder in the `smart_contracts` directory, named after your project. Each folder contains a `contract.py` file, which is the entry point for your contract implementation, and `deploy_config.py` | `deployConfig.ts` files (depending on the language chosen for the template), that perform the deployment of the contract.",
18 | "line": 26
19 | },
20 | {
21 | "file": "smart_contracts/hello_world/deploy_config.py",
22 | "description": "The default deployment scripts invoke a sample method on the starter contract that demonstrates how to interact with your deployed Algorand on-chain applications using the [`AlgoKit Typed Clients`](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/generate.md#1-typed-clients) feature.",
23 | "line": 32
24 | },
25 | {
26 | "file": "tests/digital_marketplace_test.py",
27 | "description": "If you opted to include unit tests, the default tests provided demonstrate an example of mocking, setting up fixtures, and testing smart contract calls on an AlgoKit typed client.",
28 | "line": 36
29 | },
30 | {
31 | "file": ".env.localnet.template",
32 | "description": "Environment files are a crucial mechanism that allows you to set up the [`algokit deploy`](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/deploy.md) feature to simplify deploying your contracts in CI/CD environments (please note we still recommend careful evaluation when it comes to deployment to MainNet). Clone the file and remove the `.template` suffix to apply the changes to deployment scripts and launch configurations. The network prefix `localnet|testnet|mainnet` is primarily optimized for `algokit deploy`. The order of loading the variables is `.env.{network}` < `.env`.",
33 | "line": 2
34 | },
35 | {
36 | "file": ".algokit.toml",
37 | "description": "This is the configuration file used by AlgoKit to determine version requirements, `algokit deploy` settings, and references to custom generators.",
38 | "line": 5
39 | },
40 | {
41 | "file": ".vscode/launch.json",
42 | "description": "Refer to the pre-bundled Visual Studio launch configurations, offering various options on how to execute the build and deployment of your smart contracts.",
43 | "line": 5
44 | },
45 | {
46 | "file": ".vscode/extensions.json",
47 | "description": "We highly recommend installing the recommended extensions to get the most out of this template starter project in your VSCode IDE.",
48 | "line": 3
49 | },
50 | {
51 | "file": "smart_contracts/__main__.py",
52 | "description": "Uncomment the following lines to enable complementary utilities that will generate artifacts required for the [AlgoKit AVM Debugger](https://github.com/algorandfoundation/algokit-avm-vscode-debugger) VSCode plugin available on the [VSCode Extension Marketplace](https://marketplace.visualstudio.com/items?itemName=algorandfoundation.algokit-avm-vscode-debugger). A new folder will be automatically created in the `.algokit` directory with source maps of all TEAL contracts in this workspace, as well as traces that will appear in a folder at the root of the workspace. You can then use the traces as entry points to trigger the debug extension. Make sure to have the `.algokit.toml` file available at the root of the workspace.",
53 | "line": 15
54 | }
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/projects/py-dm-beginner-en/smart_contracts/digital_marketplace/contract.py:
--------------------------------------------------------------------------------
1 | # pyright: reportMissingModuleSource=false
2 | from algopy import (
3 | # On Algorand, assets are native objects rather than smart contracts
4 | Asset,
5 | # Global is used to access global variables from the network
6 | Global,
7 | # Txn is used access information about the current transcation
8 | Txn,
9 | # By default, all numbers in the AVM are 64-bit unsigned integers
10 | UInt64,
11 | # ARC4 defines the Algorand ABI for method calling and type encoding
12 | arc4,
13 | # gtxn is used to read transaction within the same atomic group
14 | gtxn,
15 | # itxn is used to send transactions from within a smart contract
16 | itxn,
17 | )
18 |
19 |
20 | # We want the methods in our contract to follow the ARC4 standard
21 | class DigitalMarketplace(arc4.ARC4Contract):
22 | # Every asset has a unique ID
23 | # We want to store the ID for the asset we are selling
24 | asset_id: UInt64
25 |
26 | # We want to store the price for the asset we are selling
27 | unitary_price: UInt64
28 |
29 | # We want create_application to be a plublic ABI method
30 | @arc4.abimethod(
31 | # There are certain actions that a contract call can do
32 | # Some examples are UpdateApplication, DeleteApplication, and NoOp
33 | # NoOp is a call that does nothing special after it is exected
34 | allow_actions=["NoOp"],
35 | # Require that this method is only callable when creating the app
36 | create="require",
37 | )
38 | def create_application(
39 | self,
40 | # The ID of the asset we're selling
41 | asset_id: Asset,
42 | # The initial sale price
43 | unitary_price: UInt64,
44 | ) -> None:
45 | # Save the values we passed in to our method in the contract's state
46 | self.asset_id = asset_id.id
47 | self.unitary_price = unitary_price
48 |
49 | @arc4.abimethod
50 | def set_price(self, unitary_price: UInt64) -> None:
51 | # We don't want anyone to be able to come in and modify the price
52 | # You could implement some sort of RBAC,
53 | # but in this case just making sure the caller is the app creator works
54 | assert Txn.sender == Global.creator_address
55 |
56 | # Save the new price
57 | self.unitary_price = unitary_price
58 |
59 | # Before any account can receive an asset, it must opt-in to it
60 | # This method enables the application to opt-in to the asset
61 | @arc4.abimethod
62 | def opt_in_to_asset(
63 | self,
64 | # Whenever someone calls this method, they also need to send a payment
65 | # A payment transaction is a transfer of ALGO
66 | mbr_pay: gtxn.PaymentTransaction,
67 | ) -> None:
68 | # We want to make sure that the application address is not already opted in
69 | assert not Global.current_application_address.is_opted_in(Asset(self.asset_id))
70 |
71 | # Just like asserting fields in Txn, we can assert fields in the PaymentTxn
72 | # We can do this only because it is grouped atomically with our app call
73 |
74 | # Just because we made it an argument to the method, there's no gurantee
75 | # it is being sent to the aplication's address so we need to manually assert
76 | assert mbr_pay.receiver == Global.current_application_address
77 |
78 | # On Algorand, each account has a minimum balance requirement (MBR)
79 | # The MBR is locked in the account and cannot be spent (until explicitly unlocked)
80 | # Every accounts has an MBR of 0.1 ALGO (Global.min_balance)
81 | # Opting into an asset increases the MBR by 0.1 ALGO (Global.asset_opt_in_min_balance)
82 | assert mbr_pay.amount == Global.min_balance + Global.asset_opt_in_min_balance
83 |
84 | # Transactions can be sent from a user via signatures
85 | # They can also be sent programmatically from a smart contract
86 | # Here we want to issue an opt-in transaction
87 | # An opt-in transaction is simply transferring 0 of an asset to yourself
88 | itxn.AssetTransfer(
89 | xfer_asset=self.asset_id,
90 | asset_receiver=Global.current_application_address,
91 | asset_amount=0,
92 | ).submit()
93 |
94 | @arc4.abimethod
95 | def buy(
96 | self,
97 | # To buy assets, a payment must be sent
98 | buyer_txn: gtxn.PaymentTransaction,
99 | # The quantity of assets to buy
100 | quantity: UInt64,
101 | ) -> None:
102 | # We need to verify that the payment is being sent to the application
103 | # and is enough to cover the cost of the asset
104 | assert buyer_txn.sender == Txn.sender
105 | assert buyer_txn.receiver == Global.current_application_address
106 | assert buyer_txn.amount == self.unitary_price * quantity
107 |
108 | # Once we've verified the payment, we can transfer the asset
109 | itxn.AssetTransfer(
110 | xfer_asset=self.asset_id,
111 | asset_receiver=Txn.sender,
112 | asset_amount=quantity,
113 | ).submit()
114 |
115 | @arc4.abimethod(
116 | # This method is called when the application is deleted
117 | allow_actions=["DeleteApplication"]
118 | )
119 | def delete_application(self) -> None:
120 | # Only allow the creator to delete the application
121 | assert Txn.sender == Global.creator_address
122 |
123 | # Send all the unsold assets to the creator
124 | itxn.AssetTransfer(
125 | xfer_asset=self.asset_id,
126 | asset_receiver=Global.creator_address,
127 | # The amount is 0, but the asset_close_to field is set
128 | # This means that ALL assets are being sent to the asset_close_to address
129 | asset_amount=0,
130 | # Close the asset to unlock the 0.1 ALGO that was locked in opt_in_to_asset
131 | asset_close_to=Global.creator_address,
132 | ).submit()
133 |
134 | # Send the remaining balance to the creator
135 | itxn.Payment(
136 | receiver=Global.creator_address,
137 | amount=0,
138 | # Close the account to get back ALL the ALGO in the account
139 | close_remainder_to=Global.creator_address,
140 | ).submit()
141 |
--------------------------------------------------------------------------------
/projects/frontend/src/Home.tsx:
--------------------------------------------------------------------------------
1 | // src/components/Home.tsx
2 | import { Config as AlgokitConfig } from '@algorandfoundation/algokit-utils'
3 | import AlgorandClient from '@algorandfoundation/algokit-utils/types/algorand-client'
4 | import { useWallet } from '@txnlab/use-wallet'
5 | import algosdk from 'algosdk'
6 | import React, { useEffect, useState } from 'react'
7 | import ConnectWallet from './components/ConnectWallet'
8 | import MethodCall from './components/MethodCall'
9 | import { DigitalMarketplaceClient } from './contracts/DigitalMarketplace'
10 | import * as methods from './methods'
11 | import { getAlgodConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
12 |
13 | interface HomeProps {}
14 |
15 | const Home: React.FC = () => {
16 | AlgokitConfig.configure({ populateAppCallResources: true })
17 |
18 | const [openWalletModal, setOpenWalletModal] = useState(false)
19 | const [appId, setAppId] = useState(0)
20 | const [assetId, setAssetId] = useState(0n)
21 | const [unitaryPrice, setUnitaryPrice] = useState(0n)
22 | const [quantity, setQuantity] = useState(0n)
23 | const [unitsLeft, setUnitsLeft] = useState(0n)
24 | const [seller, setSeller] = useState(undefined)
25 | const { activeAddress, signer } = useWallet()
26 |
27 | useEffect(() => {
28 | dmClient
29 | .getGlobalState()
30 | .then((globalState) => {
31 | setUnitaryPrice(globalState.unitaryPrice?.asBigInt() || 0n)
32 | const id = globalState.assetId?.asBigInt() || 0n
33 | setAssetId(id)
34 | algorand.account.getAssetInformation(algosdk.getApplicationAddress(appId), id).then((info) => {
35 | setUnitsLeft(info.balance)
36 | })
37 | })
38 | .catch(() => {
39 | setUnitaryPrice(0n)
40 | setAssetId(0n)
41 | setUnitsLeft(0n)
42 | })
43 |
44 | algorand.client.algod
45 | .getApplicationByID(appId)
46 | .do()
47 | .then((response) => {
48 | setSeller(response.params.creator)
49 | })
50 | .catch(() => {
51 | setSeller(undefined)
52 | })
53 | }, [appId])
54 |
55 | const algodConfig = getAlgodConfigFromViteEnvironment()
56 | const algorand = AlgorandClient.fromConfig({ algodConfig })
57 | algorand.setDefaultSigner(signer)
58 |
59 | const dmClient = new DigitalMarketplaceClient(
60 | {
61 | resolveBy: 'id',
62 | id: appId,
63 | sender: { addr: activeAddress!, signer },
64 | },
65 | algorand.client.algod,
66 | )
67 |
68 | const toggleWalletModal = () => {
69 | setOpenWalletModal(!openWalletModal)
70 | }
71 |
72 | return (
73 |
74 |
75 |
76 |
77 | Welcome to
AlgoKit 🙂
78 |
79 |
80 | This starter has been generated using official AlgoKit React template. Refer to the resource below for next steps.
81 |