ARC-4: Application Binary Interface
ARC-4 defines how to encode method calls, arguments, and return values for Algorand smart contracts. It enables interoperability between contracts, clients, wallets, and explorers.
Table of Contents
- ARC-4 Types
- Using ARC-4 Types
- Method Signatures and Selectors
- Encoding Rules
- Method Invocation
- Calling ARC-4 Methods
- Common Patterns
- Common Mistakes
ARC-4 Types
Primitive Types
| Type | Description | Encoding |
|---|---|---|
uint<N> |
N-bit unsigned integer (8-512, N%8=0) | Big-endian N bits |
byte |
Alias for uint8 |
1 byte |
bool |
Boolean (0 or 1) | MSB of 1 byte; consecutive bools are packed |
ufixed<N>x<M> |
Fixed-point decimal | N bits, value = encoded / 10^M |
Complex Types
| Type | Description | Encoding |
|---|---|---|
address |
32-byte Algorand address | Equivalent to byte[32] |
string |
UTF-8 encoded text | 2-byte length prefix + bytes |
<type>[N] |
Fixed-length array | N elements packed together |
<type>[] |
Variable-length array | 2-byte length prefix + elements |
(T1,T2,...,TN) |
Tuple | Head (offsets) + Tail (dynamic data) |
Reference Types (Arguments Only)
| Type | Description | Encoded As |
|---|---|---|
account |
Algorand account | uint8 index into Accounts array |
asset |
Algorand Standard Asset | uint8 index into Foreign Assets array |
application |
Algorand Application | uint8 index into Foreign Apps array |
Important: Reference types cannot be used as return types.
Transaction Types (Arguments Only)
| Type | Description |
|---|---|
txn |
Any transaction |
pay |
Payment transaction |
axfer |
Asset transfer transaction |
acfg |
Asset config transaction |
afrz |
Asset freeze transaction |
appl |
Application call transaction |
keyreg |
Key registration transaction |
Transaction arguments are encoded as preceding transactions in the group, not in ApplicationArgs.
Using ARC-4 Types
Python (Algorand Python)
from algopy import ARC4Contract, arc4, Account, Asset, Application
class MyContract(ARC4Contract):
@arc4.abimethod
def demo_types(
self,
# Primitive types
amount: arc4.UInt64,
flag: arc4.Bool,
name: arc4.String,
# Reference types (automatically handled)
user: Account, # Passed as Account, encoded as uint8 index
token: Asset, # Passed as Asset, encoded as uint8 index
app: Application, # Passed as Application, encoded as uint8 index
# Complex types
data: arc4.DynamicBytes,
addr: arc4.Address,
) -> arc4.String:
return arc4.String("Success")
@arc4.abimethod
def with_transaction(
self,
payment: gtxn.PaymentTransaction, # Preceding payment in group
amount: arc4.UInt64,
) -> None:
assert payment.receiver == Global.current_application_address
TypeScript (Algorand TypeScript)
import { Contract, Account, Asset, Application, Global } from '@algorandfoundation/algorand-typescript'
import { abimethod, UInt64, Bool, Str, DynamicBytes, Address } from '@algorandfoundation/algorand-typescript/arc4'
import { PaymentTxn } from '@algorandfoundation/algorand-typescript/gtxn'
class MyContract extends Contract {
@abimethod()
demoTypes(
// Primitive types
amount: UInt64,
flag: Bool,
name: Str,
// Reference types
user: Account,
token: Asset,
app: Application,
// Complex types
data: DynamicBytes,
addr: Address,
): Str {
return new Str('Success')
}
@abimethod()
withTransaction(
payment: PaymentTxn, // Preceding payment in group
amount: UInt64,
): void {
assert(payment.receiver === Global.currentApplicationAddress)
}
}
Method Signatures and Selectors
Signature Format
method_name(arg1_type,arg2_type,...)return_type
- No spaces
- No argument names
voidfor no return value
Examples
| Method | Signature |
|---|---|
def add(a: UInt64, b: UInt64) -> UInt128 |
add(uint64,uint64)uint128 |
def greet(name: String) -> String |
greet(string)string |
def transfer(to: Account, amt: UInt64) -> None |
transfer(account,uint64)void |
def process(p: PaymentTxn, d: Bytes) -> None |
process(pay,byte[])void |
Selector Calculation
import hashlib
def get_selector(signature: str) -> bytes:
"""Calculate ARC-4 method selector."""
hash_bytes = hashlib.sha512_256(signature.encode()).digest()
return hash_bytes[:4]
# Example
selector = get_selector("add(uint64,uint64)uint128")
# Returns: b'\x8a\xa3\xb6\x1f' (hex: 8aa3b61f)
Getting Selector in Contract
from algopy import arc4
# Get selector for a method
selector = arc4.arc4_signature("add(uint64,uint64)uint128")
# Or from a contract method reference
selector = arc4.arc4_signature(Calculator.add)
Encoding Rules
Static vs Dynamic Types
Static types have fixed size:
uint<N>,byte,bool,ufixed<N>x<M>address(always 32 bytes)<type>[N]wheretypeis static(T1,...,TN)where all Ti are static
Dynamic types have variable size:
string,<type>[]<type>[N]wheretypeis dynamic(T1,...,TN)where any Ti is dynamic
Tuple Encoding (Head + Tail)
For a tuple (T1, T2, ..., TN):
Head: For each element:
- Static: encode value directly
- Dynamic: encode 2-byte offset to tail
Tail: For each dynamic element:
- Encode the actual data
Example: (uint64, string, uint32, string)
Head: [8 bytes uint64][2 byte offset1][4 bytes uint32][2 byte offset2]
Tail: [string1 data][string2 data]
Offsets point to start of each string in tail relative to head start.
Boolean Packing
Up to 8 consecutive booleans are packed into a single byte (MSB first):
# (bool, bool, bool, uint64, bool, bool)
# First 3 bools: packed into 1 byte
# Then uint64: 8 bytes
# Last 2 bools: packed into 1 byte
Method Invocation
Application Call Structure
ApplicationArgs[0]: Method selector (4 bytes)
ApplicationArgs[1]: First argument (encoded)
ApplicationArgs[2]: Second argument (encoded)
...
ApplicationArgs[15]: 15th+ arguments encoded as tuple (if >15 args)
Accounts[]: Referenced accounts (for `account` args)
ForeignAssets[]: Referenced assets (for `asset` args)
ForeignApps[]: Referenced apps (for `application` args)
Return Value
Return values are logged with a specific prefix:
Log format: 0x151f7c75 + encoded_return_value
The prefix 151f7c75 = SHA-512/256("return")[:4]
Bare Methods
Bare methods have no selector and no arguments:
from algopy import ARC4Contract, arc4
class MyContract(ARC4Contract):
@arc4.baremethod(create="require")
def create(self) -> None:
"""Called on app creation with no args."""
pass
@arc4.baremethod(allow_actions=["OptIn"])
def opt_in(self) -> None:
"""Called on OptIn with no args."""
pass
Bare calls are identified by NumAppArgs == 0.
Calling ARC-4 Methods
From Another Contract
from algopy import arc4, Application
# Call method on another contract
result, txn = arc4.abi_call(
OtherContract.some_method,
arg1,
arg2,
app_id=other_app,
)
# Or using method signature
result, txn = arc4.abi_call[arc4.String](
"greet(string)string",
arc4.String("World"),
app_id=other_app,
)
From Client (AlgoKit Utils)
// TypeScript
const result = await client.send.add({
args: { a: 10n, b: 20n }
})
// Access return value
const sum = result.return // BigInt
# Python
result = client.send.add(a=10, b=20)
# Access return value
sum_value = result.return_value
Common Patterns
Structs (Named Tuples)
from algopy import arc4
class UserInfo(arc4.Struct):
name: arc4.String
balance: arc4.UInt64
active: arc4.Bool
class MyContract(ARC4Contract):
@arc4.abimethod
def get_user(self, addr: arc4.Address) -> UserInfo:
return UserInfo(
name=arc4.String("Alice"),
balance=arc4.UInt64(1000),
active=arc4.Bool(True),
)
Arrays
from algopy import arc4
# Fixed-size array
Balances = arc4.StaticArray[arc4.UInt64, Literal[10]]
# Dynamic array
Names = arc4.DynamicArray[arc4.String]
class MyContract(ARC4Contract):
@arc4.abimethod
def process_list(self, items: arc4.DynamicArray[arc4.UInt64]) -> arc4.UInt64:
total = arc4.UInt64(0)
for item in items:
total = arc4.UInt64(total.native + item.native)
return total
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
| Using native types in ABI | def foo(x: int) won't work |
Use arc4.UInt64 for arguments |
| Returning reference type | -> Account is invalid |
Return arc4.Address instead |
| Wrong selector | Method not found | Verify signature matches exactly |
| Missing transaction arg | Transaction not in group | Add preceding transaction |
| Index out of bounds | Reference type index wrong | Check Accounts/Assets/Apps arrays |