Skip to content

Code Structure

Algorand smart contracts are ordinary Python and TypeScript classes, so the language’s own ways of splitting code across classes and modules apply to them. This page describes six such patterns and what each one costs. All six work in both Algorand Python and Algorand TypeScript, though three take a different form in each language.

Five Layers of a Structured Contract

Only the concrete contract is deployed. The four layers below it are compiled into it, which is why each one adds to the deployed program’s size.

An interface declares the Application Binary Interface (ABI) methods a contract must implement, kept separate from the implementation. Reach for one when several contracts expose the same ABI, such as competing implementations of an Algorand Request for Comments (ARC) standard, and you want the compiler to check that each one is complete.

An ABI declaration constrains method names, argument types and return types. It does not constrain behavior, so implementing an interface is not evidence that a contract follows the standard the interface came from.

Algorand TypeScript

A plain TypeScript interface, with an implements clause on the contract class. The interface is erased at compile time and carries signatures only, so it adds nothing to the deployed program and cannot hold shared state.

An incomplete implementation stops the build with Class 'X' incorrectly implements interface 'I', and nothing is emitted.

Algorand Python

An abstract contract class inheriting both ARC4Contract and ABC, with @abstractmethod on each declared method. The interface sits in the inheritance chain, so it can carry shared state and concrete helper methods as well as signatures.

An incomplete implementation is not an error. The compiler emits nothing for the class, warns that it is implicitly abstract, and exits with status 0, so a build that does not check for the artifacts it expects passes having produced no contract.

A stub describes another application’s ABI so a contract can call it without building method selectors by hand. The compiler derives each selector from the signature, checks the argument types at compile time, and types the return value. Writing one by hand is only necessary for a contract you have no specification for.

A stub is a compile-time description and nothing more. It does not check that the application you call at run time implements those methods, so passing the wrong application ID fails on chain rather than during the build. A stub also calls an application that already exists; a contract can instead compile another contract class, deploy it and call the result within one application call, which Inner Transactions covers.

Algorand TypeScript

An abstract contract class whose methods carry err('stub only') bodies, because the compiler requires every method to have a body. Calls go through arc4.abiCall, which takes the method off the class prototype.

puya-ts-clientgen generates a stub from an ARC-56 specification, and --output-client emits one for a contract you are already compiling.

Algorand Python

A class inheriting both arc4.ARC4Client and typing.Protocol, with decorated method signatures and ... bodies. Both base classes are required. Calls go through arc4.abi_call, which takes the method off the class.

puyapy-clientgen generates a stub from an ARC-56 specification, and --output-client emits one for a contract you are already compiling.

A mixin is a small class holding one reusable behavior, such as an owner check or a pause switch, that a contract inherits alongside others. Mixins that declare storage under the same key overwrite each other silently, so give each mixin its own key prefix.

Algorand TypeScript

There is no native multiple inheritance. A linear stack of behaviors can use chained extends. Combining two independent mixins needs the Polytype package, whose method resolution order is depth first: the whole prototype chain of the first base type is walked before the second is considered. The Algorand TypeScript language guide covers the syntax.

Marking the mixin class abstract keeps it out of the build.

Algorand Python

Mixins compose with ordinary multiple inheritance, following Python’s method resolution order. Each base class’s __init__ must be called explicitly. The Algorand Python language guide covers contract class structure.

Keeping a mixin out of the build takes at least one unimplemented @abstractmethod. Inheriting ABC alone does not do it, and the mixin is then compiled and deployed as a contract in its own right.

A settling mixin wraps the state changes made by the class that inherits it. It reads a value before those changes, lets the subclass act, then reconciles the difference afterwards.

The worked case is minimum balance requirement (MBR) accounting. A contract that holds boxes on behalf of its callers charges each account for the storage it causes the application to hold. The mixin reads the application account’s MBR before the subclass touches its boxes and settles the difference after, so no box size formula is hard-coded anywhere. This works because the Algorand Virtual Machine (AVM) checks the MBR once, at the end of the top-level application call, so the requirement can be exceeded partway through.

The pattern puts two constraints on the classes that inherit it. Every box the subclass creates must belong to the account being charged, because a refund goes to whoever triggers the decrease, and a box shared between two accounts would refund the wrong one. The subclass must also keep its box key prefixes clear of the prefix the mixin uses for its own credit boxes.

Algorand TypeScript

An abstract class extending Contract, with a protected helper the subclass calls after its own state changes. A subclass that overrides an inherited ABI method reaches the original with super.method().

Algorand Python

An ARC4Contract, ABC subclass with a @subroutine helper the subclass calls after its own state changes. A subclass that overrides an inherited ABI method reaches the original by naming the base, as Base.method(self).

Stateless helpers that several contracts need belong in a module of their own rather than copied into each contract.

A shared module is compiled into every contract that imports it. It is not deployed once and shared on chain, so it adds to the program size of each contract separately. Importing a large helper module into a contract already near the program size limit can push it over.

Algorand TypeScript

Ordinary functions exported from a .algo.ts module and imported where they are used. No decorator is needed.

No Algorand-maintained example covers this yet. The puya-ts-utils library listed under Additional Resources has several.

Algorand Python

Each helper is marked @subroutine in a module, and contracts bring them in with ordinary Python imports.

Error codes defined once in a module and imported where needed keep the same string in every contract that raises them, and in the clients that match on them.

Both languages support ARC-65 logged errors, which write a structured ERR:{code}:{message} string before failing the transaction, so a failed transaction carries its error in the algod response. Both compilers check the code format and reject a code or message containing :, the domain separator the standard reserves. Every distinct code and message becomes a byte string in the compiled program, so keep both short.

Algorand TypeScript

Constants in a module, imported where they are raised. For ARC-65 logged errors, loggedAssert and loggedErr.

Algorand Python

Constants in a module, imported where they are raised. For ARC-65 logged errors, logged_assert and logged_err.

TopicAlgorand PythonAlgorand TypeScript
Forcing an implementation@abstractmethod on an ARC4Contract, ABC base; an incomplete subclass is dropped with a warningA plain interface and implements; an incomplete class stops the build
Multiple inheritanceNative, using Python’s method resolution orderChained extends, or classes(A, B) from Polytype, resolved depth first
Keeping a base class undeployedNeeds at least one unimplemented @abstractmethod; inheriting ABC alone still compiles the classThe abstract class modifier is enough
Abstract methodsSupported, and can carry @arc4.abimethod at the same timeNot supported; a method declared abstract has no body, and every method needs one
Generating a call stubpuyapy-clientgen, or --output-client while compilingpuya-ts-clientgen, or --output-client while compiling
ConstructorsEach base class’s __init__ is called explicitlyState is set up with property initializers; one-time setup goes in an onCreate method
  • Polytype, the package Algorand TypeScript uses for multiple inheritance.
  • puya-ts-utils, a community library of shared helper modules and mixins written in Algorand TypeScript.