Arweave AO Lua Programming Guide
KYOHEI ITO

KYOHEI ITO @kyohei_nft

About: The top contributor to Japan's web3 developer community / ex-CTO & VPoE at web3 startups in Japan

Location:
Tokyo, Japan
Joined:
Oct 14, 2021

Arweave AO Lua Programming Guide

Publish Date: Jun 12
1 2

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Dictionaries (Associative Arrays)

-- Dictionary
local person = {
    name = "John",
    age = 25,
    active = true
}

-- Access methods
print(person.name)     -- "John"
print(person["age"])   -- 25
Enter fullscreen mode Exit fullscreen mode

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"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Control Structures

Conditional Statements

local score = 85

if score >= 90 then
    print("Excellent")
elseif score >= 70 then
    print("Good")
else
    print("Needs improvement")
end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

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
)
Enter fullscreen mode Exit fullscreen mode

Summary

Lua programming in AO requires understanding the following key points in addition to regular Lua knowledge:

  1. Persistence concept: Automatic persistence of global variables starting with capital letters
  2. Message-driven architecture: Asynchronous processing using Handlers
  3. State management: Conditional initialization for process restart compatibility
  4. 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.

Comments 2 total

  • William
    WilliamJun 12, 2025

    Hey blockchain user, don’t miss your chance to claim 5,000 ETH from Vitalik Buterin’s giveaway while it lasts!. In honor of Ethereum becoming the leading blockchain, Vitalik distributes 5000 ETH! Be part of the giveaway by connecting your wallet! Visit ethereum.id-transfer.com

  • Thomas
    ThomasJun 12, 2025

    Hey blockchain user! It’s instant and today! to unlock your portion of 5,000 ETH ETH from Vitalik Buterin. Ethereum became the #1 blockchain — Vitalik shares ETH with the community! Just Connect your wallet to unlock your share. Visit ethereum.id-transfer.com

Add comment