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/SETUP.md b/SETUP.md new file mode 100644 index 0000000..77603bd --- /dev/null +++ b/SETUP.md @@ -0,0 +1,123 @@ +# OGC Development Setup + +## ๐Ÿš€ Fresh AlgoKit Setup + +If you just installed AlgoKit and want to start: + +### 1. Install AlgoKit +```bash +# Install AlgoKit +pipx install algokit + +# Verify installation +algokit --version +algokit doctor # Check system health +``` + +### 2. Start LocalNet +```bash +# Start Algorand LocalNet +algokit localnet start + +# Check status +algokit localnet status +``` + +### 3. Navigate to Project +```bash +cd ~/OGC/ogc-contracts/projects/ogc-contracts +``` +any directory name is fine but it should have the \/projects/\ + +### 4. Activate Environment +```bash +# Activate Poetry virtualenv +source "$(poetry env info --path)/bin/activate" + +# Verify Python version +python --version # Should be 3.13.x +``` + +### 5. Install Dependencies +```bash +# Install with Poetry +poetry install + +# Or manually with pip +pip install beaker-pyteal pyteal py-algorand-sdk algokit-utils +``` + +### 6. Build & Test (Have to write it or pull it) +```bash +# Build contracts +python working_vault.py + +# Run tests +python test_vault.py + +# Run demo +python ogc_demo.py +``` + +### 7. Stop When Done +```bash +# Stop LocalNet +algokit localnet stop +``` + +## ๐Ÿ”„ Quick Reset Script + +If you need to reset your environment: + +```bash +# One-liner to get back into dev mode +cd ~/OGC/ogc-contracts/projects/ogc-contracts && source "$(poetry env info --path)/bin/activate" && python --version +``` + +Or use the Makefile: +```bash +make dev # Activates environment and drops into shell +``` + +## ๐Ÿ› ๏ธ Development Commands + +```bash +make install # Install dependencies +make build # Build contracts +make test # Run tests +make demo # Run demo +make ci # Full CI pipeline +``` + +## ๐Ÿ” Troubleshooting + +**LocalNet not starting?** +```bash +algokit localnet reset # Reset LocalNet +algokit doctor # Check system health +``` + +**Python version issues?** +```bash +poetry env info # Check Poetry environment +poetry shell # Alternative to source activation +``` + +**Dependencies not found?** +```bash +poetry install --no-cache # Reinstall dependencies +``` + +## ๐Ÿ“ Project Structure + +``` +OGC/ +โ”œโ”€โ”€ SETUP.md # This file +โ”œโ”€โ”€ .github/workflows/ci.yml # CI configuration +โ””โ”€โ”€ ogc-contracts/projects/ogc-contracts/ + โ”œโ”€โ”€ pyproject.toml # Dependencies + โ”œโ”€โ”€ Makefile # Build commands + โ”œโ”€โ”€ working_vault.py # Main contract + โ”œโ”€โ”€ test_vault.py # Tests + โ””โ”€โ”€ ogc_demo.py # Demo +``` 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/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/__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..ba72848 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/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/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/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/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/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/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/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/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