Build on Algorand
Basic Smart Contract with Typescript
Evolution from TEAL to TypeScript
Traditional Algorand smart contracts use TEAL, a low-level stack-based language.
New TypeScript-based frameworks allow writing smart contracts with familiar syntax:
- Algorand TypeScript: A partial implementation of the TypeScript language running on the Algorand Virtual Machine (AVM).
Key Components
- ARC-4 Standard: Standardized ABI for method calls.
- AlgoKit: Toolkit for development, testing, and deployment.
- Algorand TypeScript: You can find more information about the Benefits of using Algorand TypeScript.
- State Management: Use `GlobalState`, `LocalState`, `Box`, and ‘Scratch Space’ abstractions for on-chain. For more information, see storage documentation.
Core Class Structure
A contract in Algorand TypeScript is defined by declaring a class that extends the `Contract` type exported by `@algorandfoundation/algorand-typescript`. Contracts which extend the `Contract` type are ARC4 compatible contracts. Any `public` methods on the class will be exposed as ABI methods, callable from other contracts and off-chain clients. `private` and `protected` methods can only be called from within the contract itself, or its subclasses. Note that TypeScript methods are `public` by default if no access modifier is present. For more information, see Program Structure documentation
Example: Hello World Contract
import { Contract } from '@algorandfoundation/algorand-typescript'
/**
* An abstract base class for a simple example contract
*/
abstract class Intermediate extends Contract {
/**
* sayBananas method
* @returns The string "Bananas"
*/
public sayBananas(): string {
return `Bananas`
}
}
/**
* A simple hello world example contract
*/
export default class HelloWorld extends Intermediate {
/**
* sayHello method
* @param firstName The first name of the person to greet
* @param lastName THe last name of the person to greet
* @returns The string "Hello {firstName} {lastName"}
*/
public sayHello(firstName: string, lastName: string): string {
const result = `Hello ${firstName} ${lastName}`
return result
}
}
Global Storage
Global or Application storage is a key/value store of bytes or uint64 values stored against a smart contract application. The number of values used must be declared when the application is first created and will affect the minimum balance requirement for the application. For ARC4 contracts this information is captured in the ARC32 and ARC56 specification files and automatically included in deployments.
import {
GlobalState,
Contract,
uint64,
bytes,
Uint64,
contract,
} from '@algorandfoundation/algorand-typescript';
class DemoContract extends Contract {
// The property name 'globalInt' will be used as the key
globalInt = GlobalState<uint64>({ initialValue: Uint64(1) });
// Explicitly override the key
globalBytes = GlobalState<bytes>({ key: 'alternativeKey' });
}
// If using dynamic keys, state must be explicitly reserved
@contract({ stateTotals: { globalBytes: 5 } })
class DynamicAccessContract extends Contract {
test(key: string, value: string) {
// Interact with state using a dynamic key
const dynamicAccess = GlobalState<string>({ key });
dynamicAccess.value = value;
}
}
If you want more information about Storage, check out this document.
Example: Counter Contract
import { Contract, abimethod, GlobalState, Uint64 } from '@algorandfoundation/algorand-typescript'
import type { uint64 } from '@algorandfoundation/algorand-typescript'
/**
* A contract that increments a counter
*/
export default class Counter extends Contract {
public counter = GlobalState<uint64>({ initialValue: Uint64(0) })
/**
* Increments the counter and returns the new value
* @returns The new counter value
*/
@abimethod()
public increment(): uint64 {
this.counter.value = this.counter.value + 1
return this.counter.value
}
}
ARC4 Contract
Contracts which extend the Contract type are ARC4 compatible contracts. Any public methods on the class will be exposed as ABI methods, callable from other contracts and off-chain clients. private and protected methods can only be called from within the contract itself, or its subclasses. Note that TypeScript methods are public by default if no access modifier is present. A contract is considered valid even if it has no methods, though its utility is questionable.
import { Contract } from '@algorandfoundation/algorand-typescript';
class DoNothingContract extends Contract {}
class HelloWorldContract extends Contract {
sayHello(name: string) {
return `Hello ${name}`;
}
}
Best Practices
- Use Static Types: Always define explicit types for arrays, tuples, and objects to leverage TypeScript’s static typing benefits.
- Prefer UInt<64>: Utilize UInt<64> for numeric operations to align with AVM’s native types, enhancing performance and compatibility.
- Use the StaticArray generic type to define static arrays and avoid specifying array lengths using square brackets (e.g., number[10]) as it is not valid TypeScript syntax in this context.
- Limit Dynamic Arrays: Avoid excessive use of dynamic arrays, especially nested ones, to prevent inefficiencies. Also, splice is rather heavy in terms of opcode cost, so it should be used sparingly.
- Immutable Data Structures: Use immutable patterns for arrays and objects. Instead of mutating arrays directly, create new arrays with the desired changes (e.g., myArray = [...myArray, newValue]).
- Efficient Iteration: Use for...of loops for iterating over arrays, which also enables continue/break functionality.
- Type Casting: Use constructors (e.g., UintN8, UintN<64>) rather than as keyword for type casting.
For more information, see the Algorand TypeScript documentation.
Comments
You need to enroll in the course to be able to comment!