├── projects ├── .gitkeep └── challenge │ ├── smart_contracts │ ├── __init__.py │ ├── helpers │ │ ├── __init__.py │ │ ├── util.py │ │ ├── deploy.py │ │ └── build.py │ ├── artifacts │ │ └── personal_vault │ │ │ ├── PersonalVault.clear.teal │ │ │ ├── PersonalVault.approval.teal │ │ │ ├── PersonalVault.arc32.json │ │ │ └── client.py │ ├── README.md │ ├── personal_vault │ │ ├── contract.py │ │ └── deploy_config.py │ ├── config.py │ └── __main__.py │ ├── .gitattributes │ ├── poetry.toml │ ├── .env.template │ ├── .editorconfig │ ├── .env.testnet.template │ ├── .vscode │ ├── extensions.json │ ├── settings.json │ ├── launch.json │ └── tasks.json │ ├── .algokit │ └── generators │ │ └── create_contract │ │ ├── copier.yaml │ │ └── smart_contracts │ │ └── {{ contract_name }} │ │ ├── contract.py.j2 │ │ └── deploy_config.py.j2 │ ├── .copier-answers.yml │ ├── .env.localnet.template │ ├── pyproject.toml │ ├── .algokit.toml │ ├── .gitignore │ ├── .tours │ └── getting-started-with-your-algokit-project.tour │ ├── README.md │ └── poetry.lock ├── .gitattributes ├── .algokit.toml ├── .vscode └── settings.json ├── .editorconfig ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .devcontainer.json ├── python-challenge-1.code-workspace ├── .gitignore └── README.md /projects/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /projects/challenge/smart_contracts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/challenge/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /projects/challenge/smart_contracts/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/challenge/poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /projects/challenge/.env.template: -------------------------------------------------------------------------------- 1 | # this file should contain environment variables common to all environments/networks 2 | -------------------------------------------------------------------------------- /.algokit.toml: -------------------------------------------------------------------------------- 1 | [algokit] 2 | min_version = "v1.12.1" 3 | 4 | [project] 5 | type = 'workspace' 6 | projects_root_path = 'projects' 7 | -------------------------------------------------------------------------------- /projects/challenge/.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | [*.py] 10 | indent_size = 4 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Disabled due to matangover.mypy extension not supporting monorepos 3 | // To be addressed as part of https://github.com/matangover/mypy-vscode/issues/82 4 | "mypy.enabled": false 5 | } 6 | -------------------------------------------------------------------------------- /projects/challenge/.env.testnet.template: -------------------------------------------------------------------------------- 1 | # this file contains algorand network settings for interacting with testnet via algonode 2 | ALGOD_SERVER=https://testnet-api.algonode.cloud 3 | INDEXER_SERVER=https://testnet-idx.algonode.cloud 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | insert_final_newline = true 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | tab_width = 2 8 | max_line_length = 140 9 | trim_trailing_whitespace = true 10 | single_quote = true 11 | -------------------------------------------------------------------------------- /projects/challenge/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.python", 4 | "tamasfe.even-better-toml", 5 | "editorconfig.editorconfig", 6 | "vsls-contrib.codetour", 7 | "algorandfoundation.algokit-avm-vscode-debugger" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /projects/challenge/smart_contracts/helpers/util.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | def find_app_spec_file(output_dir: Path) -> str | None: 5 | for file in output_dir.iterdir(): 6 | if file.is_file() and file.suffixes == [".arc32", ".json"]: 7 | return file.name 8 | return None 9 | -------------------------------------------------------------------------------- /projects/challenge/smart_contracts/artifacts/personal_vault/PersonalVault.clear.teal: -------------------------------------------------------------------------------- 1 | #pragma version 10 2 | 3 | smart_contracts.personal_vault.contract.PersonalVault.clear_state_program: 4 | // smart_contracts/personal_vault/contract.py:14 5 | // class PersonalVault(ARC4Contract): 6 | int 1 7 | return 8 | -------------------------------------------------------------------------------- /projects/challenge/.algokit/generators/create_contract/copier.yaml: -------------------------------------------------------------------------------- 1 | _tasks: 2 | - "echo '==== Successfully initialized new smart contract 🚀 ===='" 3 | 4 | contract_name: 5 | type: str 6 | help: Name of your new contract. 7 | placeholder: "my-new-contract" 8 | default: "my-new-contract" 9 | 10 | _templates_suffix: ".j2" 11 | -------------------------------------------------------------------------------- /projects/challenge/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/contract.py.j2: -------------------------------------------------------------------------------- 1 | from algopy import ARC4Contract, arc4 2 | 3 | 4 | class {{ contract_name.split('_')|map('capitalize')|join }}(ARC4Contract): 5 | @arc4.abimethod() 6 | def hello(self, name: arc4.String) -> arc4.String: 7 | return "Hello, " + name 8 | -------------------------------------------------------------------------------- /projects/challenge/.copier-answers.yml: -------------------------------------------------------------------------------- 1 | # Changes here will be overwritten by Copier; NEVER EDIT MANUALLY 2 | _commit: 1.0.0 3 | _src_path: gh:algorandfoundation/algokit-python-template 4 | author_email: iskysun96@gmail.com 5 | author_name: iskysun96 6 | contract_name: personal_vault 7 | deployment_language: python 8 | preset_name: starter 9 | project_name: python-challenge-1 10 | 11 | -------------------------------------------------------------------------------- /projects/challenge/.env.localnet.template: -------------------------------------------------------------------------------- 1 | # this file should contain environment variables specific to algokit localnet 2 | ALGOD_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 3 | ALGOD_SERVER=http://localhost 4 | ALGOD_PORT=4001 5 | INDEXER_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 6 | INDEXER_SERVER=http://localhost 7 | INDEXER_PORT=8980 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Algorand Coding Challenge Submission 2 | 3 | **What was the bug?** 4 | 5 | 6 | 7 | **How did you fix the bug?** 8 | 9 | 10 | 11 | **Console Screenshot:** 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/challenge/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "python-challenge-1" 3 | version = "0.1.0" 4 | description = "Algorand smart contracts" 5 | authors = ["iskysun96 "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | algokit-utils = "^2.2.0" 11 | python-dotenv = "^1.0.0" 12 | algorand-python = "^1.0.0" 13 | 14 | [tool.poetry.group.dev.dependencies] 15 | algokit-client-generator = "^1.1.3" 16 | puyapy = "^1.0.0" 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | 22 | -------------------------------------------------------------------------------- /.devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "forwardPorts": [4001, 4002, 8980, 5173], 3 | "portsAttributes": { 4 | "4001": { 5 | "label": "algod" 6 | }, 7 | "4002": { 8 | "label": "kmd" 9 | }, 10 | "8980": { 11 | "label": "indexer" 12 | }, 13 | "5173": { 14 | "label": "vite" 15 | } 16 | }, 17 | "postCreateCommand": "mkdir -p ~/.config/algokit && pipx install algokit && sudo chown -R codespace:codespace ~/.config/algokit", 18 | "postStartCommand": "for i in {1..5}; do algokit localnet status > /dev/null 2>&1 && break || sleep 30; algokit localnet reset; done" 19 | } 20 | -------------------------------------------------------------------------------- /python-challenge-1.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "./", 5 | "name": "ROOT" 6 | }, 7 | { 8 | "path": "projects/challenge" 9 | } 10 | ], 11 | "settings": { 12 | "files.exclude": { 13 | "projects/": true 14 | }, 15 | "jest.disabledWorkspaceFolders": [ 16 | "ROOT", 17 | "projects" 18 | ] 19 | }, 20 | "extensions": { 21 | "recommendations": [ 22 | "joshx.workspace-terminals" 23 | ] 24 | }, 25 | "tasks": { 26 | "version": "2.0.0", 27 | "tasks": [] 28 | }, 29 | "launch": { 30 | "configurations": [] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /projects/challenge/.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": null, 25 | }, 26 | 27 | // On Windows, if execution policy is set to Signed (default) then it won't be able to activate the venv 28 | // so instead let's set it to RemoteSigned for VS Code terminal 29 | "terminal.integrated.shellArgs.windows": ["-ExecutionPolicy", "RemoteSigned"], 30 | } 31 | -------------------------------------------------------------------------------- /projects/challenge/.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/challenge/smart_contracts/README.md: -------------------------------------------------------------------------------- 1 | ## How to add new smart contracts? 2 | 3 | By the default the template creates a single `HelloWorld` contract under personal_vault 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/challenge/.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.personal_vault.client import ( 18 | PersonalVaultClient, 19 | ) 20 | 21 | app_client = PersonalVaultClient( 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/challenge/smart_contracts/personal_vault/contract.py: -------------------------------------------------------------------------------- 1 | from algopy import ( 2 | ARC4Contract, 3 | UInt64, 4 | arc4, 5 | itxn, 6 | Global, 7 | LocalState, 8 | Txn, 9 | gtxn, 10 | op, 11 | ) 12 | 13 | 14 | class PersonalVault(ARC4Contract): 15 | def __init__(self) -> None: 16 | self.balance = LocalState(UInt64) 17 | 18 | @arc4.baremethod(allow_actions=["OptIn"]) 19 | def opt_in_to_app(self) -> None: 20 | self.balance[Txn.sender] = UInt64(0) 21 | 22 | @arc4.abimethod() 23 | def deposit(self, ptxn: gtxn.PaymentTransaction) -> UInt64: 24 | assert ptxn.amount > 0, "Deposit amount must be greater than 0" 25 | assert ( 26 | ptxn.receiver == Global.current_application_id 27 | ), "Deposit receiver must be the contract address" 28 | assert ptxn.sender == Txn.sender, "Deposit sender must be the caller" 29 | assert op.app_opted_in( 30 | Txn.sender, Global.current_application_address 31 | ), "Deposit sender must opt-in to the app first." 32 | 33 | self.balance[Txn.sender] += ptxn.amount 34 | user_balance = self.balance[Txn.sender] 35 | 36 | return user_balance 37 | 38 | @arc4.abimethod(allow_actions=["CloseOut"]) 39 | def withdraw(self) -> UInt64: 40 | userBalance = self.balance[Txn.sender] 41 | 42 | itxn.Payment( 43 | receiver=Txn.sender, 44 | sender=Global.current_application_address, 45 | amount=userBalance, 46 | fee=0, 47 | ).submit() 48 | 49 | return userBalance 50 | -------------------------------------------------------------------------------- /projects/challenge/.algokit.toml: -------------------------------------------------------------------------------- 1 | [algokit] 2 | min_version = "v2.0.0" 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 = 'python-challenge-1' 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 | ] 18 | 19 | [project.deploy.localnet] 20 | environment_secrets = [] 21 | 22 | [project.run] 23 | # Commands intented for use locally and in CI 24 | build = { commands = [ 25 | 'poetry run python -m smart_contracts build', 26 | ], description = 'Build all smart contracts in the project' } 27 | lint = { commands = [ 28 | ], description = 'Perform linting' } 29 | audit-teal = { commands = [ 30 | # 🚨 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. 31 | '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', 32 | ], description = 'Audit TEAL files' } 33 | 34 | # Commands intented for CI only, prefixed with `ci-` by convention 35 | ci-teal-diff = { commands = [ 36 | 'git add -N ./smart_contracts/artifacts', 37 | 'git diff --exit-code --minimal ./smart_contracts/artifacts', 38 | ], description = 'Check TEAL files for differences' } 39 | -------------------------------------------------------------------------------- /projects/challenge/smart_contracts/helpers/deploy.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code="no-untyped-call, misc" 2 | 3 | 4 | import logging 5 | from collections.abc import Callable 6 | from pathlib import Path 7 | 8 | from algokit_utils import ( 9 | Account, 10 | ApplicationSpecification, 11 | EnsureBalanceParameters, 12 | ensure_funded, 13 | get_account, 14 | get_algod_client, 15 | get_indexer_client, 16 | ) 17 | from algosdk.util import algos_to_microalgos 18 | from algosdk.v2client.algod import AlgodClient 19 | from algosdk.v2client.indexer import IndexerClient 20 | 21 | logger = logging.getLogger(__name__) 22 | 23 | 24 | def deploy( 25 | app_spec_path: Path, 26 | deploy_callback: Callable[ 27 | [AlgodClient, IndexerClient, ApplicationSpecification, Account], None 28 | ], 29 | deployer_initial_funds: int = 2, 30 | ) -> None: 31 | # get clients 32 | # by default client configuration is loaded from environment variables 33 | algod_client = get_algod_client() 34 | indexer_client = get_indexer_client() 35 | 36 | # get app spec 37 | app_spec = ApplicationSpecification.from_json(app_spec_path.read_text()) 38 | 39 | # get deployer account by name 40 | deployer = get_account(algod_client, "DEPLOYER", fund_with_algos=0) 41 | 42 | minimum_funds_micro_algos = algos_to_microalgos(deployer_initial_funds) 43 | ensure_funded( 44 | algod_client, 45 | EnsureBalanceParameters( 46 | account_to_fund=deployer, 47 | min_spending_balance_micro_algos=minimum_funds_micro_algos, 48 | min_funding_increment_micro_algos=minimum_funds_micro_algos, 49 | ), 50 | ) 51 | 52 | # use provided callback to deploy the app 53 | deploy_callback(algod_client, indexer_client, app_spec, deployer) 54 | -------------------------------------------------------------------------------- /projects/challenge/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 # type: ignore 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/challenge/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 | "algokit", 22 | "--no-color", 23 | "compile", 24 | "python", 25 | contract_path.absolute(), 26 | f"--out-dir={output_dir}", 27 | "--output-arc32", 28 | ], 29 | stdout=subprocess.PIPE, 30 | stderr=subprocess.STDOUT, 31 | text=True, 32 | ) 33 | if build_result.returncode: 34 | raise Exception(f"Could not build contract:\n{build_result.stdout}") 35 | 36 | app_spec_file_name = find_app_spec_file(output_dir) 37 | if app_spec_file_name is None: 38 | raise Exception("Could not generate typed client, .arc32.json file not found") 39 | 40 | generate_result = subprocess.run( 41 | [ 42 | "algokit", 43 | "generate", 44 | "client", 45 | output_dir / app_spec_file_name, 46 | "--output", 47 | output_dir / f"client.{deployment_extension}", 48 | ], 49 | stdout=subprocess.PIPE, 50 | stderr=subprocess.STDOUT, 51 | text=True, 52 | ) 53 | if generate_result.returncode: 54 | if "No such command" in generate_result.stdout: 55 | raise Exception( 56 | "Could not generate typed client, requires AlgoKit 1.1 or " 57 | "later. Please update AlgoKit" 58 | ) 59 | else: 60 | raise Exception( 61 | f"Could not generate typed client:\n{generate_result.stdout}" 62 | ) 63 | return output_dir / app_spec_file_name 64 | -------------------------------------------------------------------------------- /projects/challenge/.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/challenge/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/challenge/smart_contracts/personal_vault/deploy_config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import algokit_utils 4 | from algosdk.v2client.algod import AlgodClient 5 | from algosdk.v2client.indexer import IndexerClient 6 | from algosdk.transaction import * 7 | 8 | from algokit_utils import EnsureBalanceParameters, TransactionParameters 9 | from algosdk.atomic_transaction_composer import ( 10 | TransactionWithSigner, 11 | ) 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | # define deployment behaviour based on supplied app spec 17 | def deploy( 18 | algod_client: AlgodClient, 19 | indexer_client: IndexerClient, 20 | app_spec: algokit_utils.ApplicationSpecification, 21 | deployer: algokit_utils.Account, 22 | ) -> None: 23 | from smart_contracts.artifacts.personal_vault.client import ( 24 | PersonalVaultClient, 25 | ) 26 | 27 | # 1. Instantiate the app client. Used for deploying and interacting with the app. 28 | app_client = PersonalVaultClient( 29 | algod_client, 30 | creator=deployer, 31 | indexer_client=indexer_client, 32 | ) 33 | 34 | # 2. Deploy the app to Algorand local blockchain. 35 | app_client.app_client.create() 36 | logger.info(f"Created App ID: {app_client.app_id}") 37 | 38 | # 3. Fund the app to cover Minimum Balance Requirement (https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/?from_query=minimum#minimum-balance-requirement-for-a-smart-contract) 39 | algokit_utils.ensure_funded( 40 | algod_client, 41 | EnsureBalanceParameters( 42 | account_to_fund=app_client.app_address, 43 | min_spending_balance_micro_algos=0, 44 | ), 45 | ) 46 | 47 | # 4. Create payment transaction object of 1000000 microAlgos (1 ALGO) to deposit funds to the app. 48 | sp = algod_client.suggested_params() 49 | ptxn = PaymentTxn( 50 | sender=deployer.address, sp=sp, receiver=app_client.app_address, amt=1000000 51 | ) 52 | 53 | # 5. Create a transaction with the signer so the app client knows who is signing the payment txn. 54 | ptxn_with_signer = TransactionWithSigner(ptxn, deployer.signer) 55 | 56 | """ 57 | # 6. Atomically group the payment transaction, app opt-in transaction, and the deposit method call transaction and execute them simultaneously. 58 | 59 | Why do we need to atomically group the 3 transactions? 60 | - payment txn: Algorand smart contracts are not accounts so need to send the payment to the associated contract account. 61 | - app opt-in txn: The depositor needs to opt-in to the contract so that the contract can create a local state for the depositor. 62 | - deposit method call txn: Contract method call to deposit funds to the contract. 63 | """ 64 | response = ( 65 | app_client.compose().opt_in_bare().deposit(ptxn=ptxn_with_signer).execute() 66 | ) 67 | depositResponse = response.abi_results[0] 68 | 69 | logger.info( 70 | f"Deposited {(depositResponse.return_value) // 1000000} Algo to the bank contract in transaction {depositResponse.tx_id}." 71 | ) 72 | 73 | # 7. Depositor withdraw funds from the contract. 74 | sp = algod_client.suggested_params() 75 | sp.flat_fee = True 76 | sp.fee = 2000 # doubling the base txn fee to cover the inner transaction fee of the withdraw method call. 77 | 78 | response = app_client.close_out_withdraw( 79 | transaction_parameters=TransactionParameters(suggested_params=sp) 80 | ) 81 | 82 | logger.info( 83 | f"User withdrew {response.return_value // 1000000 } Algo from the bank contract in transaction {response.tx_id}." 84 | ) 85 | -------------------------------------------------------------------------------- /.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/challenge/.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/challenge/.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/personal_vault_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/challenge/smart_contracts/artifacts/personal_vault/PersonalVault.approval.teal: -------------------------------------------------------------------------------- 1 | #pragma version 10 2 | 3 | smart_contracts.personal_vault.contract.PersonalVault.approval_program: 4 | // smart_contracts/personal_vault/contract.py:14 5 | // class PersonalVault(ARC4Contract): 6 | txn NumAppArgs 7 | bz main_bare_routing@6 8 | method "deposit(pay)uint64" 9 | method "withdraw()uint64" 10 | txna ApplicationArgs 0 11 | match main_deposit_route@2 main_withdraw_route@3 12 | err // reject transaction 13 | 14 | main_deposit_route@2: 15 | // smart_contracts/personal_vault/contract.py:22 16 | // @arc4.abimethod() 17 | txn OnCompletion 18 | ! 19 | assert // OnCompletion is NoOp 20 | txn ApplicationID 21 | assert // is not creating 22 | // smart_contracts/personal_vault/contract.py:14 23 | // class PersonalVault(ARC4Contract): 24 | txn GroupIndex 25 | int 1 26 | - 27 | dup 28 | gtxns TypeEnum 29 | int pay 30 | == 31 | assert // transaction type is pay 32 | // smart_contracts/personal_vault/contract.py:22 33 | // @arc4.abimethod() 34 | callsub deposit 35 | itob 36 | byte 0x151f7c75 37 | swap 38 | concat 39 | log 40 | int 1 41 | return 42 | 43 | main_withdraw_route@3: 44 | // smart_contracts/personal_vault/contract.py:38 45 | // @arc4.abimethod(allow_actions=["CloseOut"]) 46 | txn OnCompletion 47 | int CloseOut 48 | == 49 | assert // OnCompletion is CloseOut 50 | txn ApplicationID 51 | assert // is not creating 52 | callsub withdraw 53 | itob 54 | byte 0x151f7c75 55 | swap 56 | concat 57 | log 58 | int 1 59 | return 60 | 61 | main_bare_routing@6: 62 | // smart_contracts/personal_vault/contract.py:14 63 | // class PersonalVault(ARC4Contract): 64 | txn OnCompletion 65 | switch main_create@7 main_opt_in_to_app@8 66 | err // reject transaction 67 | 68 | main_create@7: 69 | // smart_contracts/personal_vault/contract.py:14 70 | // class PersonalVault(ARC4Contract): 71 | txn ApplicationID 72 | ! 73 | assert // is creating 74 | int 1 75 | return 76 | 77 | main_opt_in_to_app@8: 78 | // smart_contracts/personal_vault/contract.py:18 79 | // @arc4.baremethod(allow_actions=["OptIn"]) 80 | txn ApplicationID 81 | assert // is not creating 82 | // smart_contracts/personal_vault/contract.py:18-19 83 | // @arc4.baremethod(allow_actions=["OptIn"]) 84 | // def opt_in_to_app(self) -> None: 85 | callsub opt_in_to_app 86 | int 1 87 | return 88 | 89 | 90 | // smart_contracts.personal_vault.contract.PersonalVault.deposit(ptxn: uint64) -> uint64: 91 | deposit: 92 | // smart_contracts/personal_vault/contract.py:22-23 93 | // @arc4.abimethod() 94 | // def deposit(self, ptxn: gtxn.PaymentTransaction) -> UInt64: 95 | proto 1 1 96 | // smart_contracts/personal_vault/contract.py:24 97 | // assert ptxn.amount > 0, "Deposit amount must be greater than 0" 98 | frame_dig -1 99 | gtxns Amount 100 | dup 101 | assert // Deposit amount must be greater than 0 102 | // smart_contracts/personal_vault/contract.py:26 103 | // ptxn.receiver == Global.current_application_address 104 | frame_dig -1 105 | gtxns Receiver 106 | global CurrentApplicationAddress 107 | == 108 | // smart_contracts/personal_vault/contract.py:25-27 109 | // assert ( 110 | // ptxn.receiver == Global.current_application_address 111 | // ), "Deposit receiver must be the contract address" 112 | assert // Deposit receiver must be the contract address 113 | // smart_contracts/personal_vault/contract.py:28 114 | // assert ptxn.sender == Txn.sender, "Deposit sender must be the caller" 115 | frame_dig -1 116 | gtxns Sender 117 | txn Sender 118 | == 119 | assert // Deposit sender must be the caller 120 | // smart_contracts/personal_vault/contract.py:30 121 | // Txn.sender, Global.current_application_id 122 | txn Sender 123 | global CurrentApplicationID 124 | // smart_contracts/personal_vault/contract.py:29-31 125 | // assert op.app_opted_in( 126 | // Txn.sender, Global.current_application_id 127 | // ), "Deposit sender must opt-in to the app first." 128 | app_opted_in 129 | assert // Deposit sender must opt-in to the app first. 130 | // smart_contracts/personal_vault/contract.py:33 131 | // self.balance[Txn.sender] += ptxn.amount 132 | txn Sender 133 | int 0 134 | byte "balance" 135 | app_local_get_ex 136 | assert // check balance exists for account 137 | + 138 | txn Sender 139 | byte "balance" 140 | uncover 2 141 | app_local_put 142 | // smart_contracts/personal_vault/contract.py:34 143 | // user_balance = self.balance[Txn.sender] 144 | txn Sender 145 | int 0 146 | byte "balance" 147 | app_local_get_ex 148 | assert // check balance exists for account 149 | // smart_contracts/personal_vault/contract.py:36 150 | // return user_balance 151 | retsub 152 | 153 | 154 | // smart_contracts.personal_vault.contract.PersonalVault.withdraw() -> uint64: 155 | withdraw: 156 | // smart_contracts/personal_vault/contract.py:38-39 157 | // @arc4.abimethod(allow_actions=["CloseOut"]) 158 | // def withdraw(self) -> UInt64: 159 | proto 0 1 160 | // smart_contracts/personal_vault/contract.py:40 161 | // userBalance = self.balance[Txn.sender] 162 | txn Sender 163 | int 0 164 | byte "balance" 165 | app_local_get_ex 166 | assert // check balance exists for account 167 | // smart_contracts/personal_vault/contract.py:42-47 168 | // itxn.Payment( 169 | // receiver=Txn.sender, 170 | // sender=Global.current_application_address, 171 | // amount=userBalance, 172 | // fee=0, 173 | // ).submit() 174 | itxn_begin 175 | // smart_contracts/personal_vault/contract.py:43 176 | // receiver=Txn.sender, 177 | txn Sender 178 | // smart_contracts/personal_vault/contract.py:44 179 | // sender=Global.current_application_address, 180 | global CurrentApplicationAddress 181 | // smart_contracts/personal_vault/contract.py:46 182 | // fee=0, 183 | int 0 184 | itxn_field Fee 185 | dig 2 186 | itxn_field Amount 187 | itxn_field Sender 188 | itxn_field Receiver 189 | // smart_contracts/personal_vault/contract.py:42 190 | // itxn.Payment( 191 | int pay 192 | itxn_field TypeEnum 193 | // smart_contracts/personal_vault/contract.py:42-47 194 | // itxn.Payment( 195 | // receiver=Txn.sender, 196 | // sender=Global.current_application_address, 197 | // amount=userBalance, 198 | // fee=0, 199 | // ).submit() 200 | itxn_submit 201 | // smart_contracts/personal_vault/contract.py:49 202 | // return userBalance 203 | retsub 204 | 205 | 206 | // smart_contracts.personal_vault.contract.PersonalVault.opt_in_to_app() -> void: 207 | opt_in_to_app: 208 | // smart_contracts/personal_vault/contract.py:18-19 209 | // @arc4.baremethod(allow_actions=["OptIn"]) 210 | // def opt_in_to_app(self) -> None: 211 | proto 0 0 212 | // smart_contracts/personal_vault/contract.py:20 213 | // self.balance[Txn.sender] = UInt64(0) 214 | txn Sender 215 | byte "balance" 216 | int 0 217 | app_local_put 218 | retsub 219 | -------------------------------------------------------------------------------- /projects/challenge/smart_contracts/artifacts/personal_vault/PersonalVault.arc32.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": { 3 | "deposit(pay)uint64": { 4 | "call_config": { 5 | "no_op": "CALL" 6 | } 7 | }, 8 | "withdraw()uint64": { 9 | "call_config": { 10 | "close_out": "CALL" 11 | } 12 | } 13 | }, 14 | "source": { 15 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC5hcHByb3ZhbF9wcm9ncmFtOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE0CiAgICAvLyBjbGFzcyBQZXJzb25hbFZhdWx0KEFSQzRDb250cmFjdCk6CiAgICB0eG4gTnVtQXBwQXJncwogICAgYnogbWFpbl9iYXJlX3JvdXRpbmdANgogICAgbWV0aG9kICJkZXBvc2l0KHBheSl1aW50NjQiCiAgICBtZXRob2QgIndpdGhkcmF3KCl1aW50NjQiCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBtYWluX2RlcG9zaXRfcm91dGVAMiBtYWluX3dpdGhkcmF3X3JvdXRlQDMKICAgIGVyciAvLyByZWplY3QgdHJhbnNhY3Rpb24KCm1haW5fZGVwb3NpdF9yb3V0ZUAyOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjIyCiAgICAvLyBAYXJjNC5hYmltZXRob2QoKQogICAgdHhuIE9uQ29tcGxldGlvbgogICAgIQogICAgYXNzZXJ0IC8vIE9uQ29tcGxldGlvbiBpcyBOb09wCiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgYXNzZXJ0IC8vIGlzIG5vdCBjcmVhdGluZwogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE0CiAgICAvLyBjbGFzcyBQZXJzb25hbFZhdWx0KEFSQzRDb250cmFjdCk6CiAgICB0eG4gR3JvdXBJbmRleAogICAgaW50IDEKICAgIC0KICAgIGR1cAogICAgZ3R4bnMgVHlwZUVudW0KICAgIGludCBwYXkKICAgID09CiAgICBhc3NlcnQgLy8gdHJhbnNhY3Rpb24gdHlwZSBpcyBwYXkKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weToyMgogICAgLy8gQGFyYzQuYWJpbWV0aG9kKCkKICAgIGNhbGxzdWIgZGVwb3NpdAogICAgaXRvYgogICAgYnl0ZSAweDE1MWY3Yzc1CiAgICBzd2FwCiAgICBjb25jYXQKICAgIGxvZwogICAgaW50IDEKICAgIHJldHVybgoKbWFpbl93aXRoZHJhd19yb3V0ZUAzOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjM4CiAgICAvLyBAYXJjNC5hYmltZXRob2QoYWxsb3dfYWN0aW9ucz1bIkNsb3NlT3V0Il0pCiAgICB0eG4gT25Db21wbGV0aW9uCiAgICBpbnQgQ2xvc2VPdXQKICAgID09CiAgICBhc3NlcnQgLy8gT25Db21wbGV0aW9uIGlzIENsb3NlT3V0CiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgYXNzZXJ0IC8vIGlzIG5vdCBjcmVhdGluZwogICAgY2FsbHN1YiB3aXRoZHJhdwogICAgaXRvYgogICAgYnl0ZSAweDE1MWY3Yzc1CiAgICBzd2FwCiAgICBjb25jYXQKICAgIGxvZwogICAgaW50IDEKICAgIHJldHVybgoKbWFpbl9iYXJlX3JvdXRpbmdANjoKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weToxNAogICAgLy8gY2xhc3MgUGVyc29uYWxWYXVsdChBUkM0Q29udHJhY3QpOgogICAgdHhuIE9uQ29tcGxldGlvbgogICAgc3dpdGNoIG1haW5fY3JlYXRlQDcgbWFpbl9vcHRfaW5fdG9fYXBwQDgKICAgIGVyciAvLyByZWplY3QgdHJhbnNhY3Rpb24KCm1haW5fY3JlYXRlQDc6CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MTQKICAgIC8vIGNsYXNzIFBlcnNvbmFsVmF1bHQoQVJDNENvbnRyYWN0KToKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICAhCiAgICBhc3NlcnQgLy8gaXMgY3JlYXRpbmcKICAgIGludCAxCiAgICByZXR1cm4KCm1haW5fb3B0X2luX3RvX2FwcEA4OgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE4CiAgICAvLyBAYXJjNC5iYXJlbWV0aG9kKGFsbG93X2FjdGlvbnM9WyJPcHRJbiJdKQogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weToxOC0xOQogICAgLy8gQGFyYzQuYmFyZW1ldGhvZChhbGxvd19hY3Rpb25zPVsiT3B0SW4iXSkKICAgIC8vIGRlZiBvcHRfaW5fdG9fYXBwKHNlbGYpIC0+IE5vbmU6CiAgICBjYWxsc3ViIG9wdF9pbl90b19hcHAKICAgIGludCAxCiAgICByZXR1cm4KCgovLyBzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC5kZXBvc2l0KHB0eG46IHVpbnQ2NCkgLT4gdWludDY0OgpkZXBvc2l0OgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjIyLTIzCiAgICAvLyBAYXJjNC5hYmltZXRob2QoKQogICAgLy8gZGVmIGRlcG9zaXQoc2VsZiwgcHR4bjogZ3R4bi5QYXltZW50VHJhbnNhY3Rpb24pIC0+IFVJbnQ2NDoKICAgIHByb3RvIDEgMQogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjI0CiAgICAvLyBhc3NlcnQgcHR4bi5hbW91bnQgPiAwLCAiRGVwb3NpdCBhbW91bnQgbXVzdCBiZSBncmVhdGVyIHRoYW4gMCIKICAgIGZyYW1lX2RpZyAtMQogICAgZ3R4bnMgQW1vdW50CiAgICBkdXAKICAgIGFzc2VydCAvLyBEZXBvc2l0IGFtb3VudCBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAwCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjYKICAgIC8vIHB0eG4ucmVjZWl2ZXIgPT0gR2xvYmFsLmN1cnJlbnRfYXBwbGljYXRpb25fYWRkcmVzcwogICAgZnJhbWVfZGlnIC0xCiAgICBndHhucyBSZWNlaXZlcgogICAgZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbkFkZHJlc3MKICAgID09CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjUtMjcKICAgIC8vIGFzc2VydCAoCiAgICAvLyAgICAgcHR4bi5yZWNlaXZlciA9PSBHbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9hZGRyZXNzCiAgICAvLyApLCAiRGVwb3NpdCByZWNlaXZlciBtdXN0IGJlIHRoZSBjb250cmFjdCBhZGRyZXNzIgogICAgYXNzZXJ0IC8vIERlcG9zaXQgcmVjZWl2ZXIgbXVzdCBiZSB0aGUgY29udHJhY3QgYWRkcmVzcwogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjI4CiAgICAvLyBhc3NlcnQgcHR4bi5zZW5kZXIgPT0gVHhuLnNlbmRlciwgIkRlcG9zaXQgc2VuZGVyIG11c3QgYmUgdGhlIGNhbGxlciIKICAgIGZyYW1lX2RpZyAtMQogICAgZ3R4bnMgU2VuZGVyCiAgICB0eG4gU2VuZGVyCiAgICA9PQogICAgYXNzZXJ0IC8vIERlcG9zaXQgc2VuZGVyIG11c3QgYmUgdGhlIGNhbGxlcgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjMwCiAgICAvLyBUeG4uc2VuZGVyLCBHbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9pZAogICAgdHhuIFNlbmRlcgogICAgZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbklECiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjktMzEKICAgIC8vIGFzc2VydCBvcC5hcHBfb3B0ZWRfaW4oCiAgICAvLyAgICAgVHhuLnNlbmRlciwgR2xvYmFsLmN1cnJlbnRfYXBwbGljYXRpb25faWQKICAgIC8vICksICJEZXBvc2l0IHNlbmRlciBtdXN0IG9wdC1pbiB0byB0aGUgYXBwIGZpcnN0LiIKICAgIGFwcF9vcHRlZF9pbgogICAgYXNzZXJ0IC8vIERlcG9zaXQgc2VuZGVyIG11c3Qgb3B0LWluIHRvIHRoZSBhcHAgZmlyc3QuCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MzMKICAgIC8vIHNlbGYuYmFsYW5jZVtUeG4uc2VuZGVyXSArPSBwdHhuLmFtb3VudAogICAgdHhuIFNlbmRlcgogICAgaW50IDAKICAgIGJ5dGUgImJhbGFuY2UiCiAgICBhcHBfbG9jYWxfZ2V0X2V4CiAgICBhc3NlcnQgLy8gY2hlY2sgYmFsYW5jZSBleGlzdHMgZm9yIGFjY291bnQKICAgICsKICAgIHR4biBTZW5kZXIKICAgIGJ5dGUgImJhbGFuY2UiCiAgICB1bmNvdmVyIDIKICAgIGFwcF9sb2NhbF9wdXQKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTozNAogICAgLy8gdXNlcl9iYWxhbmNlID0gc2VsZi5iYWxhbmNlW1R4bi5zZW5kZXJdCiAgICB0eG4gU2VuZGVyCiAgICBpbnQgMAogICAgYnl0ZSAiYmFsYW5jZSIKICAgIGFwcF9sb2NhbF9nZXRfZXgKICAgIGFzc2VydCAvLyBjaGVjayBiYWxhbmNlIGV4aXN0cyBmb3IgYWNjb3VudAogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjM2CiAgICAvLyByZXR1cm4gdXNlcl9iYWxhbmNlCiAgICByZXRzdWIKCgovLyBzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC53aXRoZHJhdygpIC0+IHVpbnQ2NDoKd2l0aGRyYXc6CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MzgtMzkKICAgIC8vIEBhcmM0LmFiaW1ldGhvZChhbGxvd19hY3Rpb25zPVsiQ2xvc2VPdXQiXSkKICAgIC8vIGRlZiB3aXRoZHJhdyhzZWxmKSAtPiBVSW50NjQ6CiAgICBwcm90byAwIDEKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTo0MAogICAgLy8gdXNlckJhbGFuY2UgPSBzZWxmLmJhbGFuY2VbVHhuLnNlbmRlcl0KICAgIHR4biBTZW5kZXIKICAgIGludCAwCiAgICBieXRlICJiYWxhbmNlIgogICAgYXBwX2xvY2FsX2dldF9leAogICAgYXNzZXJ0IC8vIGNoZWNrIGJhbGFuY2UgZXhpc3RzIGZvciBhY2NvdW50CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6NDItNDcKICAgIC8vIGl0eG4uUGF5bWVudCgKICAgIC8vICAgICByZWNlaXZlcj1UeG4uc2VuZGVyLAogICAgLy8gICAgIHNlbmRlcj1HbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9hZGRyZXNzLAogICAgLy8gICAgIGFtb3VudD11c2VyQmFsYW5jZSwKICAgIC8vICAgICBmZWU9MCwKICAgIC8vICkuc3VibWl0KCkKICAgIGl0eG5fYmVnaW4KICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTo0MwogICAgLy8gcmVjZWl2ZXI9VHhuLnNlbmRlciwKICAgIHR4biBTZW5kZXIKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTo0NAogICAgLy8gc2VuZGVyPUdsb2JhbC5jdXJyZW50X2FwcGxpY2F0aW9uX2FkZHJlc3MsCiAgICBnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjQ2CiAgICAvLyBmZWU9MCwKICAgIGludCAwCiAgICBpdHhuX2ZpZWxkIEZlZQogICAgZGlnIDIKICAgIGl0eG5fZmllbGQgQW1vdW50CiAgICBpdHhuX2ZpZWxkIFNlbmRlcgogICAgaXR4bl9maWVsZCBSZWNlaXZlcgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjQyCiAgICAvLyBpdHhuLlBheW1lbnQoCiAgICBpbnQgcGF5CiAgICBpdHhuX2ZpZWxkIFR5cGVFbnVtCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6NDItNDcKICAgIC8vIGl0eG4uUGF5bWVudCgKICAgIC8vICAgICByZWNlaXZlcj1UeG4uc2VuZGVyLAogICAgLy8gICAgIHNlbmRlcj1HbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9hZGRyZXNzLAogICAgLy8gICAgIGFtb3VudD11c2VyQmFsYW5jZSwKICAgIC8vICAgICBmZWU9MCwKICAgIC8vICkuc3VibWl0KCkKICAgIGl0eG5fc3VibWl0CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6NDkKICAgIC8vIHJldHVybiB1c2VyQmFsYW5jZQogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLnBlcnNvbmFsX3ZhdWx0LmNvbnRyYWN0LlBlcnNvbmFsVmF1bHQub3B0X2luX3RvX2FwcCgpIC0+IHZvaWQ6Cm9wdF9pbl90b19hcHA6CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MTgtMTkKICAgIC8vIEBhcmM0LmJhcmVtZXRob2QoYWxsb3dfYWN0aW9ucz1bIk9wdEluIl0pCiAgICAvLyBkZWYgb3B0X2luX3RvX2FwcChzZWxmKSAtPiBOb25lOgogICAgcHJvdG8gMCAwCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjAKICAgIC8vIHNlbGYuYmFsYW5jZVtUeG4uc2VuZGVyXSA9IFVJbnQ2NCgwKQogICAgdHhuIFNlbmRlcgogICAgYnl0ZSAiYmFsYW5jZSIKICAgIGludCAwCiAgICBhcHBfbG9jYWxfcHV0CiAgICByZXRzdWIK", 16 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE0CiAgICAvLyBjbGFzcyBQZXJzb25hbFZhdWx0KEFSQzRDb250cmFjdCk6CiAgICBpbnQgMQogICAgcmV0dXJuCg==" 17 | }, 18 | "state": { 19 | "global": { 20 | "num_byte_slices": 0, 21 | "num_uints": 0 22 | }, 23 | "local": { 24 | "num_byte_slices": 0, 25 | "num_uints": 1 26 | } 27 | }, 28 | "schema": { 29 | "global": { 30 | "declared": {}, 31 | "reserved": {} 32 | }, 33 | "local": { 34 | "declared": { 35 | "balance": { 36 | "type": "uint64", 37 | "key": "balance" 38 | } 39 | }, 40 | "reserved": {} 41 | } 42 | }, 43 | "contract": { 44 | "name": "PersonalVault", 45 | "methods": [ 46 | { 47 | "name": "deposit", 48 | "args": [ 49 | { 50 | "type": "pay", 51 | "name": "ptxn" 52 | } 53 | ], 54 | "returns": { 55 | "type": "uint64" 56 | } 57 | }, 58 | { 59 | "name": "withdraw", 60 | "args": [], 61 | "returns": { 62 | "type": "uint64" 63 | } 64 | } 65 | ], 66 | "networks": {} 67 | }, 68 | "bare_call_config": { 69 | "opt_in": "CALL", 70 | "no_op": "CREATE" 71 | } 72 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🎮 Algorand Coding Challenge - Volume 2: 🐍 Python Challenges 2 | 3 | ## 🚩 Challenge 1: My Vault Contract is failing to build! 🏗️ 4 | > When compiling my Python smart contract, it fails to build. There seems to be 2 issues... 🤔 5 | 6 | Inside of `smart_contracts/personal_vault/contract.py` file, there is a simple vault smart contract written in Algorand Python. 7 | 8 | It is a simple contract that has a `deposit` function and a `withdraw` function and the depositor's balance is recorded in a local state after they opt in to the smart contract with the `opt_in_to_app` bare method. 9 | 10 | However, if you try to build and deploy the smart contract by opening Docker Desktop, and then running: 11 | ```bash 12 | algokit bootstrap all 13 | algokit localnet start 14 | algokit project run build # Compile the smart contract and get low-level TEAL code. 15 | ``` 16 | it will fail and show 2 errors: 17 | 18 | ```bash 19 | smart_contracts/personal_vault/contract.py:26 error: Unsupported operand types for == ("Account" and "Application") [operator] 20 | ptxn.receiver == Global.current_application_id 21 | 22 | smart_contracts/personal_vault/contract.py:30 error: Argument 2 to "app_opted_in" has incompatible type "Account"; expected "Application | UInt64 | int" [arg-type] 23 | Txn.sender, Global.current_application_address 24 | ``` 25 | 26 | **Find out what is wrong and fix the bug.** 27 | 28 | > 💬 Meet other hackers working on this challenge and get help in the [Algorand Python Discord Channel](https://discord.com/channels/491256308461207573/1182612934455722075)! 29 | 30 | ## Checkpoint 1: 🧰 Prerequisites 31 | 32 | 1. [Install Python 3.12 or higher](https://www.python.org/downloads/) 33 | 2. [Install AlgoKit](https://github.com/algorandfoundation/algokit-cli/tree/main?tab=readme-ov-file#install). 34 | 3. Install [Docker](https://www.docker.com/products/docker-desktop/). It is used to run a local Algorand network for development. 35 | 36 | ## Checkpoint 2: 💻 Set up your development environment 37 | 38 | 1. [Fork this repository.](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) 39 | 2. Clone the repository 40 | ```bash 41 | cd [DIRECTORY_OF_YOUR_CHOICE] 42 | git clone [FORKED_REPO_URL] 43 | ``` 44 | 45 | Now you have 2 ways of opening your AlgoKit project. 46 | 47 | ### With VSCode Workspaces 48 | 49 | 1. Open the cloned repository with the code editor of your choosing. 50 | 2. Open workspace mode by clicking `open workspace` inside of `python-challenge-1.code-workspace` file at the root level. 51 | 3. Go inside of the `challenge` folder. 52 | 4. To setup your dev environment using AlgoKit, run the below command: 53 | ```bash 54 | algokit project bootstrap all #algokit bootstrap all is being deprecated. Use this command from now on. 55 | ``` 56 | This command will install all dependecies and also generate a `.env` file for you. 57 | 5. Activate Python virtual environment by running: 58 | ```bash 59 | poetry shell 60 | ``` 61 | venv will automatically be activated the next time you open the project. 62 | 63 | > Please note, in addition to built-in support for [VSCode Workspaces](https://code.visualstudio.com/docs/editor/workspaces), the cli provides native support for monorepos called `algokit workspaces`. Refer to [documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/project/run.md#workspace-vs-standalone-projects) for detailed guidelines for recommended project structures and ability to leverage custom command orchestration via `algokit project run`. 64 | 65 | ### Without VSCode Workspaces 66 | 67 | All AlgoKit projects initialized with `--workspace` option has the following directory structure: 68 | 69 | ```bash 70 | ├── projects 71 | │ ├── smart-contract 72 | │ └── frontend # doesn't exist for this project 73 | │ └── ... 74 | ├── {several config files...} 75 | ├── ... 76 | ├── .algokit.toml # workspace-typed algokit project config 77 | ├── {project-name}.code-workspace 78 | ├── README.md 79 | ``` 80 | 81 | So to access a single project under the `projects` folder, it is recommended to `cd` into the project you want to work with and then open your code editor (alternatively refer to VSCode Workspace file at the root). If you are reading this and didn't open the `challenge` folder directly, go do that now!! 😁 82 | 83 | 1. cd into `projects/challenge` then open the code editor 84 | 2. To setup your dev environment using AlgoKit, run the below command: 85 | ```bash 86 | algokit project bootstrap all #algokit bootstrap all is being deprecated. Use this command from now on. 87 | ``` 88 | This command will install all dependecies and also generate a `.env` file for you. 89 | 3. Activate Python virtual environment by running below inside of `challenge` folder: 90 | ```bash 91 | poetry shell 92 | ``` 93 | venv will automatically be activated the next time you open the project. 94 | 95 | Video walkthrough of forking and cloning this repository: 96 | 97 | https://github.com/algorand-fix-the-bug-campaign/challenge-1/assets/52557585/acde8053-a8dd-4f53-8bad-45de1068bfda 98 | 99 | Now you are ready to fix the bug! 100 | 101 | ## Checkpoint 3: 🐞 Fix the bug 🧐 102 | 103 | 1. Open Docker Desktop and launch Algorand localnet by running `algokit localnet start` in your terminal [For more info click me!](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md#creating--starting-the-localnet). 104 | 3. Go to `smart_contracts/personal_vault/contract.py` and to see the source code of the personal vault smart contract. 105 | 4. Try building the contract with `algokit project run build`. It will fail. 106 | 5. Read the error, figure out what is wrong and fix the bug! 107 | 6. After fixing the bug, build and run the deploy script with the below command: 108 | ```bash 109 | algokit project run build 110 | algokit project deploy localnet 111 | ``` 112 | OR if you are on VSCode, hit F5 or go to the `Run and Debug` tab and run the debug script. 113 | 114 | If you see something like this in the console, you successfully fixed the bug! 😆 115 | image 116 | 117 | **😰 Are you struggling?** 118 | 119 | - [Algorand Smart Contract Documentation](https://developer.algorand.org/docs/get-details/ethereum_to_algorand/?from_query=ethereunm#accounts-and-smart-contracts) 120 | - [Algorand Python Documentation](https://algorandfoundation.github.io/puya/api-algopy.html#algopy.Global:~:text=current_application_address%3A%20Final%5B,executing.%20Application%20mode%20only.) 121 | - [AlgoKit CLI Documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md) 122 | 123 | ## Checkpoint 4: 💯 Submit your answer 124 | 125 | 1. After fixing the bug, push your code to your forked Github repo and [make a PR to the original repo.](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) 126 | 2. Inside the PR include: 127 | 1. What was the problem? 128 | 2. How did you solve the problem? 129 | 3. Screenshot of your terminal showing the result of running the deploy script. 130 | 131 | ## Checkpoint 5: 🏆 Claim your certificate of completion NFT! 🎓 132 | 133 | The Algorand Developer Relations team will review the submission and "approve" the PR by labeling it `Approved`. Once it's approved, we will share the magic link to claim your certificate of completion NFT in the comment of the PR! 134 | 135 | > The certificate of completion NFT is a verifiable credential minted on the [GoPlausible platform](https://goplausible.com/) that follows the W3C standard for certificates and OpenBadges standard for badges. 136 | 137 | The certificate of completion NFT for Python challenges were designed by [Maars](https://twitter.com/MaarsComics), an artist & a dev in web3. Definitely follow his work! It's awesome. 😆 138 | 139 | 🎉 Congratulations on completing the challenge Algodev! This was the final challenge of the 1st season of #AlgoCodingChallenge. Be on the lookout for the 2nd season of #AlgoCodingChallenge! 140 | 141 | 142 | ## Below is the default readme of the Python AlgoKit Template. Read to learn more about this template. 143 | 144 | This project has been generated using AlgoKit. See below for default getting started instructions. 145 | 146 | # Setup 147 | 148 | ### Pre-requisites 149 | 150 | - [Python 3.12](https://www.python.org/downloads/) or later 151 | - [Docker](https://www.docker.com/) (only required for LocalNet) 152 | 153 | ### Initial setup 154 | 155 | 1. Clone this repository locally 156 | 2. Install pre-requisites: 157 | - Make sure to have [Docker](https://www.docker.com/) installed and running on your machine. 158 | - Install `AlgoKit` - [Link](https://github.com/algorandfoundation/algokit-cli#install): The recommended version is `1.7.3`. Ensure you can execute `algokit --version` and get `1.7.1` or later. 159 | - Bootstrap your local environment; run `algokit bootstrap all` within this folder, which will: 160 | - Install `Poetry` - [Link](https://python-poetry.org/docs/#installation): The minimum required version is `^1.7`. Ensure you can execute `poetry -V` and get `1.2`+ 161 | - 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 162 | - Copy `.env.template` to `.env` 163 | - 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. 164 | 3. Open the project and start debugging / developing via: 165 | - VS Code 166 | 1. Open the repository root in VS Code 167 | 2. Install recommended extensions 168 | 3. Hit F5 (or whatever you have debug mapped to) and it should start running with breakpoint debugging. 169 | > **Note** 170 | > If using Windows: Before running for the first time you will need to select the Python Interpreter. 171 | 1. Open the command palette (Ctrl/Cmd + Shift + P) 172 | 2. Search for `Python: Select Interpreter` 173 | 3. Select `./.venv/Scripts/python.exe` 174 | - JetBrains IDEs (please note, this setup is primarily optimized for PyCharm Community Edition) 175 | 1. Open the repository root in the IDE 176 | 2. It should automatically detect it's a Poetry project and set up a Python interpreter and virtual environment. 177 | 3. Hit Shift+F10|Ctrl+R (or whatever you have debug mapped to) and it should start running with breakpoint debugging. Please note, JetBrains IDEs on Windows have a known bug that in some cases may prevent executing shell scripts as pre-launch tasks, for workarounds refer to [JetBrains forums](https://youtrack.jetbrains.com/issue/IDEA-277486/Shell-script-configuration-cannot-run-as-before-launch-task). 178 | - Other 179 | 1. Open the repository root in your text editor of choice 180 | 2. In a terminal run `poetry shell` 181 | 3. Run `python -m smart_contracts` through your debugger of choice 182 | 183 | ### Subsequently 184 | 185 | 1. If you update to the latest source code and there are new dependencies you will need to run `algokit bootstrap all` again 186 | 2. Follow step 3 above 187 | 188 | > For guidance on `smart_contracts` folder and adding new contracts to the project please see [README](smart_contracts/README.md) on the respective folder. 189 | 190 | # Tools 191 | 192 | This project makes use of Algorand Python to build Algorand smart contracts. The following tools are in use: 193 | 194 | - [Algorand](https://www.algorand.com/) - Layer 1 Blockchain; [Developer portal](https://developer.algorand.org/), [Why Algorand?](https://developer.algorand.org/docs/get-started/basics/why_algorand/) 195 | - [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) 196 | - [Algorand Python](https://github.com/algorandfoundation/puya) - A semantically and syntactically compatible, typed Python language that works with standard Python tooling and allows you to express smart contracts (apps) and smart signatures (logic signatures) for deployment on the Algorand Virtual Machine (AVM); [docs](https://github.com/algorandfoundation/puya), [examples](https://github.com/algorandfoundation/puya/tree/main/examples) 197 | - [AlgoKit Utils](https://github.com/algorandfoundation/algokit-utils-py) - A set of core Algorand utilities that make it easier to build solutions on Algorand. 198 | - [Poetry](https://python-poetry.org/): Python packaging and dependency management. 199 | 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. 200 | 201 | -------------------------------------------------------------------------------- /projects/challenge/README.md: -------------------------------------------------------------------------------- 1 | # 🎮 Algorand Coding Challenge - Volume 2: 🐍 Python Challenges 2 | 3 | ## 🚩 Challenge 1: My Vault Contract is failing to build! 🏗️ 4 | > When compiling my Python smart contract, it fails to build. There seems to be 2 issues... 🤔 5 | 6 | Inside of `smart_contracts/personal_vault/contract.py` file, there is a simple vault smart contract written in Algorand Python. 7 | 8 | It is a simple contract that has a `deposit` function and a `withdraw` function and the depositor's balance is recorded in a local state after they opt in to the smart contract with the `opt_in_to_app` bare method. 9 | 10 | However, if you try to build and deploy the smart contract by opening Docker Desktop, and then running: 11 | ```bash 12 | algokit bootstrap all 13 | algokit localnet start 14 | algokit project run build # Compile the smart contract and get low-level TEAL code. 15 | ``` 16 | it will fail and show 2 errors: 17 | 18 | ```bash 19 | smart_contracts/personal_vault/contract.py:26 error: Unsupported operand types for == ("Account" and "Application") [operator] 20 | ptxn.receiver == Global.current_application_id 21 | 22 | smart_contracts/personal_vault/contract.py:30 error: Argument 2 to "app_opted_in" has incompatible type "Account"; expected "Application | UInt64 | int" [arg-type] 23 | Txn.sender, Global.current_application_address 24 | ``` 25 | 26 | **Find out what is wrong and fix the bug.** 27 | 28 | > 💬 Meet other hackers working on this challenge and get help in the [Algorand Python Discord Channel](https://discord.com/channels/491256308461207573/1182612934455722075)! 29 | 30 | ## Checkpoint 1: 🧰 Prerequisites 31 | 32 | 1. [Install Python 3.12 or higher](https://www.python.org/downloads/) 33 | 2. [Install AlgoKit](https://github.com/algorandfoundation/algokit-cli/tree/main?tab=readme-ov-file#install). 34 | 3. Install [Docker](https://www.docker.com/products/docker-desktop/). It is used to run a local Algorand network for development. 35 | 36 | ## Checkpoint 2: 💻 Set up your development environment 37 | 38 | 1. [Fork this repository.](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) 39 | 2. Clone the repository 40 | ```bash 41 | cd [DIRECTORY_OF_YOUR_CHOICE] 42 | git clone [FORKED_REPO_URL] 43 | ``` 44 | 45 | Now you have 2 ways of opening your AlgoKit project. 46 | 47 | ### With VSCode Workspaces 48 | 49 | 1. Open the cloned repository with the code editor of your choosing. 50 | 2. Open workspace mode by clicking `open workspace` inside of `python-challenge-1.code-workspace` file at the root level. 51 | 3. Go inside of the `challenge` folder. 52 | 4. To setup your dev environment using AlgoKit, run the below command: 53 | ```bash 54 | algokit project bootstrap all #algokit bootstrap all is being deprecated. Use this command from now on. 55 | ``` 56 | This command will install all dependecies and also generate a `.env` file for you. 57 | 5. Activate Python virtual environment by running: 58 | ```bash 59 | poetry shell 60 | ``` 61 | venv will automatically be activated the next time you open the project. 62 | 63 | > Please note, in addition to built-in support for [VSCode Workspaces](https://code.visualstudio.com/docs/editor/workspaces), the cli provides native support for monorepos called `algokit workspaces`. Refer to [documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/project/run.md#workspace-vs-standalone-projects) for detailed guidelines for recommended project structures and ability to leverage custom command orchestration via `algokit project run`. 64 | 65 | ### Without VSCode Workspaces 66 | 67 | All AlgoKit projects initialized with `--workspace` option has the following directory structure: 68 | 69 | ```bash 70 | ├── projects 71 | │ ├── smart-contract 72 | │ └── frontend # doesn't exist for this project 73 | │ └── ... 74 | ├── {several config files...} 75 | ├── ... 76 | ├── .algokit.toml # workspace-typed algokit project config 77 | ├── {project-name}.code-workspace 78 | ├── README.md 79 | ``` 80 | 81 | So to access a single project under the `projects` folder, it is recommended to `cd` into the project you want to work with and then open your code editor (alternatively refer to VSCode Workspace file at the root). If you are reading this and didn't open the `challenge` folder directly, go do that now!! 😁 82 | 83 | 1. cd into `projects/challenge` then open the code editor 84 | 2. To setup your dev environment using AlgoKit, run the below command: 85 | ```bash 86 | algokit project bootstrap all #algokit bootstrap all is being deprecated. Use this command from now on. 87 | ``` 88 | This command will install all dependecies and also generate a `.env` file for you. 89 | 3. Activate Python virtual environment by running below inside of `challenge` folder: 90 | ```bash 91 | poetry shell 92 | ``` 93 | venv will automatically be activated the next time you open the project. 94 | 95 | Video walkthrough of forking and cloning this repository: 96 | 97 | https://github.com/algorand-fix-the-bug-campaign/challenge-1/assets/52557585/acde8053-a8dd-4f53-8bad-45de1068bfda 98 | 99 | Now you are ready to fix the bug! 100 | 101 | ## Checkpoint 3: 🐞 Fix the bug 🧐 102 | 103 | 1. Open Docker Desktop and launch Algorand localnet by running `algokit localnet start` in your terminal [For more info click me!](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md#creating--starting-the-localnet). 104 | 3. Go to `smart_contracts/personal_vault/contract.py` and to see the source code of the personal vault smart contract. 105 | 4. Try building the contract with `algokit project run build`. It will fail. 106 | 5. Read the error, figure out what is wrong and fix the bug! 107 | 6. After fixing the bug, build and run the deploy script with the below command: 108 | ```bash 109 | algokit project run build 110 | algokit project deploy localnet 111 | ``` 112 | OR if you are on VSCode, hit F5 or go to the `Run and Debug` tab and run the debug script. 113 | 114 | If you see something like this in the console, you successfully fixed the bug! 😆 115 | image 116 | 117 | **😰 Are you struggling?** 118 | 119 | - [Algorand Smart Contract Documentation](https://developer.algorand.org/docs/get-details/ethereum_to_algorand/?from_query=ethereunm#accounts-and-smart-contracts) 120 | - [Algorand Python Documentation](https://algorandfoundation.github.io/puya/api-algopy.html#algopy.Global:~:text=current_application_address%3A%20Final%5B,executing.%20Application%20mode%20only.) 121 | - [AlgoKit CLI Documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md) 122 | 123 | ## Checkpoint 4: 💯 Submit your answer 124 | 125 | 1. After fixing the bug, push your code to your forked Github repo and [make a PR to the original repo.](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) 126 | 2. Inside the PR include: 127 | 1. What was the problem? 128 | 2. How did you solve the problem? 129 | 3. Screenshot of your terminal showing the result of running the deploy script. 130 | 131 | ## Checkpoint 5: 🏆 Claim your certificate of completion NFT! 🎓 132 | 133 | The Algorand Developer Relations team will review the submission and "approve" the PR by labeling it `Approved`. Once it's approved, we will share the magic link to claim your certificate of completion NFT in the comment of the PR! 134 | 135 | > The certificate of completion NFT is a verifiable credential minted on the [GoPlausible platform](https://goplausible.com/) that follows the W3C standard for certificates and OpenBadges standard for badges. 136 | 137 | The certificate of completion NFT for Python challenges were designed by [Maars](https://twitter.com/MaarsComics), an artist & a dev in web3. Definitely follow his work! It's awesome. 😆 138 | 139 | 🎉 Congratulations on completing the challenge Algodev! This was the final challenge of the 1st season of #AlgoCodingChallenge. Be on the lookout for the 2nd season of #AlgoCodingChallenge! 140 | 141 | 142 | ## Below is the default readme of the Python AlgoKit Template. Read to learn more about this template. 143 | 144 | This project has been generated using AlgoKit. See below for default getting started instructions. 145 | 146 | # Setup 147 | 148 | ### Pre-requisites 149 | 150 | - [Python 3.12](https://www.python.org/downloads/) or later 151 | - [Docker](https://www.docker.com/) (only required for LocalNet) 152 | 153 | ### Initial setup 154 | 155 | 1. Clone this repository locally 156 | 2. Install pre-requisites: 157 | - Make sure to have [Docker](https://www.docker.com/) installed and running on your machine. 158 | - Install `AlgoKit` - [Link](https://github.com/algorandfoundation/algokit-cli#install): The recommended version is `1.7.3`. Ensure you can execute `algokit --version` and get `1.7.1` or later. 159 | - Bootstrap your local environment; run `algokit bootstrap all` within this folder, which will: 160 | - Install `Poetry` - [Link](https://python-poetry.org/docs/#installation): The minimum required version is `^1.7`. Ensure you can execute `poetry -V` and get `1.2`+ 161 | - 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 162 | - Copy `.env.template` to `.env` 163 | - 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. 164 | 3. Open the project and start debugging / developing via: 165 | - VS Code 166 | 1. Open the repository root in VS Code 167 | 2. Install recommended extensions 168 | 3. Hit F5 (or whatever you have debug mapped to) and it should start running with breakpoint debugging. 169 | > **Note** 170 | > If using Windows: Before running for the first time you will need to select the Python Interpreter. 171 | 1. Open the command palette (Ctrl/Cmd + Shift + P) 172 | 2. Search for `Python: Select Interpreter` 173 | 3. Select `./.venv/Scripts/python.exe` 174 | - JetBrains IDEs (please note, this setup is primarily optimized for PyCharm Community Edition) 175 | 1. Open the repository root in the IDE 176 | 2. It should automatically detect it's a Poetry project and set up a Python interpreter and virtual environment. 177 | 3. Hit Shift+F10|Ctrl+R (or whatever you have debug mapped to) and it should start running with breakpoint debugging. Please note, JetBrains IDEs on Windows have a known bug that in some cases may prevent executing shell scripts as pre-launch tasks, for workarounds refer to [JetBrains forums](https://youtrack.jetbrains.com/issue/IDEA-277486/Shell-script-configuration-cannot-run-as-before-launch-task). 178 | - Other 179 | 1. Open the repository root in your text editor of choice 180 | 2. In a terminal run `poetry shell` 181 | 3. Run `python -m smart_contracts` through your debugger of choice 182 | 183 | ### Subsequently 184 | 185 | 1. If you update to the latest source code and there are new dependencies you will need to run `algokit bootstrap all` again 186 | 2. Follow step 3 above 187 | 188 | > For guidance on `smart_contracts` folder and adding new contracts to the project please see [README](smart_contracts/README.md) on the respective folder. 189 | 190 | # Tools 191 | 192 | This project makes use of Algorand Python to build Algorand smart contracts. The following tools are in use: 193 | 194 | - [Algorand](https://www.algorand.com/) - Layer 1 Blockchain; [Developer portal](https://developer.algorand.org/), [Why Algorand?](https://developer.algorand.org/docs/get-started/basics/why_algorand/) 195 | - [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) 196 | - [Algorand Python](https://github.com/algorandfoundation/puya) - A semantically and syntactically compatible, typed Python language that works with standard Python tooling and allows you to express smart contracts (apps) and smart signatures (logic signatures) for deployment on the Algorand Virtual Machine (AVM); [docs](https://github.com/algorandfoundation/puya), [examples](https://github.com/algorandfoundation/puya/tree/main/examples) 197 | - [AlgoKit Utils](https://github.com/algorandfoundation/algokit-utils-py) - A set of core Algorand utilities that make it easier to build solutions on Algorand. 198 | - [Poetry](https://python-poetry.org/): Python packaging and dependency management. 199 | 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. 200 | 201 | -------------------------------------------------------------------------------- /projects/challenge/smart_contracts/artifacts/personal_vault/client.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | # fmt: off 3 | # mypy: disable-error-code="no-any-return, no-untyped-call, misc, type-arg" 4 | # This file was automatically generated by algokit-client-generator. 5 | # DO NOT MODIFY IT BY HAND. 6 | # requires: algokit-utils@^1.2.0 7 | import base64 8 | import dataclasses 9 | import decimal 10 | import typing 11 | from abc import ABC, abstractmethod 12 | 13 | import algokit_utils 14 | import algosdk 15 | from algosdk.v2client import models 16 | from algosdk.atomic_transaction_composer import ( 17 | AtomicTransactionComposer, 18 | AtomicTransactionResponse, 19 | SimulateAtomicTransactionResponse, 20 | TransactionSigner, 21 | TransactionWithSigner 22 | ) 23 | 24 | _APP_SPEC_JSON = r"""{ 25 | "hints": { 26 | "deposit(pay)uint64": { 27 | "call_config": { 28 | "no_op": "CALL" 29 | } 30 | }, 31 | "withdraw()uint64": { 32 | "call_config": { 33 | "close_out": "CALL" 34 | } 35 | } 36 | }, 37 | "source": { 38 | "approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC5hcHByb3ZhbF9wcm9ncmFtOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE0CiAgICAvLyBjbGFzcyBQZXJzb25hbFZhdWx0KEFSQzRDb250cmFjdCk6CiAgICB0eG4gTnVtQXBwQXJncwogICAgYnogbWFpbl9iYXJlX3JvdXRpbmdANgogICAgbWV0aG9kICJkZXBvc2l0KHBheSl1aW50NjQiCiAgICBtZXRob2QgIndpdGhkcmF3KCl1aW50NjQiCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBtYWluX2RlcG9zaXRfcm91dGVAMiBtYWluX3dpdGhkcmF3X3JvdXRlQDMKICAgIGVyciAvLyByZWplY3QgdHJhbnNhY3Rpb24KCm1haW5fZGVwb3NpdF9yb3V0ZUAyOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjIyCiAgICAvLyBAYXJjNC5hYmltZXRob2QoKQogICAgdHhuIE9uQ29tcGxldGlvbgogICAgIQogICAgYXNzZXJ0IC8vIE9uQ29tcGxldGlvbiBpcyBOb09wCiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgYXNzZXJ0IC8vIGlzIG5vdCBjcmVhdGluZwogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE0CiAgICAvLyBjbGFzcyBQZXJzb25hbFZhdWx0KEFSQzRDb250cmFjdCk6CiAgICB0eG4gR3JvdXBJbmRleAogICAgaW50IDEKICAgIC0KICAgIGR1cAogICAgZ3R4bnMgVHlwZUVudW0KICAgIGludCBwYXkKICAgID09CiAgICBhc3NlcnQgLy8gdHJhbnNhY3Rpb24gdHlwZSBpcyBwYXkKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weToyMgogICAgLy8gQGFyYzQuYWJpbWV0aG9kKCkKICAgIGNhbGxzdWIgZGVwb3NpdAogICAgaXRvYgogICAgYnl0ZSAweDE1MWY3Yzc1CiAgICBzd2FwCiAgICBjb25jYXQKICAgIGxvZwogICAgaW50IDEKICAgIHJldHVybgoKbWFpbl93aXRoZHJhd19yb3V0ZUAzOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjM4CiAgICAvLyBAYXJjNC5hYmltZXRob2QoYWxsb3dfYWN0aW9ucz1bIkNsb3NlT3V0Il0pCiAgICB0eG4gT25Db21wbGV0aW9uCiAgICBpbnQgQ2xvc2VPdXQKICAgID09CiAgICBhc3NlcnQgLy8gT25Db21wbGV0aW9uIGlzIENsb3NlT3V0CiAgICB0eG4gQXBwbGljYXRpb25JRAogICAgYXNzZXJ0IC8vIGlzIG5vdCBjcmVhdGluZwogICAgY2FsbHN1YiB3aXRoZHJhdwogICAgaXRvYgogICAgYnl0ZSAweDE1MWY3Yzc1CiAgICBzd2FwCiAgICBjb25jYXQKICAgIGxvZwogICAgaW50IDEKICAgIHJldHVybgoKbWFpbl9iYXJlX3JvdXRpbmdANjoKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weToxNAogICAgLy8gY2xhc3MgUGVyc29uYWxWYXVsdChBUkM0Q29udHJhY3QpOgogICAgdHhuIE9uQ29tcGxldGlvbgogICAgc3dpdGNoIG1haW5fY3JlYXRlQDcgbWFpbl9vcHRfaW5fdG9fYXBwQDgKICAgIGVyciAvLyByZWplY3QgdHJhbnNhY3Rpb24KCm1haW5fY3JlYXRlQDc6CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MTQKICAgIC8vIGNsYXNzIFBlcnNvbmFsVmF1bHQoQVJDNENvbnRyYWN0KToKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICAhCiAgICBhc3NlcnQgLy8gaXMgY3JlYXRpbmcKICAgIGludCAxCiAgICByZXR1cm4KCm1haW5fb3B0X2luX3RvX2FwcEA4OgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE4CiAgICAvLyBAYXJjNC5iYXJlbWV0aG9kKGFsbG93X2FjdGlvbnM9WyJPcHRJbiJdKQogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBpcyBub3QgY3JlYXRpbmcKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weToxOC0xOQogICAgLy8gQGFyYzQuYmFyZW1ldGhvZChhbGxvd19hY3Rpb25zPVsiT3B0SW4iXSkKICAgIC8vIGRlZiBvcHRfaW5fdG9fYXBwKHNlbGYpIC0+IE5vbmU6CiAgICBjYWxsc3ViIG9wdF9pbl90b19hcHAKICAgIGludCAxCiAgICByZXR1cm4KCgovLyBzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC5kZXBvc2l0KHB0eG46IHVpbnQ2NCkgLT4gdWludDY0OgpkZXBvc2l0OgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjIyLTIzCiAgICAvLyBAYXJjNC5hYmltZXRob2QoKQogICAgLy8gZGVmIGRlcG9zaXQoc2VsZiwgcHR4bjogZ3R4bi5QYXltZW50VHJhbnNhY3Rpb24pIC0+IFVJbnQ2NDoKICAgIHByb3RvIDEgMQogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjI0CiAgICAvLyBhc3NlcnQgcHR4bi5hbW91bnQgPiAwLCAiRGVwb3NpdCBhbW91bnQgbXVzdCBiZSBncmVhdGVyIHRoYW4gMCIKICAgIGZyYW1lX2RpZyAtMQogICAgZ3R4bnMgQW1vdW50CiAgICBkdXAKICAgIGFzc2VydCAvLyBEZXBvc2l0IGFtb3VudCBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAwCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjYKICAgIC8vIHB0eG4ucmVjZWl2ZXIgPT0gR2xvYmFsLmN1cnJlbnRfYXBwbGljYXRpb25fYWRkcmVzcwogICAgZnJhbWVfZGlnIC0xCiAgICBndHhucyBSZWNlaXZlcgogICAgZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbkFkZHJlc3MKICAgID09CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjUtMjcKICAgIC8vIGFzc2VydCAoCiAgICAvLyAgICAgcHR4bi5yZWNlaXZlciA9PSBHbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9hZGRyZXNzCiAgICAvLyApLCAiRGVwb3NpdCByZWNlaXZlciBtdXN0IGJlIHRoZSBjb250cmFjdCBhZGRyZXNzIgogICAgYXNzZXJ0IC8vIERlcG9zaXQgcmVjZWl2ZXIgbXVzdCBiZSB0aGUgY29udHJhY3QgYWRkcmVzcwogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjI4CiAgICAvLyBhc3NlcnQgcHR4bi5zZW5kZXIgPT0gVHhuLnNlbmRlciwgIkRlcG9zaXQgc2VuZGVyIG11c3QgYmUgdGhlIGNhbGxlciIKICAgIGZyYW1lX2RpZyAtMQogICAgZ3R4bnMgU2VuZGVyCiAgICB0eG4gU2VuZGVyCiAgICA9PQogICAgYXNzZXJ0IC8vIERlcG9zaXQgc2VuZGVyIG11c3QgYmUgdGhlIGNhbGxlcgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjMwCiAgICAvLyBUeG4uc2VuZGVyLCBHbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9pZAogICAgdHhuIFNlbmRlcgogICAgZ2xvYmFsIEN1cnJlbnRBcHBsaWNhdGlvbklECiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjktMzEKICAgIC8vIGFzc2VydCBvcC5hcHBfb3B0ZWRfaW4oCiAgICAvLyAgICAgVHhuLnNlbmRlciwgR2xvYmFsLmN1cnJlbnRfYXBwbGljYXRpb25faWQKICAgIC8vICksICJEZXBvc2l0IHNlbmRlciBtdXN0IG9wdC1pbiB0byB0aGUgYXBwIGZpcnN0LiIKICAgIGFwcF9vcHRlZF9pbgogICAgYXNzZXJ0IC8vIERlcG9zaXQgc2VuZGVyIG11c3Qgb3B0LWluIHRvIHRoZSBhcHAgZmlyc3QuCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MzMKICAgIC8vIHNlbGYuYmFsYW5jZVtUeG4uc2VuZGVyXSArPSBwdHhuLmFtb3VudAogICAgdHhuIFNlbmRlcgogICAgaW50IDAKICAgIGJ5dGUgImJhbGFuY2UiCiAgICBhcHBfbG9jYWxfZ2V0X2V4CiAgICBhc3NlcnQgLy8gY2hlY2sgYmFsYW5jZSBleGlzdHMgZm9yIGFjY291bnQKICAgICsKICAgIHR4biBTZW5kZXIKICAgIGJ5dGUgImJhbGFuY2UiCiAgICB1bmNvdmVyIDIKICAgIGFwcF9sb2NhbF9wdXQKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTozNAogICAgLy8gdXNlcl9iYWxhbmNlID0gc2VsZi5iYWxhbmNlW1R4bi5zZW5kZXJdCiAgICB0eG4gU2VuZGVyCiAgICBpbnQgMAogICAgYnl0ZSAiYmFsYW5jZSIKICAgIGFwcF9sb2NhbF9nZXRfZXgKICAgIGFzc2VydCAvLyBjaGVjayBiYWxhbmNlIGV4aXN0cyBmb3IgYWNjb3VudAogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjM2CiAgICAvLyByZXR1cm4gdXNlcl9iYWxhbmNlCiAgICByZXRzdWIKCgovLyBzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC53aXRoZHJhdygpIC0+IHVpbnQ2NDoKd2l0aGRyYXc6CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MzgtMzkKICAgIC8vIEBhcmM0LmFiaW1ldGhvZChhbGxvd19hY3Rpb25zPVsiQ2xvc2VPdXQiXSkKICAgIC8vIGRlZiB3aXRoZHJhdyhzZWxmKSAtPiBVSW50NjQ6CiAgICBwcm90byAwIDEKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTo0MAogICAgLy8gdXNlckJhbGFuY2UgPSBzZWxmLmJhbGFuY2VbVHhuLnNlbmRlcl0KICAgIHR4biBTZW5kZXIKICAgIGludCAwCiAgICBieXRlICJiYWxhbmNlIgogICAgYXBwX2xvY2FsX2dldF9leAogICAgYXNzZXJ0IC8vIGNoZWNrIGJhbGFuY2UgZXhpc3RzIGZvciBhY2NvdW50CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6NDItNDcKICAgIC8vIGl0eG4uUGF5bWVudCgKICAgIC8vICAgICByZWNlaXZlcj1UeG4uc2VuZGVyLAogICAgLy8gICAgIHNlbmRlcj1HbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9hZGRyZXNzLAogICAgLy8gICAgIGFtb3VudD11c2VyQmFsYW5jZSwKICAgIC8vICAgICBmZWU9MCwKICAgIC8vICkuc3VibWl0KCkKICAgIGl0eG5fYmVnaW4KICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTo0MwogICAgLy8gcmVjZWl2ZXI9VHhuLnNlbmRlciwKICAgIHR4biBTZW5kZXIKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9wZXJzb25hbF92YXVsdC9jb250cmFjdC5weTo0NAogICAgLy8gc2VuZGVyPUdsb2JhbC5jdXJyZW50X2FwcGxpY2F0aW9uX2FkZHJlc3MsCiAgICBnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjQ2CiAgICAvLyBmZWU9MCwKICAgIGludCAwCiAgICBpdHhuX2ZpZWxkIEZlZQogICAgZGlnIDIKICAgIGl0eG5fZmllbGQgQW1vdW50CiAgICBpdHhuX2ZpZWxkIFNlbmRlcgogICAgaXR4bl9maWVsZCBSZWNlaXZlcgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjQyCiAgICAvLyBpdHhuLlBheW1lbnQoCiAgICBpbnQgcGF5CiAgICBpdHhuX2ZpZWxkIFR5cGVFbnVtCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6NDItNDcKICAgIC8vIGl0eG4uUGF5bWVudCgKICAgIC8vICAgICByZWNlaXZlcj1UeG4uc2VuZGVyLAogICAgLy8gICAgIHNlbmRlcj1HbG9iYWwuY3VycmVudF9hcHBsaWNhdGlvbl9hZGRyZXNzLAogICAgLy8gICAgIGFtb3VudD11c2VyQmFsYW5jZSwKICAgIC8vICAgICBmZWU9MCwKICAgIC8vICkuc3VibWl0KCkKICAgIGl0eG5fc3VibWl0CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6NDkKICAgIC8vIHJldHVybiB1c2VyQmFsYW5jZQogICAgcmV0c3ViCgoKLy8gc21hcnRfY29udHJhY3RzLnBlcnNvbmFsX3ZhdWx0LmNvbnRyYWN0LlBlcnNvbmFsVmF1bHQub3B0X2luX3RvX2FwcCgpIC0+IHZvaWQ6Cm9wdF9pbl90b19hcHA6CiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MTgtMTkKICAgIC8vIEBhcmM0LmJhcmVtZXRob2QoYWxsb3dfYWN0aW9ucz1bIk9wdEluIl0pCiAgICAvLyBkZWYgb3B0X2luX3RvX2FwcChzZWxmKSAtPiBOb25lOgogICAgcHJvdG8gMCAwCiAgICAvLyBzbWFydF9jb250cmFjdHMvcGVyc29uYWxfdmF1bHQvY29udHJhY3QucHk6MjAKICAgIC8vIHNlbGYuYmFsYW5jZVtUeG4uc2VuZGVyXSA9IFVJbnQ2NCgwKQogICAgdHhuIFNlbmRlcgogICAgYnl0ZSAiYmFsYW5jZSIKICAgIGludCAwCiAgICBhcHBfbG9jYWxfcHV0CiAgICByZXRzdWIK", 39 | "clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMucGVyc29uYWxfdmF1bHQuY29udHJhY3QuUGVyc29uYWxWYXVsdC5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgLy8gc21hcnRfY29udHJhY3RzL3BlcnNvbmFsX3ZhdWx0L2NvbnRyYWN0LnB5OjE0CiAgICAvLyBjbGFzcyBQZXJzb25hbFZhdWx0KEFSQzRDb250cmFjdCk6CiAgICBpbnQgMQogICAgcmV0dXJuCg==" 40 | }, 41 | "state": { 42 | "global": { 43 | "num_byte_slices": 0, 44 | "num_uints": 0 45 | }, 46 | "local": { 47 | "num_byte_slices": 0, 48 | "num_uints": 1 49 | } 50 | }, 51 | "schema": { 52 | "global": { 53 | "declared": {}, 54 | "reserved": {} 55 | }, 56 | "local": { 57 | "declared": { 58 | "balance": { 59 | "type": "uint64", 60 | "key": "balance" 61 | } 62 | }, 63 | "reserved": {} 64 | } 65 | }, 66 | "contract": { 67 | "name": "PersonalVault", 68 | "methods": [ 69 | { 70 | "name": "deposit", 71 | "args": [ 72 | { 73 | "type": "pay", 74 | "name": "ptxn" 75 | } 76 | ], 77 | "returns": { 78 | "type": "uint64" 79 | } 80 | }, 81 | { 82 | "name": "withdraw", 83 | "args": [], 84 | "returns": { 85 | "type": "uint64" 86 | } 87 | } 88 | ], 89 | "networks": {} 90 | }, 91 | "bare_call_config": { 92 | "no_op": "CREATE", 93 | "opt_in": "CALL" 94 | } 95 | }""" 96 | APP_SPEC = algokit_utils.ApplicationSpecification.from_json(_APP_SPEC_JSON) 97 | _TReturn = typing.TypeVar("_TReturn") 98 | 99 | 100 | class _ArgsBase(ABC, typing.Generic[_TReturn]): 101 | @staticmethod 102 | @abstractmethod 103 | def method() -> str: 104 | ... 105 | 106 | 107 | _TArgs = typing.TypeVar("_TArgs", bound=_ArgsBase[typing.Any]) 108 | 109 | 110 | @dataclasses.dataclass(kw_only=True) 111 | class _TArgsHolder(typing.Generic[_TArgs]): 112 | args: _TArgs 113 | 114 | 115 | def _filter_none(value: dict | typing.Any) -> dict | typing.Any: 116 | if isinstance(value, dict): 117 | return {k: _filter_none(v) for k, v in value.items() if v is not None} 118 | return value 119 | 120 | 121 | def _as_dict(data: typing.Any, *, convert_all: bool = True) -> dict[str, typing.Any]: 122 | if data is None: 123 | return {} 124 | if not dataclasses.is_dataclass(data): 125 | raise TypeError(f"{data} must be a dataclass") 126 | if convert_all: 127 | result = dataclasses.asdict(data) 128 | else: 129 | result = {f.name: getattr(data, f.name) for f in dataclasses.fields(data)} 130 | return _filter_none(result) 131 | 132 | 133 | def _convert_transaction_parameters( 134 | transaction_parameters: algokit_utils.TransactionParameters | None, 135 | ) -> algokit_utils.TransactionParametersDict: 136 | return typing.cast(algokit_utils.TransactionParametersDict, _as_dict(transaction_parameters)) 137 | 138 | 139 | def _convert_call_transaction_parameters( 140 | transaction_parameters: algokit_utils.TransactionParameters | None, 141 | ) -> algokit_utils.OnCompleteCallParametersDict: 142 | return typing.cast(algokit_utils.OnCompleteCallParametersDict, _as_dict(transaction_parameters)) 143 | 144 | 145 | def _convert_create_transaction_parameters( 146 | transaction_parameters: algokit_utils.TransactionParameters | None, 147 | on_complete: algokit_utils.OnCompleteActionName, 148 | ) -> algokit_utils.CreateCallParametersDict: 149 | result = typing.cast(algokit_utils.CreateCallParametersDict, _as_dict(transaction_parameters)) 150 | on_complete_enum = on_complete.replace("_", " ").title().replace(" ", "") + "OC" 151 | result["on_complete"] = getattr(algosdk.transaction.OnComplete, on_complete_enum) 152 | return result 153 | 154 | 155 | def _convert_deploy_args( 156 | deploy_args: algokit_utils.DeployCallArgs | None, 157 | ) -> algokit_utils.ABICreateCallArgsDict | None: 158 | if deploy_args is None: 159 | return None 160 | 161 | deploy_args_dict = typing.cast(algokit_utils.ABICreateCallArgsDict, _as_dict(deploy_args)) 162 | if isinstance(deploy_args, _TArgsHolder): 163 | deploy_args_dict["args"] = _as_dict(deploy_args.args) 164 | deploy_args_dict["method"] = deploy_args.args.method() 165 | 166 | return deploy_args_dict 167 | 168 | 169 | @dataclasses.dataclass(kw_only=True) 170 | class DepositArgs(_ArgsBase[int]): 171 | ptxn: TransactionWithSigner 172 | 173 | @staticmethod 174 | def method() -> str: 175 | return "deposit(pay)uint64" 176 | 177 | 178 | @dataclasses.dataclass(kw_only=True) 179 | class WithdrawArgs(_ArgsBase[int]): 180 | @staticmethod 181 | def method() -> str: 182 | return "withdraw()uint64" 183 | 184 | 185 | class LocalState: 186 | def __init__(self, data: dict[bytes, bytes | int]): 187 | self.balance = typing.cast(int, data.get(b"balance")) 188 | 189 | 190 | @dataclasses.dataclass(kw_only=True) 191 | class SimulateOptions: 192 | allow_more_logs: bool = dataclasses.field(default=False) 193 | allow_empty_signatures: bool = dataclasses.field(default=False) 194 | extra_opcode_budget: int = dataclasses.field(default=0) 195 | exec_trace_config: models.SimulateTraceConfig | None = dataclasses.field(default=None) 196 | 197 | 198 | class Composer: 199 | 200 | def __init__(self, app_client: algokit_utils.ApplicationClient, atc: AtomicTransactionComposer): 201 | self.app_client = app_client 202 | self.atc = atc 203 | 204 | def build(self) -> AtomicTransactionComposer: 205 | return self.atc 206 | 207 | def simulate(self, options: SimulateOptions | None = None) -> SimulateAtomicTransactionResponse: 208 | request = models.SimulateRequest( 209 | allow_more_logs=options.allow_more_logs, 210 | allow_empty_signatures=options.allow_empty_signatures, 211 | extra_opcode_budget=options.extra_opcode_budget, 212 | exec_trace_config=options.exec_trace_config, 213 | txn_groups=[] 214 | ) if options else None 215 | result = self.atc.simulate(self.app_client.algod_client, request) 216 | return result 217 | 218 | def execute(self) -> AtomicTransactionResponse: 219 | return self.app_client.execute_atc(self.atc) 220 | 221 | def deposit( 222 | self, 223 | *, 224 | ptxn: TransactionWithSigner, 225 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 226 | ) -> "Composer": 227 | """Adds a call to `deposit(pay)uint64` ABI method 228 | 229 | :param TransactionWithSigner ptxn: The `ptxn` ABI parameter 230 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 231 | :returns Composer: This Composer instance""" 232 | 233 | args = DepositArgs( 234 | ptxn=ptxn, 235 | ) 236 | self.app_client.compose_call( 237 | self.atc, 238 | call_abi_method=args.method(), 239 | transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), 240 | **_as_dict(args, convert_all=True), 241 | ) 242 | return self 243 | 244 | def create_bare( 245 | self, 246 | *, 247 | on_complete: typing.Literal["no_op"] = "no_op", 248 | transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, 249 | ) -> "Composer": 250 | """Adds a call to create an application using the no_op bare method 251 | 252 | :param typing.Literal[no_op] on_complete: On completion type to use 253 | :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters 254 | :returns Composer: This Composer instance""" 255 | 256 | self.app_client.compose_create( 257 | self.atc, 258 | call_abi_method=False, 259 | transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), 260 | ) 261 | return self 262 | 263 | def opt_in_bare( 264 | self, 265 | *, 266 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 267 | ) -> "Composer": 268 | """Adds a calls to the opt_in bare method 269 | 270 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 271 | :returns Composer: This Composer instance""" 272 | 273 | self.app_client.compose_opt_in( 274 | self.atc, 275 | call_abi_method=False, 276 | transaction_parameters=_convert_transaction_parameters(transaction_parameters), 277 | ) 278 | return self 279 | 280 | def close_out_withdraw( 281 | self, 282 | *, 283 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 284 | ) -> "Composer": 285 | """Adds a call to `withdraw()uint64` ABI method 286 | 287 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 288 | :returns Composer: This Composer instance""" 289 | 290 | args = WithdrawArgs() 291 | self.app_client.compose_close_out( 292 | self.atc, 293 | call_abi_method=args.method(), 294 | transaction_parameters=_convert_transaction_parameters(transaction_parameters), 295 | **_as_dict(args, convert_all=True), 296 | ) 297 | return self 298 | 299 | def clear_state( 300 | self, 301 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 302 | app_args: list[bytes] | None = None, 303 | ) -> "Composer": 304 | """Adds a call to the application with on completion set to ClearState 305 | 306 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 307 | :param list[bytes] | None app_args: (optional) Application args to pass""" 308 | 309 | self.app_client.compose_clear_state(self.atc, _convert_transaction_parameters(transaction_parameters), app_args) 310 | return self 311 | 312 | 313 | class PersonalVaultClient: 314 | """A class for interacting with the PersonalVault app providing high productivity and 315 | strongly typed methods to deploy and call the app""" 316 | 317 | @typing.overload 318 | def __init__( 319 | self, 320 | algod_client: algosdk.v2client.algod.AlgodClient, 321 | *, 322 | app_id: int = 0, 323 | signer: TransactionSigner | algokit_utils.Account | None = None, 324 | sender: str | None = None, 325 | suggested_params: algosdk.transaction.SuggestedParams | None = None, 326 | template_values: algokit_utils.TemplateValueMapping | None = None, 327 | app_name: str | None = None, 328 | ) -> None: 329 | ... 330 | 331 | @typing.overload 332 | def __init__( 333 | self, 334 | algod_client: algosdk.v2client.algod.AlgodClient, 335 | *, 336 | creator: str | algokit_utils.Account, 337 | indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, 338 | existing_deployments: algokit_utils.AppLookup | None = None, 339 | signer: TransactionSigner | algokit_utils.Account | None = None, 340 | sender: str | None = None, 341 | suggested_params: algosdk.transaction.SuggestedParams | None = None, 342 | template_values: algokit_utils.TemplateValueMapping | None = None, 343 | app_name: str | None = None, 344 | ) -> None: 345 | ... 346 | 347 | def __init__( 348 | self, 349 | algod_client: algosdk.v2client.algod.AlgodClient, 350 | *, 351 | creator: str | algokit_utils.Account | None = None, 352 | indexer_client: algosdk.v2client.indexer.IndexerClient | None = None, 353 | existing_deployments: algokit_utils.AppLookup | None = None, 354 | app_id: int = 0, 355 | signer: TransactionSigner | algokit_utils.Account | None = None, 356 | sender: str | None = None, 357 | suggested_params: algosdk.transaction.SuggestedParams | None = None, 358 | template_values: algokit_utils.TemplateValueMapping | None = None, 359 | app_name: str | None = None, 360 | ) -> None: 361 | """ 362 | PersonalVaultClient can be created with an app_id to interact with an existing application, alternatively 363 | it can be created with a creator and indexer_client specified to find existing applications by name and creator. 364 | 365 | :param AlgodClient algod_client: AlgoSDK algod client 366 | :param int app_id: The app_id of an existing application, to instead find the application by creator and name 367 | use the creator and indexer_client parameters 368 | :param str | Account creator: The address or Account of the app creator to resolve the app_id 369 | :param IndexerClient indexer_client: AlgoSDK indexer client, only required if deploying or finding app_id by 370 | creator and app name 371 | :param AppLookup existing_deployments: 372 | :param TransactionSigner | Account signer: Account or signer to use to sign transactions, if not specified and 373 | creator was passed as an Account will use that. 374 | :param str sender: Address to use as the sender for all transactions, will use the address associated with the 375 | signer if not specified. 376 | :param TemplateValueMapping template_values: Values to use for TMPL_* template variables, dictionary keys should 377 | *NOT* include the TMPL_ prefix 378 | :param str | None app_name: Name of application to use when deploying, defaults to name defined on the 379 | Application Specification 380 | """ 381 | 382 | self.app_spec = APP_SPEC 383 | 384 | # calling full __init__ signature, so ignoring mypy warning about overloads 385 | self.app_client = algokit_utils.ApplicationClient( # type: ignore[call-overload, misc] 386 | algod_client=algod_client, 387 | app_spec=self.app_spec, 388 | app_id=app_id, 389 | creator=creator, 390 | indexer_client=indexer_client, 391 | existing_deployments=existing_deployments, 392 | signer=signer, 393 | sender=sender, 394 | suggested_params=suggested_params, 395 | template_values=template_values, 396 | app_name=app_name, 397 | ) 398 | 399 | @property 400 | def algod_client(self) -> algosdk.v2client.algod.AlgodClient: 401 | return self.app_client.algod_client 402 | 403 | @property 404 | def app_id(self) -> int: 405 | return self.app_client.app_id 406 | 407 | @app_id.setter 408 | def app_id(self, value: int) -> None: 409 | self.app_client.app_id = value 410 | 411 | @property 412 | def app_address(self) -> str: 413 | return self.app_client.app_address 414 | 415 | @property 416 | def sender(self) -> str | None: 417 | return self.app_client.sender 418 | 419 | @sender.setter 420 | def sender(self, value: str) -> None: 421 | self.app_client.sender = value 422 | 423 | @property 424 | def signer(self) -> TransactionSigner | None: 425 | return self.app_client.signer 426 | 427 | @signer.setter 428 | def signer(self, value: TransactionSigner) -> None: 429 | self.app_client.signer = value 430 | 431 | @property 432 | def suggested_params(self) -> algosdk.transaction.SuggestedParams | None: 433 | return self.app_client.suggested_params 434 | 435 | @suggested_params.setter 436 | def suggested_params(self, value: algosdk.transaction.SuggestedParams | None) -> None: 437 | self.app_client.suggested_params = value 438 | 439 | def get_local_state(self, account: str | None = None) -> LocalState: 440 | """Returns the application's local state wrapped in a strongly typed class with options to format the stored value""" 441 | 442 | state = typing.cast(dict[bytes, bytes | int], self.app_client.get_local_state(account, raw=True)) 443 | return LocalState(state) 444 | 445 | def deposit( 446 | self, 447 | *, 448 | ptxn: TransactionWithSigner, 449 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 450 | ) -> algokit_utils.ABITransactionResponse[int]: 451 | """Calls `deposit(pay)uint64` ABI method 452 | 453 | :param TransactionWithSigner ptxn: The `ptxn` ABI parameter 454 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 455 | :returns algokit_utils.ABITransactionResponse[int]: The result of the transaction""" 456 | 457 | args = DepositArgs( 458 | ptxn=ptxn, 459 | ) 460 | result = self.app_client.call( 461 | call_abi_method=args.method(), 462 | transaction_parameters=_convert_call_transaction_parameters(transaction_parameters), 463 | **_as_dict(args, convert_all=True), 464 | ) 465 | return result 466 | 467 | def create_bare( 468 | self, 469 | *, 470 | on_complete: typing.Literal["no_op"] = "no_op", 471 | transaction_parameters: algokit_utils.CreateTransactionParameters | None = None, 472 | ) -> algokit_utils.TransactionResponse: 473 | """Creates an application using the no_op bare method 474 | 475 | :param typing.Literal[no_op] on_complete: On completion type to use 476 | :param algokit_utils.CreateTransactionParameters transaction_parameters: (optional) Additional transaction parameters 477 | :returns algokit_utils.TransactionResponse: The result of the transaction""" 478 | 479 | result = self.app_client.create( 480 | call_abi_method=False, 481 | transaction_parameters=_convert_create_transaction_parameters(transaction_parameters, on_complete), 482 | ) 483 | return result 484 | 485 | def opt_in_bare( 486 | self, 487 | *, 488 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 489 | ) -> algokit_utils.TransactionResponse: 490 | """Calls the opt_in bare method 491 | 492 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 493 | :returns algokit_utils.TransactionResponse: The result of the transaction""" 494 | 495 | result = self.app_client.opt_in( 496 | call_abi_method=False, 497 | transaction_parameters=_convert_transaction_parameters(transaction_parameters), 498 | ) 499 | return result 500 | 501 | def close_out_withdraw( 502 | self, 503 | *, 504 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 505 | ) -> algokit_utils.ABITransactionResponse[int]: 506 | """Calls `withdraw()uint64` ABI method 507 | 508 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 509 | :returns algokit_utils.ABITransactionResponse[int]: The result of the transaction""" 510 | 511 | args = WithdrawArgs() 512 | result = self.app_client.close_out( 513 | call_abi_method=args.method(), 514 | transaction_parameters=_convert_transaction_parameters(transaction_parameters), 515 | **_as_dict(args, convert_all=True), 516 | ) 517 | return result 518 | 519 | def clear_state( 520 | self, 521 | transaction_parameters: algokit_utils.TransactionParameters | None = None, 522 | app_args: list[bytes] | None = None, 523 | ) -> algokit_utils.TransactionResponse: 524 | """Calls the application with on completion set to ClearState 525 | 526 | :param algokit_utils.TransactionParameters transaction_parameters: (optional) Additional transaction parameters 527 | :param list[bytes] | None app_args: (optional) Application args to pass 528 | :returns algokit_utils.TransactionResponse: The result of the transaction""" 529 | 530 | return self.app_client.clear_state(_convert_transaction_parameters(transaction_parameters), app_args) 531 | 532 | def deploy( 533 | self, 534 | version: str | None = None, 535 | *, 536 | signer: TransactionSigner | None = None, 537 | sender: str | None = None, 538 | allow_update: bool | None = None, 539 | allow_delete: bool | None = None, 540 | on_update: algokit_utils.OnUpdate = algokit_utils.OnUpdate.Fail, 541 | on_schema_break: algokit_utils.OnSchemaBreak = algokit_utils.OnSchemaBreak.Fail, 542 | template_values: algokit_utils.TemplateValueMapping | None = None, 543 | create_args: algokit_utils.DeployCallArgs | None = None, 544 | update_args: algokit_utils.DeployCallArgs | None = None, 545 | delete_args: algokit_utils.DeployCallArgs | None = None, 546 | ) -> algokit_utils.DeployResponse: 547 | """Deploy an application and update client to reference it. 548 | 549 | Idempotently deploy (create, update/delete if changed) an app against the given name via the given creator 550 | account, including deploy-time template placeholder substitutions. 551 | To understand the architecture decisions behind this functionality please see 552 | 553 | 554 | ```{note} 555 | If there is a breaking state schema change to an existing app (and `on_schema_break` is set to 556 | 'ReplaceApp' the existing app will be deleted and re-created. 557 | ``` 558 | 559 | ```{note} 560 | If there is an update (different TEAL code) to an existing app (and `on_update` is set to 'ReplaceApp') 561 | the existing app will be deleted and re-created. 562 | ``` 563 | 564 | :param str version: version to use when creating or updating app, if None version will be auto incremented 565 | :param algosdk.atomic_transaction_composer.TransactionSigner signer: signer to use when deploying app 566 | , if None uses self.signer 567 | :param str sender: sender address to use when deploying app, if None uses self.sender 568 | :param bool allow_delete: Used to set the `TMPL_DELETABLE` template variable to conditionally control if an app 569 | can be deleted 570 | :param bool allow_update: Used to set the `TMPL_UPDATABLE` template variable to conditionally control if an app 571 | can be updated 572 | :param OnUpdate on_update: Determines what action to take if an application update is required 573 | :param OnSchemaBreak on_schema_break: Determines what action to take if an application schema requirements 574 | has increased beyond the current allocation 575 | :param dict[str, int|str|bytes] template_values: Values to use for `TMPL_*` template variables, dictionary keys 576 | should *NOT* include the TMPL_ prefix 577 | :param algokit_utils.DeployCallArgs | None create_args: Arguments used when creating an application 578 | :param algokit_utils.DeployCallArgs | None update_args: Arguments used when updating an application 579 | :param algokit_utils.DeployCallArgs | None delete_args: Arguments used when deleting an application 580 | :return DeployResponse: details action taken and relevant transactions 581 | :raises DeploymentError: If the deployment failed""" 582 | 583 | return self.app_client.deploy( 584 | version, 585 | signer=signer, 586 | sender=sender, 587 | allow_update=allow_update, 588 | allow_delete=allow_delete, 589 | on_update=on_update, 590 | on_schema_break=on_schema_break, 591 | template_values=template_values, 592 | create_args=_convert_deploy_args(create_args), 593 | update_args=_convert_deploy_args(update_args), 594 | delete_args=_convert_deploy_args(delete_args), 595 | ) 596 | 597 | def compose(self, atc: AtomicTransactionComposer | None = None) -> Composer: 598 | return Composer(self.app_client, atc or AtomicTransactionComposer()) 599 | -------------------------------------------------------------------------------- /projects/challenge/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "algokit-client-generator" 5 | version = "1.1.3" 6 | description = "Algorand typed client Generator" 7 | optional = false 8 | python-versions = "<4.0,>=3.10" 9 | files = [ 10 | {file = "algokit_client_generator-1.1.3-py3-none-any.whl", hash = "sha256:8b6dffe8ce6ae0708a7842248a4fddb61e55e56889d1ab67bbc0f2028a051d7a"}, 11 | ] 12 | 13 | [package.dependencies] 14 | algokit-utils = "2.2.1" 15 | 16 | [[package]] 17 | name = "algokit-utils" 18 | version = "2.2.1" 19 | description = "Utilities for Algorand development for use by AlgoKit" 20 | optional = false 21 | python-versions = ">=3.10,<4.0" 22 | files = [ 23 | {file = "algokit_utils-2.2.1-py3-none-any.whl", hash = "sha256:5d669211500e3ae9d29a93ec9b2cc174a3b4aca48b345f475ee8ba955df5f555"}, 24 | ] 25 | 26 | [package.dependencies] 27 | deprecated = ">=1.2.14,<2.0.0" 28 | httpx = ">=0.23.1,<0.24.0" 29 | py-algorand-sdk = ">=2.4.0,<3.0.0" 30 | 31 | [[package]] 32 | name = "algorand-python" 33 | version = "1.0.1" 34 | description = "API for writing Algorand Python Smart contracts" 35 | optional = false 36 | python-versions = "<4.0,>=3.12" 37 | files = [ 38 | {file = "algorand_python-1.0.1-py3-none-any.whl", hash = "sha256:b2938d0e2dd67a165a3b9e3b4e6cc86ece60394a005afba9a275ad6dd6456c6a"}, 39 | ] 40 | 41 | [[package]] 42 | name = "anyio" 43 | version = "4.3.0" 44 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 45 | optional = false 46 | python-versions = ">=3.8" 47 | files = [ 48 | {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, 49 | {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, 50 | ] 51 | 52 | [package.dependencies] 53 | idna = ">=2.8" 54 | sniffio = ">=1.1" 55 | 56 | [package.extras] 57 | doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 58 | test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] 59 | trio = ["trio (>=0.23)"] 60 | 61 | [[package]] 62 | name = "attrs" 63 | version = "23.2.0" 64 | description = "Classes Without Boilerplate" 65 | optional = false 66 | python-versions = ">=3.7" 67 | files = [ 68 | {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, 69 | {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, 70 | ] 71 | 72 | [package.extras] 73 | cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] 74 | dev = ["attrs[tests]", "pre-commit"] 75 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] 76 | tests = ["attrs[tests-no-zope]", "zope-interface"] 77 | tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] 78 | tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] 79 | 80 | [[package]] 81 | name = "certifi" 82 | version = "2024.2.2" 83 | description = "Python package for providing Mozilla's CA Bundle." 84 | optional = false 85 | python-versions = ">=3.6" 86 | files = [ 87 | {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, 88 | {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, 89 | ] 90 | 91 | [[package]] 92 | name = "cffi" 93 | version = "1.16.0" 94 | description = "Foreign Function Interface for Python calling C code." 95 | optional = false 96 | python-versions = ">=3.8" 97 | files = [ 98 | {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, 99 | {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, 100 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, 101 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, 102 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, 103 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, 104 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, 105 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, 106 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, 107 | {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, 108 | {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, 109 | {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, 110 | {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, 111 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, 112 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, 113 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, 114 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, 115 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, 116 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, 117 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, 118 | {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, 119 | {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, 120 | {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, 121 | {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, 122 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, 123 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, 124 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, 125 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, 126 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, 127 | {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, 128 | {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, 129 | {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, 130 | {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, 131 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, 132 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, 133 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, 134 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, 135 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, 136 | {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, 137 | {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, 138 | {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, 139 | {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, 140 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, 141 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, 142 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, 143 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, 144 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, 145 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, 146 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, 147 | {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, 148 | {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, 149 | {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, 150 | ] 151 | 152 | [package.dependencies] 153 | pycparser = "*" 154 | 155 | [[package]] 156 | name = "deprecated" 157 | version = "1.2.14" 158 | description = "Python @deprecated decorator to deprecate old python classes, functions or methods." 159 | optional = false 160 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 161 | files = [ 162 | {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, 163 | {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, 164 | ] 165 | 166 | [package.dependencies] 167 | wrapt = ">=1.10,<2" 168 | 169 | [package.extras] 170 | dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] 171 | 172 | [[package]] 173 | name = "docstring-parser" 174 | version = "0.16" 175 | description = "Parse Python docstrings in reST, Google and Numpydoc format" 176 | optional = false 177 | python-versions = ">=3.6,<4.0" 178 | files = [ 179 | {file = "docstring_parser-0.16-py3-none-any.whl", hash = "sha256:bf0a1387354d3691d102edef7ec124f219ef639982d096e26e3b60aeffa90637"}, 180 | {file = "docstring_parser-0.16.tar.gz", hash = "sha256:538beabd0af1e2db0146b6bd3caa526c35a34d61af9fd2887f3a8a27a739aa6e"}, 181 | ] 182 | 183 | [[package]] 184 | name = "h11" 185 | version = "0.14.0" 186 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 187 | optional = false 188 | python-versions = ">=3.7" 189 | files = [ 190 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 191 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 192 | ] 193 | 194 | [[package]] 195 | name = "httpcore" 196 | version = "0.16.3" 197 | description = "A minimal low-level HTTP client." 198 | optional = false 199 | python-versions = ">=3.7" 200 | files = [ 201 | {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, 202 | {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, 203 | ] 204 | 205 | [package.dependencies] 206 | anyio = ">=3.0,<5.0" 207 | certifi = "*" 208 | h11 = ">=0.13,<0.15" 209 | sniffio = "==1.*" 210 | 211 | [package.extras] 212 | http2 = ["h2 (>=3,<5)"] 213 | socks = ["socksio (==1.*)"] 214 | 215 | [[package]] 216 | name = "httpx" 217 | version = "0.23.3" 218 | description = "The next generation HTTP client." 219 | optional = false 220 | python-versions = ">=3.7" 221 | files = [ 222 | {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, 223 | {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, 224 | ] 225 | 226 | [package.dependencies] 227 | certifi = "*" 228 | httpcore = ">=0.15.0,<0.17.0" 229 | rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} 230 | sniffio = "*" 231 | 232 | [package.extras] 233 | brotli = ["brotli", "brotlicffi"] 234 | cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] 235 | http2 = ["h2 (>=3,<5)"] 236 | socks = ["socksio (==1.*)"] 237 | 238 | [[package]] 239 | name = "idna" 240 | version = "3.6" 241 | description = "Internationalized Domain Names in Applications (IDNA)" 242 | optional = false 243 | python-versions = ">=3.5" 244 | files = [ 245 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 246 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 247 | ] 248 | 249 | [[package]] 250 | name = "immutabledict" 251 | version = "4.2.0" 252 | description = "Immutable wrapper around dictionaries (a fork of frozendict)" 253 | optional = false 254 | python-versions = ">=3.8,<4.0" 255 | files = [ 256 | {file = "immutabledict-4.2.0-py3-none-any.whl", hash = "sha256:d728b2c2410d698d95e6200237feb50a695584d20289ad3379a439aa3d90baba"}, 257 | {file = "immutabledict-4.2.0.tar.gz", hash = "sha256:e003fd81aad2377a5a758bf7e1086cf3b70b63e9a5cc2f46bce8d0a2b4727c5f"}, 258 | ] 259 | 260 | [[package]] 261 | name = "msgpack" 262 | version = "1.0.8" 263 | description = "MessagePack serializer" 264 | optional = false 265 | python-versions = ">=3.8" 266 | files = [ 267 | {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, 268 | {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, 269 | {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, 270 | {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, 271 | {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, 272 | {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, 273 | {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, 274 | {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, 275 | {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, 276 | {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, 277 | {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, 278 | {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, 279 | {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, 280 | {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, 281 | {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, 282 | {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, 283 | {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, 284 | {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, 285 | {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, 286 | {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, 287 | {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, 288 | {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, 289 | {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, 290 | {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, 291 | {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, 292 | {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, 293 | {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, 294 | {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, 295 | {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, 296 | {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, 297 | {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, 298 | {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, 299 | {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, 300 | {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, 301 | {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, 302 | {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, 303 | {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, 304 | {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, 305 | {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, 306 | {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, 307 | {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, 308 | {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, 309 | {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, 310 | {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, 311 | {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, 312 | {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, 313 | {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, 314 | {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, 315 | {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, 316 | {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, 317 | {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, 318 | {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, 319 | {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, 320 | {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, 321 | {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, 322 | {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, 323 | {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, 324 | ] 325 | 326 | [[package]] 327 | name = "mypy-extensions" 328 | version = "1.0.0" 329 | description = "Type system extensions for programs checked with the mypy type checker." 330 | optional = false 331 | python-versions = ">=3.5" 332 | files = [ 333 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 334 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 335 | ] 336 | 337 | [[package]] 338 | name = "networkx" 339 | version = "3.2.1" 340 | description = "Python package for creating and manipulating graphs and networks" 341 | optional = false 342 | python-versions = ">=3.9" 343 | files = [ 344 | {file = "networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2"}, 345 | {file = "networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6"}, 346 | ] 347 | 348 | [package.extras] 349 | default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] 350 | developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] 351 | doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] 352 | extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] 353 | test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] 354 | 355 | [[package]] 356 | name = "packaging" 357 | version = "24.0" 358 | description = "Core utilities for Python packages" 359 | optional = false 360 | python-versions = ">=3.7" 361 | files = [ 362 | {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, 363 | {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, 364 | ] 365 | 366 | [[package]] 367 | name = "puyapy" 368 | version = "1.0.1" 369 | description = "An optimising compiler for Algorand Python" 370 | optional = false 371 | python-versions = "<4.0,>=3.12" 372 | files = [ 373 | {file = "puyapy-1.0.1-py3-none-any.whl", hash = "sha256:afef6d685d8aa4d19e97baed91a1e013913ee216f3f19ac00490f2e2c96032d5"}, 374 | ] 375 | 376 | [package.dependencies] 377 | attrs = ">=23.2.0,<24.0.0" 378 | docstring-parser = ">=0.14.1" 379 | immutabledict = ">=4.2.0,<5.0.0" 380 | mypy_extensions = ">=1.0.0,<2.0.0" 381 | networkx = ">=3.1,<4.0" 382 | packaging = ">=24.0,<25.0" 383 | pycryptodomex = ">=3.6.0,<4" 384 | structlog = ">=24.1.0,<25.0.0" 385 | typing-extensions = ">=4.10.0,<5.0.0" 386 | 387 | [[package]] 388 | name = "py-algorand-sdk" 389 | version = "2.5.0" 390 | description = "Algorand SDK in Python" 391 | optional = false 392 | python-versions = ">=3.8" 393 | files = [ 394 | {file = "py-algorand-sdk-2.5.0.tar.gz", hash = "sha256:014b475595aedb82d9ac056d835f65f5ba44f6373aafef9ea4cfe515c648bcef"}, 395 | {file = "py_algorand_sdk-2.5.0-py3-none-any.whl", hash = "sha256:d1cb2a191edcda1e6febd2f4d830a5d441b243dfc47f9e2008630b52b2d61561"}, 396 | ] 397 | 398 | [package.dependencies] 399 | msgpack = ">=1.0.0,<2" 400 | pycryptodomex = ">=3.6.0,<4" 401 | pynacl = ">=1.4.0,<2" 402 | 403 | [[package]] 404 | name = "pycparser" 405 | version = "2.21" 406 | description = "C parser in Python" 407 | optional = false 408 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 409 | files = [ 410 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 411 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 412 | ] 413 | 414 | [[package]] 415 | name = "pycryptodomex" 416 | version = "3.20.0" 417 | description = "Cryptographic library for Python" 418 | optional = false 419 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 420 | files = [ 421 | {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, 422 | {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, 423 | {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, 424 | {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, 425 | {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, 426 | {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, 427 | {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, 428 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, 429 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, 430 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, 431 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, 432 | {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, 433 | {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, 434 | {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, 435 | {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, 436 | {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, 437 | {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, 438 | {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, 439 | {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, 440 | {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, 441 | {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, 442 | {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, 443 | {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, 444 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, 445 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, 446 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, 447 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, 448 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, 449 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, 450 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, 451 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, 452 | {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, 453 | ] 454 | 455 | [[package]] 456 | name = "pynacl" 457 | version = "1.5.0" 458 | description = "Python binding to the Networking and Cryptography (NaCl) library" 459 | optional = false 460 | python-versions = ">=3.6" 461 | files = [ 462 | {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, 463 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, 464 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, 465 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, 466 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, 467 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, 468 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, 469 | {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, 470 | {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, 471 | {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, 472 | ] 473 | 474 | [package.dependencies] 475 | cffi = ">=1.4.1" 476 | 477 | [package.extras] 478 | docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] 479 | tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] 480 | 481 | [[package]] 482 | name = "python-dotenv" 483 | version = "1.0.1" 484 | description = "Read key-value pairs from a .env file and set them as environment variables" 485 | optional = false 486 | python-versions = ">=3.8" 487 | files = [ 488 | {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, 489 | {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, 490 | ] 491 | 492 | [package.extras] 493 | cli = ["click (>=5.0)"] 494 | 495 | [[package]] 496 | name = "rfc3986" 497 | version = "1.5.0" 498 | description = "Validating URI References per RFC 3986" 499 | optional = false 500 | python-versions = "*" 501 | files = [ 502 | {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, 503 | {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, 504 | ] 505 | 506 | [package.dependencies] 507 | idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} 508 | 509 | [package.extras] 510 | idna2008 = ["idna"] 511 | 512 | [[package]] 513 | name = "sniffio" 514 | version = "1.3.1" 515 | description = "Sniff out which async library your code is running under" 516 | optional = false 517 | python-versions = ">=3.7" 518 | files = [ 519 | {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, 520 | {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, 521 | ] 522 | 523 | [[package]] 524 | name = "structlog" 525 | version = "24.1.0" 526 | description = "Structured Logging for Python" 527 | optional = false 528 | python-versions = ">=3.8" 529 | files = [ 530 | {file = "structlog-24.1.0-py3-none-any.whl", hash = "sha256:3f6efe7d25fab6e86f277713c218044669906537bb717c1807a09d46bca0714d"}, 531 | {file = "structlog-24.1.0.tar.gz", hash = "sha256:41a09886e4d55df25bdcb9b5c9674bccfab723ff43e0a86a1b7b236be8e57b16"}, 532 | ] 533 | 534 | [package.extras] 535 | dev = ["structlog[tests,typing]"] 536 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted"] 537 | tests = ["freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"] 538 | typing = ["mypy (>=1.4)", "rich", "twisted"] 539 | 540 | [[package]] 541 | name = "typing-extensions" 542 | version = "4.10.0" 543 | description = "Backported and Experimental Type Hints for Python 3.8+" 544 | optional = false 545 | python-versions = ">=3.8" 546 | files = [ 547 | {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, 548 | {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, 549 | ] 550 | 551 | [[package]] 552 | name = "wrapt" 553 | version = "1.16.0" 554 | description = "Module for decorators, wrappers and monkey patching." 555 | optional = false 556 | python-versions = ">=3.6" 557 | files = [ 558 | {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, 559 | {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, 560 | {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, 561 | {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, 562 | {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, 563 | {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, 564 | {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, 565 | {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, 566 | {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, 567 | {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, 568 | {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, 569 | {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, 570 | {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, 571 | {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, 572 | {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, 573 | {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, 574 | {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, 575 | {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, 576 | {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, 577 | {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, 578 | {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, 579 | {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, 580 | {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, 581 | {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, 582 | {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, 583 | {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, 584 | {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, 585 | {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, 586 | {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, 587 | {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, 588 | {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, 589 | {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, 590 | {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, 591 | {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, 592 | {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, 593 | {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, 594 | {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, 595 | {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, 596 | {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, 597 | {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, 598 | {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, 599 | {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, 600 | {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, 601 | {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, 602 | {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, 603 | {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, 604 | {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, 605 | {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, 606 | {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, 607 | {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, 608 | {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, 609 | {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, 610 | {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, 611 | {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, 612 | {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, 613 | {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, 614 | {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, 615 | {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, 616 | {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, 617 | {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, 618 | {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, 619 | {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, 620 | {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, 621 | {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, 622 | {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, 623 | {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, 624 | {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, 625 | {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, 626 | {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, 627 | {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, 628 | ] 629 | 630 | [metadata] 631 | lock-version = "2.0" 632 | python-versions = "^3.12" 633 | content-hash = "877f39693166e44aac864ab21cf1ee4c332fa869023d1306a4011c8b61a8f3ba" 634 | --------------------------------------------------------------------------------