Algorand Python (Puya)
Write Algorand smart contracts using Python syntax with type hints.
Overview
Puya allows you to write Algorand smart contracts using Python syntax. It compiles Python code with type hints to efficient TEAL bytecode.
Note
Puya uses a subset of Python. Not all Python features are available - the compiler will tell you if you use unsupported features.
Basic Contract Structure
counter.py Python
1from algopy import ARC4Contract, GlobalState, UInt64
2
3class Counter(ARC4Contract):
4 def __init__(self) -> None:
5 self.count = GlobalState(UInt64(0))
6
7 @arc4.abimethod
8 def increment(self) -> None:
9 self.count.value += 1
10
11 @arc4.abimethod
12 def get_count(self) -> UInt64:
13 return self.count.value
State Management
Global State
Python
1from algopy import ARC4Contract, GlobalState, UInt64, Bytes, Account
2
3class MyContract(ARC4Contract):
4 def __init__(self) -> None:
5 # Typed global state
6 self.counter = GlobalState(UInt64(0))
7 self.owner = GlobalState(Account())
8 self.data = GlobalState(Bytes())
Local State
Python
1from algopy import ARC4Contract, LocalState, UInt64, Txn
2
3class UserContract(ARC4Contract):
4 def __init__(self) -> None:
5 self.balance = LocalState(UInt64)
6
7 @arc4.abimethod
8 def deposit(self) -> None:
9 self.balance[Txn.sender] += Txn.amount
Box Storage
Python
1from algopy import ARC4Contract, Box, BoxMap, Bytes, arc4
2
3class StorageContract(ARC4Contract):
4 def __init__(self) -> None:
5 self.config = Box(Bytes, key=b"config")
6 self.users = BoxMap(arc4.Address, UserData, key_prefix=b"u")
Types
Primitive Types
Python
1from algopy import UInt64, Bytes, String, Bool
2
3# 64-bit unsigned integer
4count: UInt64 = UInt64(100)
5
6# Byte array
7data: Bytes = Bytes(b"hello")
8
9# String (UTF-8 bytes)
10name: String = String("Alice")
11
12# Boolean
13active: Bool = Bool(True)
ARC4 Types
Python
1from algopy import arc4
2
3# Fixed-size unsigned integers
4small: arc4.UInt8 = arc4.UInt8(255)
5medium: arc4.UInt32 = arc4.UInt32(1000000)
6large: arc4.UInt256 = arc4.UInt256(10**50)
7
8# Address
9addr: arc4.Address = arc4.Address(Txn.sender)
10
11# Dynamic array
12numbers: arc4.DynamicArray[arc4.UInt64] = arc4.DynamicArray[arc4.UInt64]()
13
14# Static array
15fixed: arc4.StaticArray[arc4.UInt8, typing.Literal[4]] = ...
Methods
ABI Methods
Python
1from algopy import ARC4Contract, arc4, UInt64
2
3class Calculator(ARC4Contract):
4 @arc4.abimethod
5 def add(self, a: UInt64, b: UInt64) -> UInt64:
6 return a + b
7
8 @arc4.abimethod
9 def multiply(self, a: UInt64, b: UInt64) -> UInt64:
10 return a * b
11
12 @arc4.abimethod(create="require")
13 def create(self) -> None:
14 """Called only on contract creation"""
15 self.owner.value = Txn.sender
Bare Methods
Python
1from algopy import ARC4Contract, arc4
2
3class MyContract(ARC4Contract):
4 @arc4.baremethod(create="require")
5 def create(self) -> None:
6 pass
7
8 @arc4.baremethod(opt_in="require")
9 def opt_in(self) -> None:
10 pass
11
12 @arc4.baremethod(delete_application="require")
13 def delete(self) -> None:
14 assert Txn.sender == self.owner.value
Transaction Context
Python
1from algopy import Txn, Global, itxn
2
3# Access current transaction
4sender = Txn.sender
5amount = Txn.amount
6app_id = Txn.application_id
7
8# Access global state
9timestamp = Global.latest_timestamp
10current_round = Global.round
11
12# Inner transactions
13itxn.Payment(
14 receiver=recipient,
15 amount=UInt64(1_000_000) # 1 ALGO
16).submit()
17
18itxn.AssetTransfer(
19 xfer_asset=asset_id,
20 asset_receiver=recipient,
21 asset_amount=amount
22).submit()
Assertions
Python
1from algopy import ARC4Contract, arc4, Txn, assert_
2
3class Guarded(ARC4Contract):
4 @arc4.abimethod
5 def admin_only(self) -> None:
6 assert Txn.sender == self.owner.value, "Not authorized"
7
8 @arc4.abimethod
9 def withdraw(self, amount: UInt64) -> None:
10 assert_(amount > 0, "Amount must be positive")
11 assert_(self.balance.value >= amount, "Insufficient balance")
Structs
Python
1from algopy import arc4, ARC4Contract
2
3class UserProfile(arc4.Struct):
4 name: arc4.String
5 balance: arc4.UInt64
6 active: arc4.Bool
7
8class Users(ARC4Contract):
9 def __init__(self) -> None:
10 self.profiles = BoxMap(arc4.Address, UserProfile, key_prefix=b"p")
11
12 @arc4.abimethod
13 def create_profile(self, name: arc4.String) -> None:
14 profile = UserProfile(
15 name=name,
16 balance=arc4.UInt64(0),
17 active=arc4.Bool(True)
18 )
19 self.profiles[arc4.Address(Txn.sender)] = profile