diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..267c81c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,118 @@ +name: OGC CI + +on: + push: + branches: [ main, develop, ci-runner ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install pipx and AlgoKit + run: | + python -m pip install --user pipx + python -m pipx ensurepath + pipx install algokit==2.9.0 + echo "$HOME/.local/bin" >> $GITHUB_PATH + + - name: Verify AlgoKit installation + run: algokit --version + + - name: Start LocalNet + run: | + algokit localnet start + sleep 10 # Give LocalNet time to fully start + + - name: Install Poetry + run: | + python -m pip install poetry==1.8.3 + poetry --version + + - name: Find project directory + id: find-project + run: | + # Find directory with pyproject.toml + PROJECT_DIR=$(find . -name "pyproject.toml" -type f | head -1 | xargs dirname) + if [ -z "$PROJECT_DIR" ]; then + echo "No pyproject.toml found, trying common patterns..." + for pattern in "*contracts*/projects/*" "*contracts*" "contracts" "."; do + if find $pattern -name "*.py" -path "*/smart_contracts/*" 2>/dev/null | head -1; then + PROJECT_DIR=$(find $pattern -name "*.py" -path "*/smart_contracts/*" 2>/dev/null | head -1 | xargs dirname | xargs dirname) + break + fi + done + fi + echo "PROJECT_DIR=$PROJECT_DIR" >> $GITHUB_OUTPUT + echo "Found project directory: $PROJECT_DIR" + + - name: Install dependencies + working-directory: ${{ steps.find-project.outputs.PROJECT_DIR }} + run: | + if [ -f "pyproject.toml" ]; then + poetry install --no-interaction --no-root + else + echo "No pyproject.toml found, installing with pip" + pip install beaker-pyteal pyteal py-algorand-sdk algokit-utils python-dotenv setuptools + fi + + - name: Build contracts + working-directory: ${{ steps.find-project.outputs.PROJECT_DIR }} + run: | + # Find and build any vault/contract files + for contract in *vault*.py *contract*.py; do + if [ -f "$contract" ]; then + echo "Building $contract..." + if [ -f "pyproject.toml" ]; then + poetry run python "$contract" + else + python "$contract" + fi + fi + done + # List any artifacts created + find . -name "artifacts" -type d -exec ls -la {} \; 2>/dev/null || echo "No artifacts directory found" + + - name: Run tests + working-directory: ${{ steps.find-project.outputs.PROJECT_DIR }} + run: | + # Find and run test files + for test in test_*.py *_test.py; do + if [ -f "$test" ]; then + echo "Running $test..." + if [ -f "pyproject.toml" ]; then + poetry run python "$test" + else + python "$test" + fi + fi + done + + - name: Run demos + working-directory: ${{ steps.find-project.outputs.PROJECT_DIR }} + run: | + # Find and run demo files + for demo in *demo*.py demo_*.py; do + if [ -f "$demo" ]; then + echo "Running $demo..." + if [ -f "pyproject.toml" ]; then + poetry run python "$demo" + else + python "$demo" + fi + fi + done + + - name: Stop LocalNet + if: always() + run: algokit localnet stop \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..7acdc73 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13.7 \ No newline at end of file diff --git a/DEMO.md b/DEMO.md new file mode 100644 index 0000000..64f3c54 --- /dev/null +++ b/DEMO.md @@ -0,0 +1,127 @@ +# OGC Demo Commands + +## ๐Ÿ“ Where to Run Commands + +**All commands must be run from:** +```bash +/Users/eltonbaidoo/OGC/ogc-contracts/projects/ogc-contracts/ +``` + +## ๐Ÿš€ Quick Start + +### 1. Navigate & Activate +```bash +cd ~/OGC/ogc-contracts/projects/ogc-contracts +source "$(poetry env info --path)/bin/activate" +python --version # Should show 3.13.x +``` + +### 2. Create OGC Token +```bash +python create_ogc_token.py +``` +**Expected Output:** +``` +๐Ÿช™ Creating OGC Token... +โœ… OGC Token Created! + Asset ID: 1033 + Total Supply: 1,000,000,000 OGC + Creator: VYXZFE4BGH... +``` + +### 3. Verify Token +```bash +python verify_token.py +``` +**Expected Output:** +``` +๐Ÿ” Verifying OGC Token... +โœ… OGC Token Found! + Asset ID: 1033 + Name: OGC Token + Unit: OGC + Total: 1,000,000,000 +``` + +### 4. Demo ALGO Vault +```bash +python ogc_demo.py +``` +**Expected Output:** +``` +๐Ÿš€ OGC - Out The Groupchat Demo +๐ŸŽฏ Goal: 2.0 ALGO +โœ… Fund Created: APP_ID 1036 +๐Ÿ’ฐ Total: 1.5 ALGO +โณ Need 0.5 more ALGO +``` + +### 5. Run Tests +```bash +python test_vault.py +``` +**Expected Output:** +``` +๐Ÿงช Testing OGC Vault Basic Flow +โœ… Deployed: APP_ID 1037 +โœ… Contribution test passed: 0.5 ALGO +๐Ÿ Tests PASSED +``` + +## ๐ŸŽฏ One-Liner Commands + +### Full Demo Sequence +```bash +cd ~/OGC/ogc-contracts/projects/ogc-contracts && source "$(poetry env info --path)/bin/activate" && python create_ogc_token.py && python verify_token.py && python ogc_demo.py +``` + +### Quick Token Demo +```bash +python create_ogc_token.py && python verify_token.py +``` + +### Quick Vault Demo +```bash +python ogc_demo.py && python test_vault.py +``` + +## ๐Ÿ› ๏ธ Makefile Commands + +```bash +make demo # Run ogc_demo.py +make test # Run test_vault.py +make build # Build working_vault.py +make ci # Run full pipeline +``` + +## ๐Ÿ“Š What Each Command Does + +| Command | Purpose | Output | +|---------|---------|---------| +| `create_ogc_token.py` | Mints OGC token on LocalNet | Asset ID number | +| `verify_token.py` | Confirms token exists | Token details | +| `ogc_demo.py` | Shows group funding scenario | ALGO vault demo | +| `test_vault.py` | Tests contract functions | Test results | +| `working_vault.py` | Builds smart contract | Artifacts created | + +## ๐ŸŽค For Hackathon Presentation + +**Run in this order:** +1. `python create_ogc_token.py` - "We minted OGC token" +2. `python verify_token.py` - "Token verified on Algorand" +3. `python ogc_demo.py` - "Group funding demo" +4. `python test_vault.py` - "All tests pass" + +## ๐Ÿ”ง Troubleshooting + +**If commands fail:** +```bash +# Check you're in right directory +pwd # Should show: /Users/eltonbaidoo/OGC/ogc-contracts/projects/ogc-contracts + +# Check environment is active +which python # Should show venv path + +# Reactivate if needed +source "$(poetry env info --path)/bin/activate" +``` \ No newline at end of file diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md new file mode 100644 index 0000000..668f41f --- /dev/null +++ b/PROJECT_STATUS.md @@ -0,0 +1,62 @@ +# OGC Project Status Overview + +## โœ… What's Working + +### Local Development +- ALGO vault contracts (working_vault.py) +- OGC token creation (1B tokens, 6 decimals) +- Full demo scripts (LocalNet) +- CI/CD pipeline (GitHub Actions) + +### TestNet Ready +- Wallet format detection (Universal vs Legacy) +- Balance checking scripts +- ALGO sending scripts +- Contract deployment scripts +- Sender contract (can send ALGO to any wallet) + +## ๐ŸŽฏ Your Current Setup + +### Saved Legacy Wallet +- **Address**: `ZHMX3URT56ZLWQY3Y74CRXVOAVEOELOAVMXJZDT76IFORLPWMWMPJVAEN4` +- **Format**: 25-word mnemonic (Python SDK compatible) + +### Available Scripts +- **Check formats**: `universal_wallet.py` +- **Check balances**: `check_any_balance.py` +- **Send ALGO**: `flexible_send.py` +- **Deploy contracts**: `deploy_sender.py`, `deploy_receiver.py` +- **Contract operations**: `send_from_contract.py` + +## โ“ What We Need to Know + +1. **Do you have contracts deployed?** (Need APP_IDs) +2. **Which wallet are you using?** (Universal 24-word or Legacy 25-word) +3. **What do you want to test?** (Send to contract, send from contract, check balances) + +## ๐Ÿš€ Next Steps + +**Tell me what you want to test and I'll guide you through it!** + +## ๐Ÿ“ Project Structure + +``` +OGC/ +โ”œโ”€โ”€ PROJECT_STATUS.md # This file +โ”œโ”€โ”€ SETUP.md # Development setup +โ”œโ”€โ”€ TANGIBLE_DEMO.md # Demo commands +โ””โ”€โ”€ ogc-contracts/projects/ogc-contracts/ + โ”œโ”€โ”€ working_vault.py # Main ALGO vault + โ”œโ”€โ”€ create_ogc_token.py # Token creation + โ”œโ”€โ”€ full_demo.py # Complete demo + โ”œโ”€โ”€ universal_wallet.py # Wallet format handler + โ”œโ”€โ”€ flexible_send.py # ALGO sender + โ””โ”€โ”€ check_any_balance.py # Balance checker +``` + +## ๐ŸŽฏ Demo Ready + +- **Local Demo**: Full ALGO vault + token creation working +- **TestNet Demo**: Scripts ready for live transactions +- **CI/CD**: Automated testing and building +- **Documentation**: Setup guides and command references \ No newline at end of file diff --git a/TANGIBLE_DEMO.md b/TANGIBLE_DEMO.md new file mode 100644 index 0000000..2b7d75f --- /dev/null +++ b/TANGIBLE_DEMO.md @@ -0,0 +1,127 @@ +# OGC Tangible Demo Commands + +## ๐Ÿ“ Where to Run Commands + +**All commands must be run from:** +```bash +/Users/eltonbaidoo/OGC/ogc-contracts/projects/ogc-contracts/ +``` + +## ๐Ÿš€ Quick Start + +### 1. Navigate & Activate +```bash +cd ~/OGC/ogc-contracts/projects/ogc-contracts +source "$(poetry env info --path)/bin/activate" +python --version # Should show 3.13.x +``` + +### 2. Create OGC Token +```bash +python create_ogc_token.py +``` +**Expected Output:** +``` +๐Ÿช™ Creating OGC Token... +โœ… OGC Token Created! + Asset ID: 1033 + Total Supply: 1,000,000,000 OGC + Creator: VYXZFE4BGH... +``` + +### 3. Verify Token +```bash +python verify_token.py +``` +**Expected Output:** +``` +๐Ÿ” Verifying OGC Token... +โœ… OGC Token Found! + Asset ID: 1033 + Name: OGC Token + Unit: OGC + Total: 1,000,000,000 +``` + +### 4. Demo ALGO Vault +```bash +python ogc_demo.py +``` +**Expected Output:** +``` +๐Ÿš€ OGC - Out The Groupchat Demo +๐ŸŽฏ Goal: 2.0 ALGO +โœ… Fund Created: APP_ID 1036 +๐Ÿ’ฐ Total: 1.5 ALGO +โณ Need 0.5 more ALGO +``` + +### 5. Run Tests +```bash +python test_vault.py +``` +**Expected Output:** +``` +๐Ÿงช Testing OGC Vault Basic Flow +โœ… Deployed: APP_ID 1037 +โœ… Contribution test passed: 0.5 ALGO +๐Ÿ Tests PASSED +``` + +## ๐ŸŽฏ One-Liner Commands + +### Full Demo Sequence +```bash +cd ~/OGC/ogc-contracts/projects/ogc-contracts && source "$(poetry env info --path)/bin/activate" && python create_ogc_token.py && python verify_token.py && python ogc_demo.py +``` + +### Quick Token Demo +```bash +python create_ogc_token.py && python verify_token.py +``` + +### Quick Vault Demo +```bash +python ogc_demo.py && python test_vault.py +``` + +## ๐Ÿ› ๏ธ Makefile Commands + +```bash +make demo # Run ogc_demo.py +make test # Run test_vault.py +make build # Build working_vault.py +make ci # Run full pipeline +``` + +## ๐Ÿ“Š What Each Command Does + +| Command | Purpose | Output | +|---------|---------|---------| +| `create_ogc_token.py` | Mints OGC token on LocalNet | Asset ID number | +| `verify_token.py` | Confirms token exists | Token details | +| `ogc_demo.py` | Shows group funding scenario | ALGO vault demo | +| `test_vault.py` | Tests contract functions | Test results | +| `working_vault.py` | Builds smart contract | Artifacts created | + +## ๐ŸŽค For Hackathon Presentation + +**Run in this order:** +1. `python create_ogc_token.py` - "We minted OGC token" +2. `python verify_token.py` - "Token verified on Algorand" +3. `python ogc_demo.py` - "Group funding demo" +4. `python test_vault.py` - "All tests pass" + +## ๐Ÿ”ง Troubleshooting + +**If commands fail:** +```bash +# Check you're in right directory +pwd # Should show: /Users/eltonbaidoo/OGC/ogc-contracts/projects/ogc-contracts + +# Check environment is active +which python # Should show venv path + +# Reactivate if needed +source "$(poetry env info --path)/bin/activate" +``` \ No newline at end of file diff --git a/ogc-contracts/.algokit.toml b/ogc-contracts/.algokit.toml new file mode 100644 index 0000000..885e1fd --- /dev/null +++ b/ogc-contracts/.algokit.toml @@ -0,0 +1,10 @@ +[algokit] +min_version = "v1.12.1" + +[project] +type = 'workspace' +projects_root_path = 'projects' + +[generate.devcontainer] +description = "Generate a default 'devcontainer.json' configuration that pre-installs algokit and launches Algorand sandbox as part of codespace container provisioning." +path = ".algokit/generators/create-devcontainer" diff --git a/ogc-contracts/.algokit/generators/create-devcontainer/copier.yaml b/ogc-contracts/.algokit/generators/create-devcontainer/copier.yaml new file mode 100644 index 0000000..e98f334 --- /dev/null +++ b/ogc-contracts/.algokit/generators/create-devcontainer/copier.yaml @@ -0,0 +1,4 @@ +_tasks: + - "echo '==== Successfully generated new .devcontainer.json file ๐Ÿš€ ===='" + +_templates_suffix: ".j2" diff --git a/ogc-contracts/.algokit/generators/create-devcontainer/devcontainer.json b/ogc-contracts/.algokit/generators/create-devcontainer/devcontainer.json new file mode 100644 index 0000000..6452c65 --- /dev/null +++ b/ogc-contracts/.algokit/generators/create-devcontainer/devcontainer.json @@ -0,0 +1,19 @@ +{ + "forwardPorts": [4001, 4002, 8980, 5173], + "portsAttributes": { + "4001": { + "label": "algod" + }, + "4002": { + "label": "kmd" + }, + "8980": { + "label": "indexer" + }, + "5173": { + "label": "vite" + } + }, + "postCreateCommand": "mkdir -p ~/.config/algokit && pipx install algokit && sudo chown -R codespace:codespace ~/.config/algokit", + "postStartCommand": "for i in {1..5}; do algokit localnet status > /dev/null 2>&1 && break || sleep 30; algokit localnet reset; done" +} diff --git a/ogc-contracts/.editorconfig b/ogc-contracts/.editorconfig new file mode 100644 index 0000000..5e550a1 --- /dev/null +++ b/ogc-contracts/.editorconfig @@ -0,0 +1,10 @@ +[*] +charset = utf-8 +insert_final_newline = true +end_of_line = lf +indent_style = space +indent_size = 2 +tab_width = 2 +max_line_length = 140 +trim_trailing_whitespace = true +single_quote = true diff --git a/ogc-contracts/.gitattributes b/ogc-contracts/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/ogc-contracts/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/ogc-contracts/.gitignore b/ogc-contracts/.gitignore new file mode 100644 index 0000000..4105eb2 --- /dev/null +++ b/ogc-contracts/.gitignore @@ -0,0 +1,170 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Ruff (linter) +.ruff_cache/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ +!.idea/runConfigurations + +# macOS +.DS_Store + +# Received approval test files +*.received.* + +# NPM +node_modules + diff --git a/ogc-contracts/.vscode/settings.json b/ogc-contracts/.vscode/settings.json new file mode 100644 index 0000000..033fd10 --- /dev/null +++ b/ogc-contracts/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + // Disabled due to matangover.mypy extension not supporting monorepos + // To be addressed as part of https://github.com/matangover/mypy-vscode/issues/82 + "mypy.enabled": false +} diff --git a/ogc-contracts/README.md b/ogc-contracts/README.md new file mode 100644 index 0000000..9786725 --- /dev/null +++ b/ogc-contracts/README.md @@ -0,0 +1,22 @@ +# ogc-contracts + +Welcome to your new AlgoKit project! + +This is your workspace root. A `workspace` in AlgoKit is an orchestrated collection of standalone projects (backends, smart contracts, frontend apps and etc). + +By default, `projects_root_path` parameter is set to `projects`. Which instructs AlgoKit CLI to create a new directory under `projects` directory when new project is instantiated via `algokit init` at the root of the workspace. + +## Getting Started + +To get started refer to `README.md` files in respective sub-projects in the `projects` directory. + +To learn more about algokit, visit [documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md). + +### GitHub Codespaces + +To get started execute: + +1. `algokit generate devcontainer` - invoking this command from the root of this repository will create a `devcontainer.json` file with all the configuration needed to run this project in a GitHub codespace. [Run the repository inside a codespace](https://docs.github.com/en/codespaces/getting-started/quickstart) to get started. +2. `algokit init` - invoke this command inside a github codespace to launch an interactive wizard to guide you through the process of creating a new AlgoKit project + +Powered by [Copier templates](https://copier.readthedocs.io/en/stable/). diff --git a/ogc-contracts/ogc-contracts.code-workspace b/ogc-contracts/ogc-contracts.code-workspace new file mode 100644 index 0000000..09ecd5c --- /dev/null +++ b/ogc-contracts/ogc-contracts.code-workspace @@ -0,0 +1,32 @@ +{ + "folders": [ + { + "path": "./", + "name": "ROOT" + }, + { + "path": "projects/ogc-contracts" + } + ], + "settings": { + "files.exclude": { + "projects/": true + }, + "jest.disabledWorkspaceFolders": [ + "ROOT", + "projects" + ] + }, + "extensions": { + "recommendations": [ + "joshx.workspace-terminals" + ] + }, + "tasks": { + "version": "2.0.0", + "tasks": [] + }, + "launch": { + "configurations": [] + } +} \ No newline at end of file diff --git a/ogc-contracts/projects/.gitkeep b/ogc-contracts/projects/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ogc-contracts/projects/ogc-contracts/.algokit.toml b/ogc-contracts/projects/ogc-contracts/.algokit.toml new file mode 100644 index 0000000..1631bd5 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit.toml @@ -0,0 +1,46 @@ +[algokit] +min_version = "v2.0.0" + +[generate.smart-contract] +description = "Generate a new smart contract for existing project" +path = ".algokit/generators/create_contract" + +[generate.env-file] +description = "Generate a new generic or Algorand network specific .env file" +path = ".algokit/generators/create_env_file" + +[project] +type = 'contract' +name = 'ogc-contracts' +artifacts = 'smart_contracts/artifacts' + +[project.deploy] +command = "poetry run python -m smart_contracts deploy" + +[project.deploy.testnet] +environment_secrets = [ + "DEPLOYER_MNEMONIC", +] + +[project.deploy.mainnet] +environment_secrets = [ + "DEPLOYER_MNEMONIC", +] + +[project.run] +# Commands intented for use locally and in CI +build = { commands = [ + 'poetry run python -m smart_contracts build', +], description = 'Build all smart contracts in the project' } +lint = { commands = [ +], description = 'Perform linting' } +audit-teal = { commands = [ + # ๐Ÿšจ 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. + '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', +], description = 'Audit TEAL files' } + +# Commands intented for CI only, prefixed with `ci-` by convention +ci-teal-diff = { commands = [ + 'git add -N ./smart_contracts/artifacts', + 'git diff --exit-code --minimal ./smart_contracts/artifacts', +], description = 'Check TEAL files for differences' } diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/.copier-answers.yml b/ogc-contracts/projects/ogc-contracts/.algokit/.copier-answers.yml new file mode 100644 index 0000000..f5b6255 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/.copier-answers.yml @@ -0,0 +1,10 @@ +# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY +_commit: 1.6.3 +_src_path: gh:algorandfoundation/algokit-python-template +author_email: baidooelton76@gmail.com +author_name: Elton Baidoo +contract_name: ogc_vault +deployment_language: python +preset_name: starter +project_name: ogc-contracts + diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/copier.yaml b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/copier.yaml new file mode 100644 index 0000000..73805de --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/copier.yaml @@ -0,0 +1,10 @@ +_tasks: + - "echo '==== Successfully initialized new smart contract ๐Ÿš€ ===='" + +contract_name: + type: str + help: Name of your new contract. + placeholder: "my-new-contract" + default: "my-new-contract" + +_templates_suffix: ".j2" diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/contract.py.j2 b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/contract.py.j2 new file mode 100644 index 0000000..829e3a0 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/contract.py.j2 @@ -0,0 +1,9 @@ +# pyright: reportMissingModuleSource=false +from algopy import ARC4Contract, String +from algopy.arc4 import abimethod + + +class {{ contract_name.split('_')|map('capitalize')|join }}(ARC4Contract): + @abimethod() + def hello(self, name: String) -> String: + return "Hello, " + name diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 new file mode 100644 index 0000000..a2ff46d --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_contract/smart_contracts/{{ contract_name }}/deploy_config.py.j2 @@ -0,0 +1,44 @@ +import logging + +import algokit_utils + +logger = logging.getLogger(__name__) + + +# define deployment behaviour based on supplied app spec +def deploy() -> None: + from smart_contracts.artifacts.{{ contract_name }}.{{ contract_name }}_client import ( + {{ contract_name.split('_')|map('capitalize')|join }}Factory, + HelloArgs, + ) + + algorand = algokit_utils.AlgorandClient.from_environment() + deployer_ = algorand.account.from_environment("DEPLOYER") + + factory = algorand.client.get_typed_app_factory( + {{ contract_name.split('_')|map('capitalize')|join }}Factory, default_sender=deployer_.address + ) + + app_client, result = factory.deploy( + on_update=algokit_utils.OnUpdate.AppendApp, + on_schema_break=algokit_utils.OnSchemaBreak.AppendApp, + ) + + if result.operation_performed in [ + algokit_utils.OperationPerformed.Create, + algokit_utils.OperationPerformed.Replace, + ]: + algorand.send.payment( + algokit_utils.PaymentParams( + amount=algokit_utils.AlgoAmount(algo=1), + sender=deployer_.address, + receiver=app_client.app_address, + ) + ) + + name = "world" + response = app_client.send.hello(args=HelloArgs(name=name)) + logger.info( + f"Called hello on {app_client.app_name} ({app_client.app_id}) " + f"with name={name}, received: {response.abi_return}" + ) diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/copier.yaml b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/copier.yaml new file mode 100644 index 0000000..afa2cac --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/copier.yaml @@ -0,0 +1,49 @@ +_tasks: + - "echo '==== Successfully generated new .env file ๐Ÿš€ ===='" + +target_network: + type: str + help: Name of your target network. + choices: + - mainnet + - testnet + - localnet + - custom + default: "localnet" + when: "{{ not use_generic_env }}" + +custom_network_name: + type: str + help: Name of your custom Algorand network. + placeholder: "custom" + when: "{{ not use_generic_env and target_network == 'custom' }}" + +is_localnet: + type: bool + help: Whether to deploy on localnet. + placeholder: "true" + default: "{{ target_network == 'localnet' and not use_generic_env }}" + when: 'false' + +is_testnet: + type: bool + help: Whether to deploy on testnet. + placeholder: "true" + default: "{{ target_network == 'testnet' and not use_generic_env }}" + when: 'false' + +is_mainnet: + type: bool + help: Whether to deploy on mainnet. + placeholder: "true" + default: "{{ target_network == 'mainnet' and not use_generic_env }}" + when: 'false' + +is_customnet: + type: bool + help: Whether to deploy on custom network. + placeholder: "true" + default: "{{ target_network == 'custom' and not use_generic_env }}" + when: 'false' + +_templates_suffix: ".j2" diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_customnet %}.env.{{custom_network_name}}{% endif %} b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_customnet %}.env.{{custom_network_name}}{% endif %} new file mode 100644 index 0000000..cfc9f21 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_customnet %}.env.{{custom_network_name}}{% endif %} @@ -0,0 +1,7 @@ +# this file contains algorand network settings for interacting with testnet via algonode +ALGOD_TOKEN={YOUR_ALGOD_TOKEN} +ALGOD_SERVER={YOUR_ALGOD_SERVER_URL} +ALGOD_PORT={YOUR_ALGOD_PORT} +INDEXER_TOKEN={YOUR_INDEXER_TOKEN} +INDEXER_SERVER={YOUR_INDEXER_SERVER_URL} +INDEXER_PORT={YOUR_INDEXER_PORT} diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_localnet %}.env.localnet{% endif %} b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_localnet %}.env.localnet{% endif %} new file mode 100644 index 0000000..fcbf442 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_localnet %}.env.localnet{% endif %} @@ -0,0 +1,7 @@ +# this file should contain environment variables specific to algokit localnet +ALGOD_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +ALGOD_SERVER=http://localhost +ALGOD_PORT=4001 +INDEXER_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +INDEXER_SERVER=http://localhost +INDEXER_PORT=8980 diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_mainnet %}.env.mainnet{% endif %} b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_mainnet %}.env.mainnet{% endif %} new file mode 100644 index 0000000..bb9a787 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_mainnet %}.env.mainnet{% endif %} @@ -0,0 +1,3 @@ +# this file contains algorand network settings for interacting with testnet via algonode +ALGOD_SERVER=https://mainnet-api.algonode.cloud +INDEXER_SERVER=https://mainnet-idx.algonode.cloud diff --git a/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_testnet %}.env.testnet{% endif %} b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_testnet %}.env.testnet{% endif %} new file mode 100644 index 0000000..eeea43d --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.algokit/generators/create_env_file/{% if is_testnet %}.env.testnet{% endif %} @@ -0,0 +1,3 @@ +# this file contains algorand network settings for interacting with testnet via algonode +ALGOD_SERVER=https://testnet-api.algonode.cloud +INDEXER_SERVER=https://testnet-idx.algonode.cloud diff --git a/ogc-contracts/projects/ogc-contracts/.editorconfig b/ogc-contracts/projects/ogc-contracts/.editorconfig new file mode 100644 index 0000000..e2fda34 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.editorconfig @@ -0,0 +1,10 @@ +root=true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true + +[*.py] +indent_size = 4 diff --git a/ogc-contracts/projects/ogc-contracts/.gitignore b/ogc-contracts/projects/ogc-contracts/.gitignore new file mode 100644 index 0000000..35bd956 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.gitignore @@ -0,0 +1,183 @@ +# Environment variables +.env + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ +coverage/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +.env.* +!.env.*.template +!.env.template +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Ruff (linter) +.ruff_cache/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea +!.idea/ +.idea/* +!.idea/runConfigurations/ + +# macOS +.DS_Store + +# Received approval test files +*.received.* + +# NPM +node_modules + +# AlgoKit +debug_traces/ +.algokit/static-analysis/ # Replace with .algokit/static-analysis/tealer/ to enable snapshot checks in CI +.algokit/sources diff --git a/ogc-contracts/projects/ogc-contracts/.tours/getting-started-with-your-algokit-project.tour b/ogc-contracts/projects/ogc-contracts/.tours/getting-started-with-your-algokit-project.tour new file mode 100644 index 0000000..728218d --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.tours/getting-started-with-your-algokit-project.tour @@ -0,0 +1,51 @@ +{ + "$schema": "https://aka.ms/codetour-schema", + "title": "Getting Started with Your AlgoKit Project", + "steps": [ + { + "file": "README.md", + "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.", + "line": 3 + }, + { + "file": "README.md", + "description": "Start by ensuring you have followed the setup of pre-requisites.", + "line": 9 + }, + { + "file": ".algokit.toml", + "description": "This is the main configuration file used by algokit-cli to manage the project. 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` (see how it is defined in the `.algokit.toml`, you can create your own generators if needed). 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. Additionally you can define custom commands to run (similar to `npm` scripts), see definitions under `[project]` section in `.algokit.toml`.", + "line": 1 + }, + { + "file": "smart_contracts/hello_world/deploy_config.py", + "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. The invocation if deploy is aliased in `.algokit.toml` file, allowing simple deployments via `algokit project deploy` command.", + "line": 32 + }, + { + "file": ".env.localnet.template", + "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`.", + "line": 2 + }, + { + "file": ".vscode/launch.json", + "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. Alternatively execute `algokit project run` to see list of available custom commands.", + "line": 5 + }, + { + "file": ".vscode/extensions.json", + "description": "We highly recommend installing the recommended extensions to get the most out of this template starter project in your VSCode IDE.", + "line": 3 + }, + { + "file": "smart_contracts/__main__.py", + "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.", + "line": 15 + }, + { + "file": "smart_contracts/_helpers/__init__.py", + "description": "This folder contains helper scripts for contract management. These automate tasks like compiling, generating clients, and deploying. Usually, you won't need to edit these files, but advanced users can expand them for custom needs.", + "line": 1 + } + ] +} diff --git a/ogc-contracts/projects/ogc-contracts/.vscode/extensions.json b/ogc-contracts/projects/ogc-contracts/.vscode/extensions.json new file mode 100644 index 0000000..e16b76b --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "ms-python.python", + "tamasfe.even-better-toml", + "editorconfig.editorconfig", + "vsls-contrib.codetour", + "algorandfoundation.algokit-avm-vscode-debugger" + ] +} diff --git a/ogc-contracts/projects/ogc-contracts/.vscode/launch.json b/ogc-contracts/projects/ogc-contracts/.vscode/launch.json new file mode 100644 index 0000000..dbaea6e --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.vscode/launch.json @@ -0,0 +1,52 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Build & Deploy contracts", + "type": "python", + "request": "launch", + "module": "smart_contracts", + "cwd": "${workspaceFolder}", + "preLaunchTask": "Start AlgoKit LocalNet", + "env": { + "ALGOD_TOKEN": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "ALGOD_SERVER": "http://localhost", + "ALGOD_PORT": "4001", + "INDEXER_TOKEN": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "INDEXER_SERVER": "http://localhost", + "INDEXER_PORT": "8980" + } + }, + { + "name": "Deploy contracts", + "type": "python", + "request": "launch", + "module": "smart_contracts", + "args": ["deploy"], + "cwd": "${workspaceFolder}", + "env": { + "ALGOD_TOKEN": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "ALGOD_SERVER": "http://localhost", + "ALGOD_PORT": "4001", + "INDEXER_TOKEN": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "INDEXER_SERVER": "http://localhost", + "INDEXER_PORT": "8980" + } + }, + { + "name": "Build contracts", + "type": "python", + "request": "launch", + "module": "smart_contracts", + "args": ["build"], + "cwd": "${workspaceFolder}" + }, + { + "type": "avm", + "request": "launch", + "name": "Debug TEAL via AlgoKit AVM Debugger", + "simulateTraceFile": "${workspaceFolder}/${command:PickSimulateTraceFile}", + "stopOnEntry": true + } + ] +} diff --git a/ogc-contracts/projects/ogc-contracts/.vscode/settings.json b/ogc-contracts/projects/ogc-contracts/.vscode/settings.json new file mode 100644 index 0000000..bbfb30d --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.vscode/settings.json @@ -0,0 +1,34 @@ +{ + // General - see also /.editorconfig + "editor.formatOnSave": true, + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + ".mypy_cache": true, + ".pytest_cache": true, + ".ruff_cache": true, + "**/__pycache__": true, + ".idea": true + }, + + // Python + "python.analysis.autoImportCompletions": true, + "python.analysis.extraPaths": ["${workspaceFolder}/smart_contracts"], + "python.analysis.diagnosticSeverityOverrides": { + "reportMissingModuleSource": "none" + }, + "python.defaultInterpreterPath": "${workspaceFolder}/.venv", + "[python]": { + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + // Prevent default import sorting from running; Ruff will sort imports for us anyway + "source.organizeImports": "never" + }, + "editor.defaultFormatter": null, + }, + + // On Windows, if execution policy is set to Signed (default) then it won't be able to activate the venv + // so instead let's set it to RemoteSigned for VS Code terminal + "terminal.integrated.shellArgs.windows": ["-ExecutionPolicy", "RemoteSigned"], +} diff --git a/ogc-contracts/projects/ogc-contracts/.vscode/tasks.json b/ogc-contracts/projects/ogc-contracts/.vscode/tasks.json new file mode 100644 index 0000000..eb1e767 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/.vscode/tasks.json @@ -0,0 +1,79 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Build contracts", + "command": "${workspaceFolder}/.venv/bin/python", + "windows": { + "command": "${workspaceFolder}/.venv/Scripts/python.exe" + }, + "args": ["-m", "smart_contracts", "build"], + "options": { + "cwd": "${workspaceFolder}" + }, + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "label": "Build contracts (+ LocalNet)", + "command": "${workspaceFolder}/.venv/bin/python", + "windows": { + "command": "${workspaceFolder}/.venv/Scripts/python.exe" + }, + "args": ["-m", "smart_contracts", "build"], + "options": { + "cwd": "${workspaceFolder}" + }, + "dependsOn": "Start AlgoKit LocalNet", + "problemMatcher": [] + }, + { + "label": "Start AlgoKit LocalNet", + "command": "algokit", + "args": ["localnet", "start"], + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Stop AlgoKit LocalNet", + "command": "algokit", + "args": ["localnet", "stop"], + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Reset AlgoKit LocalNet", + "command": "algokit", + "args": ["localnet", "reset"], + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "Analyze TEAL contracts with AlgoKit Tealer integration", + "command": "algokit", + "args": [ + "task", + "analyze", + "${workspaceFolder}/.algokit", + "--recursive", + "--force" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + } + ] +} diff --git a/ogc-contracts/projects/ogc-contracts/Makefile b/ogc-contracts/projects/ogc-contracts/Makefile new file mode 100644 index 0000000..4f9b9c6 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/Makefile @@ -0,0 +1,55 @@ +.PHONY: help install build deploy test demo clean + +help: ## Show this help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +install: ## Install dependencies + poetry install --no-interaction + +build: ## Build smart contracts + @echo "๐Ÿ”จ Building contracts..." + @for contract in *vault*.py *contract*.py; do \ + if [ -f "$$contract" ]; then \ + echo "Building $$contract..."; \ + poetry run python "$$contract"; \ + fi; \ + done + @echo "โœ… Contracts built in artifacts/" + +deploy: ## Deploy to LocalNet + @for deploy in deploy_*.py *_deploy.py; do \ + if [ -f "$$deploy" ]; then \ + echo "Running $$deploy..."; \ + poetry run python "$$deploy"; \ + break; \ + fi; \ + done + +test: ## Run test suite + @for test in test_*.py *_test.py; do \ + if [ -f "$$test" ]; then \ + echo "Running $$test..."; \ + poetry run python "$$test"; \ + fi; \ + done + +demo: ## Run demos + @for demo in *demo*.py demo_*.py; do \ + if [ -f "$$demo" ]; then \ + echo "Running $$demo..."; \ + poetry run python "$$demo"; \ + fi; \ + done + +clean: ## Clean artifacts + rm -rf artifacts/ + rm -rf __pycache__/ + rm -rf .pytest_cache/ + +localnet-start: ## Start LocalNet + algokit localnet start + +localnet-stop: ## Stop LocalNet + algokit localnet stop + +ci: install build test demo ## Run full CI pipeline \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/README.md b/ogc-contracts/projects/ogc-contracts/README.md new file mode 100644 index 0000000..0e659a5 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/README.md @@ -0,0 +1,35 @@ +# OGC Smart Contracts + +Smart contracts for the OGC (Out The Groupchat) project. + +## Quick Start + +```bash +# Install dependencies +make install + +# Build contracts +make build + +# Run tests +make test + +# Run demo +make demo +``` + +## CI/CD + +This project uses GitHub Actions for continuous integration: +- โœ… Builds contracts on every push +- โœ… Runs test suite +- โœ… Validates demo scenarios +- โœ… Uses pinned dependencies for reproducibility + +## Dependencies + +All dependencies are pinned in `pyproject.toml` for reproducible builds: +- `beaker-pyteal==1.1.1` +- `pyteal==0.24.1` +- `py-algorand-sdk==2.10.0` +- `algokit-utils==2.4.0` \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/TESTNET_DEMO.md b/ogc-contracts/projects/ogc-contracts/TESTNET_DEMO.md new file mode 100644 index 0000000..7fb1da4 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/TESTNET_DEMO.md @@ -0,0 +1,52 @@ +# OGC TestNet Demo Guide + +## ๐Ÿš€ Quick Demo Steps + +### 1. Deploy Vault +```bash +python deploy_with_address.py +``` +- Enter your 25-word mnemonic +- Note the APP_ID and APP_ADDRESS + +### 2. Send ALGO via Pera +- Open Pera Wallet โ†’ TestNet +- Send 2+ ALGO to APP_ADDRESS +- Confirm transaction + +### 3. Check Balance +```bash +python check_balance_testnet.py +# Enter APP_ID when prompted +``` + +### 4. Get App Address (if needed) +```bash +python get_app_address.py +# Enter APP_ID when prompted +``` + +### 5. Trigger Release (after goal met) +```bash +python call_release_testnet.py +# Enter APP_ID and mnemonic when prompted +``` + +## ๐Ÿ“‹ Your TestNet Address +``` +SXIEIE2D7FOKUNQXUFUZIRYKE75RYD5KBN5BOYZFXLIL7LOTFX4VK3U7CE +``` + +## ๐Ÿ”— Useful Links +- **TestNet Dispenser**: https://testnet.algoexplorer.io/dispenser +- **TestNet Explorer**: https://testnet.algoexplorer.io/ +- **Pera Wallet**: https://perawallet.app/ + +## ๐ŸŽฏ Demo Flow +1. **Deploy**: `python deploy_with_address.py` +2. **Fund**: Send ALGO via Pera to app address +3. **Verify**: `python check_balance_testnet.py` +4. **Release**: `python call_release_testnet.py` +5. **Show**: All transactions on TestNet explorer + +**Total demo time: 3-5 minutes** โšก \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/__pycache__/simple_vault.cpython-313.pyc b/ogc-contracts/projects/ogc-contracts/__pycache__/simple_vault.cpython-313.pyc new file mode 100644 index 0000000..f9b31b9 Binary files /dev/null and b/ogc-contracts/projects/ogc-contracts/__pycache__/simple_vault.cpython-313.pyc differ diff --git a/ogc-contracts/projects/ogc-contracts/__pycache__/working_vault.cpython-313.pyc b/ogc-contracts/projects/ogc-contracts/__pycache__/working_vault.cpython-313.pyc new file mode 100644 index 0000000..6a90219 Binary files /dev/null and b/ogc-contracts/projects/ogc-contracts/__pycache__/working_vault.cpython-313.pyc differ diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/application.json b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/application.json new file mode 100644 index 0000000..6dda22d --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/application.json @@ -0,0 +1,158 @@ +{ + "hints": { + "create(uint64,uint64,address)uint64": { + "call_config": { + "no_op": "CREATE" + } + }, + "opt_in()void": { + "call_config": { + "opt_in": "CALL" + } + }, + "pause()void": { + "call_config": { + "no_op": "CALL" + } + }, + "unpause()void": { + "call_config": { + "no_op": "CALL" + } + }, + "contribute(pay)void": { + "call_config": { + "no_op": "CALL" + } + }, + "release()void": { + "call_config": { + "no_op": "CALL" + } + }, + "refund()void": { + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQpieXRlY2Jsb2NrIDB4NjM2ZjZlNzQ3MjY5NjI3NTc0NjU2NCAweDcwNjE3NTczNjU2NCAweDY3NmY2MTZjIDB4NjQ2NTYxNjQ2YzY5NmU2NSAweDcyNjU2MzY1Njk3NjY1NzIKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMApwdXNoYnl0ZXMgMHg1MjczNTMyMSAvLyAiY3JlYXRlKHVpbnQ2NCx1aW50NjQsYWRkcmVzcyl1aW50NjQiCj09CmJueiBtYWluX2wxNAp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDMwYzZkNThhIC8vICJvcHRfaW4oKXZvaWQiCj09CmJueiBtYWluX2wxMwp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDAxNzhmOTRiIC8vICJwYXVzZSgpdm9pZCIKPT0KYm56IG1haW5fbDEyCnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4MWI1MjlkZTggLy8gInVucGF1c2UoKXZvaWQiCj09CmJueiBtYWluX2wxMQp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweGZlNzE3YjZkIC8vICJjb250cmlidXRlKHBheSl2b2lkIgo9PQpibnogbWFpbl9sMTAKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMApwdXNoYnl0ZXMgMHgwNzZiYmQ0ZCAvLyAicmVsZWFzZSgpdm9pZCIKPT0KYm56IG1haW5fbDkKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMApwdXNoYnl0ZXMgMHgyM2U2MjlmNyAvLyAicmVmdW5kKCl2b2lkIgo9PQpibnogbWFpbl9sOAplcnIKbWFpbl9sODoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKY2FsbHN1YiByZWZ1bmRjYXN0ZXJfMTQKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDk6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgcmVsZWFzZWNhc3Rlcl8xMwppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sMTA6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgY29udHJpYnV0ZWNhc3Rlcl8xMgppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sMTE6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgdW5wYXVzZWNhc3Rlcl8xMQppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sMTI6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgcGF1c2VjYXN0ZXJfMTAKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDEzOgp0eG4gT25Db21wbGV0aW9uCmludGNfMSAvLyBPcHRJbgo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKY2FsbHN1YiBvcHRpbmNhc3Rlcl85CmludGNfMSAvLyAxCnJldHVybgptYWluX2wxNDoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAo9PQomJgphc3NlcnQKY2FsbHN1YiBjcmVhdGVjYXN0ZXJfOAppbnRjXzEgLy8gMQpyZXR1cm4KCi8vIGNyZWF0ZQpjcmVhdGVfMDoKcHJvdG8gMyAxCmludGNfMCAvLyAwCmJ5dGVjXzIgLy8gImdvYWwiCmZyYW1lX2RpZyAtMwphcHBfZ2xvYmFsX3B1dApieXRlY18zIC8vICJkZWFkbGluZSIKZnJhbWVfZGlnIC0yCmFwcF9nbG9iYWxfcHV0CmJ5dGVjIDQgLy8gInJlY2VpdmVyIgpmcmFtZV9kaWcgLTEKYXBwX2dsb2JhbF9wdXQKYnl0ZWNfMSAvLyAicGF1c2VkIgppbnRjXzAgLy8gMAphcHBfZ2xvYmFsX3B1dApnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uSUQKZnJhbWVfYnVyeSAwCnJldHN1YgoKLy8gb3B0X2luCm9wdGluXzE6CnByb3RvIDAgMAp0eG4gU2VuZGVyCmJ5dGVjXzAgLy8gImNvbnRyaWJ1dGVkIgppbnRjXzAgLy8gMAphcHBfbG9jYWxfcHV0CnJldHN1YgoKLy8gX25vdF9wYXVzZWQKbm90cGF1c2VkXzI6CnByb3RvIDAgMApieXRlY18xIC8vICJwYXVzZWQiCmFwcF9nbG9iYWxfZ2V0CmludGNfMCAvLyAwCj09CmFzc2VydApyZXRzdWIKCi8vIHBhdXNlCnBhdXNlXzM6CnByb3RvIDAgMAp0eG4gU2VuZGVyCmdsb2JhbCBDcmVhdG9yQWRkcmVzcwo9PQphc3NlcnQKYnl0ZWNfMSAvLyAicGF1c2VkIgppbnRjXzEgLy8gMQphcHBfZ2xvYmFsX3B1dApyZXRzdWIKCi8vIHVucGF1c2UKdW5wYXVzZV80Ogpwcm90byAwIDAKdHhuIFNlbmRlcgpnbG9iYWwgQ3JlYXRvckFkZHJlc3MKPT0KYXNzZXJ0CmJ5dGVjXzEgLy8gInBhdXNlZCIKaW50Y18wIC8vIDAKYXBwX2dsb2JhbF9wdXQKcmV0c3ViCgovLyBjb250cmlidXRlCmNvbnRyaWJ1dGVfNToKcHJvdG8gMSAwCmNhbGxzdWIgbm90cGF1c2VkXzIKZnJhbWVfZGlnIC0xCmd0eG5zIFJlY2VpdmVyCmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCj09CmZyYW1lX2RpZyAtMQpndHhucyBBbW91bnQKaW50Y18wIC8vIDAKPgomJgphc3NlcnQKdHhuIFNlbmRlcgpieXRlY18wIC8vICJjb250cmlidXRlZCIKdHhuIFNlbmRlcgpieXRlY18wIC8vICJjb250cmlidXRlZCIKYXBwX2xvY2FsX2dldApmcmFtZV9kaWcgLTEKZ3R4bnMgQW1vdW50CisKYXBwX2xvY2FsX3B1dApyZXRzdWIKCi8vIHJlbGVhc2UKcmVsZWFzZV82Ogpwcm90byAwIDAKZ2xvYmFsIFJvdW5kCmJ5dGVjXzMgLy8gImRlYWRsaW5lIgphcHBfZ2xvYmFsX2dldAo+PQpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwpiYWxhbmNlCmJ5dGVjXzIgLy8gImdvYWwiCmFwcF9nbG9iYWxfZ2V0Cj49CiYmCmFzc2VydAppdHhuX2JlZ2luCmludGNfMSAvLyBwYXkKaXR4bl9maWVsZCBUeXBlRW51bQpieXRlYyA0IC8vICJyZWNlaXZlciIKYXBwX2dsb2JhbF9nZXQKaXR4bl9maWVsZCBSZWNlaXZlcgpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwpiYWxhbmNlCnB1c2hpbnQgMTAwMDAwMCAvLyAxMDAwMDAwCi0KaXR4bl9maWVsZCBBbW91bnQKaXR4bl9zdWJtaXQKcmV0c3ViCgovLyByZWZ1bmQKcmVmdW5kXzc6CnByb3RvIDAgMApnbG9iYWwgUm91bmQKYnl0ZWNfMyAvLyAiZGVhZGxpbmUiCmFwcF9nbG9iYWxfZ2V0Cj49Cmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCmJhbGFuY2UKYnl0ZWNfMiAvLyAiZ29hbCIKYXBwX2dsb2JhbF9nZXQKPAomJgphc3NlcnQKdHhuIFNlbmRlcgpieXRlY18wIC8vICJjb250cmlidXRlZCIKYXBwX2xvY2FsX2dldApzdG9yZSAwCmxvYWQgMAppbnRjXzAgLy8gMAo+CmFzc2VydAppdHhuX2JlZ2luCmludGNfMSAvLyBwYXkKaXR4bl9maWVsZCBUeXBlRW51bQp0eG4gU2VuZGVyCml0eG5fZmllbGQgUmVjZWl2ZXIKbG9hZCAwCml0eG5fZmllbGQgQW1vdW50Cml0eG5fc3VibWl0CnR4biBTZW5kZXIKYnl0ZWNfMCAvLyAiY29udHJpYnV0ZWQiCmludGNfMCAvLyAwCmFwcF9sb2NhbF9wdXQKcmV0c3ViCgovLyBjcmVhdGVfY2FzdGVyCmNyZWF0ZWNhc3Rlcl84Ogpwcm90byAwIDAKaW50Y18wIC8vIDAKZHVwbiAyCnB1c2hieXRlcyAweCAvLyAiIgp0eG5hIEFwcGxpY2F0aW9uQXJncyAxCmJ0b2kKZnJhbWVfYnVyeSAxCnR4bmEgQXBwbGljYXRpb25BcmdzIDIKYnRvaQpmcmFtZV9idXJ5IDIKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMwpmcmFtZV9idXJ5IDMKZnJhbWVfZGlnIDEKZnJhbWVfZGlnIDIKZnJhbWVfZGlnIDMKY2FsbHN1YiBjcmVhdGVfMApmcmFtZV9idXJ5IDAKcHVzaGJ5dGVzIDB4MTUxZjdjNzUgLy8gMHgxNTFmN2M3NQpmcmFtZV9kaWcgMAppdG9iCmNvbmNhdApsb2cKcmV0c3ViCgovLyBvcHRfaW5fY2FzdGVyCm9wdGluY2FzdGVyXzk6CnByb3RvIDAgMApjYWxsc3ViIG9wdGluXzEKcmV0c3ViCgovLyBwYXVzZV9jYXN0ZXIKcGF1c2VjYXN0ZXJfMTA6CnByb3RvIDAgMApjYWxsc3ViIHBhdXNlXzMKcmV0c3ViCgovLyB1bnBhdXNlX2Nhc3Rlcgp1bnBhdXNlY2FzdGVyXzExOgpwcm90byAwIDAKY2FsbHN1YiB1bnBhdXNlXzQKcmV0c3ViCgovLyBjb250cmlidXRlX2Nhc3Rlcgpjb250cmlidXRlY2FzdGVyXzEyOgpwcm90byAwIDAKaW50Y18wIC8vIDAKdHhuIEdyb3VwSW5kZXgKaW50Y18xIC8vIDEKLQpmcmFtZV9idXJ5IDAKZnJhbWVfZGlnIDAKZ3R4bnMgVHlwZUVudW0KaW50Y18xIC8vIHBheQo9PQphc3NlcnQKZnJhbWVfZGlnIDAKY2FsbHN1YiBjb250cmlidXRlXzUKcmV0c3ViCgovLyByZWxlYXNlX2Nhc3RlcgpyZWxlYXNlY2FzdGVyXzEzOgpwcm90byAwIDAKY2FsbHN1YiByZWxlYXNlXzYKcmV0c3ViCgovLyByZWZ1bmRfY2FzdGVyCnJlZnVuZGNhc3Rlcl8xNDoKcHJvdG8gMCAwCmNhbGxzdWIgcmVmdW5kXzcKcmV0c3Vi", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 1, + "num_uints": 3 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": { + "deadline": { + "type": "uint64", + "key": "deadline", + "descr": "" + }, + "goal": { + "type": "uint64", + "key": "goal", + "descr": "" + }, + "paused": { + "type": "uint64", + "key": "paused", + "descr": "" + }, + "receiver": { + "type": "bytes", + "key": "receiver", + "descr": "" + } + }, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "OGC_Vault", + "methods": [ + { + "name": "create", + "args": [ + { + "type": "uint64", + "name": "goal_amount" + }, + { + "type": "uint64", + "name": "deadline_round" + }, + { + "type": "address", + "name": "receiver_addr" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "opt_in", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "pause", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "unpause", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "contribute", + "args": [ + { + "type": "pay", + "name": "payment" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "release", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "refund", + "args": [], + "returns": { + "type": "void" + } + } + ], + "networks": {} + }, + "bare_call_config": {} +} \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/approval.teal b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/approval.teal new file mode 100644 index 0000000..936c36d --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/approval.teal @@ -0,0 +1,341 @@ +#pragma version 8 +intcblock 0 1 +bytecblock 0x636f6e7472696275746564 0x706175736564 0x676f616c 0x646561646c696e65 0x7265636569766572 +txna ApplicationArgs 0 +pushbytes 0x52735321 // "create(uint64,uint64,address)uint64" +== +bnz main_l14 +txna ApplicationArgs 0 +pushbytes 0x30c6d58a // "opt_in()void" +== +bnz main_l13 +txna ApplicationArgs 0 +pushbytes 0x0178f94b // "pause()void" +== +bnz main_l12 +txna ApplicationArgs 0 +pushbytes 0x1b529de8 // "unpause()void" +== +bnz main_l11 +txna ApplicationArgs 0 +pushbytes 0xfe717b6d // "contribute(pay)void" +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0x076bbd4d // "release()void" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0x23e629f7 // "refund()void" +== +bnz main_l8 +err +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub refundcaster_14 +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub releasecaster_13 +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub contributecaster_12 +intc_1 // 1 +return +main_l11: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub unpausecaster_11 +intc_1 // 1 +return +main_l12: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub pausecaster_10 +intc_1 // 1 +return +main_l13: +txn OnCompletion +intc_1 // OptIn +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub optincaster_9 +intc_1 // 1 +return +main_l14: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +== +&& +assert +callsub createcaster_8 +intc_1 // 1 +return + +// create +create_0: +proto 3 1 +intc_0 // 0 +bytec_2 // "goal" +frame_dig -3 +app_global_put +bytec_3 // "deadline" +frame_dig -2 +app_global_put +bytec 4 // "receiver" +frame_dig -1 +app_global_put +bytec_1 // "paused" +intc_0 // 0 +app_global_put +global CurrentApplicationID +frame_bury 0 +retsub + +// opt_in +optin_1: +proto 0 0 +txn Sender +bytec_0 // "contributed" +intc_0 // 0 +app_local_put +retsub + +// _not_paused +notpaused_2: +proto 0 0 +bytec_1 // "paused" +app_global_get +intc_0 // 0 +== +assert +retsub + +// pause +pause_3: +proto 0 0 +txn Sender +global CreatorAddress +== +assert +bytec_1 // "paused" +intc_1 // 1 +app_global_put +retsub + +// unpause +unpause_4: +proto 0 0 +txn Sender +global CreatorAddress +== +assert +bytec_1 // "paused" +intc_0 // 0 +app_global_put +retsub + +// contribute +contribute_5: +proto 1 0 +callsub notpaused_2 +frame_dig -1 +gtxns Receiver +global CurrentApplicationAddress +== +frame_dig -1 +gtxns Amount +intc_0 // 0 +> +&& +assert +txn Sender +bytec_0 // "contributed" +txn Sender +bytec_0 // "contributed" +app_local_get +frame_dig -1 +gtxns Amount ++ +app_local_put +retsub + +// release +release_6: +proto 0 0 +global Round +bytec_3 // "deadline" +app_global_get +>= +global CurrentApplicationAddress +balance +bytec_2 // "goal" +app_global_get +>= +&& +assert +itxn_begin +intc_1 // pay +itxn_field TypeEnum +bytec 4 // "receiver" +app_global_get +itxn_field Receiver +global CurrentApplicationAddress +balance +pushint 1000000 // 1000000 +- +itxn_field Amount +itxn_submit +retsub + +// refund +refund_7: +proto 0 0 +global Round +bytec_3 // "deadline" +app_global_get +>= +global CurrentApplicationAddress +balance +bytec_2 // "goal" +app_global_get +< +&& +assert +txn Sender +bytec_0 // "contributed" +app_local_get +store 0 +load 0 +intc_0 // 0 +> +assert +itxn_begin +intc_1 // pay +itxn_field TypeEnum +txn Sender +itxn_field Receiver +load 0 +itxn_field Amount +itxn_submit +txn Sender +bytec_0 // "contributed" +intc_0 // 0 +app_local_put +retsub + +// create_caster +createcaster_8: +proto 0 0 +intc_0 // 0 +dupn 2 +pushbytes 0x // "" +txna ApplicationArgs 1 +btoi +frame_bury 1 +txna ApplicationArgs 2 +btoi +frame_bury 2 +txna ApplicationArgs 3 +frame_bury 3 +frame_dig 1 +frame_dig 2 +frame_dig 3 +callsub create_0 +frame_bury 0 +pushbytes 0x151f7c75 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// opt_in_caster +optincaster_9: +proto 0 0 +callsub optin_1 +retsub + +// pause_caster +pausecaster_10: +proto 0 0 +callsub pause_3 +retsub + +// unpause_caster +unpausecaster_11: +proto 0 0 +callsub unpause_4 +retsub + +// contribute_caster +contributecaster_12: +proto 0 0 +intc_0 // 0 +txn GroupIndex +intc_1 // 1 +- +frame_bury 0 +frame_dig 0 +gtxns TypeEnum +intc_1 // pay +== +assert +frame_dig 0 +callsub contribute_5 +retsub + +// release_caster +releasecaster_13: +proto 0 0 +callsub release_6 +retsub + +// refund_caster +refundcaster_14: +proto 0 0 +callsub refund_7 +retsub \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/clear.teal b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/clear.teal new file mode 100644 index 0000000..e741f0e --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/contract.json b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/contract.json new file mode 100644 index 0000000..bd7bd56 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/ogc_vault/contract.json @@ -0,0 +1,73 @@ +{ + "name": "OGC_Vault", + "methods": [ + { + "name": "create", + "args": [ + { + "type": "uint64", + "name": "goal_amount" + }, + { + "type": "uint64", + "name": "deadline_round" + }, + { + "type": "address", + "name": "receiver_addr" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "opt_in", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "pause", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "unpause", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "contribute", + "args": [ + { + "type": "pay", + "name": "payment" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "release", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "refund", + "args": [], + "returns": { + "type": "void" + } + } + ], + "networks": {} +} \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/application.json b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/application.json new file mode 100644 index 0000000..b84dc86 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/application.json @@ -0,0 +1,115 @@ +{ + "hints": { + "create(uint64,uint64,address)uint64": { + "call_config": { + "no_op": "CREATE" + } + }, + "contribute(pay)void": { + "call_config": { + "no_op": "CALL" + } + }, + "release()void": { + "call_config": { + "no_op": "CALL" + } + }, + "get_goal()uint64": { + "read_only": true, + "call_config": { + "no_op": "CALL" + } + }, + "get_total()uint64": { + "read_only": true, + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQpieXRlY2Jsb2NrIDB4NzQ2Zjc0NjE2YyAweDY3NmY2MTZjIDB4MTUxZjdjNzUgMHg2NDY1NjE2NDZjNjk2ZTY1IDB4NzI2NTYzNjU2OTc2NjU3Mgp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDUyNzM1MzIxIC8vICJjcmVhdGUodWludDY0LHVpbnQ2NCxhZGRyZXNzKXVpbnQ2NCIKPT0KYm56IG1haW5fbDEwCnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4ZmU3MTdiNmQgLy8gImNvbnRyaWJ1dGUocGF5KXZvaWQiCj09CmJueiBtYWluX2w5CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4MDc2YmJkNGQgLy8gInJlbGVhc2UoKXZvaWQiCj09CmJueiBtYWluX2w4CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4OTU5OWJiMjUgLy8gImdldF9nb2FsKCl1aW50NjQiCj09CmJueiBtYWluX2w3CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4M2Q1NDYwNjQgLy8gImdldF90b3RhbCgpdWludDY0Igo9PQpibnogbWFpbl9sNgplcnIKbWFpbl9sNjoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKY2FsbHN1YiBnZXR0b3RhbGNhc3Rlcl85CmludGNfMSAvLyAxCnJldHVybgptYWluX2w3Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIGdldGdvYWxjYXN0ZXJfOAppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sODoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKY2FsbHN1YiByZWxlYXNlY2FzdGVyXzcKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDk6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgY29udHJpYnV0ZWNhc3Rlcl82CmludGNfMSAvLyAxCnJldHVybgptYWluX2wxMDoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAo9PQomJgphc3NlcnQKY2FsbHN1YiBjcmVhdGVjYXN0ZXJfNQppbnRjXzEgLy8gMQpyZXR1cm4KCi8vIGNyZWF0ZQpjcmVhdGVfMDoKcHJvdG8gMyAxCmludGNfMCAvLyAwCmJ5dGVjXzEgLy8gImdvYWwiCmZyYW1lX2RpZyAtMwphcHBfZ2xvYmFsX3B1dApieXRlY18zIC8vICJkZWFkbGluZSIKZnJhbWVfZGlnIC0yCmFwcF9nbG9iYWxfcHV0CmJ5dGVjIDQgLy8gInJlY2VpdmVyIgpmcmFtZV9kaWcgLTEKYXBwX2dsb2JhbF9wdXQKYnl0ZWNfMCAvLyAidG90YWwiCmludGNfMCAvLyAwCmFwcF9nbG9iYWxfcHV0Cmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25JRApmcmFtZV9idXJ5IDAKcmV0c3ViCgovLyBjb250cmlidXRlCmNvbnRyaWJ1dGVfMToKcHJvdG8gMSAwCmZyYW1lX2RpZyAtMQpndHhucyBSZWNlaXZlcgpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwo9PQpmcmFtZV9kaWcgLTEKZ3R4bnMgQW1vdW50CmludGNfMCAvLyAwCj4KJiYKYXNzZXJ0CmJ5dGVjXzAgLy8gInRvdGFsIgpieXRlY18wIC8vICJ0b3RhbCIKYXBwX2dsb2JhbF9nZXQKZnJhbWVfZGlnIC0xCmd0eG5zIEFtb3VudAorCmFwcF9nbG9iYWxfcHV0CnJldHN1YgoKLy8gcmVsZWFzZQpyZWxlYXNlXzI6CnByb3RvIDAgMApnbG9iYWwgUm91bmQKYnl0ZWNfMyAvLyAiZGVhZGxpbmUiCmFwcF9nbG9iYWxfZ2V0Cj49Cmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCmJhbGFuY2UKYnl0ZWNfMSAvLyAiZ29hbCIKYXBwX2dsb2JhbF9nZXQKPj0KJiYKYXNzZXJ0Cml0eG5fYmVnaW4KaW50Y18xIC8vIHBheQppdHhuX2ZpZWxkIFR5cGVFbnVtCmJ5dGVjIDQgLy8gInJlY2VpdmVyIgphcHBfZ2xvYmFsX2dldAppdHhuX2ZpZWxkIFJlY2VpdmVyCmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCmJhbGFuY2UKcHVzaGludCAxMDAwMDAwIC8vIDEwMDAwMDAKLQppdHhuX2ZpZWxkIEFtb3VudAppdHhuX3N1Ym1pdApyZXRzdWIKCi8vIGdldF9nb2FsCmdldGdvYWxfMzoKcHJvdG8gMCAxCmludGNfMCAvLyAwCmJ5dGVjXzEgLy8gImdvYWwiCmFwcF9nbG9iYWxfZ2V0CmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIGdldF90b3RhbApnZXR0b3RhbF80Ogpwcm90byAwIDEKaW50Y18wIC8vIDAKYnl0ZWNfMCAvLyAidG90YWwiCmFwcF9nbG9iYWxfZ2V0CmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIGNyZWF0ZV9jYXN0ZXIKY3JlYXRlY2FzdGVyXzU6CnByb3RvIDAgMAppbnRjXzAgLy8gMApkdXBuIDIKcHVzaGJ5dGVzIDB4IC8vICIiCnR4bmEgQXBwbGljYXRpb25BcmdzIDEKYnRvaQpmcmFtZV9idXJ5IDEKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMgpidG9pCmZyYW1lX2J1cnkgMgp0eG5hIEFwcGxpY2F0aW9uQXJncyAzCmZyYW1lX2J1cnkgMwpmcmFtZV9kaWcgMQpmcmFtZV9kaWcgMgpmcmFtZV9kaWcgMwpjYWxsc3ViIGNyZWF0ZV8wCmZyYW1lX2J1cnkgMApieXRlY18yIC8vIDB4MTUxZjdjNzUKZnJhbWVfZGlnIDAKaXRvYgpjb25jYXQKbG9nCnJldHN1YgoKLy8gY29udHJpYnV0ZV9jYXN0ZXIKY29udHJpYnV0ZWNhc3Rlcl82Ogpwcm90byAwIDAKaW50Y18wIC8vIDAKdHhuIEdyb3VwSW5kZXgKaW50Y18xIC8vIDEKLQpmcmFtZV9idXJ5IDAKZnJhbWVfZGlnIDAKZ3R4bnMgVHlwZUVudW0KaW50Y18xIC8vIHBheQo9PQphc3NlcnQKZnJhbWVfZGlnIDAKY2FsbHN1YiBjb250cmlidXRlXzEKcmV0c3ViCgovLyByZWxlYXNlX2Nhc3RlcgpyZWxlYXNlY2FzdGVyXzc6CnByb3RvIDAgMApjYWxsc3ViIHJlbGVhc2VfMgpyZXRzdWIKCi8vIGdldF9nb2FsX2Nhc3RlcgpnZXRnb2FsY2FzdGVyXzg6CnByb3RvIDAgMAppbnRjXzAgLy8gMApjYWxsc3ViIGdldGdvYWxfMwpmcmFtZV9idXJ5IDAKYnl0ZWNfMiAvLyAweDE1MWY3Yzc1CmZyYW1lX2RpZyAwCml0b2IKY29uY2F0CmxvZwpyZXRzdWIKCi8vIGdldF90b3RhbF9jYXN0ZXIKZ2V0dG90YWxjYXN0ZXJfOToKcHJvdG8gMCAwCmludGNfMCAvLyAwCmNhbGxzdWIgZ2V0dG90YWxfNApmcmFtZV9idXJ5IDAKYnl0ZWNfMiAvLyAweDE1MWY3Yzc1CmZyYW1lX2RpZyAwCml0b2IKY29uY2F0CmxvZwpyZXRzdWI=", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 0, + "num_uints": 0 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": {}, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "SimpleVault", + "methods": [ + { + "name": "create", + "args": [ + { + "type": "uint64", + "name": "goal_amount" + }, + { + "type": "uint64", + "name": "deadline_round" + }, + { + "type": "address", + "name": "receiver_addr" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "contribute", + "args": [ + { + "type": "pay", + "name": "payment" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "release", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "get_goal", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "get_total", + "args": [], + "returns": { + "type": "uint64" + } + } + ], + "networks": {} + }, + "bare_call_config": {} +} \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/approval.teal b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/approval.teal new file mode 100644 index 0000000..77eb4b3 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/approval.teal @@ -0,0 +1,247 @@ +#pragma version 8 +intcblock 0 1 +bytecblock 0x746f74616c 0x676f616c 0x151f7c75 0x646561646c696e65 0x7265636569766572 +txna ApplicationArgs 0 +pushbytes 0x52735321 // "create(uint64,uint64,address)uint64" +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0xfe717b6d // "contribute(pay)void" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0x076bbd4d // "release()void" +== +bnz main_l8 +txna ApplicationArgs 0 +pushbytes 0x9599bb25 // "get_goal()uint64" +== +bnz main_l7 +txna ApplicationArgs 0 +pushbytes 0x3d546064 // "get_total()uint64" +== +bnz main_l6 +err +main_l6: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub gettotalcaster_9 +intc_1 // 1 +return +main_l7: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub getgoalcaster_8 +intc_1 // 1 +return +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub releasecaster_7 +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub contributecaster_6 +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +== +&& +assert +callsub createcaster_5 +intc_1 // 1 +return + +// create +create_0: +proto 3 1 +intc_0 // 0 +bytec_1 // "goal" +frame_dig -3 +app_global_put +bytec_3 // "deadline" +frame_dig -2 +app_global_put +bytec 4 // "receiver" +frame_dig -1 +app_global_put +bytec_0 // "total" +intc_0 // 0 +app_global_put +global CurrentApplicationID +frame_bury 0 +retsub + +// contribute +contribute_1: +proto 1 0 +frame_dig -1 +gtxns Receiver +global CurrentApplicationAddress +== +frame_dig -1 +gtxns Amount +intc_0 // 0 +> +&& +assert +bytec_0 // "total" +bytec_0 // "total" +app_global_get +frame_dig -1 +gtxns Amount ++ +app_global_put +retsub + +// release +release_2: +proto 0 0 +global Round +bytec_3 // "deadline" +app_global_get +>= +global CurrentApplicationAddress +balance +bytec_1 // "goal" +app_global_get +>= +&& +assert +itxn_begin +intc_1 // pay +itxn_field TypeEnum +bytec 4 // "receiver" +app_global_get +itxn_field Receiver +global CurrentApplicationAddress +balance +pushint 1000000 // 1000000 +- +itxn_field Amount +itxn_submit +retsub + +// get_goal +getgoal_3: +proto 0 1 +intc_0 // 0 +bytec_1 // "goal" +app_global_get +frame_bury 0 +retsub + +// get_total +gettotal_4: +proto 0 1 +intc_0 // 0 +bytec_0 // "total" +app_global_get +frame_bury 0 +retsub + +// create_caster +createcaster_5: +proto 0 0 +intc_0 // 0 +dupn 2 +pushbytes 0x // "" +txna ApplicationArgs 1 +btoi +frame_bury 1 +txna ApplicationArgs 2 +btoi +frame_bury 2 +txna ApplicationArgs 3 +frame_bury 3 +frame_dig 1 +frame_dig 2 +frame_dig 3 +callsub create_0 +frame_bury 0 +bytec_2 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// contribute_caster +contributecaster_6: +proto 0 0 +intc_0 // 0 +txn GroupIndex +intc_1 // 1 +- +frame_bury 0 +frame_dig 0 +gtxns TypeEnum +intc_1 // pay +== +assert +frame_dig 0 +callsub contribute_1 +retsub + +// release_caster +releasecaster_7: +proto 0 0 +callsub release_2 +retsub + +// get_goal_caster +getgoalcaster_8: +proto 0 0 +intc_0 // 0 +callsub getgoal_3 +frame_bury 0 +bytec_2 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// get_total_caster +gettotalcaster_9: +proto 0 0 +intc_0 // 0 +callsub gettotal_4 +frame_bury 0 +bytec_2 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/clear.teal b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/clear.teal new file mode 100644 index 0000000..e741f0e --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/contract.json b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/contract.json new file mode 100644 index 0000000..206bbef --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/simple_vault/contract.json @@ -0,0 +1,59 @@ +{ + "name": "SimpleVault", + "methods": [ + { + "name": "create", + "args": [ + { + "type": "uint64", + "name": "goal_amount" + }, + { + "type": "uint64", + "name": "deadline_round" + }, + { + "type": "address", + "name": "receiver_addr" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "contribute", + "args": [ + { + "type": "pay", + "name": "payment" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "release", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "get_goal", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "get_total", + "args": [], + "returns": { + "type": "uint64" + } + } + ], + "networks": {} +} \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/application.json b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/application.json new file mode 100644 index 0000000..526e2ca --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/application.json @@ -0,0 +1,136 @@ +{ + "hints": { + "create(uint64,uint64,address)uint64": { + "call_config": { + "no_op": "CREATE" + } + }, + "contribute(pay)void": { + "call_config": { + "no_op": "CALL" + } + }, + "release()void": { + "call_config": { + "no_op": "CALL" + } + }, + "get_goal()uint64": { + "read_only": true, + "call_config": { + "no_op": "CALL" + } + }, + "get_total()uint64": { + "read_only": true, + "call_config": { + "no_op": "CALL" + } + } + }, + "source": { + "approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQpieXRlY2Jsb2NrIDB4NzQ2Zjc0NjE2YyAweDY3NmY2MTZjIDB4MTUxZjdjNzUgMHg2NDY1NjE2NDZjNjk2ZTY1IDB4NzI2NTYzNjU2OTc2NjU3Mgp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDUyNzM1MzIxIC8vICJjcmVhdGUodWludDY0LHVpbnQ2NCxhZGRyZXNzKXVpbnQ2NCIKPT0KYm56IG1haW5fbDEwCnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4ZmU3MTdiNmQgLy8gImNvbnRyaWJ1dGUocGF5KXZvaWQiCj09CmJueiBtYWluX2w5CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4MDc2YmJkNGQgLy8gInJlbGVhc2UoKXZvaWQiCj09CmJueiBtYWluX2w4CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4OTU5OWJiMjUgLy8gImdldF9nb2FsKCl1aW50NjQiCj09CmJueiBtYWluX2w3CnR4bmEgQXBwbGljYXRpb25BcmdzIDAKcHVzaGJ5dGVzIDB4M2Q1NDYwNjQgLy8gImdldF90b3RhbCgpdWludDY0Igo9PQpibnogbWFpbl9sNgplcnIKbWFpbl9sNjoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKY2FsbHN1YiBnZXR0b3RhbGNhc3Rlcl85CmludGNfMSAvLyAxCnJldHVybgptYWluX2w3Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCiE9CiYmCmFzc2VydApjYWxsc3ViIGdldGdvYWxjYXN0ZXJfOAppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sODoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKY2FsbHN1YiByZWxlYXNlY2FzdGVyXzcKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDk6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CmNhbGxzdWIgY29udHJpYnV0ZWNhc3Rlcl82CmludGNfMSAvLyAxCnJldHVybgptYWluX2wxMDoKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAo9PQomJgphc3NlcnQKY2FsbHN1YiBjcmVhdGVjYXN0ZXJfNQppbnRjXzEgLy8gMQpyZXR1cm4KCi8vIGNyZWF0ZQpjcmVhdGVfMDoKcHJvdG8gMyAxCmludGNfMCAvLyAwCmJ5dGVjXzEgLy8gImdvYWwiCmZyYW1lX2RpZyAtMwphcHBfZ2xvYmFsX3B1dApieXRlY18zIC8vICJkZWFkbGluZSIKZnJhbWVfZGlnIC0yCmFwcF9nbG9iYWxfcHV0CmJ5dGVjIDQgLy8gInJlY2VpdmVyIgpmcmFtZV9kaWcgLTEKYXBwX2dsb2JhbF9wdXQKYnl0ZWNfMCAvLyAidG90YWwiCmludGNfMCAvLyAwCmFwcF9nbG9iYWxfcHV0Cmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25JRApmcmFtZV9idXJ5IDAKcmV0c3ViCgovLyBjb250cmlidXRlCmNvbnRyaWJ1dGVfMToKcHJvdG8gMSAwCmZyYW1lX2RpZyAtMQpndHhucyBSZWNlaXZlcgpnbG9iYWwgQ3VycmVudEFwcGxpY2F0aW9uQWRkcmVzcwo9PQpmcmFtZV9kaWcgLTEKZ3R4bnMgQW1vdW50CmludGNfMCAvLyAwCj4KJiYKYXNzZXJ0CmJ5dGVjXzAgLy8gInRvdGFsIgpieXRlY18wIC8vICJ0b3RhbCIKYXBwX2dsb2JhbF9nZXQKZnJhbWVfZGlnIC0xCmd0eG5zIEFtb3VudAorCmFwcF9nbG9iYWxfcHV0CnJldHN1YgoKLy8gcmVsZWFzZQpyZWxlYXNlXzI6CnByb3RvIDAgMApnbG9iYWwgUm91bmQKYnl0ZWNfMyAvLyAiZGVhZGxpbmUiCmFwcF9nbG9iYWxfZ2V0Cj49Cmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCmJhbGFuY2UKYnl0ZWNfMSAvLyAiZ29hbCIKYXBwX2dsb2JhbF9nZXQKPj0KJiYKYXNzZXJ0Cml0eG5fYmVnaW4KaW50Y18xIC8vIHBheQppdHhuX2ZpZWxkIFR5cGVFbnVtCmJ5dGVjIDQgLy8gInJlY2VpdmVyIgphcHBfZ2xvYmFsX2dldAppdHhuX2ZpZWxkIFJlY2VpdmVyCmdsb2JhbCBDdXJyZW50QXBwbGljYXRpb25BZGRyZXNzCmJhbGFuY2UKcHVzaGludCAxMDAwMDAwIC8vIDEwMDAwMDAKLQppdHhuX2ZpZWxkIEFtb3VudAppdHhuX3N1Ym1pdApyZXRzdWIKCi8vIGdldF9nb2FsCmdldGdvYWxfMzoKcHJvdG8gMCAxCmludGNfMCAvLyAwCmJ5dGVjXzEgLy8gImdvYWwiCmFwcF9nbG9iYWxfZ2V0CmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIGdldF90b3RhbApnZXR0b3RhbF80Ogpwcm90byAwIDEKaW50Y18wIC8vIDAKYnl0ZWNfMCAvLyAidG90YWwiCmFwcF9nbG9iYWxfZ2V0CmZyYW1lX2J1cnkgMApyZXRzdWIKCi8vIGNyZWF0ZV9jYXN0ZXIKY3JlYXRlY2FzdGVyXzU6CnByb3RvIDAgMAppbnRjXzAgLy8gMApkdXBuIDIKcHVzaGJ5dGVzIDB4IC8vICIiCnR4bmEgQXBwbGljYXRpb25BcmdzIDEKYnRvaQpmcmFtZV9idXJ5IDEKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMgpidG9pCmZyYW1lX2J1cnkgMgp0eG5hIEFwcGxpY2F0aW9uQXJncyAzCmZyYW1lX2J1cnkgMwpmcmFtZV9kaWcgMQpmcmFtZV9kaWcgMgpmcmFtZV9kaWcgMwpjYWxsc3ViIGNyZWF0ZV8wCmZyYW1lX2J1cnkgMApieXRlY18yIC8vIDB4MTUxZjdjNzUKZnJhbWVfZGlnIDAKaXRvYgpjb25jYXQKbG9nCnJldHN1YgoKLy8gY29udHJpYnV0ZV9jYXN0ZXIKY29udHJpYnV0ZWNhc3Rlcl82Ogpwcm90byAwIDAKaW50Y18wIC8vIDAKdHhuIEdyb3VwSW5kZXgKaW50Y18xIC8vIDEKLQpmcmFtZV9idXJ5IDAKZnJhbWVfZGlnIDAKZ3R4bnMgVHlwZUVudW0KaW50Y18xIC8vIHBheQo9PQphc3NlcnQKZnJhbWVfZGlnIDAKY2FsbHN1YiBjb250cmlidXRlXzEKcmV0c3ViCgovLyByZWxlYXNlX2Nhc3RlcgpyZWxlYXNlY2FzdGVyXzc6CnByb3RvIDAgMApjYWxsc3ViIHJlbGVhc2VfMgpyZXRzdWIKCi8vIGdldF9nb2FsX2Nhc3RlcgpnZXRnb2FsY2FzdGVyXzg6CnByb3RvIDAgMAppbnRjXzAgLy8gMApjYWxsc3ViIGdldGdvYWxfMwpmcmFtZV9idXJ5IDAKYnl0ZWNfMiAvLyAweDE1MWY3Yzc1CmZyYW1lX2RpZyAwCml0b2IKY29uY2F0CmxvZwpyZXRzdWIKCi8vIGdldF90b3RhbF9jYXN0ZXIKZ2V0dG90YWxjYXN0ZXJfOToKcHJvdG8gMCAwCmludGNfMCAvLyAwCmNhbGxzdWIgZ2V0dG90YWxfNApmcmFtZV9idXJ5IDAKYnl0ZWNfMiAvLyAweDE1MWY3Yzc1CmZyYW1lX2RpZyAwCml0b2IKY29uY2F0CmxvZwpyZXRzdWI=", + "clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu" + }, + "state": { + "global": { + "num_byte_slices": 1, + "num_uints": 3 + }, + "local": { + "num_byte_slices": 0, + "num_uints": 0 + } + }, + "schema": { + "global": { + "declared": { + "deadline": { + "type": "uint64", + "key": "deadline", + "descr": "" + }, + "goal": { + "type": "uint64", + "key": "goal", + "descr": "" + }, + "receiver": { + "type": "bytes", + "key": "receiver", + "descr": "" + }, + "total": { + "type": "uint64", + "key": "total", + "descr": "" + } + }, + "reserved": {} + }, + "local": { + "declared": {}, + "reserved": {} + } + }, + "contract": { + "name": "WorkingVault", + "methods": [ + { + "name": "create", + "args": [ + { + "type": "uint64", + "name": "goal_amount" + }, + { + "type": "uint64", + "name": "deadline_round" + }, + { + "type": "address", + "name": "receiver_addr" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "contribute", + "args": [ + { + "type": "pay", + "name": "payment" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "release", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "get_goal", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "get_total", + "args": [], + "returns": { + "type": "uint64" + } + } + ], + "networks": {} + }, + "bare_call_config": {} +} \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/approval.teal b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/approval.teal new file mode 100644 index 0000000..77eb4b3 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/approval.teal @@ -0,0 +1,247 @@ +#pragma version 8 +intcblock 0 1 +bytecblock 0x746f74616c 0x676f616c 0x151f7c75 0x646561646c696e65 0x7265636569766572 +txna ApplicationArgs 0 +pushbytes 0x52735321 // "create(uint64,uint64,address)uint64" +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0xfe717b6d // "contribute(pay)void" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0x076bbd4d // "release()void" +== +bnz main_l8 +txna ApplicationArgs 0 +pushbytes 0x9599bb25 // "get_goal()uint64" +== +bnz main_l7 +txna ApplicationArgs 0 +pushbytes 0x3d546064 // "get_total()uint64" +== +bnz main_l6 +err +main_l6: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub gettotalcaster_9 +intc_1 // 1 +return +main_l7: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub getgoalcaster_8 +intc_1 // 1 +return +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub releasecaster_7 +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +callsub contributecaster_6 +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +== +&& +assert +callsub createcaster_5 +intc_1 // 1 +return + +// create +create_0: +proto 3 1 +intc_0 // 0 +bytec_1 // "goal" +frame_dig -3 +app_global_put +bytec_3 // "deadline" +frame_dig -2 +app_global_put +bytec 4 // "receiver" +frame_dig -1 +app_global_put +bytec_0 // "total" +intc_0 // 0 +app_global_put +global CurrentApplicationID +frame_bury 0 +retsub + +// contribute +contribute_1: +proto 1 0 +frame_dig -1 +gtxns Receiver +global CurrentApplicationAddress +== +frame_dig -1 +gtxns Amount +intc_0 // 0 +> +&& +assert +bytec_0 // "total" +bytec_0 // "total" +app_global_get +frame_dig -1 +gtxns Amount ++ +app_global_put +retsub + +// release +release_2: +proto 0 0 +global Round +bytec_3 // "deadline" +app_global_get +>= +global CurrentApplicationAddress +balance +bytec_1 // "goal" +app_global_get +>= +&& +assert +itxn_begin +intc_1 // pay +itxn_field TypeEnum +bytec 4 // "receiver" +app_global_get +itxn_field Receiver +global CurrentApplicationAddress +balance +pushint 1000000 // 1000000 +- +itxn_field Amount +itxn_submit +retsub + +// get_goal +getgoal_3: +proto 0 1 +intc_0 // 0 +bytec_1 // "goal" +app_global_get +frame_bury 0 +retsub + +// get_total +gettotal_4: +proto 0 1 +intc_0 // 0 +bytec_0 // "total" +app_global_get +frame_bury 0 +retsub + +// create_caster +createcaster_5: +proto 0 0 +intc_0 // 0 +dupn 2 +pushbytes 0x // "" +txna ApplicationArgs 1 +btoi +frame_bury 1 +txna ApplicationArgs 2 +btoi +frame_bury 2 +txna ApplicationArgs 3 +frame_bury 3 +frame_dig 1 +frame_dig 2 +frame_dig 3 +callsub create_0 +frame_bury 0 +bytec_2 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// contribute_caster +contributecaster_6: +proto 0 0 +intc_0 // 0 +txn GroupIndex +intc_1 // 1 +- +frame_bury 0 +frame_dig 0 +gtxns TypeEnum +intc_1 // pay +== +assert +frame_dig 0 +callsub contribute_1 +retsub + +// release_caster +releasecaster_7: +proto 0 0 +callsub release_2 +retsub + +// get_goal_caster +getgoalcaster_8: +proto 0 0 +intc_0 // 0 +callsub getgoal_3 +frame_bury 0 +bytec_2 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub + +// get_total_caster +gettotalcaster_9: +proto 0 0 +intc_0 // 0 +callsub gettotal_4 +frame_bury 0 +bytec_2 // 0x151f7c75 +frame_dig 0 +itob +concat +log +retsub \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/clear.teal b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/clear.teal new file mode 100644 index 0000000..e741f0e --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/contract.json b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/contract.json new file mode 100644 index 0000000..1b9deb9 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/artifacts/working_vault/contract.json @@ -0,0 +1,59 @@ +{ + "name": "WorkingVault", + "methods": [ + { + "name": "create", + "args": [ + { + "type": "uint64", + "name": "goal_amount" + }, + { + "type": "uint64", + "name": "deadline_round" + }, + { + "type": "address", + "name": "receiver_addr" + } + ], + "returns": { + "type": "uint64" + } + }, + { + "name": "contribute", + "args": [ + { + "type": "pay", + "name": "payment" + } + ], + "returns": { + "type": "void" + } + }, + { + "name": "release", + "args": [], + "returns": { + "type": "void" + } + }, + { + "name": "get_goal", + "args": [], + "returns": { + "type": "uint64" + } + }, + { + "name": "get_total", + "args": [], + "returns": { + "type": "uint64" + } + } + ], + "networks": {} +} \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/asa_vault.py b/ogc-contracts/projects/ogc-contracts/asa_vault.py new file mode 100644 index 0000000..9ba08b0 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/asa_vault.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""ASA-compatible vault contract""" + +from pyteal import * +from beaker import * + +class ASAVaultState: + goal = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("goal"), default=Int(0)) + deadline = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("deadline"), default=Int(0)) + receiver = GlobalStateValue(stack_type=TealType.bytes, key=Bytes("receiver"), default=Bytes("")) + asset_id = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("asset_id"), default=Int(0)) + total = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("total"), default=Int(0)) + +app = Application("ASAVault", state=ASAVaultState) + +@app.create +def create(goal_amount: abi.Uint64, deadline_round: abi.Uint64, receiver_addr: abi.Address, asset: abi.Asset, *, output: abi.Uint64): + return Seq( + app.state.goal.set(goal_amount.get()), + app.state.deadline.set(deadline_round.get()), + app.state.receiver.set(receiver_addr.get()), + app.state.asset_id.set(asset.asset_id()), + app.state.total.set(Int(0)), + output.set(Global.current_application_id()), + ) + +@app.external +def contribute_asa(axfer: abi.AssetTransferTransaction): + return Seq( + Assert(And( + axfer.get().xfer_asset() == app.state.asset_id.get(), + axfer.get().asset_receiver() == Global.current_application_address(), + axfer.get().asset_amount() > Int(0), + )), + app.state.total.set(app.state.total.get() + axfer.get().asset_amount()), + ) + +@app.external +def release_asa(): + now = Global.round() + return Seq( + Assert(And(now >= app.state.deadline.get(), app.state.total.get() >= app.state.goal.get())), + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields({ + TxnField.type_enum: TxnType.AssetTransfer, + TxnField.xfer_asset: app.state.asset_id.get(), + TxnField.asset_receiver: app.state.receiver.get(), + TxnField.asset_amount: app.state.total.get(), + }), + InnerTxnBuilder.Submit(), + ) + +@app.external(read_only=True) +def get_goal(*, output: abi.Uint64): + return output.set(app.state.goal.get()) + +@app.external(read_only=True) +def get_total(*, output: abi.Uint64): + return output.set(app.state.total.get()) + +@app.external(read_only=True) +def get_asset_id(*, output: abi.Uint64): + return output.set(app.state.asset_id.get()) + +if __name__ == "__main__": + app.build().export("./artifacts/asa_vault") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/build_vault.py b/ogc-contracts/projects/ogc-contracts/build_vault.py new file mode 100644 index 0000000..443a5ab --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/build_vault.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Build script for OGC Vault contract""" + +import os +from smart_contracts.ogc_vault.contract import app + +def main(): + # Create artifacts directory + artifacts_dir = "artifacts/ogc_vault" + os.makedirs(artifacts_dir, exist_ok=True) + + # Build and export the contract + print("Building OGC Vault contract...") + app.build().export(artifacts_dir) + print(f"Contract artifacts exported to {artifacts_dir}") + + # List generated files + print("\nGenerated files:") + for file in os.listdir(artifacts_dir): + print(f" {file}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/call_release_testnet.py b/ogc-contracts/projects/ogc-contracts/call_release_testnet.py new file mode 100644 index 0000000..675a357 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/call_release_testnet.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +"""Call release function on TestNet""" + +import algosdk +from algosdk.v2client import algod +from algosdk import mnemonic +from algosdk.future import transaction as tx + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +APP_ID = int(input("APP_ID: ")) +mnemo = input("24-word TestNet mnemonic: ").strip() + +try: + sk = mnemonic.to_private_key(mnemo) + addr = algosdk.account.address_from_private_key(sk) + print(f"Calling from: {addr}") + + client = algod.AlgodClient("", ALGOD_URL, "") + sp = client.suggested_params() + + # Call release method + app_args = [b"release"] + txn = tx.ApplicationNoOpTxn(addr, sp, APP_ID, app_args=app_args) + stx = txn.sign(sk) + txid = client.send_transaction(stx) + + print(f"Transaction sent: {txid}") + print("Waiting for confirmation...") + + algosdk.transaction.wait_for_confirmation(client, txid, 4) + print("โœ… Release called successfully!") + print(f"Explorer: https://testnet.algoexplorer.io/tx/{txid}") + +except Exception as e: + print(f"โŒ Error: {e}") + print("Make sure:") + print("- APP_ID is correct") + print("- Mnemonic is valid TestNet account") + print("- Account has some ALGO for fees") + print("- Deadline has passed and goal was met") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/check_any_balance.py b/ogc-contracts/projects/ogc-contracts/check_any_balance.py new file mode 100644 index 0000000..afe15ba --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/check_any_balance.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Check balance of any address or contract""" + +from algosdk.v2client import algod +from algosdk.logic import get_application_address + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def check_any_balance(): + print("๐Ÿ’ฐ Check Balance") + print("1. Check wallet address") + print("2. Check contract (APP_ID)") + + choice = input("Choose (1 or 2): ").strip() + + client = algod.AlgodClient("", ALGOD_URL, "") + + if choice == "1": + # Check wallet address + address = input("Paste wallet address: ").strip() + try: + info = client.account_info(address) + balance = info["amount"] / 1_000_000 + + print(f"\n๐Ÿ’ฐ Wallet Balance") + print(f" Address: {address}") + print(f" Balance: {balance} ALGO") + print(f" Explorer: https://testnet.algoexplorer.io/address/{address}") + + except Exception as e: + print(f"โŒ Error: {e}") + + elif choice == "2": + # Check contract + app_id = int(input("Contract APP_ID: ")) + app_addr = get_application_address(app_id) + + try: + info = client.account_info(app_addr) + balance = info["amount"] / 1_000_000 + + print(f"\n๐Ÿ’ฐ Contract Balance") + print(f" APP_ID: {app_id}") + print(f" Address: {app_addr}") + print(f" Balance: {balance} ALGO") + print(f" Explorer: https://testnet.algoexplorer.io/address/{app_addr}") + + except Exception as e: + print(f"โŒ Error: {e}") + + else: + print("โŒ Invalid choice") + +if __name__ == "__main__": + check_any_balance() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/check_balance_testnet.py b/ogc-contracts/projects/ogc-contracts/check_balance_testnet.py new file mode 100644 index 0000000..26154fc --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/check_balance_testnet.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Check app balance on TestNet""" + +from algosdk.v2client import algod +from algosdk.logic import get_application_address + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +APP_ID = int(input("APP_ID: ")) +app_addr = get_application_address(APP_ID) + +client = algod.AlgodClient("", ALGOD_URL, "") +info = client.account_info(app_addr) + +print(f"APP_ADDRESS: {app_addr}") +print(f"Balance: {info['amount']/1_000_000} ALGO ({info['amount']} microALGO)") +print(f"Explorer: https://testnet.algoexplorer.io/address/{app_addr}") + +# Check if it's an app account +if info.get("apps-total-schema"): + print("โœ… This is an application account") +else: + print("โš ๏ธ This might not be an app account") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/check_contract_balance.py b/ogc-contracts/projects/ogc-contracts/check_contract_balance.py new file mode 100644 index 0000000..46b9d14 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/check_contract_balance.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Check contract balance""" + +from algosdk.v2client import algod +from algosdk.logic import get_application_address + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def check_balance(): + APP_ID = int(input("Contract APP_ID: ")) + app_addr = get_application_address(APP_ID) + + client = algod.AlgodClient("", ALGOD_URL, "") + + try: + info = client.account_info(app_addr) + balance = info["amount"] / 1_000_000 + + print(f"๐Ÿ’ฐ Contract Balance") + print(f" APP_ID: {APP_ID}") + print(f" Address: {app_addr}") + print(f" Balance: {balance} ALGO") + print(f" Explorer: https://testnet.algoexplorer.io/address/{app_addr}") + + except Exception as e: + print(f"โŒ Error: {e}") + +if __name__ == "__main__": + check_balance() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/check_echo_status.py b/ogc-contracts/projects/ogc-contracts/check_echo_status.py new file mode 100644 index 0000000..04b816b --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/check_echo_status.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Check Echo Contract Status""" + +from algosdk.v2client import algod +from algosdk.logic import get_application_address +from beaker import client +from echo_contract import app +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def check_echo_status(): + APP_ID = int(input("Echo Contract APP_ID: ")) + app_addr = get_application_address(APP_ID) + + client_algod = algod.AlgodClient("", ALGOD_URL, "") + + # Check app balance + try: + info = client_algod.account_info(app_addr) + balance = info["amount"] / 1_000_000 + + print(f"๐Ÿ”„ Echo Contract Status") + print(f" APP_ID: {APP_ID}") + print(f" Address: {app_addr}") + print(f" Balance: {balance} ALGO") + print(f" Explorer: https://testnet.algoexplorer.io/address/{app_addr}") + + # Try to read contract state + try: + app_info = client_algod.application_info(APP_ID) + global_state = app_info.get("params", {}).get("global-state", []) + + total_received = 0 + last_sender = "" + + for item in global_state: + key = item["key"] + value = item["value"] + + if key == "dG90YWw=": # "total" in base64 + total_received = value["uint"] / 1_000_000 + elif key == "c2VuZGVy": # "sender" in base64 + last_sender = value.get("bytes", "") + + print(f"\n๐Ÿ“Š Contract Stats:") + print(f" Total Received: {total_received} ALGO") + if last_sender: + print(f" Last Sender: {last_sender}") + + except Exception as e: + print(f" Could not read state: {e}") + + print(f"\n๐Ÿ’ก To Test:") + print(f" Send ALGO to: {app_addr}") + print(f" It will automatically bounce back!") + + except Exception as e: + print(f"โŒ Error: {e}") + +if __name__ == "__main__": + check_echo_status() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/check_receiver.py b/ogc-contracts/projects/ogc-contracts/check_receiver.py new file mode 100644 index 0000000..7aea631 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/check_receiver.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Check Simple Receiver Status""" + +from algosdk.v2client import algod +from algosdk.logic import get_application_address + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def check_receiver(): + APP_ID = int(input("Receiver APP_ID: ")) + app_addr = get_application_address(APP_ID) + + client = algod.AlgodClient("", ALGOD_URL, "") + + try: + # Check app balance + info = client.account_info(app_addr) + balance = info["amount"] / 1_000_000 + + print(f"๐Ÿ“ฅ Simple Receiver Status") + print(f" APP_ID: {APP_ID}") + print(f" Address: {app_addr}") + print(f" Balance: {balance} ALGO") + print(f" Explorer: https://testnet.algoexplorer.io/address/{app_addr}") + + # Check recent transactions + print(f"\n๐Ÿ” Recent Activity:") + print(f" View all transactions: https://testnet.algoexplorer.io/address/{app_addr}") + + if balance > 0: + print(f"โœ… SUCCESS! Contract has received {balance} ALGO") + else: + print(f"โณ No ALGO received yet") + print(f" Send ALGO to: {app_addr}") + + except Exception as e: + print(f"โŒ Error: {e}") + +if __name__ == "__main__": + check_receiver() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/create_ogc_token.py b/ogc-contracts/projects/ogc-contracts/create_ogc_token.py new file mode 100644 index 0000000..e51456f --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/create_ogc_token.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""Create OGC Token (ASA) for demo""" + +from beaker import sandbox +from algosdk import transaction as tx + +def create_ogc_token(): + print("๐Ÿช™ Creating OGC Token...") + + algod = sandbox.get_algod_client() + creator = sandbox.get_accounts().pop() + sp = algod.suggested_params() + + # Create ASA + txn = tx.AssetCreateTxn( + sender=creator.address, + sp=sp, + total=1_000_000_000, # 1 billion tokens + decimals=6, # 6 decimal places + default_frozen=False, + unit_name="OGC", + asset_name="OGC Token", + url="https://ogc.example.com", + manager=creator.address, + reserve=creator.address, + freeze=None, + clawback=None, + ) + + stx = txn.sign(creator.private_key) + txid = algod.send_transaction(stx) + + # Wait for confirmation + import time + time.sleep(2) # Simple wait + confirmed = algod.pending_transaction_info(txid) + asset_id = confirmed["asset-index"] + + print(f"โœ… OGC Token Created!") + print(f" Asset ID: {asset_id}") + print(f" Total Supply: 1,000,000,000 OGC") + print(f" Creator: {creator.address}") + + return asset_id, creator + +if __name__ == "__main__": + asset_id, creator = create_ogc_token() + print(f"\n๐Ÿ’ก Use Asset ID {asset_id} in your demos!") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_echo_testnet.py b/ogc-contracts/projects/ogc-contracts/deploy_echo_testnet.py new file mode 100644 index 0000000..a4f081c --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_echo_testnet.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +"""Deploy Echo Contract to TestNet""" + +from algosdk.v2client import algod +from algosdk import mnemonic +from beaker import client +from echo_contract import app +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def deploy_echo_contract(): + print("๐Ÿ”„ Deploying Echo Contract to TestNet") + print("This contract automatically sends ALGO back when it receives it!") + + # Get deployer account + mnemo = input("Enter your 25-word TestNet mnemonic: ").strip() + try: + sk = mnemonic.to_private_key(mnemo) + addr = algosdk.account.address_from_private_key(sk) + print(f"Deploying from: {addr}") + except Exception as e: + print(f"โŒ Invalid mnemonic: {e}") + return + + # Connect to TestNet + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + # Check balance + try: + info = algod_client.account_info(addr) + balance = info["amount"] / 1_000_000 + print(f"Account balance: {balance} ALGO") + + if balance < 1.0: + print("โŒ Need at least 1 ALGO for deployment and funding") + print("Get TestNet ALGO from: https://testnet.algoexplorer.io/dispenser") + return + except Exception as e: + print(f"โŒ Could not check balance: {e}") + return + + try: + # Create signer + signer = algosdk.atomic_transaction_composer.AccountTransactionSigner(sk) + + # Deploy + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=signer, + ) + + print("Deploying Echo Contract...") + app_id, app_addr, _ = app_client.create() + + print(f"\nโœ… Echo Contract Deployed!") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + print(f" Explorer: https://testnet.algoexplorer.io/application/{app_id}") + + # Fund the app for inner transactions (needs ALGO to send back) + print(f"\nFunding contract with 2 ALGO for operations...") + sp = algod_client.suggested_params() + fund_txn = algosdk.transaction.PaymentTxn(addr, sp, app_addr, 2_000_000) + stx = fund_txn.sign(sk) + txid = algod_client.send_transaction(stx) + algosdk.transaction.wait_for_confirmation(algod_client, txid, 4) + print(f"โœ… Contract funded") + + print(f"\n๐ŸŽฏ YOUR PERMANENT ECHO ADDRESS:") + print(f"๐Ÿ“ {app_addr}") + print(f"\n๐Ÿ“ฑ How to Test:") + print(f"1. Send ANY amount of ALGO to: {app_addr}") + print(f"2. Contract will automatically send it back (minus 0.001 ALGO fee)") + print(f"3. Check transactions on: https://testnet.algoexplorer.io/address/{app_addr}") + + print(f"\n๐Ÿ” Monitor Contract:") + print(f"python check_echo_status.py") + + return app_id, app_addr + + except Exception as e: + print(f"โŒ Deployment failed: {e}") + return None, None + +if __name__ == "__main__": + deploy_echo_contract() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_receiver.py b/ogc-contracts/projects/ogc-contracts/deploy_receiver.py new file mode 100644 index 0000000..6dde2a8 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_receiver.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Deploy Simple Receiver to TestNet""" + +from algosdk.v2client import algod +from algosdk import mnemonic +from beaker import client +from simple_receiver import app +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def deploy_receiver(): + print("๐Ÿ“ฅ Deploying Simple ALGO Receiver to TestNet") + + # Get deployer account + mnemo = input("Enter your mnemonic (24 or 25 words): ").strip() + + # Count words + words = mnemo.split() + print(f"Found {len(words)} words") + + if len(words) == 24: + print("โš ๏ธ You have 24 words, but Algorand needs 25") + print("Check Pera Wallet again - there might be one more word") + return + elif len(words) != 25: + print(f"โŒ Expected 25 words, got {len(words)}") + return + + try: + sk = mnemonic.to_private_key(mnemo) + addr = algosdk.account.address_from_private_key(sk) + print(f"โœ… Valid mnemonic") + print(f"Deploying from: {addr}") + except Exception as e: + print(f"โŒ Invalid mnemonic: {e}") + return + + # Connect to TestNet + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + # Check balance + try: + info = algod_client.account_info(addr) + balance = info["amount"] / 1_000_000 + print(f"Account balance: {balance} ALGO") + + if balance < 0.5: + print("โŒ Need at least 0.5 ALGO for deployment") + print("Get TestNet ALGO from: https://testnet.algoexplorer.io/dispenser") + return + except Exception as e: + print(f"โŒ Could not check balance: {e}") + return + + try: + # Create signer + signer = algosdk.atomic_transaction_composer.AccountTransactionSigner(sk) + + # Deploy + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=signer, + ) + + print("Deploying Simple Receiver...") + app_id, app_addr, _ = app_client.create() + + print(f"\nโœ… Simple Receiver Deployed!") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + print(f" Explorer: https://testnet.algoexplorer.io/application/{app_id}") + + print(f"\n๐ŸŽฏ YOUR PERMANENT RECEIVER ADDRESS:") + print(f"๐Ÿ“ {app_addr}") + + print(f"\n๐Ÿ“ฑ How to Send ALGO:") + print(f"1. Open Pera Wallet โ†’ TestNet") + print(f"2. Send ANY amount to: {app_addr}") + print(f"3. Contract will receive and log it") + print(f"4. Check status: python check_receiver.py") + + return app_id, app_addr + + except Exception as e: + print(f"โŒ Deployment failed: {e}") + return None, None + +if __name__ == "__main__": + deploy_receiver() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_sender.py b/ogc-contracts/projects/ogc-contracts/deploy_sender.py new file mode 100644 index 0000000..f763ae9 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_sender.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +"""Deploy Sender Contract to TestNet""" + +from algosdk.v2client import algod +from algosdk import mnemonic +from beaker import client +from sender_contract import app +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def deploy_sender(): + print("๐Ÿ“ค Deploying ALGO Sender Contract to TestNet") + + # Load from environment + import os + from dotenv import load_dotenv + load_dotenv() + + mnemo = os.getenv('TESTNET_MNEMONIC') + if not mnemo: + print("โŒ TESTNET_MNEMONIC not found in .env file") + return + + try: + sk = mnemonic.to_private_key(mnemo) + addr = algosdk.account.address_from_private_key(sk) + print(f"Deploying from: {addr}") + except Exception as e: + print(f"โŒ Invalid mnemonic: {e}") + return + + # Connect to TestNet + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + # Check balance + try: + info = algod_client.account_info(addr) + balance = info["amount"] / 1_000_000 + print(f"Account balance: {balance} ALGO") + + if balance < 1.0: + print("โŒ Need at least 1 ALGO for deployment and funding") + print("Get TestNet ALGO from: https://testnet.algoexplorer.io/dispenser") + print(f"Your address: {addr}") + return + except Exception as e: + print(f"โŒ Could not check balance: {e}") + return + + try: + # Create signer + signer = algosdk.atomic_transaction_composer.AccountTransactionSigner(sk) + + # Deploy + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=signer, + ) + + print("Deploying Sender Contract...") + app_id, app_addr, _ = app_client.create() + + print(f"\nโœ… Sender Contract Deployed!") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + print(f" Explorer: https://testnet.algoexplorer.io/application/{app_id}") + + # Fund the contract so it can send ALGO + print(f"\nFunding contract with 5 ALGO...") + sp = algod_client.suggested_params() + fund_txn = algosdk.transaction.PaymentTxn(addr, sp, app_addr, 5_000_000) + stx = fund_txn.sign(sk) + txid = algod_client.send_transaction(stx) + algosdk.transaction.wait_for_confirmation(algod_client, txid, 4) + print(f"โœ… Contract funded with 5 ALGO") + + print(f"\n๐ŸŽฏ CONTRACT CAN NOW SEND ALGO!") + print(f" Use: python send_from_contract.py") + print(f" APP_ID: {app_id}") + + return app_id, app_addr + + except Exception as e: + print(f"โŒ Deployment failed: {e}") + return None, None + +if __name__ == "__main__": + deploy_sender() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_simple.py b/ogc-contracts/projects/ogc-contracts/deploy_simple.py new file mode 100644 index 0000000..dab59fa --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_simple.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Deploy simple vault""" + +from beaker import sandbox, client +from simple_vault import app + +def main(): + algod_client = sandbox.get_algod_client() + acct = sandbox.get_accounts().pop() + + goal = 1_000_000 # 1 ALGO + current_round = algod_client.status()["last-round"] + deadline_round = current_round + 1000 + receiver = acct.address + + print(f"Deploying Simple Vault...") + print(f" Goal: {goal} microALGO") + print(f" Deadline: Round {deadline_round}") + print(f" Receiver: {receiver}") + + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=acct.signer, + ) + + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=receiver, + ) + + print(f"\nโœ… Deployed Simple Vault:") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_testnet.py b/ogc-contracts/projects/ogc-contracts/deploy_testnet.py new file mode 100644 index 0000000..02b9bc5 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_testnet.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Deploy OGC Vault to TestNet""" + +from algosdk.v2client import algod +from algosdk import mnemonic +from beaker import client +from working_vault import app +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def deploy_to_testnet(): + print("๐Ÿš€ Deploying OGC Vault to TestNet") + + # Get deployer account + mnemo = input("Enter your 25-word TestNet mnemonic: ").strip() + try: + sk = mnemonic.to_private_key(mnemo) + addr = algosdk.account.address_from_private_key(sk) + print(f"Deploying from: {addr}") + except Exception as e: + print(f"โŒ Invalid mnemonic: {e}") + return + + # Connect to TestNet + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + # Check balance + info = algod_client.account_info(addr) + balance = info["amount"] / 1_000_000 + print(f"Account balance: {balance} ALGO") + + if balance < 0.5: + print("โŒ Need at least 0.5 ALGO for deployment") + print("Get TestNet ALGO from: https://testnet.algoexplorer.io/dispenser") + return + + # Deployment parameters + goal = int(input("Goal amount (ALGO): ")) * 1_000_000 + deadline_rounds = int(input("Deadline (rounds from now): ")) + receiver = input("Receiver address (or press Enter for deployer): ").strip() + + if not receiver: + receiver = addr + + current_round = algod_client.status()["last-round"] + deadline_round = current_round + deadline_rounds + + print(f"\n๐Ÿ“‹ Deployment Config:") + print(f" Goal: {goal/1_000_000} ALGO") + print(f" Deadline: Round {deadline_round} (in {deadline_rounds} rounds)") + print(f" Receiver: {receiver}") + + confirm = input("\nDeploy? (y/N): ").strip().lower() + if confirm != 'y': + print("Cancelled") + return + + try: + # Create signer + signer = algosdk.atomic_transaction_composer.AccountTransactionSigner(sk) + + # Deploy + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=signer, + ) + + print("Deploying...") + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=receiver, + ) + + print(f"\nโœ… Deployed Successfully!") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + print(f" Explorer: https://testnet.algoexplorer.io/application/{app_id}") + + # Fund the app for inner transactions + print(f"\nFunding app with 0.5 ALGO for operations...") + sp = algod_client.suggested_params() + fund_txn = algosdk.transaction.PaymentTxn(addr, sp, app_addr, 500_000) + stx = fund_txn.sign(sk) + txid = algod_client.send_transaction(stx) + algosdk.transaction.wait_for_confirmation(algod_client, txid, 4) + print(f"โœ… App funded") + + print(f"\n๐ŸŽฏ Next Steps:") + print(f"1. Send ALGO to: {app_addr}") + print(f"2. Check balance: python check_balance_testnet.py") + print(f"3. After deadline: python call_release_testnet.py") + + except Exception as e: + print(f"โŒ Deployment failed: {e}") + +if __name__ == "__main__": + deploy_to_testnet() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_vault.py b/ogc-contracts/projects/ogc-contracts/deploy_vault.py new file mode 100644 index 0000000..d2907ba --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_vault.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Deploy script for OGC Vault contract""" + +from beaker import sandbox, client +from smart_contracts.ogc_vault.contract import app + +def main(): + # Get sandbox client and account + algod_client = sandbox.get_algod_client() + acct = sandbox.get_accounts().pop() + + # Deploy parameters + goal = 1_000_000 # 1 ALGO goal + current_round = algod_client.status()["last-round"] + deadline_round = current_round + 1000 # ~1 hour from now + receiver = acct.address # Use deployer as receiver for demo + + print(f"Deploying OGC Vault...") + print(f" Goal: {goal} microALGO ({goal/1_000_000} ALGO)") + print(f" Deadline: Round {deadline_round} (current: {current_round})") + print(f" Receiver: {receiver}") + + # Create the app + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=acct.signer, + ) + + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=receiver, + ) + + print(f"\nโœ… Deployed OGC Vault:") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + print(f"\nNext steps:") + print(f"1. Fund the app: python fund_app.py") + print(f"2. Test contribute: python scripts/contribute.py") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_with_address.py b/ogc-contracts/projects/ogc-contracts/deploy_with_address.py new file mode 100644 index 0000000..c40a94b --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_with_address.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Deploy OGC Vault to TestNet with provided address""" + +from algosdk.v2client import algod +from algosdk import mnemonic +from beaker import client +from working_vault import app +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" +YOUR_ADDRESS = "SXIEIE2D7FOKUNQXUFUZIRYKE75RYD5KBN5BOYZFXLIL7LOTFX4VK3U7CE" + +def deploy_to_testnet(): + print("๐Ÿš€ Deploying OGC Vault to TestNet") + print(f"Using address: {YOUR_ADDRESS}") + + # Get private key + mnemo = input("Enter your 24-word mnemonic for this address: ").strip() + try: + sk = mnemonic.to_private_key(mnemo) + addr = algosdk.account.address_from_private_key(sk) + + if addr != YOUR_ADDRESS: + print(f"โŒ Mnemonic doesn't match address!") + print(f"Expected: {YOUR_ADDRESS}") + print(f"Got: {addr}") + return + + print("โœ… Address verified") + except Exception as e: + print(f"โŒ Invalid mnemonic: {e}") + return + + # Connect to TestNet + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + # Check balance + try: + info = algod_client.account_info(addr) + balance = info["amount"] / 1_000_000 + print(f"Account balance: {balance} ALGO") + + if balance < 0.5: + print("โŒ Need at least 0.5 ALGO for deployment") + print("Get TestNet ALGO from: https://testnet.algoexplorer.io/dispenser") + return + except Exception as e: + print(f"โŒ Could not check balance: {e}") + return + + # Simple deployment with defaults + goal = 2_000_000 # 2 ALGO goal + current_round = algod_client.status()["last-round"] + deadline_round = current_round + 1000 # 1000 rounds (~1 hour) + receiver = addr # You are the receiver + + print(f"\n๐Ÿ“‹ Deployment Config:") + print(f" Goal: {goal/1_000_000} ALGO") + print(f" Deadline: Round {deadline_round} (in 1000 rounds)") + print(f" Receiver: {receiver}") + + try: + # Create signer + signer = algosdk.atomic_transaction_composer.AccountTransactionSigner(sk) + + # Deploy + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=signer, + ) + + print("Deploying...") + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=receiver, + ) + + print(f"\nโœ… Deployed Successfully!") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + print(f" Explorer: https://testnet.algoexplorer.io/application/{app_id}") + + # Fund the app for inner transactions + print(f"\nFunding app with 0.5 ALGO for operations...") + sp = algod_client.suggested_params() + fund_txn = algosdk.transaction.PaymentTxn(addr, sp, app_addr, 500_000) + stx = fund_txn.sign(sk) + txid = algod_client.send_transaction(stx) + algosdk.transaction.wait_for_confirmation(algod_client, txid, 4) + print(f"โœ… App funded") + + print(f"\n๐ŸŽฏ SAVE THESE:") + print(f"APP_ID: {app_id}") + print(f"APP_ADDRESS: {app_addr}") + + print(f"\n๐Ÿ“ฑ Next Steps:") + print(f"1. Open Pera Wallet โ†’ TestNet") + print(f"2. Send 2+ ALGO to: {app_addr}") + print(f"3. Check: python check_balance_testnet.py") + print(f"4. Release: python call_release_testnet.py") + + return app_id, app_addr + + except Exception as e: + print(f"โŒ Deployment failed: {e}") + return None, None + +if __name__ == "__main__": + deploy_to_testnet() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/deploy_working.py b/ogc-contracts/projects/ogc-contracts/deploy_working.py new file mode 100644 index 0000000..f16432b --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/deploy_working.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +"""Deploy working vault""" + +from beaker import sandbox, client +from working_vault import app + +def main(): + algod_client = sandbox.get_algod_client() + acct = sandbox.get_accounts().pop() + + goal = 1_000_000 # 1 ALGO + current_round = algod_client.status()["last-round"] + deadline_round = current_round + 1000 + receiver = acct.address + + print(f"Deploying Working Vault...") + print(f" Goal: {goal} microALGO") + print(f" Deadline: Round {deadline_round}") + print(f" Receiver: {receiver}") + + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=acct.signer, + ) + + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=receiver, + ) + + print(f"\nโœ… Deployed Working Vault:") + print(f" APP_ID: {app_id}") + print(f" APP_ADDRESS: {app_addr}") + + # Fund the app for inner transactions + from algosdk import transaction as tx + sp = algod_client.suggested_params() + fund_txn = tx.PaymentTxn(acct.address, sp, app_addr, 3_000_000) + signed_fund = fund_txn.sign(acct.private_key) + algod_client.send_transaction(signed_fund) + print(f" Funded with 3 ALGO for inner transactions") + + # Test contribute + contribute_amount = 500_000 # 0.5 ALGO + from algosdk.atomic_transaction_composer import TransactionWithSigner + pmt = tx.PaymentTxn(acct.address, sp, app_addr, contribute_amount) + pmt_with_signer = TransactionWithSigner(pmt, acct.signer) + result = app_client.call("contribute", payment=pmt_with_signer) + print(f" Test contribution: {contribute_amount} microALGO - Success!") + + # Check total + total_result = app_client.call("get_total") + print(f" Current total: {total_result.return_value} microALGO") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/echo_contract.py b/ogc-contracts/projects/ogc-contracts/echo_contract.py new file mode 100644 index 0000000..a1f5e04 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/echo_contract.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Echo Contract - Automatically sends ALGO back when received""" + +from pyteal import * +from beaker import * + +class EchoState: + total_received = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("total"), default=Int(0)) + last_sender = GlobalStateValue(stack_type=TealType.bytes, key=Bytes("sender"), default=Bytes("")) + +app = Application("EchoContract", state=EchoState) + +@app.create +def create(*, output: abi.Uint64): + return Seq( + app.state.total_received.set(Int(0)), + app.state.last_sender.set(Bytes("")), + output.set(Global.current_application_id()), + ) + +@app.external +def echo_payment(payment: abi.PaymentTransaction): + """Receives ALGO and immediately sends it back""" + return Seq( + # Verify payment is to this app + Assert(And( + payment.get().receiver() == Global.current_application_address(), + payment.get().amount() > Int(0), + )), + + # Update state + app.state.total_received.set(app.state.total_received.get() + payment.get().amount()), + app.state.last_sender.set(payment.get().sender()), + + # Send ALGO back to sender (minus fees) + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields({ + TxnField.type_enum: TxnType.Payment, + TxnField.receiver: payment.get().sender(), + TxnField.amount: payment.get().amount() - Int(1000), # Subtract 0.001 ALGO fee + }), + InnerTxnBuilder.Submit(), + ) + +@app.external(read_only=True) +def get_total(*, output: abi.Uint64): + return output.set(app.state.total_received.get()) + +@app.external(read_only=True) +def get_last_sender(*, output: abi.String): + return output.set(app.state.last_sender.get()) + +if __name__ == "__main__": + app.build().export("./artifacts/echo_contract") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/flexible_send.py b/ogc-contracts/projects/ogc-contracts/flexible_send.py new file mode 100644 index 0000000..b9e084a --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/flexible_send.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""Send ALGO - supports both wallet formats""" + +from algosdk.v2client import algod +from algosdk import mnemonic, transaction as tx, account +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def flexible_send(): + print("๐Ÿ“ค Flexible ALGO Sender") + print("Works with Universal (24-word) and Legacy (25-word) wallets") + + print("\n1. Use saved Legacy wallet (25-word)") + print("2. Enter your mnemonic (24 or 25 words)") + print("3. Create new Legacy wallet") + + choice = input("Choose (1, 2, or 3): ").strip() + + if choice == "1": + # Use saved Legacy wallet from environment + import os + from dotenv import load_dotenv + load_dotenv() + + mnemo = os.getenv('TESTNET_MNEMONIC') + if not mnemo: + print("โŒ TESTNET_MNEMONIC not found in .env file") + return + sk = mnemonic.to_private_key(mnemo) + sender_addr = algosdk.account.address_from_private_key(sk) + print(f"Using saved wallet: {sender_addr}") + + elif choice == "2": + # Try user's mnemonic + user_mnemonic = input("Paste your mnemonic: ").strip() + words = user_mnemonic.split() + + if len(words) == 24: + print("โŒ Universal (24-word) format detected") + print("Python SDK requires Legacy (25-word) format") + print("Options:") + print("- Use WalletConnect integration") + print("- Create Legacy wallet (option 3)") + print("- Import Legacy wallet to Pera") + return + + elif len(words) == 25: + try: + sk = mnemonic.to_private_key(user_mnemonic) + sender_addr = algosdk.account.address_from_private_key(sk) + print(f"โœ… Legacy wallet detected: {sender_addr}") + except Exception as e: + print(f"โŒ Invalid mnemonic: {e}") + return + else: + print(f"โŒ Invalid: Expected 24 or 25 words, got {len(words)}") + return + + elif choice == "3": + # Create new Legacy wallet + print("Creating new Legacy wallet...") + sk, sender_addr = account.generate_account() + mnemo = mnemonic.from_private_key(sk) + + print(f"โœ… New Legacy Wallet:") + print(f" Address: {sender_addr}") + print(f" Mnemonic: {mnemo}") + print(f" Save this for future use!") + + use_new = input("Use this wallet now? (y/N): ").strip().lower() + if use_new != 'y': + return + else: + print("โŒ Invalid choice") + return + + # Continue with sending + recipient = input("Recipient address: ").strip() + amount_algo = float(input("Amount (ALGO): ")) + amount_micro = int(amount_algo * 1_000_000) + + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + try: + # Check balance + info = algod_client.account_info(sender_addr) + balance = info["amount"] / 1_000_000 + print(f"Balance: {balance} ALGO") + + if balance < amount_algo + 0.001: + print(f"โŒ Insufficient balance") + return + + # Send transaction + sp = algod_client.suggested_params() + txn = tx.PaymentTxn(sender_addr, sp, recipient, amount_micro) + stx = txn.sign(sk) + txid = algod_client.send_transaction(stx) + + print(f"โœ… Sent {amount_algo} ALGO to {recipient}") + print(f"TX: https://testnet.algoexplorer.io/tx/{txid}") + + except Exception as e: + print(f"โŒ Failed: {e}") + +if __name__ == "__main__": + flexible_send() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/full_demo.py b/ogc-contracts/projects/ogc-contracts/full_demo.py new file mode 100644 index 0000000..08baae4 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/full_demo.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Full OGC Demo - ALGO Vault + Token Creation""" + +from beaker import sandbox, client +from working_vault import app +from algosdk import transaction as tx +from algosdk.atomic_transaction_composer import TransactionWithSigner +import time + +def full_ogc_demo(): + print("๐Ÿš€ OGC - Complete Demo") + print("=" * 50) + + algod_client = sandbox.get_algod_client() + organizer = sandbox.get_accounts().pop() + alice = sandbox.get_accounts().pop() + bob = sandbox.get_accounts().pop() + + # Part 1: Create OGC Token + print("\n๐Ÿช™ Part 1: Creating OGC Token") + sp = algod_client.suggested_params() + + token_txn = tx.AssetCreateTxn( + sender=organizer.address, + sp=sp, + total=1_000_000_000, + decimals=6, + default_frozen=False, + unit_name="OGC", + asset_name="OGC Token", + manager=organizer.address, + reserve=organizer.address, + freeze=None, + clawback=None, + ) + + stx = token_txn.sign(organizer.private_key) + txid = algod_client.send_transaction(stx) + time.sleep(2) + confirmed = algod_client.pending_transaction_info(txid) + asset_id = confirmed["asset-index"] + + print(f"โœ… OGC Token Created: Asset ID {asset_id}") + + # Part 2: ALGO Vault Demo + print(f"\n๐Ÿ’ฐ Part 2: ALGO Vault Demo") + goal = 5_000_000 # 5 ALGO + current_round = algod_client.status()["last-round"] + deadline_round = current_round + 100 + + print(f"๐ŸŽฏ Goal: {goal/1_000_000} ALGO") + + # Deploy vault + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=organizer.signer, + ) + + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=organizer.address, + ) + + print(f"โœ… Vault Deployed: APP_ID {app_id}") + + # Fund app for operations + fund_txn = tx.PaymentTxn(organizer.address, sp, app_addr, 3_000_000) + algod_client.send_transaction(fund_txn.sign(organizer.private_key)) + + # Alice contributes 2 ALGO + alice_amount = 2_000_000 + print(f"๐Ÿ‘ค Alice contributes {alice_amount/1_000_000} ALGO") + + pmt_alice = tx.PaymentTxn(alice.address, sp, app_addr, alice_amount) + pmt_alice_signed = TransactionWithSigner(pmt_alice, alice.signer) + + app_client_alice = client.ApplicationClient( + client=algod_client, app=app, app_id=app_id, signer=alice.signer + ) + app_client_alice.call("contribute", payment=pmt_alice_signed) + + total_after_alice = app_client.call("get_total").return_value + print(f"๐Ÿ’ณ Total after Alice: {total_after_alice/1_000_000} ALGO") + + # Bob contributes 3 ALGO + bob_amount = 3_000_000 + print(f"๐Ÿ‘ค Bob contributes {bob_amount/1_000_000} ALGO") + + pmt_bob = tx.PaymentTxn(bob.address, sp, app_addr, bob_amount) + pmt_bob_signed = TransactionWithSigner(pmt_bob, bob.signer) + + app_client_bob = client.ApplicationClient( + client=algod_client, app=app, app_id=app_id, signer=bob.signer + ) + app_client_bob.call("contribute", payment=pmt_bob_signed) + + final_total = app_client.call("get_total").return_value + goal_amount = app_client.call("get_goal").return_value + + print(f"๐Ÿ’ณ Final Total: {final_total/1_000_000} ALGO") + print(f"๐ŸŽฏ Goal: {goal_amount/1_000_000} ALGO") + + if final_total >= goal_amount: + print(f"๐ŸŽ‰ SUCCESS! Goal Reached!") + print(f" Ready to release funds after deadline") + else: + needed = goal_amount - final_total + print(f"โณ Need {needed/1_000_000} more ALGO") + + # Part 3: Token Info + print(f"\n๐Ÿช™ Part 3: Token Summary") + try: + asset_info = algod_client.asset_info(asset_id) + params = asset_info["params"] + print(f"โœ… Token Details:") + print(f" Name: {params['name']}") + print(f" Symbol: {params['unit-name']}") + print(f" Total Supply: {params['total']:,}") + print(f" Decimals: {params['decimals']}") + except Exception as e: + print(f"โŒ Token info error: {e}") + + print(f"\n๐Ÿ† Demo Complete!") + print(f" ALGO Vault: APP_ID {app_id}") + print(f" OGC Token: Asset ID {asset_id}") + print(f" Total Raised: {final_total/1_000_000} ALGO") + print(f" Goal Status: {'โœ… Reached' if final_total >= goal_amount else 'โณ Pending'}") + + return { + "vault_app_id": app_id, + "token_asset_id": asset_id, + "total_raised": final_total, + "goal": goal_amount, + "success": final_total >= goal_amount + } + +if __name__ == "__main__": + result = full_ogc_demo() + print(f"\n๐Ÿ“Š Final Results:") + print(f" Vault: {result['vault_app_id']}") + print(f" Token: {result['token_asset_id']}") + print(f" Success: {'๐ŸŽ‰' if result['success'] else 'โณ'}") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/fund_app.py b/ogc-contracts/projects/ogc-contracts/fund_app.py new file mode 100644 index 0000000..8490730 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/fund_app.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Fund the deployed app for inner transactions""" + +from beaker import sandbox +from algosdk import transaction as tx + +def main(): + algod = sandbox.get_algod_client() + acct = sandbox.get_accounts().pop() + sp = algod.suggested_params() + + app_addr = input("Paste APP_ADDRESS: ").strip() + + # Fund with 3 ALGO for inner transactions + txn = tx.PaymentTxn(acct.address, sp, app_addr, 3_000_000) + signed_txn = txn.sign(acct.private_key) + + txid = algod.send_transaction(signed_txn) + print(f"Funding transaction sent: {txid}") + print("App funded with 3 ALGO for inner transactions") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/get_app_address.py b/ogc-contracts/projects/ogc-contracts/get_app_address.py new file mode 100644 index 0000000..a8158bb --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/get_app_address.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +"""Get app address from APP_ID""" + +from algosdk.logic import get_application_address + +APP_ID = int(input("APP_ID: ")) +app_addr = get_application_address(APP_ID) +print(f"APP_ADDRESS: {app_addr}") +print(f"Send ALGO to this address in Pera Wallet (TestNet)") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/ogc_demo.py b/ogc-contracts/projects/ogc-contracts/ogc_demo.py new file mode 100644 index 0000000..b249ed8 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/ogc_demo.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""OGC Demo for CI""" + +from beaker import sandbox, client +from working_vault import app +from algosdk import transaction as tx +from algosdk.atomic_transaction_composer import TransactionWithSigner + +def ogc_demo(): + print("๐Ÿš€ OGC - Out The Groupchat Demo") + + algod_client = sandbox.get_algod_client() + organizer = sandbox.get_accounts().pop() + alice = sandbox.get_accounts().pop() + + goal = 2_000_000 # 2 ALGO + current_round = algod_client.status()["last-round"] + deadline_round = current_round + 50 + + print(f"๐ŸŽฏ Goal: {goal/1_000_000} ALGO") + + # Deploy + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=organizer.signer, + ) + + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=organizer.address, + ) + + print(f"โœ… Fund Created: APP_ID {app_id}") + + # Fund app + sp = algod_client.suggested_params() + fund_txn = tx.PaymentTxn(organizer.address, sp, app_addr, 3_000_000) + algod_client.send_transaction(fund_txn.sign(organizer.private_key)) + + # Alice contributes + alice_amount = 1_500_000 + pmt = tx.PaymentTxn(alice.address, sp, app_addr, alice_amount) + pmt_signed = TransactionWithSigner(pmt, alice.signer) + + app_client_alice = client.ApplicationClient( + client=algod_client, app=app, app_id=app_id, signer=alice.signer + ) + app_client_alice.call("contribute", payment=pmt_signed) + + total = app_client.call("get_total").return_value + print(f"๐Ÿ’ฐ Total: {total/1_000_000} ALGO") + + if total >= goal: + print("๐ŸŽ‰ Goal reached!") + else: + print(f"โณ Need {(goal-total)/1_000_000} more ALGO") + + return {"app_id": app_id, "total": total, "goal": goal} + +if __name__ == "__main__": + result = ogc_demo() + print(f"๐Ÿ Demo complete: {result['total']/1_000_000} ALGO raised") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/poetry.lock b/ogc-contracts/projects/ogc-contracts/poetry.lock new file mode 100644 index 0000000..458c6e5 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/poetry.lock @@ -0,0 +1,856 @@ +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. + +[[package]] +name = "algokit-utils" +version = "2.4.0" +description = "Utilities for Algorand development for use by AlgoKit" +optional = false +python-versions = "<4.0,>=3.10" +groups = ["main"] +files = [ + {file = "algokit_utils-2.4.0-py3-none-any.whl", hash = "sha256:acf1f6ea7be59b3bfcc425d54f416d07270ddbd79452c1111f427f43f78fa7dd"}, +] + +[package.dependencies] +deprecated = ">=1.2.14,<2.0.0" +httpx = ">=0.23.1,<0.24.0" +py-algorand-sdk = ">=2.4.0,<3.0.0" + +[[package]] +name = "anyio" +version = "4.10.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "beaker-pyteal" +version = "1.1.1" +description = "A Framework for building PyTeal Applications" +optional = false +python-versions = ">=3.10,<4.0" +groups = ["main"] +files = [ + {file = "beaker_pyteal-1.1.1-py3-none-any.whl", hash = "sha256:a85a4568213acbd097cd70d8acd71d0e7187b71a4dc5c1fd9760ae8c1433571e"}, +] + +[package.dependencies] +algokit-utils = ">=2.0.0,<3.0.0" +py-algorand-sdk = ">=2.0.0" +pyteal = ">=0.24,<0.25" + +[[package]] +name = "black" +version = "24.10.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "click" +version = "8.2.1" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "deprecated" +version = "1.2.18" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] +files = [ + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] + +[[package]] +name = "docstring-parser" +version = "0.14.1" +description = "Parse Python docstrings in reST, Google and Numpydoc format" +optional = false +python-versions = ">=3.6,<4.0" +groups = ["main"] +files = [ + {file = "docstring_parser-0.14.1-py3-none-any.whl", hash = "sha256:14ac6ec1f1ba6905c4d8cb90fd0bc55394f5678183752c90e44812bf28d7a515"}, + {file = "docstring_parser-0.14.1.tar.gz", hash = "sha256:2c77522e31b7c88b1ab457a1f3c9ae38947ad719732260ba77ee8a3deb58622a"}, +] + +[[package]] +name = "executing" +version = "1.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"}, + {file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"}, +] + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] + +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + +[[package]] +name = "httpcore" +version = "0.16.3" +description = "A minimal low-level HTTP client." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, + {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, +] + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = "==1.*" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "httpx" +version = "0.23.3" +description = "The next generation HTTP client." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, + {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, +] + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (==1.*)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +groups = ["dev"] +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "msgpack" +version = "1.1.1" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, + {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, + {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2"}, + {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4"}, + {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0"}, + {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26"}, + {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75"}, + {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338"}, + {file = "msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd"}, + {file = "msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8"}, + {file = "msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558"}, + {file = "msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d"}, + {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0"}, + {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f"}, + {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704"}, + {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2"}, + {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2"}, + {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752"}, + {file = "msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295"}, + {file = "msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458"}, + {file = "msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238"}, + {file = "msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157"}, + {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce"}, + {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a"}, + {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c"}, + {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b"}, + {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef"}, + {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a"}, + {file = "msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c"}, + {file = "msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4"}, + {file = "msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0"}, + {file = "msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9"}, + {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8"}, + {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a"}, + {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac"}, + {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b"}, + {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7"}, + {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5"}, + {file = "msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323"}, + {file = "msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69"}, + {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285"}, + {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600"}, + {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9"}, + {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78"}, + {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a"}, + {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6"}, + {file = "msgpack-1.1.1-cp38-cp38-win32.whl", hash = "sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142"}, + {file = "msgpack-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad"}, + {file = "msgpack-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b"}, + {file = "msgpack-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232"}, + {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf"}, + {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf"}, + {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90"}, + {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1"}, + {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88"}, + {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478"}, + {file = "msgpack-1.1.1-cp39-cp39-win32.whl", hash = "sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57"}, + {file = "msgpack-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084"}, + {file = "msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "py-algorand-sdk" +version = "2.10.0" +description = "Algorand SDK in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "py_algorand_sdk-2.10.0-py3-none-any.whl", hash = "sha256:511a8f172977b4d6940dceb036abb040d70f2ed2c7b090251818f0ba6f1b3af8"}, + {file = "py_algorand_sdk-2.10.0.tar.gz", hash = "sha256:521f997a53219210feac519e86d0fb36d18be7e36d528162cf328b4b058e423f"}, +] + +[package.dependencies] +msgpack = ">=1.0.0,<2" +pycryptodomex = ">=3.6.0,<4" +pynacl = ">=1.4.0,<2" + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pycryptodomex" +version = "3.23.0" +description = "Cryptographic library for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +files = [ + {file = "pycryptodomex-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:add243d204e125f189819db65eed55e6b4713f70a7e9576c043178656529cec7"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1c6d919fc8429e5cb228ba8c0d4d03d202a560b421c14867a65f6042990adc8e"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1c3a65ad441746b250d781910d26b7ed0a396733c6f2dbc3327bd7051ec8a541"}, + {file = "pycryptodomex-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:47f6d318fe864d02d5e59a20a18834819596c4ed1d3c917801b22b92b3ffa648"}, + {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:d9825410197a97685d6a1fa2a86196430b01877d64458a20e95d4fd00d739a08"}, + {file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:267a3038f87a8565bd834317dbf053a02055915acf353bf42ededb9edaf72010"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708"}, + {file = "pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9"}, + {file = "pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51"}, + {file = "pycryptodomex-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:febec69c0291efd056c65691b6d9a339f8b4bc43c6635b8699471248fe897fea"}, + {file = "pycryptodomex-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:c84b239a1f4ec62e9c789aafe0543f0594f0acd90c8d9e15bcece3efe55eca66"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea"}, + {file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff"}, + {file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245"}, + {file = "pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da"}, +] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]] +name = "pyteal" +version = "0.24.1" +description = "Algorand Smart Contracts in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "pyteal-0.24.1-py3-none-any.whl", hash = "sha256:19c601f0ea4d1a0be41a3fe48cd3807558a0e907cd47d0dca5df60977d78f2c4"}, + {file = "pyteal-0.24.1.tar.gz", hash = "sha256:172d796981f8f9d3a9a8fbe71a71a49cf185509780f46d82e29aaa692386d1fa"}, +] + +[package.dependencies] +docstring-parser = "0.14.1" +executing = "1.2.0" +py-algorand-sdk = ">=2.0.0,<3.0.0" +semantic-version = ">=2.9.0,<3.0.0" +tabulate = ">=0.9.0,<0.10.0" + +[[package]] +name = "pytest" +version = "8.4.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] + +[package.dependencies] +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} + +[package.extras] +idna2008 = ["idna"] + +[[package]] +name = "semantic-version" +version = "2.10.0" +description = "A library implementing the 'SemVer' scheme." +optional = false +python-versions = ">=2.7" +groups = ["main"] +files = [ + {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"}, + {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"}, +] + +[package.extras] +dev = ["Django (>=1.11)", "check-manifest", "colorama (<=0.4.1) ; python_version == \"3.4\"", "coverage", "flake8", "nose2", "readme-renderer (<25.0) ; python_version == \"3.4\"", "tox", "wheel", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "wrapt" +version = "1.17.3" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2"}, + {file = "wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775"}, + {file = "wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05"}, + {file = "wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418"}, + {file = "wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390"}, + {file = "wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6"}, + {file = "wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85"}, + {file = "wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311"}, + {file = "wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5"}, + {file = "wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2"}, + {file = "wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89"}, + {file = "wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77"}, + {file = "wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba"}, + {file = "wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828"}, + {file = "wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396"}, + {file = "wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc"}, + {file = "wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe"}, + {file = "wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c"}, + {file = "wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77"}, + {file = "wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277"}, + {file = "wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa"}, + {file = "wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050"}, + {file = "wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8"}, + {file = "wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb"}, + {file = "wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235"}, + {file = "wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b"}, + {file = "wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7"}, + {file = "wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4"}, + {file = "wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10"}, + {file = "wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6"}, + {file = "wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067"}, + {file = "wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e"}, + {file = "wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056"}, + {file = "wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804"}, + {file = "wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116"}, + {file = "wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:70d86fa5197b8947a2fa70260b48e400bf2ccacdcab97bb7de47e3d1e6312225"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df7d30371a2accfe4013e90445f6388c570f103d61019b6b7c57e0265250072a"}, + {file = "wrapt-1.17.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:caea3e9c79d5f0d2c6d9ab96111601797ea5da8e6d0723f77eabb0d4068d2b2f"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:758895b01d546812d1f42204bd443b8c433c44d090248bf22689df673ccafe00"}, + {file = "wrapt-1.17.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02b551d101f31694fc785e58e0720ef7d9a10c4e62c1c9358ce6f63f23e30a56"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:656873859b3b50eeebe6db8b1455e99d90c26ab058db8e427046dbc35c3140a5"}, + {file = "wrapt-1.17.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a9a2203361a6e6404f80b99234fe7fb37d1fc73487b5a78dc1aa5b97201e0f22"}, + {file = "wrapt-1.17.3-cp38-cp38-win32.whl", hash = "sha256:55cbbc356c2842f39bcc553cf695932e8b30e30e797f961860afb308e6b1bb7c"}, + {file = "wrapt-1.17.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad85e269fe54d506b240d2d7b9f5f2057c2aa9a2ea5b32c66f8902f768117ed2"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ce38e66630599e1193798285706903110d4f057aab3168a34b7fdc85569afc"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:65d1d00fbfb3ea5f20add88bbc0f815150dbbde3b026e6c24759466c8b5a9ef9"}, + {file = "wrapt-1.17.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a7c06742645f914f26c7f1fa47b8bc4c91d222f76ee20116c43d5ef0912bba2d"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e18f01b0c3e4a07fe6dfdb00e29049ba17eadbc5e7609a2a3a4af83ab7d710a"}, + {file = "wrapt-1.17.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f5f51a6466667a5a356e6381d362d259125b57f059103dd9fdc8c0cf1d14139"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:59923aa12d0157f6b82d686c3fd8e1166fa8cdfb3e17b42ce3b6147ff81528df"}, + {file = "wrapt-1.17.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:46acc57b331e0b3bcb3e1ca3b421d65637915cfcd65eb783cb2f78a511193f9b"}, + {file = "wrapt-1.17.3-cp39-cp39-win32.whl", hash = "sha256:3e62d15d3cfa26e3d0788094de7b64efa75f3a53875cdbccdf78547aed547a81"}, + {file = "wrapt-1.17.3-cp39-cp39-win_amd64.whl", hash = "sha256:1f23fa283f51c890eda8e34e4937079114c74b4c81d2b2f1f1d94948f5cc3d7f"}, + {file = "wrapt-1.17.3-cp39-cp39-win_arm64.whl", hash = "sha256:24c2ed34dc222ed754247a2702b1e1e89fdbaa4016f324b4b8f1a802d4ffe87f"}, + {file = "wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22"}, + {file = "wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "^3.13" +content-hash = "182f59be05605d03705a5d4debb2e3bfe4e5a3094f937ba98e7e9a4955b398be" diff --git a/ogc-contracts/projects/ogc-contracts/poetry.toml b/ogc-contracts/projects/ogc-contracts/poetry.toml new file mode 100644 index 0000000..5fcef8c --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/poetry.toml @@ -0,0 +1,3 @@ +[virtualenvs] +in-project = true +prefer-active-python = true diff --git a/ogc-contracts/projects/ogc-contracts/pyproject.toml b/ogc-contracts/projects/ogc-contracts/pyproject.toml new file mode 100644 index 0000000..1d46d84 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/pyproject.toml @@ -0,0 +1,32 @@ +[tool.poetry] +name = "ogc-contracts" +version = "0.1.0" +description = "OGC - Out The Groupchat: Decentralized group funding on Algorand" +authors = ["OGC Team"] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.13" +beaker-pyteal = "1.1.1" +pyteal = "0.24.1" +py-algorand-sdk = "2.10.0" +algokit-utils = "2.4.0" +python-dotenv = "1.1.1" +setuptools = "80.9.0" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.0.0" +black = "^24.0.0" +isort = "^5.13.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 88 +target-version = ['py313'] + +[tool.isort] +profile = "black" +line_length = 88 \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/scripts/contribute.py b/ogc-contracts/projects/ogc-contracts/scripts/contribute.py new file mode 100644 index 0000000..99b4440 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/scripts/contribute.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Test contribution to the vault""" + +from beaker import sandbox, client +import sys +sys.path.append('..') +from smart_contracts.ogc_vault.contract import app +from algosdk import transaction as tx + +def main(): + APP_ID = int(input("APP_ID: ").strip()) + AMOUNT = 200_000 # 0.2 ALGO + + algod = sandbox.get_algod_client() + acct = sandbox.get_accounts().pop() + sp = algod.suggested_params() + + # Create app client + app_client = client.ApplicationClient( + client=algod, + app=app, + app_id=APP_ID, + signer=acct.signer, + ) + + # First opt-in to the app + try: + app_client.opt_in() + print("Opted into the app") + except Exception as e: + print(f"Opt-in failed (might already be opted in): {e}") + + # Create payment transaction + pmt = tx.PaymentTxn(acct.address, sp, app_client.app_addr, AMOUNT) + + # Call contribute method with payment + result = app_client.call( + "contribute", + payment=pmt, + ) + + print(f"โœ… Contributed {AMOUNT} microALGO ({AMOUNT/1_000_000} ALGO)") + print(f"Transaction ID: {result.tx_id}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/send_from_contract.py b/ogc-contracts/projects/ogc-contracts/send_from_contract.py new file mode 100644 index 0000000..e12902d --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/send_from_contract.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Send ALGO from contract to any wallet""" + +from algosdk.v2client import algod +from algosdk import mnemonic +from beaker import client +from sender_contract import app +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def send_from_contract(): + print("๐Ÿ“ค Send ALGO from Contract to Wallet") + + # Load contract owner mnemonic from environment + import os + from dotenv import load_dotenv + load_dotenv() + + mnemo = os.getenv('TESTNET_MNEMONIC') + if not mnemo: + print("โŒ TESTNET_MNEMONIC not found in .env file") + return + + APP_ID = int(input("Sender Contract APP_ID: ")) + recipient = input("Recipient wallet address: ").strip() + amount_algo = float(input("Amount to send (ALGO): ")) + amount_micro = int(amount_algo * 1_000_000) + + try: + sk = mnemonic.to_private_key(mnemo) + addr = algosdk.account.address_from_private_key(sk) + + # Connect to TestNet + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + # Create app client + signer = algosdk.atomic_transaction_composer.AccountTransactionSigner(sk) + app_client = client.ApplicationClient( + client=algod_client, + app=app, + app_id=APP_ID, + signer=signer, + ) + + print(f"\n๐Ÿ“ค Sending {amount_algo} ALGO to {recipient}...") + + # Call send_algo method + app_client.call("send_algo", recipient=recipient, amount=amount_micro) + + print(f"โœ… SUCCESS! Sent {amount_algo} ALGO from contract!") + print(f" To: {recipient}") + print(f" Check: https://testnet.algoexplorer.io/address/{recipient}") + + # Check contract balance + balance = app_client.call("get_balance").return_value + total_sent = app_client.call("get_total_sent").return_value + + print(f"\n๐Ÿ“Š Contract Status:") + print(f" Remaining balance: {balance/1_000_000} ALGO") + print(f" Total sent: {total_sent/1_000_000} ALGO") + + except Exception as e: + print(f"โŒ Send failed: {e}") + +if __name__ == "__main__": + send_from_contract() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/send_to_address.py b/ogc-contracts/projects/ogc-contracts/send_to_address.py new file mode 100644 index 0000000..ae32ed2 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/send_to_address.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Send ALGO from your wallet to any address""" + +from algosdk.v2client import algod +from algosdk import mnemonic, transaction as tx +import algosdk + +ALGOD_URL = "https://testnet-api.algonode.cloud" + +def send_to_address(): + print("๐Ÿ“ค Send ALGO to Any Address") + + # Option to use saved wallet or enter mnemonic + print("1. Use saved wallet") + print("2. Enter your own mnemonic") + print("3. View saved wallet details") + + choice = input("Choose (1, 2, or 3): ").strip() + + if choice == "1": + # Use the generated wallet + mnemo = "swear suffer shrimp clinic cause differ nice space update mansion cradle brisk unknown lecture host clarify again faint divide decrease renew choice still abstract tomorrow" + print("Using saved wallet: ZHMX3URT56ZLWQY3Y74CRXVOAVEOELOAVMXJZDT76IFORLPWMWMPJVAEN4") + elif choice == "2": + mnemo = input("Enter your 25-word mnemonic: ").strip() + elif choice == "3": + # View wallet details + address = "ZHMX3URT56ZLWQY3Y74CRXVOAVEOELOAVMXJZDT76IFORLPWMWMPJVAEN4" + mnemonic_saved = "swear suffer shrimp clinic cause differ nice space update mansion cradle brisk unknown lecture host clarify again faint divide decrease renew choice still abstract tomorrow" + print(f"\n๐Ÿ” Saved Wallet Details:") + print(f"Address: {address}") + print(f"Mnemonic: {mnemonic_saved}") + print(f"Explorer: https://testnet.algoexplorer.io/address/{address}") + return + else: + print("โŒ Invalid choice") + return + + try: + sk = mnemonic.to_private_key(mnemo) + sender_addr = algosdk.account.address_from_private_key(sk) + print(f"Sending from: {sender_addr}") + except Exception as e: + print(f"โŒ Invalid mnemonic: {e}") + return + + # Get recipient and amount + recipient = input("Paste recipient address: ").strip() + amount_algo = float(input("Amount to send (ALGO): ")) + amount_micro = int(amount_algo * 1_000_000) + + # Connect to TestNet + algod_client = algod.AlgodClient("", ALGOD_URL, "") + + try: + # Check sender balance + info = algod_client.account_info(sender_addr) + balance = info["amount"] / 1_000_000 + print(f"Your balance: {balance} ALGO") + + if balance < amount_algo + 0.001: # Need extra for fees + print(f"โŒ Insufficient balance. Need {amount_algo + 0.001} ALGO") + return + + # Create and send transaction + sp = algod_client.suggested_params() + txn = tx.PaymentTxn(sender_addr, sp, recipient, amount_micro) + stx = txn.sign(sk) + txid = algod_client.send_transaction(stx) + + print(f"\n๐Ÿ“ค Transaction sent!") + print(f" Amount: {amount_algo} ALGO") + print(f" To: {recipient}") + print(f" TX ID: {txid}") + + # Wait for confirmation + print("Waiting for confirmation...") + algosdk.transaction.wait_for_confirmation(algod_client, txid, 4) + print(f"โœ… SUCCESS! Transaction confirmed!") + print(f" Explorer: https://testnet.algoexplorer.io/tx/{txid}") + + except Exception as e: + print(f"โŒ Send failed: {e}") + +if __name__ == "__main__": + send_to_address() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/sender_contract.py b/ogc-contracts/projects/ogc-contracts/sender_contract.py new file mode 100644 index 0000000..004a831 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/sender_contract.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Contract that can send ALGO to any wallet""" + +from pyteal import * +from beaker import * + +class SenderState: + owner = GlobalStateValue(stack_type=TealType.bytes, key=Bytes("owner"), default=Bytes("")) + total_sent = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("sent"), default=Int(0)) + +app = Application("SenderContract", state=SenderState) + +@app.create +def create(*, output: abi.Uint64): + return Seq( + app.state.owner.set(Txn.sender()), + app.state.total_sent.set(Int(0)), + output.set(Global.current_application_id()), + ) + +@app.external +def send_algo(recipient: abi.Address, amount: abi.Uint64): + """Send ALGO to any address (only owner can call)""" + return Seq( + # Only owner can send + Assert(Txn.sender() == app.state.owner.get()), + + # Send ALGO to recipient + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields({ + TxnField.type_enum: TxnType.Payment, + TxnField.receiver: recipient.get(), + TxnField.amount: amount.get(), + }), + InnerTxnBuilder.Submit(), + + # Update total sent + app.state.total_sent.set(app.state.total_sent.get() + amount.get()), + ) + +@app.external(read_only=True) +def get_balance(*, output: abi.Uint64): + return output.set(Balance(Global.current_application_address())) + +@app.external(read_only=True) +def get_total_sent(*, output: abi.Uint64): + return output.set(app.state.total_sent.get()) + +if __name__ == "__main__": + app.build().export("./artifacts/sender_contract") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/simple_receiver.py b/ogc-contracts/projects/ogc-contracts/simple_receiver.py new file mode 100644 index 0000000..120606f --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/simple_receiver.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Simple ALGO Receiver Contract""" + +from pyteal import * +from beaker import * + +class ReceiverState: + total_received = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("total"), default=Int(0)) + transaction_count = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("count"), default=Int(0)) + +app = Application("SimpleReceiver", state=ReceiverState) + +@app.create +def create(*, output: abi.Uint64): + return Seq( + app.state.total_received.set(Int(0)), + app.state.transaction_count.set(Int(0)), + output.set(Global.current_application_id()), + ) + +@app.external +def receive_payment(payment: abi.PaymentTransaction): + """Receives ALGO and logs it""" + return Seq( + # Verify payment is to this app + Assert(And( + payment.get().receiver() == Global.current_application_address(), + payment.get().amount() > Int(0), + )), + + # Update counters + app.state.total_received.set(app.state.total_received.get() + payment.get().amount()), + app.state.transaction_count.set(app.state.transaction_count.get() + Int(1)), + ) + +@app.external(read_only=True) +def get_total(*, output: abi.Uint64): + return output.set(app.state.total_received.get()) + +@app.external(read_only=True) +def get_count(*, output: abi.Uint64): + return output.set(app.state.transaction_count.get()) + +if __name__ == "__main__": + app.build().export("./artifacts/simple_receiver") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/simple_vault.py b/ogc-contracts/projects/ogc-contracts/simple_vault.py new file mode 100644 index 0000000..44607ae --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/simple_vault.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Simple working vault contract""" + +from pyteal import * +from beaker import * + +# Simple vault without local state for now +app = Application("SimpleVault") + +# Global state +goal = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("goal"), default=Int(0)) +deadline = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("deadline"), default=Int(0)) +receiver = GlobalStateValue(stack_type=TealType.bytes, key=Bytes("receiver"), default=Bytes("")) +total_contributed = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("total"), default=Int(0)) + +@app.create +def create(goal_amount: abi.Uint64, deadline_round: abi.Uint64, receiver_addr: abi.Address, *, output: abi.Uint64): + return Seq( + goal.set(goal_amount.get()), + deadline.set(deadline_round.get()), + receiver.set(receiver_addr.get()), + total_contributed.set(Int(0)), + output.set(Global.current_application_id()), + ) + +@app.external +def contribute(payment: abi.PaymentTransaction): + return Seq( + Assert(And( + payment.get().receiver() == Global.current_application_address(), + payment.get().amount() > Int(0), + )), + total_contributed.set(total_contributed.get() + payment.get().amount()), + ) + +@app.external +def release(): + now = Global.round() + bal = Balance(Global.current_application_address()) + return Seq( + Assert(And(now >= deadline.get(), bal >= goal.get())), + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields({ + TxnField.type_enum: TxnType.Payment, + TxnField.receiver: receiver.get(), + TxnField.amount: bal - Int(1_000_000), + }), + InnerTxnBuilder.Submit(), + ) + +@app.external(read_only=True) +def get_goal(*, output: abi.Uint64): + return output.set(goal.get()) + +@app.external(read_only=True) +def get_total(*, output: abi.Uint64): + return output.set(total_contributed.get()) + +if __name__ == "__main__": + app.build().export("./artifacts/simple_vault") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/__init__.py b/ogc-contracts/projects/ogc-contracts/smart_contracts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/__main__.py b/ogc-contracts/projects/ogc-contracts/smart_contracts/__main__.py new file mode 100644 index 0000000..6bb9664 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/smart_contracts/__main__.py @@ -0,0 +1,211 @@ +import dataclasses +import importlib +import logging +import subprocess +import sys +from collections.abc import Callable +from pathlib import Path +from shutil import rmtree + +from algokit_utils.config import config +from dotenv import load_dotenv + +# Set trace_all to True to capture all transactions, defaults to capturing traces only on failure +# Learn more about using AlgoKit AVM Debugger to debug your TEAL source codes and inspect various kinds of +# Algorand transactions in atomic groups -> https://github.com/algorandfoundation/algokit-avm-vscode-debugger +config.configure(debug=True, trace_all=False) + +# Set up logging and load environment variables. +logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s %(levelname)-10s: %(message)s" +) +logger = logging.getLogger(__name__) +logger.info("Loading .env") +load_dotenv() + +# Determine the root path based on this file's location. +root_path = Path(__file__).parent + +# ----------------------- Contract Configuration ----------------------- # + + +@dataclasses.dataclass +class SmartContract: + path: Path + name: str + deploy: Callable[[], None] | None = None + + +def import_contract(folder: Path) -> Path: + """Imports the contract from a folder if it exists.""" + contract_path = folder / "contract.py" + if contract_path.exists(): + return contract_path + else: + raise Exception(f"Contract not found in {folder}") + + +def import_deploy_if_exists(folder: Path) -> Callable[[], None] | None: + """Imports the deploy function from a folder if it exists.""" + try: + module_name = f"{folder.parent.name}.{folder.name}.deploy_config" + deploy_module = importlib.import_module(module_name) + return deploy_module.deploy # type: ignore[no-any-return, misc] + except ImportError: + return None + + +def has_contract_file(directory: Path) -> bool: + """Checks whether the directory contains a contract.py file.""" + return (directory / "contract.py").exists() + + +# Use the current directory (root_path) as the base for contract folders and exclude +# folders that start with '_' (internal helpers). +contracts: list[SmartContract] = [ + SmartContract( + path=import_contract(folder), + name=folder.name, + deploy=import_deploy_if_exists(folder), + ) + for folder in root_path.iterdir() + if folder.is_dir() and has_contract_file(folder) and not folder.name.startswith("_") +] + +# -------------------------- Build Logic -------------------------- # + +deployment_extension = "py" + + +def _get_output_path(output_dir: Path, deployment_extension: str) -> Path: + """Constructs the output path for the generated client file.""" + return output_dir / Path( + "{contract_name}" + + ("_client" if deployment_extension == "py" else "Client") + + f".{deployment_extension}" + ) + + +def build(output_dir: Path, contract_path: Path) -> Path: + """ + Builds the contract by exporting (compiling) its source and generating a client. + If the output directory already exists, it is cleared. + """ + output_dir = output_dir.resolve() + if output_dir.exists(): + rmtree(output_dir) + output_dir.mkdir(exist_ok=True, parents=True) + logger.info(f"Exporting {contract_path} to {output_dir}") + + build_result = subprocess.run( + [ + "algokit", + "--no-color", + "compile", + "python", + str(contract_path.resolve()), + f"--out-dir={output_dir}", + "--no-output-arc32", + "--output-arc56", + "--output-source-map", + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + if build_result.returncode: + raise Exception(f"Could not build contract:\n{build_result.stdout}") + + # Look for arc56.json files and generate the client based on them. + app_spec_file_names: list[str] = [ + file.name for file in output_dir.glob("*.arc56.json") + ] + + client_file: str | None = None + if not app_spec_file_names: + logger.warning( + "No '*.arc56.json' file found (likely a logic signature being compiled). Skipping client generation." + ) + else: + for file_name in app_spec_file_names: + client_file = file_name + print(file_name) + generate_result = subprocess.run( + [ + "algokit", + "generate", + "client", + str(output_dir), + "--output", + str(_get_output_path(output_dir, deployment_extension)), + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + ) + if generate_result.returncode: + if "No such command" in generate_result.stdout: + raise Exception( + "Could not generate typed client, requires AlgoKit 2.0.0 or later. Please update AlgoKit" + ) + else: + raise Exception( + f"Could not generate typed client:\n{generate_result.stdout}" + ) + if client_file: + return output_dir / client_file + return output_dir + + +# --------------------------- Main Logic --------------------------- # + + +def main(action: str, contract_name: str | None = None) -> None: + """Main entry point to build and/or deploy smart contracts.""" + artifact_path = root_path / "artifacts" + # Filter contracts based on an optional specific contract name. + filtered_contracts = [ + contract + for contract in contracts + if contract_name is None or contract.name == contract_name + ] + + match action: + case "build": + for contract in filtered_contracts: + logger.info(f"Building app at {contract.path}") + build(artifact_path / contract.name, contract.path) + case "deploy": + for contract in filtered_contracts: + output_dir = artifact_path / contract.name + app_spec_file_name = next( + ( + file.name + for file in output_dir.iterdir() + if file.is_file() and file.suffixes == [".arc56", ".json"] + ), + None, + ) + if app_spec_file_name is None: + raise Exception("Could not deploy app, .arc56.json file not found") + if contract.deploy: + logger.info(f"Deploying app {contract.name}") + contract.deploy() + case "all": + for contract in filtered_contracts: + logger.info(f"Building app at {contract.path}") + build(artifact_path / contract.name, contract.path) + if contract.deploy: + logger.info(f"Deploying {contract.name}") + contract.deploy() + case _: + logger.error(f"Unknown action: {action}") + + +if __name__ == "__main__": + if len(sys.argv) > 2: + main(sys.argv[1], sys.argv[2]) + elif len(sys.argv) > 1: + main(sys.argv[1]) + else: + main("all") diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/__pycache__/__init__.cpython-313.pyc b/ogc-contracts/projects/ogc-contracts/smart_contracts/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..ebf031b Binary files /dev/null and b/ogc-contracts/projects/ogc-contracts/smart_contracts/__pycache__/__init__.cpython-313.pyc differ diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/__pycache__/__main__.cpython-313.pyc b/ogc-contracts/projects/ogc-contracts/smart_contracts/__pycache__/__main__.cpython-313.pyc new file mode 100644 index 0000000..45e3fa5 Binary files /dev/null and b/ogc-contracts/projects/ogc-contracts/smart_contracts/__pycache__/__main__.cpython-313.pyc differ diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/__pycache__/contract.cpython-313.pyc b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/__pycache__/contract.cpython-313.pyc new file mode 100644 index 0000000..3d235be Binary files /dev/null and b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/__pycache__/contract.cpython-313.pyc differ diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/__pycache__/deploy_config.cpython-313.pyc b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/__pycache__/deploy_config.cpython-313.pyc new file mode 100644 index 0000000..48f34ce Binary files /dev/null and b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/__pycache__/deploy_config.cpython-313.pyc differ diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/contract.py b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/contract.py new file mode 100644 index 0000000..345c600 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/contract.py @@ -0,0 +1,56 @@ +from pyteal import * +from beaker import * + +class VaultState: + goal = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("goal"), default=Int(0)) + deadline = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("deadline"), default=Int(0)) + receiver = GlobalStateValue(stack_type=TealType.bytes, key=Bytes("receiver"), default=Bytes("")) + total = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("total"), default=Int(0)) + +app = Application("OGC_Vault", state=VaultState) + +@app.create +def create(goal_amount: abi.Uint64, deadline_round: abi.Uint64, receiver_addr: abi.Address, *, output: abi.Uint64): + return Seq( + app.state.goal.set(goal_amount.get()), + app.state.deadline.set(deadline_round.get()), + app.state.receiver.set(receiver_addr.get()), + app.state.total.set(Int(0)), + output.set(Global.current_application_id()), + ) + +@app.external +def contribute(payment: abi.PaymentTransaction): + return Seq( + Assert(And( + payment.get().receiver() == Global.current_application_address(), + payment.get().amount() > Int(0), + )), + app.state.total.set(app.state.total.get() + payment.get().amount()), + ) + +@app.external +def release(): + now = Global.round() + bal = Balance(Global.current_application_address()) + return Seq( + Assert(And(now >= app.state.deadline.get(), bal >= app.state.goal.get())), + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields({ + TxnField.type_enum: TxnType.Payment, + TxnField.receiver: app.state.receiver.get(), + TxnField.amount: bal - Int(1_000_000), + }), + InnerTxnBuilder.Submit(), + ) + +@app.external(read_only=True) +def get_goal(*, output: abi.Uint64): + return output.set(app.state.goal.get()) + +@app.external(read_only=True) +def get_total(*, output: abi.Uint64): + return output.set(app.state.total.get()) + +if __name__ == "__main__": + app.build().export("./artifacts/ogc_vault") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/deploy_config.py b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/deploy_config.py new file mode 100644 index 0000000..f545be1 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/smart_contracts/ogc_vault/deploy_config.py @@ -0,0 +1,31 @@ +import logging +from beaker import sandbox +from smart_contracts.ogc_vault.contract import app + +logger = logging.getLogger(__name__) + +def deploy() -> None: + # Get sandbox client and account + algod_client = sandbox.get_algod_client() + acct = sandbox.get_accounts().pop() + + # Deploy parameters + goal = 1_000_000 # 1 ALGO goal + deadline_round = algod_client.status()["last-round"] + 1000 # ~1 hour from now + receiver = acct.address # Use deployer as receiver for demo + + # Create the app + app_id, app_addr, _ = app.create( + sender=acct, + suggested_params=algod_client.suggested_params(), + goal=goal, + deadline_round=deadline_round, + receiver=receiver, + ) + + logger.info(f"Deployed OGC Vault:") + logger.info(f" APP_ID: {app_id}") + logger.info(f" APP_ADDRESS: {app_addr}") + logger.info(f" Goal: {goal} microALGO") + logger.info(f" Deadline: Round {deadline_round}") + logger.info(f" Receiver: {receiver}") diff --git a/ogc-contracts/projects/ogc-contracts/test_echo_local.py b/ogc-contracts/projects/ogc-contracts/test_echo_local.py new file mode 100644 index 0000000..cc85e44 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/test_echo_local.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +"""Test Echo Contract Locally""" + +from beaker import sandbox, client +from echo_contract import app +from algosdk import transaction as tx +from algosdk.atomic_transaction_composer import TransactionWithSigner + +def test_echo_contract(): + print("๐Ÿ”„ Testing Echo Contract Locally") + + algod_client = sandbox.get_algod_client() + deployer = sandbox.get_accounts().pop() + sender = sandbox.get_accounts().pop() + + # Deploy contract + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=deployer.signer, + ) + + app_id, app_addr, _ = app_client.create() + print(f"โœ… Echo Contract Deployed: APP_ID {app_id}") + print(f" Address: {app_addr}") + + # Fund the contract so it can send ALGO back + sp = algod_client.suggested_params() + fund_txn = tx.PaymentTxn(deployer.address, sp, app_addr, 5_000_000) # 5 ALGO + algod_client.send_transaction(fund_txn.sign(deployer.private_key)) + print(f"โœ… Contract funded with 5 ALGO") + + # Test: Send 1 ALGO to contract + test_amount = 1_000_000 # 1 ALGO + print(f"\n๐Ÿงช Test: Sending {test_amount/1_000_000} ALGO to contract...") + + # Get sender's balance before + sender_balance_before = algod_client.account_info(sender.address)["amount"] + + # Send payment to contract + payment = tx.PaymentTxn(sender.address, sp, app_addr, test_amount) + payment_signed = TransactionWithSigner(payment, sender.signer) + + # Call echo_payment method + app_client_sender = client.ApplicationClient( + client=algod_client, app=app, app_id=app_id, signer=sender.signer + ) + + try: + app_client_sender.call("echo_payment", payment=payment_signed) + print("โœ… Echo payment successful!") + + # Check sender's balance after + sender_balance_after = algod_client.account_info(sender.address)["amount"] + difference = (sender_balance_after - sender_balance_before) / 1_000_000 + + print(f"๐Ÿ“Š Results:") + print(f" Sent: {test_amount/1_000_000} ALGO") + print(f" Net change: {difference} ALGO (should be ~-0.002 for fees)") + + # Check contract stats + total = app_client.call("get_total").return_value + print(f" Contract total received: {total/1_000_000} ALGO") + + print(f"\n๐ŸŽ‰ SUCCESS! Contract automatically sent ALGO back!") + + except Exception as e: + print(f"โŒ Test failed: {e}") + +if __name__ == "__main__": + test_echo_contract() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/test_token.py b/ogc-contracts/projects/ogc-contracts/test_token.py new file mode 100644 index 0000000..57706cf --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/test_token.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Test OGC token transfers""" + +from beaker import sandbox +from algosdk import transaction as tx + +def test_token_transfer(): + print("๐Ÿงช Testing OGC Token Transfer...") + + algod = sandbox.get_algod_client() + creator, alice = sandbox.get_accounts()[:2] + sp = algod.suggested_params() + + # Get asset ID from user + asset_id = int(input("Enter Asset ID from create_ogc_token.py: ")) + + # Alice opts into the token + print("๐Ÿ“ Alice opting into OGC token...") + optin = tx.AssetTransferTxn( + sender=alice.address, + sp=sp, + receiver=alice.address, + amt=0, + index=asset_id + ) + algod.send_transaction(optin.sign(alice.private_key)) + import time + time.sleep(2) # Wait for opt-in to confirm + print("โœ… Alice opted in") + + # Creator sends tokens to Alice + print("๐Ÿ’ธ Sending 1000 OGC to Alice...") + transfer = tx.AssetTransferTxn( + sender=creator.address, + sp=sp, + receiver=alice.address, + amt=1000_000_000, # 1000 OGC (6 decimals) + index=asset_id + ) + algod.send_transaction(transfer.sign(creator.private_key)) + print("โœ… Transfer complete!") + + # Check Alice's balance + alice_info = algod.account_info(alice.address) + for asset in alice_info.get("assets", []): + if asset["asset-id"] == asset_id: + balance = asset["amount"] / 1_000_000 # Convert from micro-units + print(f"๐Ÿ’ฐ Alice's OGC balance: {balance} OGC") + break + + print("๐ŸŽ‰ Token test successful!") + +if __name__ == "__main__": + test_token_transfer() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/test_vault.py b/ogc-contracts/projects/ogc-contracts/test_vault.py new file mode 100644 index 0000000..d2272bf --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/test_vault.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Test suite for OGC Vault""" + +from beaker import sandbox, client +from working_vault import app +from algosdk import transaction as tx +from algosdk.atomic_transaction_composer import TransactionWithSigner + +def test_vault_basic(): + print("๐Ÿงช Testing OGC Vault Basic Flow") + + algod_client = sandbox.get_algod_client() + creator = sandbox.get_accounts().pop() + contributor = sandbox.get_accounts().pop() + + goal = 1_000_000 # 1 ALGO + current_round = algod_client.status()["last-round"] + deadline_round = current_round + 100 + + # Deploy + app_client = client.ApplicationClient( + client=algod_client, + app=app, + signer=creator.signer, + ) + + app_id, app_addr, _ = app_client.create( + goal_amount=goal, + deadline_round=deadline_round, + receiver_addr=creator.address, + ) + + print(f"โœ… Deployed: APP_ID {app_id}") + + # Fund app + sp = algod_client.suggested_params() + fund_txn = tx.PaymentTxn(creator.address, sp, app_addr, 2_000_000) + algod_client.send_transaction(fund_txn.sign(creator.private_key)) + + # Test contribution + contrib_amount = 500_000 + pmt = tx.PaymentTxn(contributor.address, sp, app_addr, contrib_amount) + pmt_with_signer = TransactionWithSigner(pmt, contributor.signer) + + app_client_contrib = client.ApplicationClient( + client=algod_client, app=app, app_id=app_id, signer=contributor.signer + ) + app_client_contrib.call("contribute", payment=pmt_with_signer) + + # Verify + total = app_client.call("get_total").return_value + goal_check = app_client.call("get_goal").return_value + + assert total == contrib_amount, f"Expected {contrib_amount}, got {total}" + assert goal_check == goal, f"Expected {goal}, got {goal_check}" + + print(f"โœ… Contribution test passed: {total/1_000_000} ALGO") + return True + +if __name__ == "__main__": + success = test_vault_basic() + print(f"๐Ÿ Tests {'PASSED' if success else 'FAILED'}") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/universal_wallet.py b/ogc-contracts/projects/ogc-contracts/universal_wallet.py new file mode 100644 index 0000000..1fb30ac --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/universal_wallet.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Handle both Universal (24-word) and Legacy (25-word) wallets""" + +from algosdk import mnemonic, account +import algosdk + +def detect_wallet_type(): + print("๐Ÿ” Wallet Format Detector") + print("1. Test your mnemonic") + print("2. Create Legacy wallet (25-word)") + print("3. View saved Legacy wallet") + + choice = input("Choose (1, 2, or 3): ").strip() + + if choice == "1": + # Test user's mnemonic + user_mnemonic = input("Paste your mnemonic: ").strip() + words = user_mnemonic.split() + + print(f"\n๐Ÿ“Š Analysis:") + print(f" Word count: {len(words)}") + + if len(words) == 24: + print(" Format: Universal (24-word)") + print(" โš ๏ธ Python SDK needs 25-word Legacy format") + print(" ๐Ÿ’ก Use WalletConnect or create Legacy wallet") + + elif len(words) == 25: + print(" Format: Legacy (25-word)") + try: + sk = mnemonic.to_private_key(user_mnemonic) + addr = algosdk.account.address_from_private_key(sk) + print(f" โœ… Valid! Address: {addr}") + except Exception as e: + print(f" โŒ Invalid: {e}") + + else: + print(f" โŒ Invalid: Expected 24 or 25 words, got {len(words)}") + + elif choice == "2": + # Create new Legacy wallet + print("\n๐Ÿ”‘ Creating Legacy Wallet (25-word)...") + sk, addr = account.generate_account() + mnemo = mnemonic.from_private_key(sk) + + print(f"โœ… Legacy Wallet Created:") + print(f" Address: {addr}") + print(f" Mnemonic: {mnemo}") + print(f" Words: {len(mnemo.split())}") + print(f" Compatible with Python SDK: โœ…") + + elif choice == "3": + # Show saved Legacy wallet + address = "ZHMX3URT56ZLWQY3Y74CRXVOAVEOELOAVMXJZDT76IFORLPWMWMPJVAEN4" + mnemo = "swear suffer shrimp clinic cause differ nice space update mansion cradle brisk unknown lecture host clarify again faint divide decrease renew choice still abstract tomorrow" + + print(f"\n๐Ÿ” Saved Legacy Wallet:") + print(f" Address: {address}") + print(f" Mnemonic: {mnemo}") + print(f" Words: {len(mnemo.split())}") + print(f" Format: Legacy (Python SDK compatible)") + + else: + print("โŒ Invalid choice") + +if __name__ == "__main__": + detect_wallet_type() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/vault.py b/ogc-contracts/projects/ogc-contracts/vault.py new file mode 100644 index 0000000..1cfe513 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/vault.py @@ -0,0 +1,57 @@ +# smart_contracts/vault.py +from pyteal import * +from beaker import * + +class G: + goal = GlobalStateValue(stack_type=TealType.uint64, default=Int(0)) + deadline = GlobalStateValue(stack_type=TealType.uint64, default=Int(0)) # round # + receiver = GlobalStateValue(stack_type=TealType.bytes, default=Bytes("")) + total = GlobalStateValue(stack_type=TealType.uint64, default=Int(0)) + +app = Application("OGC_Vault", state=G()) + +@app.create +def create(goal: abi.Uint64, deadline_round: abi.Uint64, receiver: abi.Address, *, output: abi.Uint64): + return Seq( + app.state.goal.set(goal.get()), + app.state.deadline.set(deadline_round.get()), + app.state.receiver.set(receiver.get()), + app.state.total.set(Int(0)), + output.set(Global.current_application_id()), + ) + +@app.external +def contribute(payment: abi.PaymentTransaction): + return Seq( + Assert(And( + payment.get().receiver() == Global.current_application_address(), + payment.get().amount() > Int(0), + )), + app.state.total.set(app.state.total.get() + payment.get().amount()), + ) + +@app.external +def release(): + now = Global.round() + bal = Balance(Global.current_application_address()) + return Seq( + Assert(And(now >= app.state.deadline.get(), bal >= app.state.goal.get())), + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields({ + TxnField.type_enum: TxnType.Payment, + TxnField.receiver: app.state.receiver.get(), + TxnField.amount: bal - Int(1_000_000), # leave min balance + }), + InnerTxnBuilder.Submit(), + ) + +@app.external(read_only=True) +def get_goal(*, output: abi.Uint64): + return output.set(app.state.goal.get()) + +@app.external(read_only=True) +def get_total(*, output: abi.Uint64): + return output.set(app.state.total.get()) + +if __name__ == "__main__": + app.build().export("./artifacts") diff --git a/ogc-contracts/projects/ogc-contracts/verify_token.py b/ogc-contracts/projects/ogc-contracts/verify_token.py new file mode 100644 index 0000000..ed983c3 --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/verify_token.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Verify OGC token exists""" + +from beaker import sandbox + +def verify_token(): + print("๐Ÿ” Verifying OGC Token...") + + algod = sandbox.get_algod_client() + asset_id = 1033 # From create_ogc_token.py output + + try: + # Get asset info + asset_info = algod.asset_info(asset_id) + params = asset_info["params"] + + print(f"โœ… OGC Token Found!") + print(f" Asset ID: {asset_id}") + print(f" Name: {params['name']}") + print(f" Unit: {params['unit-name']}") + print(f" Total: {params['total']:,}") + print(f" Decimals: {params['decimals']}") + print(f" Creator: {params['creator']}") + + return True + + except Exception as e: + print(f"โŒ Token not found: {e}") + return False + +if __name__ == "__main__": + success = verify_token() + print(f"๐Ÿ Verification {'PASSED' if success else 'FAILED'}") \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/view_wallet.py b/ogc-contracts/projects/ogc-contracts/view_wallet.py new file mode 100644 index 0000000..a9cffdf --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/view_wallet.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""View saved wallet details""" + +def view_wallet(): + print("๐Ÿ” Your Saved TestNet Wallet") + print("=" * 50) + + import os + from dotenv import load_dotenv + load_dotenv() + + address = os.getenv('TESTNET_ADDRESS', 'Not found in .env') + mnemonic = os.getenv('TESTNET_MNEMONIC', 'Not found in .env') + + print(f"Address: {address}") + print(f"\nMnemonic (25 words):") + print(f"{mnemonic}") + + print(f"\n๐Ÿ”— Links:") + print(f"TestNet Explorer: https://testnet.algoexplorer.io/address/{address}") + print(f"Get TestNet ALGO: https://testnet.algoexplorer.io/dispenser") + + print(f"\n๐Ÿ“ฑ Import to Pera Wallet:") + print(f"1. Open Pera Wallet") + print(f"2. Add Account โ†’ Import Account") + print(f"3. Enter the 25-word mnemonic above") + print(f"4. Switch to TestNet") + + print(f"\nโš ๏ธ SECURITY:") + print(f"- This is for TestNet only (no real value)") + print(f"- Never share MainNet mnemonics") + print(f"- Keep this safe for your demos") + +if __name__ == "__main__": + view_wallet() \ No newline at end of file diff --git a/ogc-contracts/projects/ogc-contracts/working_vault.py b/ogc-contracts/projects/ogc-contracts/working_vault.py new file mode 100644 index 0000000..0f8d0ec --- /dev/null +++ b/ogc-contracts/projects/ogc-contracts/working_vault.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Working vault with proper schema""" + +from pyteal import * +from beaker import * + +class VaultState: + goal = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("goal"), default=Int(0)) + deadline = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("deadline"), default=Int(0)) + receiver = GlobalStateValue(stack_type=TealType.bytes, key=Bytes("receiver"), default=Bytes("")) + total = GlobalStateValue(stack_type=TealType.uint64, key=Bytes("total"), default=Int(0)) + +app = Application("WorkingVault", state=VaultState) + +@app.create +def create(goal_amount: abi.Uint64, deadline_round: abi.Uint64, receiver_addr: abi.Address, *, output: abi.Uint64): + return Seq( + app.state.goal.set(goal_amount.get()), + app.state.deadline.set(deadline_round.get()), + app.state.receiver.set(receiver_addr.get()), + app.state.total.set(Int(0)), + output.set(Global.current_application_id()), + ) + +@app.external +def contribute(payment: abi.PaymentTransaction): + return Seq( + Assert(And( + payment.get().receiver() == Global.current_application_address(), + payment.get().amount() > Int(0), + )), + app.state.total.set(app.state.total.get() + payment.get().amount()), + ) + +@app.external +def release(): + now = Global.round() + bal = Balance(Global.current_application_address()) + return Seq( + Assert(And(now >= app.state.deadline.get(), bal >= app.state.goal.get())), + InnerTxnBuilder.Begin(), + InnerTxnBuilder.SetFields({ + TxnField.type_enum: TxnType.Payment, + TxnField.receiver: app.state.receiver.get(), + TxnField.amount: bal - Int(1_000_000), + }), + InnerTxnBuilder.Submit(), + ) + +@app.external(read_only=True) +def get_goal(*, output: abi.Uint64): + return output.set(app.state.goal.get()) + +@app.external(read_only=True) +def get_total(*, output: abi.Uint64): + return output.set(app.state.total.get()) + +if __name__ == "__main__": + app.build().export("./artifacts/working_vault") \ No newline at end of file