Arweave AO Bootcamp 6/8 Custom JavaScript VM on AO
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 Bootcamp 6/8 Custom JavaScript VM on AO

Publish Date: Jun 4
3 1

What Will You Learn in This Chapter?

In this chapter, you'll learn how to develop AOS (Actor Operating System), the first implementation of the AO protocol.
AOS is a lightweight Lua-based actor runtime that combines Actor Model × Persistent Storage × Distributed Messaging into a new execution environment.

You'll gain hands-on experience by writing actual code, going through the complete process from process creation → logic registration → message sending and receiving step by step.

What You'll Master in This Chapter:

  • Overview of AOS and usage of the aoconnect development library
  • Connection methods to MU/SU/CU and process creation flow
  • Defining and registering handlers in Lua
  • Message sending, receiving, and simulation with dryrun
  • Building practical processes like Key-Value stores
  • Best practices for secure and reusable AOS development

What is AOS

AOS (Actor Operating System) is a Lua-based distributed actor execution environment developed as the first implementation of the AO protocol.
It faithfully implements the actor model and messaging structures defined by AO, designed to enable anyone to easily build and deploy actor-based applications.

AOS has the following characteristics:

  1. Lightweight Lua Runtime
  • Lua is an extremely lightweight scripting language also used for embedded applications, making it easy to compile to Wasm.
  1. Full Actor Model Support
  • Each process has independent state and an inbox, communicating with each other through asynchronous messages.
  1. Arweave-based Persistence
  • Process state and message history are permanently stored on Arweave and can be restored at any time.
  1. Dynamic Logic Registration via Eval
  • Even after deployment, you can flexibly extend or modify logic by sending Lua scripts to processes.
  1. Complete Automation via Scripts
  • Process creation, manipulation, and testing can all be controlled from JavaScript through the aoconnect library.

AOS is the ideal entry point for understanding AO's fundamental concepts and quickly building your own distributed actor applications.
In this chapter, we'll learn the complete flow of process development using AOS with actual code examples.

AOS Setup

AOS is one of the first implementations of AO and uses the Lua language.
To use AOS, first install the aoconnect library.

npm i @permaweb/aoconnect
Enter fullscreen mode Exit fullscreen mode

Connecting to Units

To use AOS in a local environment, connect by specifying the URLs for each unit (MU/SU/CU).

const { createDataItemSigner, connect } = require("@permaweb/aoconnect");
const { result, message, spawn, dryrun } = connect({
  MU_URL: "http://localhost:4002",
  CU_URL: "http://localhost:4004",
  GATEWAY_URL: "http://localhost:4000",
});
Enter fullscreen mode Exit fullscreen mode

For mainnet usage, you can connect simply as follows:

const { result, message, spawn, dryrun } = require("@permaweb/aoconnect");
Enter fullscreen mode Exit fullscreen mode

Spawning an AOS Process

To spawn an AOS process, you need the AOS module ID and scheduler wallet address.
Here's the basic code for spawning a process:

const wait = (ms) => new Promise((res) => setTimeout(() => res(), ms));

const pid = await spawn({
  module: AOS_MODULE_TXID, // The module transaction ID you noted earlier
  scheduler: SCHEDULER_WALLET, // Scheduler wallet address
  signer: createDataItemSigner(jwk),
});

await wait(1000);

console.log(`Generated Process ID: ${pid}`);
Enter fullscreen mode Exit fullscreen mode

Once an AO process is spawned, it exists permanently on Arweave and can receive and process messages.

Lua Handler Basics

In AOS, you write process business logic using the Lua language.
The concept of "handlers" is particularly important.
Handlers are functions that execute when a message matching specific conditions is received.

AOS provides the following global variables and modules:

  1. Inbox: Table storing unprocessed messages
  2. Send: Function for sending messages
  3. Spawn: Function for spawning new processes
  4. Handlers: Table for managing handlers
  5. ao: Module providing process information and message sending functionality
  6. json: Module for JSON encoding/decoding

Here's the basic structure of a handler:

Handlers.add(
  "handler-name",  -- Handler identifier
  function(msg)    -- Matching function: determines if a message should be processed by this handler
    return msg.Action == "specific-action"  -- Returns true if condition matches
  end,
  function(msg)    -- Handler function: processes the message
    -- Logic to process the message
    ao.send({Target = msg.From, Data = "response message"})
  end
)
Enter fullscreen mode Exit fullscreen mode

Handlers.utils provides commonly used matching functions:

-- Match messages with matching tags
Handlers.utils.hasMatchingTag("Action", "Get")

-- Match messages with matching data
Handlers.utils.hasMatchingData("ping")

-- Utility for easily sending responses
Handlers.utils.reply("pong")
Enter fullscreen mode Exit fullscreen mode

Creating and Registering Handlers

Let's implement a simple Key-Value store here.
Save the following Lua code to a kv-store.lua file:

local ao = require('ao')
Store = Store or {}

Handlers.add(
   "Get",
   Handlers.utils.hasMatchingTag("Action", "Get"),
   function (msg)
      assert(type(msg.Key) == 'string', 'Key is required!')
      ao.send({ Target = msg.From, Tags = { Value = Store[msg.Key] }})
   end
)

Handlers.add(
   "Set",
   Handlers.utils.hasMatchingTag("Action", "Set"),
   function (msg)
      assert(type(msg.Key) == 'string', 'Key is required!')
      assert(type(msg.Value) == 'string', 'Value is required!')
      Store[msg.Key] = msg.Value
      Handlers.utils.reply("Value stored!")(msg)
   end
)
Enter fullscreen mode Exit fullscreen mode

To add this code to an existing process, use the Eval action as follows:

const { readFileSync } = require("fs");
const { resolve } = require("path");

const lua = readFileSync(resolve(__dirname, "kv-store.lua"), "utf8");
const mid = await message({
  process: pid,
  signer: createDataItemSigner(jwk),
  tags: [{ name: "Action", value: "Eval" }],
  data: lua,
});

const res = await result({ process: pid, message: mid });
console.log(res);
Enter fullscreen mode Exit fullscreen mode

This adds Key-Value store functionality to the process.

Sending and Receiving Messages

To send messages to a process, use the message function:

const mid1 = await message({
  process: pid,
  signer: createDataItemSigner(jwk),
  tags: [
    { name: "Action", value: "Set" },
    { name: "Key", value: "test" },
    { name: "Value", value: "abc" },
  ],
});
const res1 = await result({ process: pid, message: mid1 });
console.log(res1.Messages[0]);
Enter fullscreen mode Exit fullscreen mode

For read-only queries, using the dryrun function is efficient:

const res2 = await dryrun({
  process: pid,
  signer: createDataItemSigner(jwk),
  tags: [
    { name: "Action", value: "Get" },
    { name: "Key", value: "test" },
  ],
});
console.log(res2.Messages[0].Tags);
Enter fullscreen mode Exit fullscreen mode

dryrun doesn't actually save messages to Arweave and simulates using the current state, making it suitable for read operations.

Development Patterns and Best Practices

Here are some best practices for AOS development:

  1. State Management:
  • When using global variables, don't forget initialization (Store = Store or {})
  • Manage large data structures with proper nesting
  1. Error Handling:
  • Use assert to validate inputs
  • Make error messages specific and clear
  1. Modularization:
  • Group related functionality together
  • Create reusable functions
  1. Debugging:
  • Use the ao.log function to output debug information
  • Output complex data structures to logs using json.encode
  1. Security:
  • Always validate untrusted data
  • Perform authentication checks for critical operations
  1. Testing:
    • Create dedicated processes for testing
    • Use dryrun to verify changes before applying to production

Following these practices will help you develop safer and more efficient AOS applications.

Comments 1 total

Add comment