Session 05.3
Smart Contracts
Algorand

PYTEAL
CONTRACTS.

Design and deploy stateful smart contracts on Algorand using PyTeal — a Python DSL that compiles to TEAL bytecode executed by the Algorand Virtual Machine.

01

Execution Environment

Algorand smart contracts run on algod (Algorand daemon), which executes transactions, validates blocks, and maintains blockchain state.

TEAL

Transaction Execution Approval Language — a low-level, stack-based bytecode language.

PyTeal

A Python library that provides a high-level abstraction for writing TEAL programs safely.

Important

PyTeal is NOT executed on the blockchain. It compiles to TEAL, and only the generated TEAL runs on-chain.

Smart Contract Lifecycle

Python DSL for contract logic

TEAL Stack-Based Execution

TEAL is a stack-based language. Operations push/pop values from the stack. All operations are deterministic and bounded.

// Example: Add 5 + 3
TEAL Instructions:
int 5
int 3
+
Stack State:
[5]
[5, 3]
[8] ✓
Deterministic

Same input always produces same output. No randomness on-chain.

No Loops

Bounded execution. No infinite loops. Cost is predictable.

Approval Model

Contract returns 0 (reject) or non-zero (approve). Binary outcome.

02

Contract Types

Stateless (LogicSig)

Purely validation logic. Signs transactions if conditions are met. Cannot store data.

Use case: Escrow, Delegated signatures

Stateful (Applications)

Can store and modify on-chain state. Has global and local storage. Our focus.

Use case: DeFi, DAOs, Games, NFT logic
State Storage Model

Global State

Shared across all users — 64 key-value slots

{ 'counter': 42, 'admin': 'ALGO...' }

Local State

Per-user storage — 16 key-value slots per user

User A: { 'balance': 100 }
User B: { 'balance': 250 }
03

Approval & Clear Programs

Every stateful smart contract requires two programs: an Approval Program that handles all logic, and a Clear Program that executes when users opt-out.

The Approval Program determines whether a transaction should be accepted or rejected. It contains the main business logic.

counter.py
from pyteal import *

def approval_program():
    # Global state key
    counter_key = Bytes("counter")
    
    # Handle application creation
    on_create = Seq([
        App.globalPut(counter_key, Int(0)),
        Approve()
    ])
    
    # Handle NoOp calls (increment)
    on_increment = Seq([
        App.globalPut(
            counter_key,
            App.globalGet(counter_key) + Int(1)
        ),
        Approve()
    ])
    
    # Main router
    program = Cond(
        [Txn.application_id() == Int(0), on_create],
        [Txn.on_completion() == OnComplete.NoOp, on_increment],
        [Txn.on_completion() == OnComplete.OptIn, Approve()],
        [Txn.on_completion() == OnComplete.CloseOut, Approve()],
        [Txn.on_completion() == OnComplete.DeleteApplication, 
         Return(Txn.sender() == Global.creator_address())],
    )
    
    return program

# Compile to TEAL
if __name__ == "__main__":
    print(compileTeal(
        approval_program(), 
        mode=Mode.Application, 
        version=8
    ))
04

Application Call Types

Algorand applications respond to different OnComplete actions, each triggering specific logic paths in your contract.

Create

app_id == 0

Deploy a new application. Runs once during initial creation.

OnComplete: NoOp

NoOp

NoOp

Standard function call. Used for most contract interactions.

OnComplete: NoOp

OptIn

OptIn

User opts into the app. Allocates local state storage.

OnComplete: OptIn

CloseOut

CloseOut

User opts out gracefully. Clears local state.

OnComplete: CloseOut

ClearState

ClearState

Force opt-out via Clear Program. Always succeeds.

OnComplete: ClearState

Delete

DeleteApplication

Remove the application. Usually restricted to creator.

OnComplete: Delete
05

Deployment

Deploy your contract using the Algorand SDK. This involves compiling TEAL, defining state schemas, and submitting a creation transaction.

01Compile PyTeal to TEAL bytecode
02Define global/local state schemas
03Create ApplicationCreateTxn
04Sign with creator's private key
05Submit to network and get App ID

Algorand Sandbox

For development, use Algorand Sandbox — Docker containers running a local Algorand node with KMD wallet service.

# Start sandbox
./sandbox up
# Enter algod container
./sandbox enter algod
deploy.py
from algosdk import account, mnemonic
from algosdk.v2client import algod
from algosdk.transaction import ApplicationCreateTxn, StateSchema
import base64

# Connect to Algorand node
algod_client = algod.AlgodClient(
    algod_token="",
    algod_address="https://testnet-api.algonode.cloud"
)

# Load compiled TEAL
with open("approval.teal", "r") as f:
    approval_source = f.read()
with open("clear.teal", "r") as f:
    clear_source = f.read()

# Compile TEAL to bytecode
approval_program = base64.b64decode(
    algod_client.compile(approval_source)["result"]
)
clear_program = base64.b64decode(
    algod_client.compile(clear_source)["result"]
)

# Define state schema
global_schema = StateSchema(num_uints=1, num_byte_slices=0)
local_schema = StateSchema(num_uints=0, num_byte_slices=0)

# Create application transaction
params = algod_client.suggested_params()
txn = ApplicationCreateTxn(
    sender=creator_address,
    sp=params,
    on_complete=OnComplete.NoOpOC,
    approval_program=approval_program,
    clear_program=clear_program,
    global_schema=global_schema,
    local_schema=local_schema,
)

# Sign and submit
signed_txn = txn.sign(creator_private_key)
txid = algod_client.send_transaction(signed_txn)
print(f"Application ID: {wait_for_confirmation(txid)}")
06

Contract Interaction

Once deployed, interact with your contract using ApplicationNoOpTxn for function calls, and read state directly from the application info.

interact.py
from algosdk.transaction import ApplicationNoOpTxn

# Call the counter contract (increment)
params = algod_client.suggested_params()
txn = ApplicationNoOpTxn(
    sender=user_address,
    sp=params,
    index=app_id,  # Application ID from deployment
)

# Sign and submit
signed_txn = txn.sign(user_private_key)
txid = algod_client.send_transaction(signed_txn)
result = wait_for_confirmation(txid)

# Read global state
app_info = algod_client.application_info(app_id)
state = app_info["params"]["global-state"]
for item in state:
    key = base64.b64decode(item["key"]).decode()
    value = item["value"]["uint"]
    print(f"{key}: {value}")  # counter: 1

Deployment Checklist

Define state schemas before deployment (cannot be changed)

Ensure Clear Program always approves for safe opt-out

Handle all OnComplete types in Approval Program

Test on Algorand Sandbox or TestNet before MainNet

Verify minimum balance requirements for users

Consider upgradeability patterns if logic may change

Complete Development Workflow

counter.py
PyTeal Logic
compile.py
Generate TEAL
approval.teal
Bytecode
goal deploy
On-Chain
App #123
Live Contract

Key Learnings

Only TEAL runs on-chain

PyTeal is a development tool that compiles to TEAL bytecode

Transaction-driven execution

Smart contracts are only executed when triggered by transactions

Immediate finality

State changes are atomic — once confirmed, they're permanent

Explicit state typing

State is stored efficiently with explicit types (uint, bytes)

Portable logic

Same code works on DevNet, TestNet, and MainNet

Bounded computation

No infinite loops — all execution has predictable cost