Arweave AO (Actor Oriented) uses the Lua programming language to describe processes. This article explains the basic syntax for writing Lua in AO and AO-specific patterns.
Variables and Data Types
Basic Variable Declaration
-- Global variables
MyVariable = "Hello World"
Counter = 42
IsActive = true
-- Local variables
local localVar = "Local variable"
local number = 123
Data Types
Lua is a dynamically typed language that supports the following data types:
-- Numeric types
local integer = 42
local float = 3.14
local scientific = 1.23e-4
-- String types
local str1 = "Double quotes"
local str2 = 'Single quotes'
local multiline = [[
Multi-line
strings are possible
]]
-- Boolean values
local isTrue = true
local isFalse = false
-- nil (undefined value)
local nothing = nil
Tables (Arrays and Dictionaries)
Lua tables are flexible data structures that function as both arrays and dictionaries.
Arrays
-- Arrays (indices start from 1)
local fruits = {"apple", "banana", "orange"}
print(fruits[1]) -- "apple"
print(#fruits) -- 3 (array length)
Dictionaries (Associative Arrays)
-- Dictionary
local person = {
name = "John",
age = 25,
active = true
}
-- Access methods
print(person.name) -- "John"
print(person["age"]) -- 25
Composite Tables
-- Mixed arrays and dictionaries
local mixed = {
"first", -- [1]
"second", -- [2]
name = "Mixed", -- ["name"]
count = 100 -- ["count"]
}
-- Nested tables
local config = {
database = {
host = "localhost",
port = 5432,
credentials = {
username = "admin",
password = "secret"
}
}
}
Functions
Basic Function Definition
-- Regular function
function greet(name)
return "Hello, " .. name
end
-- Local function
local function add(a, b)
return a + b
end
-- Anonymous function
local multiply = function(x, y)
return x * y
end
Advanced Function Features
-- Multiple return values
function getCoordinates()
return 10, 20
end
local x, y = getCoordinates()
-- Variable arguments
function sum(...)
local args = {...}
local total = 0
for i = 1, #args do
total = total + args[i]
end
return total
end
-- Usage example
local result = sum(1, 2, 3, 4, 5) -- 15
Control Structures
Conditional Statements
local score = 85
if score >= 90 then
print("Excellent")
elseif score >= 70 then
print("Good")
else
print("Needs improvement")
end
Loop Processing
-- Numeric loop
for i = 1, 10 do
print("Count: " .. i)
end
-- Reverse loop
for i = 10, 1, -1 do
print(i)
end
-- Array iteration
local items = {"a", "b", "c"}
for i = 1, #items do
print(i, items[i])
end
-- Key-value pair iteration (all elements)
local data = {name = "John", age = 30, city = "Tokyo"}
for key, value in pairs(data) do
print(key, value)
end
-- Array-specific iteration (consecutive indices only)
for index, value in ipairs(items) do
print(index, value)
end
-- while statement
local count = 0
while count < 5 do
print("Count: " .. count)
count = count + 1
end
AO-Specific Syntax
AO has several special rules in addition to regular Lua.
Persisted Variables
-- Global variables starting with capital letters are automatically persisted
PersistentData = PersistentData or {}
TotalSupply = 1000000
Balances = Balances or {}
-- Conditional initialization pattern (for process restart compatibility)
if not Owner then
Owner = ao.env.Process.Owner
end
AO Built-in Functions
-- Message sending
ao.send({
Target = "ProcessID",
Tags = {
Action = "Transfer",
Amount = "100"
},
Data = "Additional data"
})
-- Environment variable access
local processId = ao.id
local owner = ao.env.Process.Owner
Message Handlers
AO's characteristic message processing system.
Basic Handler
Handlers.add(
"balance_check", -- Handler name
Handlers.utils.hasMatchingTag("Action", "Balance"), -- Condition
function(msg) -- Processing function
local account = msg.Tags.Account or msg.From
local balance = Balances[account] or 0
ao.send({
Target = msg.From,
Tags = {
Action = "Balance-Response",
Balance = tostring(balance)
}
})
end
)
Handlers with Complex Conditions
-- Custom condition function
local function isValidTransfer(msg)
return msg.Tags.Action == "Transfer" and
msg.Tags.To and
msg.Tags.Amount and
tonumber(msg.Tags.Amount) > 0
end
Handlers.add(
"transfer",
isValidTransfer,
function(msg)
local from = msg.From
local to = msg.Tags.To
local amount = tonumber(msg.Tags.Amount)
-- Transfer processing
if Balances[from] and Balances[from] >= amount then
Balances[from] = Balances[from] - amount
Balances[to] = (Balances[to] or 0) + amount
ao.send({
Target = msg.From,
Tags = { Action = "Transfer-Success" }
})
else
ao.send({
Target = msg.From,
Tags = { Action = "Transfer-Error", Error = "Insufficient balance" }
})
end
end
)
Common Patterns
Safe Type Conversion
-- Safe numeric conversion
local function safeToNumber(value, default)
local num = tonumber(value)
return num and num or (default or 0)
end
-- Safe string conversion
local function safeToString(value)
return tostring(value or "")
end
Validation Functions
local function validateTransfer(from, to, amount)
-- Sender/recipient check
if not from or not to or from == to then
return false, "Invalid sender or recipient"
end
-- Amount check
local numAmount = tonumber(amount)
if not numAmount or numAmount <= 0 then
return false, "Invalid amount"
end
-- Balance check
if not Balances[from] or Balances[from] < numAmount then
return false, "Insufficient balance"
end
return true, numAmount
end
-- Usage example
local isValid, amountOrError = validateTransfer(msg.From, msg.Tags.To, msg.Tags.Amount)
if not isValid then
ao.send({
Target = msg.From,
Tags = { Action = "Error", Message = amountOrError }
})
return
end
Utility Functions
-- Table copying
local function deepCopy(original)
local copy = {}
for key, value in pairs(original) do
if type(value) == "table" then
copy[key] = deepCopy(value)
else
copy[key] = value
end
end
return copy
end
-- Check if table is empty
local function isEmpty(tbl)
return next(tbl) == nil
end
-- Search for element in array
local function findInArray(array, value)
for i, v in ipairs(array) do
if v == value then
return i
end
end
return nil
end
Error Handling
pcall (Protected Call)
-- Safe function execution
local success, result = pcall(function()
return someRiskyFunction()
end)
if success then
print("Success: " .. tostring(result))
else
print("Error: " .. tostring(result))
end
assert
-- Condition check
local function divide(a, b)
assert(type(a) == "number", "First argument must be a number")
assert(type(b) == "number", "Second argument must be a number")
assert(b ~= 0, "Cannot divide by zero")
return a / b
end
Custom Error Handling
local function safeExecute(func, errorHandler)
local success, result = pcall(func)
if success then
return result
else
if errorHandler then
return errorHandler(result)
else
print("An error occurred: " .. tostring(result))
return nil
end
end
end
-- Usage example
local result = safeExecute(
function() return calculateSomething() end,
function(error) return "Calculation failed: " .. error end
)
Practical Example: Simple Token Contract
Here's a practical example combining the knowledge covered so far:
-- Token basic information
TokenName = "MyToken"
TokenSymbol = "MTK"
TotalSupply = 1000000
Balances = Balances or {}
-- Initialization
if not Owner then
Owner = ao.env.Process.Owner
Balances[Owner] = TotalSupply
end
-- Utility functions
local function hasBalance(account, amount)
return Balances[account] and Balances[account] >= amount
end
-- Balance inquiry handler
Handlers.add(
"balance",
Handlers.utils.hasMatchingTag("Action", "Balance"),
function(msg)
local account = msg.Tags.Account or msg.From
local balance = Balances[account] or 0
ao.send({
Target = msg.From,
Tags = {
Action = "Balance-Response",
Account = account,
Balance = tostring(balance)
}
})
end
)
-- Transfer handler
Handlers.add(
"transfer",
Handlers.utils.hasMatchingTag("Action", "Transfer"),
function(msg)
local from = msg.From
local to = msg.Tags.To
local amount = tonumber(msg.Tags.Amount)
-- Validation
if not to or not amount or amount <= 0 then
ao.send({
Target = from,
Tags = { Action = "Transfer-Error", Error = "Invalid parameters" }
})
return
end
if not hasBalance(from, amount) then
ao.send({
Target = from,
Tags = { Action = "Transfer-Error", Error = "Insufficient balance" }
})
return
end
-- Execute transfer
Balances[from] = Balances[from] - amount
Balances[to] = (Balances[to] or 0) + amount
ao.send({
Target = from,
Tags = {
Action = "Transfer-Success",
From = from,
To = to,
Amount = tostring(amount)
}
})
end
)
Summary
Lua programming in AO requires understanding the following key points in addition to regular Lua knowledge:
- Persistence concept: Automatic persistence of global variables starting with capital letters
- Message-driven architecture: Asynchronous processing using Handlers
- State management: Conditional initialization for process restart compatibility
- Error handling: Robust error handling in distributed environments
Understanding these concepts enables you to build efficient and secure processes on the AO platform. While AO's distributed computing environment requires asynchronous, message-based thinking that differs from traditional synchronous programming, Lua's simple syntax helps reduce that complexity.
Top comments (2)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.