Smart Contract Development in TEAL (Algorand)
Algorand isn't EVM-compatible, and you can't write for it "like Ethereum." AVM (Algorand Virtual Machine) works on stack principle, TEAL is an assembly-like language with strict limits on program size and operation count. If you come with Solidity experience, first reaction to TEAL is surprise. Second is respect for how much you can do within these constraints.
AVM Specifics You Must Understand Immediately
TEAL (Transaction Execution Approval Language) is a stack-based language for Algorand Smart Contracts (ASC1). Programs are two types: LogicSig (signs transactions without account) and Application (full stateful smart contract with storage).
Key AVM 10 (current version) limits:
| Parameter | Limit |
|---|---|
| Program size (approval + clear) | 8192 bytes each |
| Stack depth | 1000 elements |
| Scratch space | 256 slots |
| Global state | 64 key-value pairs |
| Local state (per account) | 16 key-value pairs |
| Box storage (unlimited key-value) | payment for bytes |
| Opcodes per transaction | 20,000 (basic) |
| Opcodes with group | up to 320,000 |
20,000 opcode limit isn't 20,000 lines of code. One keccak256 costs 130 opcodes, ed25519verify — 1900. Opcode budget calculation is mandatory for complex contracts.
PyTeal vs TEAL: When to Use What
TEAL directly — for LogicSig, simple approval programs, final optimization. Reading and debugging TEAL is simpler than it seems if you understand stack model.
PyTeal — Python framework compiling to TEAL. Convenient for complex logic: conditions, loops (via recursion), ABI-compatible contracts. Version PyTeal 0.20+ supports ARC-4 ABI, letting you generate typed interface contracts.
Beaker (overlay on PyTeal) — provides higher-level syntax, automatic ABI schema generation, simplified state work. Use Beaker for new projects if client isn't restricted to specific framework.
Simple approval contract structure example in PyTeal:
from pyteal import *
def approval_program():
handle_creation = Seq([
App.globalPut(Bytes("owner"), Txn.sender()),
Approve()
])
is_owner = Txn.sender() == App.globalGet(Bytes("owner"))
handle_optin = Approve()
handle_closeout = Approve()
program = Cond(
[Txn.application_id() == Int(0), handle_creation],
[Txn.on_completion() == OnComplete.OptIn, handle_optin],
[Txn.on_completion() == OnComplete.CloseOut, handle_closeout],
[is_owner, Approve()],
[Int(1), Reject()]
)
return program
Where Problems Usually Arise
Box storage vs Local/Global state. Before AVM 8, developers squeezed data into global state (64 keys) or local state (16 keys per account). With Box storage limits are gone — each box can store up to 32,768 bytes, unlimited box count (just ALGO budget for storage). Not using Box storage in new projects is architectural error.
Grouped transactions (atomic group). In Algorand you can't call smart contract from another in one transaction — no EVM call equivalent. Instead use grouped transactions: multiple transactions, atomically processed together. Fundamental difference from EVM, changes complex protocol architecture. DeFi "flash loan → swap → repay" in Algorand is atomic group of three Application Call transactions, verifying each other via gtxn.
Inner transactions (AVM 6+). Contract can spawn up to 256 inner transactions in one call — ALGO transfer, ASA, another app call. This is the main composability mechanism in the ecosystem. Mistake — trying to implement something via pure stacking when task needs token transfer inside logic.
ARC-4 and ABI compatibility. ARC-4 standard describes ABI for Algorand contracts — argument types, return values, methods. Contracts without ARC-4 are harder to integrate with SDK (AlgoKit Utils, algokit-client-generator). Generate ABI schema automatically via algokit.
Tools and Stack
- AlgoKit — official CLI from Algorand Foundation. Project templates, local devnet via Docker (AlgoKit LocalNet), deployment, contract interaction.
- algopy (new Python framework) — compiles to TEAL via AVM compiler, static typing, stricter ABI control than Beaker. Preferred for new projects over PyTeal.
- algokit-client-generator — generates TypeScript client from ARC-4 JSON spec. Like typechain for EVM.
- Algorand Sandbox / AlgoKit LocalNet — local node for development.
- Dappflow — web interface for inspecting transactions and contract state, better than raw API.
Write tests in Python (pytest + algokit-utils) for unit tests and TypeScript (Jest + algosdk) for integration. Coverage via pytest-cov.
What Development Looks Like
Analytics (0.5-1 day). Determine contract type (stateful application vs LogicSig), state architecture (global/local/box), need for inner transactions, ARC standards compatibility (ARC-4, ARC-20 for smart ASA, ARC-200 for ASA-based tokens).
Development (2-4 days). Write approval + clear programs. Parallel — unit tests on AlgoKit LocalNet. Check opcode budget on critical paths via debug: true in algod API.
Integration. Generate ABI JSON, client in TypeScript/Python. Test on Algorand Testnet before mainnet.
Deployment. Application deploys via ApplicationCreateTxn. Upgradeability via UpdateApplicationTxn if approval program allows. Immutability via hardcode Int(0) in update/delete handlers.
Timeline — 3 to 5 days for medium complexity contract. Complex protocols with multiple linked apps and atomic group logic — up to 2 weeks.
Common Mistakes
Not accounting for minimum balance requirement. Each account opt-in in application must have minimum 0.1 ALGO + 0.025 ALGO per local state key. Box storage requires 0.0025 ALGO per byte + 0.0025 per key. Contracts forgetting this break on write attempt — transaction reverts with below min balance.
Mixing Application Call and Asset Transfer in wrong order. In atomic group, transaction order matters. Contract reads gtxn 0 — should be exactly the transaction you expect. Index confusion guarantees bug during testing.
Using LogicSig where Application is needed. LogicSig signs transactions but doesn't store state. If logic needs global state (balance, counter, address list) — need stateful Application. LogicSig good for delegated authorization and escrow.







