Alpha preview. Not for production use.
Working anonymously. to save your work permanently.
Reference

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 integer
  • bytes - Byte array
  • string - UTF-8 string (alias for bytes)
  • boolean - True/false (stored as uint64)

Algorand Types

  • Address - 32-byte Algorand address
  • Asset - ASA reference
  • Application - App reference
  • Account - 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