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

Build Your First Contract

A complete step-by-step tutorial to build, test, and deploy a counter smart contract.

What you'll build

A counter contract with the following features:

  • Increment and decrement functionality
  • Owner-only reset capability
  • Event logging for state changes
  • Full test coverage using Ghost testing

1. Project Setup

First, let's create a new project:

  1. Go to Projects
  2. Click New Project
  3. Name it "counter-tutorial"
  4. Select Algorand TypeScript
  5. Click Create

You'll be taken to the editor with a starter template.

2. Define the Contract Structure

Replace the starter code with our counter contract. Let's build it piece by piece.

Import and Class Definition

counter.algo.ts TypeScript
1import { Contract } from '@algorandfoundation/algorand-typescript'
2
3class Counter extends Contract {
4  // Contract implementation goes here
5}

Every PuyaTS contract extends the Contract base class from the Algorand TypeScript library.

3. Add State Variables

Our counter needs to store two pieces of data:

counter.algo.ts TypeScript
1import { Contract, GlobalState, Address, uint64 } from '@algorandfoundation/algorand-typescript'
2
3class Counter extends Contract {
4  // The current count value
5  count = GlobalState<uint64>({ initialValue: 0 })
6
7  // The owner who can reset the counter
8  owner = GlobalState<Address>()
9}
Note
GlobalState stores data persistently on the blockchain. The initialValue is set when the contract is first created.

4. Add the Create Method

The createApplication method runs when the contract is first deployed:

counter.algo.ts TypeScript
1import { Contract, GlobalState, Address, Txn, uint64 } from '@algorandfoundation/algorand-typescript'
2
3class Counter extends Contract {
4  count = GlobalState<uint64>({ initialValue: 0 })
5  owner = GlobalState<Address>()
6
7  // Called when contract is created
8  createApplication(): void {
9    // Set the deployer as the owner
10    this.owner.value = Txn.sender
11  }
12}

Txn.sender gives us the address of whoever is calling the contract - in this case, the deployer.

5. Add Counter Methods

Now let's add the core functionality:

counter.algo.ts TypeScript
1import {
2  Contract,
3  GlobalState,
4  Address,
5  Txn,
6  assert,
7  uint64
8} from '@algorandfoundation/algorand-typescript'
9
10class Counter extends Contract {
11  count = GlobalState<uint64>({ initialValue: 0 })
12  owner = GlobalState<Address>()
13
14  createApplication(): void {
15    this.owner.value = Txn.sender
16  }
17
18  // Increment the counter by 1
19  increment(): void {
20    this.count.value = this.count.value + 1
21  }
22
23  // Decrement the counter by 1
24  decrement(): void {
25    assert(this.count.value > 0, 'Counter cannot go below zero')
26    this.count.value = this.count.value - 1
27  }
28
29  // Get the current count
30  getCount(): uint64 {
31    return this.count.value
32  }
33
34  // Reset to zero (owner only)
35  reset(): void {
36    assert(Txn.sender === this.owner.value, 'Only owner can reset')
37    this.count.value = 0
38  }
39}
Using assert()
The assert() function checks a condition and fails the transaction with an error message if it's false. Use it for input validation and access control.

6. Build the Contract

With our code complete, let's compile it:

  1. Click the Build button (or press Cmd/Ctrl + B)
  2. Wait for compilation to complete
  3. Check the build panel for success or errors

If successful, you'll see artifacts named after your contract:

  • Counter.approval.teal - The main contract logic
  • Counter.clear.teal - The clear state program
  • Counter.arc56.json - The ARC-56 specification (ABI)
  • CounterClient.ts - Generated TypeScript client
  • Counter.docs.md - Human-readable documentation
Build Errors?
If you see errors, check for typos in import statements or method names. The error messages will tell you the line number and issue.

7. Test with Ghost Testing

Before deploying, let's test our contract without using real blockchain resources.

  1. Click the Ghost tab in the right panel
  2. Click New Test
  3. Add test calls in sequence:
    • increment() - Should succeed
    • getCount() - Should return 1
    • increment() - Should succeed
    • getCount() - Should return 2
    • decrement() - Should succeed
    • getCount() - Should return 1
  4. Click Run Test

Ghost testing shows you:

  • State changes - What global/local state was modified
  • Return values - What each method returned
  • Opcode cost - How much computation each call used
  • Execution trace - Step-by-step execution details

8. Test Edge Cases

Let's verify our safety checks work:

Test: Decrement Below Zero

  1. Create a new test
  2. Add decrement() without any increments
  3. Run the test - it should fail with "Counter cannot go below zero"

Test: Unauthorized Reset

  1. Create a new test
  2. Change the test sender to a different address
  3. Add reset()
  4. Run the test - it should fail with "Only owner can reset"
Tip
Testing failure cases is just as important as testing success cases. Make sure your access controls work!

9. Deploy to Testnet

Once tests pass, let's deploy to Algorand Testnet:

  1. Go to the Deploy panel
  2. Select Algorand Testnet as the network
  3. Configure a signing account:
    • Use a connected wallet, OR
    • Add a signing account in Settings (with a funded testnet account)
  4. Click Deploy
  5. Review and sign the transaction
Need Testnet ALGO?
Get free testnet ALGO from the Algorand Testnet Dispenser.

After deployment, you'll see:

  • App ID - Your contract's unique identifier
  • App Address - The contract's account address
  • Transaction ID - The deployment transaction

10. Interact with Your Contract

Now you can call your deployed contract:

  1. Go to Explorer
  2. Enter your App ID
  3. You'll see the contract's current state and available methods
  4. Click on a method to call it

Try calling increment() a few times, then check getCount() to see the updated value!

Complete Code

Here's the final contract for reference:

counter.algo.ts TypeScript
1import {
2  Contract,
3  GlobalState,
4  Address,
5  Txn,
6  assert,
7  uint64
8} from '@algorandfoundation/algorand-typescript'
9
10/**
11 * A simple counter contract with owner-only reset.
12 */
13class Counter extends Contract {
14  /** Current counter value */
15  count = GlobalState<uint64>({ initialValue: 0 })
16
17  /** Address that can reset the counter */
18  owner = GlobalState<Address>()
19
20  /** Initialize the contract and set deployer as owner */
21  createApplication(): void {
22    this.owner.value = Txn.sender
23  }
24
25  /** Increment the counter by 1 */
26  increment(): void {
27    this.count.value = this.count.value + 1
28  }
29
30  /** Decrement the counter by 1 (cannot go below 0) */
31  decrement(): void {
32    assert(this.count.value > 0, 'Counter cannot go below zero')
33    this.count.value = this.count.value - 1
34  }
35
36  /** Get the current counter value */
37  getCount(): uint64 {
38    return this.count.value
39  }
40
41  /** Reset counter to 0 (owner only) */
42  reset(): void {
43    assert(Txn.sender === this.owner.value, 'Only owner can reset')
44    this.count.value = 0
45  }
46}

Next Steps

Congratulations! You've built, tested, and deployed a complete smart contract. Continue learning: