Algorand TypeScript (PuyaTS)
Write type-safe Algorand smart contracts using TypeScript syntax.
Overview
PuyaTS (Algorand TypeScript) lets you write smart contracts using familiar TypeScript syntax while compiling to efficient TEAL bytecode. It provides full type safety and excellent tooling support.
Tip
PuyaTS is the recommended language for most developers, especially those familiar with TypeScript or JavaScript.
Basic Contract Structure
Every PuyaTS contract extends the Contract base class:
my-contract.algo.ts TypeScript
1import { Contract } from '@algorandfoundation/algorand-typescript'
2
3class MyContract extends Contract {
4 // State declarations
5
6 // Lifecycle methods
7
8 // Public methods
9}
State Management
Global State
Persistent storage that belongs to the contract:
TypeScript
1import { Contract, GlobalState, Address, uint64, bytes } from '@algorandfoundation/algorand-typescript'
2
3class Counter extends Contract {
4 // With initial value
5 count = GlobalState<uint64>({ initialValue: 0 })
6
7 // Without initial value (must be set in createApplication)
8 owner = GlobalState<Address>()
9
10 // With custom key
11 data = GlobalState<bytes>({ key: 'my_data' })
12}
Local State
Per-user storage for accounts that opt into the contract:
TypeScript
1import { Contract, LocalState, Txn, Global, uint64 } from '@algorandfoundation/algorand-typescript'
2
3class UserBalance extends Contract {
4 balance = LocalState<uint64>({ key: 'bal' })
5 lastActive = LocalState<uint64>({ key: 'last' })
6
7 deposit(): void {
8 this.balance(Txn.sender).value += Txn.amount
9 this.lastActive(Txn.sender).value = Global.latestTimestamp
10 }
11}
Box Storage
Flexible storage for larger or dynamic data:
TypeScript
1import { Contract, Box, BoxMap } from '@algorandfoundation/algorand-typescript'
2
3class Storage extends Contract {
4 // Single box
5 config = Box<ConfigData>({ key: 'config' })
6
7 // Map of boxes
8 userProfiles = BoxMap<Address, UserProfile>({ prefix: 'u' })
9
10 setProfile(profile: UserProfile): void {
11 this.userProfiles(Txn.sender).value = profile
12 }
13}
Types
Primitive Types
uint64- 64-bit unsigned integerbytes- Byte arraystring- UTF-8 string (alias for bytes)boolean- True/false (stored as uint64)
Algorand Types
Address- 32-byte Algorand addressAsset- ASA referenceApplication- App referenceAccount- Account reference
Fixed-Size Types
TypeScript
1import { Uint8, Uint16, Uint32, Uint128, Uint256 } from '@algorandfoundation/algorand-typescript'
2
3// Fixed-width integers
4const small: Uint8 = 255
5const medium: Uint32 = 1000000
6const large: Uint256 = 2n ** 200n
Methods
Lifecycle Methods
TypeScript
1class MyContract extends Contract {
2 // Called when contract is created
3 createApplication(): void {
4 this.owner.value = Txn.sender
5 }
6
7 // Called when contract is updated
8 updateApplication(): void {
9 assert(Txn.sender === this.owner.value)
10 }
11
12 // Called when contract is deleted
13 deleteApplication(): void {
14 assert(Txn.sender === this.owner.value)
15 }
16
17 // Called when user opts in
18 optInToApplication(): void {
19 this.balance(Txn.sender).value = 0
20 }
21}
Public Methods
Any public method becomes callable via ABI:
TypeScript
1import { Contract, GlobalState, uint64 } from '@algorandfoundation/algorand-typescript'
2
3class Calculator extends Contract {
4 value = GlobalState<uint64>({ initialValue: 0 })
5 min = GlobalState<uint64>({ initialValue: 0 })
6 max = GlobalState<uint64>({ initialValue: 0 })
7
8 // No parameters, no return
9 reset(): void {
10 this.value.value = 0
11 }
12
13 // With parameters
14 add(a: uint64, b: uint64): void {
15 this.value.value = a + b
16 }
17
18 // With return value
19 getValue(): uint64 {
20 return this.value.value
21 }
22
23 // Multiple returns (tuple)
24 getStats(): [uint64, uint64] {
25 return [this.min.value, this.max.value]
26 }
27}
Transaction Context
Current Transaction
TypeScript
1import { Txn, Global } from '@algorandfoundation/algorand-typescript'
2
3// Transaction properties
4const sender = Txn.sender // Sender address
5const amount = Txn.amount // Payment amount
6const appId = Txn.applicationId // Current app ID
7const args = Txn.applicationArgs // Raw arguments
8
9// Global properties
10const time = Global.latestTimestamp // Current timestamp
11const round = Global.round // Current round
12const groupSize = Global.groupSize // Group size
Inner Transactions
TypeScript
1import { Contract, GlobalState, Address, Asset, Txn, assert, sendPayment, sendAssetTransfer, uint64 } from '@algorandfoundation/algorand-typescript'
2
3class Treasury extends Contract {
4 owner = GlobalState<Address>()
5
6 withdraw(amount: uint64, receiver: Address): void {
7 assert(Txn.sender === this.owner.value)
8
9 sendPayment({
10 receiver: receiver,
11 amount: amount
12 })
13 }
14
15 sendToken(asset: Asset, amount: uint64, receiver: Address): void {
16 sendAssetTransfer({
17 xferAsset: asset,
18 assetReceiver: receiver,
19 assetAmount: amount
20 })
21 }
22}
Assertions & Errors
TypeScript
1import { Contract, GlobalState, Address, Txn, assert, err, uint64 } from '@algorandfoundation/algorand-typescript'
2
3class Guarded extends Contract {
4 owner = GlobalState<Address>()
5 balance = GlobalState<uint64>({ initialValue: 0 })
6
7 // Assert with message
8 withdraw(amount: uint64): void {
9 assert(amount > 0, 'Amount must be positive')
10 assert(Txn.sender === this.owner.value, 'Not authorized')
11 assert(this.balance.value >= amount, 'Insufficient balance')
12
13 this.balance.value -= amount
14 }
15
16 // Unconditional error
17 deprecated(): void {
18 err('This method is deprecated')
19 }
20}
Structs
Define custom data structures:
TypeScript
1import { Contract, Struct, BoxMap, Address, Txn, uint64 } from '@algorandfoundation/algorand-typescript'
2
3// Define a struct
4class UserProfile extends Struct {
5 name: string
6 balance: uint64
7 isActive: boolean
8}
9
10class Users extends Contract {
11 profiles = BoxMap<Address, UserProfile>({ prefix: 'p' })
12
13 createProfile(name: string): void {
14 this.profiles(Txn.sender).value = new UserProfile({
15 name: name,
16 balance: 0,
17 isActive: true
18 })
19 }
20}
Best Practices
- Use descriptive names - Make code self-documenting
- Validate inputs early - Assert at the start of methods
- Use appropriate types - uint64 for numbers, Address for addresses
- Document with comments - Add JSDoc comments for methods
- Test thoroughly - Use Ghost testing before deployment