Algorand TypeScript SDK
A partial implementation of TypeScript for the Algorand Virtual Machine
What is Algorand TypeScript?
Algorand TypeScript is a partial implementation of the TypeScript programming language that runs on the Algorand Virtual Machine (AVM). It includes a statically typed framework for developing Algorand smart contracts and logic signatures, with TypeScript interfaces to underlying AVM functionality that works with standard TypeScript tooling.
It maintains the syntax and semantics of TypeScript, so a developer who knows TypeScript can make safe assumptions about the behavior of the compiled code when running on the AVM. Algorand TypeScript is also executable TypeScript that can be run and debugged on a Node.js virtual machine with transpilation to EcmaScript and run from automated tests.
Benefits of Using Algorand TypeScript
Rapid Development
TypeScript's concise syntax allows for quick prototyping and iteration of smart contract ideas.
Lower Barrier to Entry
TypeScript's popularity means more developers can transition into blockchain development without learning a new language.
Ease of Use
Designed to work with standard TypeScript tooling, making it easy for TypeScript developers to start building smart contracts.
Efficiency
Compiled by PuyaTS optimizer that ensures AVM bytecode execution semantics match TypeScript code.
Modularity
Supports modular solution components, facilitating efficient parallel development by small teams.
TypeScript Implementation for AVM
Algorand TypeScript maintains the syntax and semantics of TypeScript, supporting a subset of the language that will grow over time. However, due to the restricted nature of the AVM, it will never be a complete implementation.
Algorand TypeScript is compiled for execution on the AVM by PuyaTs, a TypeScript frontend for the Puya optimizing compiler that ensures the resulting AVM bytecode execution semantics that match the given TypeScript code. PuyaTs produces output directly compatible with AlgoKit-typed clients to simplify deployment and calling.
Key Differences from Standard TypeScript
Types Affect Behavior
In TypeScript, using types, as expressions, or type arguments don't affect the compiled JS. In Algorand TypeScript, however, types fundamentally change the compiled TEAL. For example, the literal expression 1 results in int 1 in TEAL, but 1 as uint8 results in byte 0x01. This also means that arithmetic is done differently on these numbers and they have different overflow protections.
// Standard: 1 results in int 1 in TEAL const value = 1; // Typed: 1 as uint8 results in byte 0x01 const typedValue = 1 as uint8;
Numbers Can Be Bigger
In TypeScript, numeric literals with absolute values equal to 2^53 or greater are too large to be represented accurately as integers. In Algorand TypeScript, however, numeric literals can be much larger (up to 2^512) if properly type cast as uint512.
Types May Be Required
All JavaScript is valid TypeScript, but that is not the case with Algorand TypeScript. In certain cases, types are required and the compiler will throw an error if they are missing. For example, types are always required when defining a method or when defining an array.
Supported Primitives
Algorand TypeScript supports several primitive types and data structures that are optimized for blockchain operations. These primitives are designed to work efficiently with the AVM while maintaining familiar TypeScript syntax.
Static Arrays
Static arrays are the most efficient and capable type of arrays in TypeScript for Algorand development. They have a fixed length and offer improved performance and type safety.
// Example: Array of 10 unsigned 64-bit integers
StaticArray<uint64, 10>
// Static arrays can be partially initialized
const x: StaticArray<uint64, 3> = [1] // [1, undefined, undefined]
const y: StaticArray<uint64, 3> = [1, 0, 0] // [1, 0, 0]
// Iteration example
staticArrayIteration(): uint64 {
const a: StaticArray<uint64, 3> = [1, 2, 3];
let sum = 0;
for (const v of a) {
sum += v;
}
return sum; // 6
}Supported Methods: length
Dynamic Arrays
Dynamic arrays are supported in Algorand TypeScript. Algorand TypeScript will chop off the length prefix of dynamic arrays during runtime. Nested dynamic types are encoded as dynamic tuples.
Supported Methods: pop, push, splice, length
Objects
Objects can be defined much like in TypeScript. The same efficiencies of static vs dynamic types also apply to objects. Under the hood, Algorand TypeScript objects are just tuples.
// These result in the same byteslice:
[uint64, uint8]
{ foo: uint64, bar: uint8 }
// Example usage
type MyType = { foo: uint64, bar: uint8 }
const x: MyType = { foo: 1, bar: 2}
const y: MyType = { bar: 2, foo: 1 }Number Types
Integers
The Algorand Virtual Machine (AVM) natively supports unsigned 64-bit integers (uint64). Using uint64 for numeric operations ensures optimal performance. You can define specific-width unsigned integers with the uint<N> generic type.
// Specific-width unsigned integers uint8, uint16, uint32, uint64, uint128, uint256, uint512 // Correct: Unsigned 64-bit integer const n1: UInt<64> = 1; // Correct: Unsigned 8-bit integer const n2: UInt<8> = 1;
Unsigned Fixed-Point Decimals
To represent decimal values, use the ufixed<N, M> generic type. The first type argument is the bit width, which must be divisible by 8. The second argument is the number of decimal places, which must be less than 160.
// Correct: Unsigned 64-bit with two decimal places const price: UFixed<64, 2> = 1.23; // Incorrect: Missing type definition const invalidPrice = 1.23; // ERROR: Missing type // Incorrect: Precision exceeds defined decimal places const invalidPrice2: UFixed<64, 2> = 1.234; // ERROR
Math Operations
Algorand TypeScript requires explicit handling of math operations to ensure type safety and prevent overflow errors. Basic arithmetic operations (+, -, *, /) are supported but require explicit type handling.
// Results must be explicitly typed using:
// Constructor
const sum = Uint64(x + y);
// Type annotation
const sum: uint64 = x + y;
// Return type annotation
function add(x: uint64, y: uint64): uint64 {
return x + y;
}
// Overflow handling
const a = UintN8(255);
const b = UintN8(255);
const c = UintN8(a + b); // Error: Overflow
// Better performance approach
const a: uint64 = 255;
const b: uint64 = 255;
const c: uint64 = a + b;
return UintN8(c - 255); // Only convert at the endPass by Reference
All arrays and objects are passed by reference even if in contract state, much like TypeScript. Algorand TypeScript, however, will not let a function mutate an array that was passed as an argument. If you wish to pass by value you can use clone.
const x: uint64[] = [1, 2, 3]; const y = x; y[0] = 4; log(y); // [4, 2, 3] log(x); // [4, 2, 3] const z = clone(x); z[1] = 5; log(x); // [4, 2, 3] note x has NOT changed log(z); // [4, 5, 3]
Limitations
- • Dynamic types and booleans are much more expensive to use and have some limitations
- • Nested dynamic arrays (e.g., uint64[][]) are very inefficient and not recommended
- • Functions cannot mutate arrays passed as arguments
- • forEach is not supported - use for...of loops instead
- • Dynamic array splice method is heavy in terms of opcode cost
- • No Object methods are supported in Algorand TypeScript
- • Number-related type errors might not show in IDE and only throw during compilation
- • Static array instantiation using brackets (uint64[10]) is not valid TypeScript syntax
PuyaTs Compiler
Algorand TypeScript is compiled for execution on the AVM by PuyaTs, a TypeScript frontend for the Puya optimizing compiler that ensures the resulting AVM bytecode execution semantics that match the given TypeScript code.
PuyaTs produces output that is directly compatible with AlgoKit typed clients to make deployment and calling easy. This seamless integration allows developers to use familiar TypeScript development workflows while targeting the Algorand blockchain.
Testing and Debugging
The algorand-typescript-testing package allows for efficient unit testing of Algorand TypeScript smart contracts in an offline environment. It emulates key AVM behaviors without requiring a network connection, offering fast and reliable testing capabilities with a familiar TypeScript interface.
// Example test structure
import { AlgodTestClient } from 'algorand-typescript-testing';
describe('MyContract', () => {
it('should execute correctly', () => {
const client = new AlgodTestClient();
// Test your contract logic here
expect(contract.method()).toBe(expectedResult);
});
});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.
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.
Efficient Iteration
Use for...of loops for iterating over arrays, which also enables continue/break functionality.
