HyperBEAM
Install WAO & HyperBEAM
yarn add wao
In addition to the WAO SDK, you will need to install HyperBEAM on your local computer (recommended) or a cloud server. The required systems are not the only ones you can install HyperBEAM on. I was, for example, able to install HyperBEAM on Arch Linux by installing the necessary dependencies. You could throw installation errors at LLMs like ChatGPT and Claude and likely be able to figure it out.
If you want to test Mainnet AOS modules, you need to install the WAO fork of HyperBEAM, which includes a custom helper device for testing.
git clone --branch wao https://github.com/weavedb/HyperBEAM.git
Make sure you compiled it and are able to run rebar3 shell
, but you don't need to have it running yet. You might need to add environment variables depending on how you installed it on your system. WAO SDK handles that too.
CMAKE_POLICY_VERSION_MINIMUM=3.5 CC=gcc-12 CXX=g++-12 rebar3 shell
Create a Project
To create a project, you could use npx wao create APP_NAME
,
npx wao create myapp && cd myapp
or you could manually install wao
into your project.
mkdir myapp && cd myapp && yarn init && yarn add wao
mkdir test && touch test/test.js
Make sure your package.json
looks something like the following to enable ES6 and test commands with wasm64
. The wasm64 flag is unnecessary for NodeJS v24 and later. Also, you need to disable concurrency so the test won't try to run multiple HyperBEAM nodes on duplicate ports.
{
"name": "myapp",
"version": "1.0.0",
"type": "module",
"scripts": {
"test": "node --experimental-wasm-memory64 --test --test-concurrency=1",
"test-only": "node --experimental-wasm-memory64 --test-only --test-concurrency=1",
"test-all": "node --experimental-wasm-memory64 --test --test-concurrency=1 test/**/*.test.js"
},
"dependencies": {
"wao": "^0.22.1"
}
}
If your HyperBEAM installation requires environment variables, define them in .env.hyperbeam
.
CC=gcc-12
CXX=g++-12
CMAKE_POLICY_VERSION_MINIMUM=3.5
Write Tests
HyperBEAM
class let you create a HyperBEAM sandbox by starting a fresh HyperBEAM node before testing and shutting it down when tests are complete.
You can interact with the HyperBEAM node with HB
(hbeam.hb
).
import assert from "assert"
import { describe, it, before, after, beforeEach } from "node:test"
import { HyperBEAM } from "wao/test"
/*
The link to your HyperBEAM node directory.
It's relative to your app root folder, not the test folder.
*/
const cwd = "../HyperBEAM"
describe("HyperBEAM", function () {
let hbeam, hb
// start a hyperbeam node and wait till it's ready, reset storage for test
before(async () => {
hbeam = await new HyperBEAM({ cwd, reset: true }).ready()
})
beforeEach(async () => (hb = hbeam.hb))
// kill the node after testing
after(async () => hbeam.kill())
it("should run a HyperBEAM node", async () => {
// change config
await hb.post({ path: "/~meta@1.0/info", test_config: "abc" })
// get config
const { out } = await hb.get({ path: "/~meta@1.0/info" })
assert.equal(out.test_config, "abc")
})
})
You can also skip the HyperBEAM
class and connect with an already running remote node by specifying the node url
to HB
.
import assert from "assert"
import { describe, it, before, after, beforeEach } from "node:test"
import { acc } from "wao/test"
import { HB } from "wao"
const cwd = "../HyperBEAM"
describe("HyperBEAM", function () {
let hb
// using one of the pre-generated accounts from acc for test
beforeEach(async () => {
hb = new HB({ jwk: acc[0].jwk, url: "http://localhost:10001" })
})
it("should connect to a HyperBEAM node", async () => {
// get build info
const build = await hb.g("/~meta@1.0/build")
assert.equal(build.node, "HyperBEAM")
})
})
HyperBEAM has a test device to test basic features with the HB methods.
spawn
: spawn a processschedule
: schedule a message to a processcompute
: compute the result of a slotmessages
: list messages in a process
it("should test test-device@1.0", async () => {
const { pid } = await hb.spawn({ "execution-device": "test-device@1.0" })
const { slot } = await hb.schedule({ pid })
const res = await hb.compute({ pid, slot })
assert.equal(res.results["assignment-slot"], 1)
const {
edges: [ edge0, { node: { assignment, message } }]
} = await hb.messages({ pid, from: 0, to: 1 })
assert.equal(message.Target, pid)
})
AO-Core Pathing and HTTP Message Signatures
It is extremely important to understand the pathing scheme of AO-Core protocol and HTTP Message Signature (RFC 9421) when interacting with HyperBEAM nodes.
get
and post
help you to construct complex messages and send signed requests to HyperBEAM nodes.
it("should interact with meta@1.0", async () => {
// change node configuration
await hb.post({ path: "/~meta@1.0/info", test_config: 123 })
const { out } = await hb.get({ path: "/~meta@1.0/info" })
assert.equal(out.test_config, 123)
})
g
and p
are the shortcut methods for get
and post
.
it("should interact with meta@1.0 #2", async () => {
await hb.p("/~meta@1.0/info", { test_config2: "abc" })
const out = await hb.g("/~meta@1.0/info")
assert.equal(out.test_config2, "abc")
})
Spawn AOS Processes
You can also spawn and interact with AOS processes on HyperBEAM.
const data = `
local count = 0
Handlers.add("Inc", "Inc", function (msg)
count = count + 1
msg.reply({ Data = "Count: "..tostring(count) })
end)
Handlers.add("Get", "Get", function (msg)
msg.reply({ Data = "Count: "..tostring(count) })
end)`
it("should spawn a legacynet compatible AOS process", async () => {
const { pid } = await hb.spawnLegacy()
const { slot } = await hb.scheduleLegacy({ pid, data })
const r = await hb.computeLegacy({ pid, slot })
const { slot: slot2 } = await hb.scheduleLegacy({ pid, action: "Inc" })
const r2 = await hb.computeLegacy({ pid, slot: slot2 })
assert.equal(r2.Messages[0].Data, "Count: 1")
const { slot: slot3 } = await hb.scheduleLegacy({ pid, action: "Inc" })
const r4 = await hb.computeLegacy({ pid, slot: slot3 })
const r3 = await hb.dryrun({ pid, action: "Get" })
assert.equal(r3.Messages[0].Data, "Count: 2")
})
Create Custom Devices
You can create your own custom devices in Erlang, Rust, and C++.
Create your device under /HyperBEAM/src
.
const data = `
local count = 0
Handlers.add("Inc", "Inc", function (msg)
count = count + 1
msg.reply({ Data = "Count: "..tostring(count) })
end)
Handlers.add("Get", "Get", function (msg)
msg.reply({ Data = "Count: "..tostring(count) })
end)`
``
`
Define it in `preloaded_devices` in `/HyperBEAM/src/hb_opts.erl`
```erlang [/HyperBEAM/src/hb_opts.erl]
preloaded_devices => [
#{<<"name">> => <<"ans104@1.0">>, <<"module">> => dev_codec_ans104},
#{<<"name">> => <<"compute@1.0">>, <<"module">> => dev_cu},
...
#{<<"name">> => <<"foo@1.0">>, <<"module">> => dev_foo}
],
Test the device with WAO.
import assert from "assert"
import { describe, it, before, after, beforeEach } from "node:test"
import { HyperBEAM } from "wao"
const cwd = "../HyperBEAM" // HyperBEAM directory
describe("Hyperbeam Custom Devices", function () {
let hbeam, hb
before(async () => {
hbeam = await new HyperBEAM({ cwd, reset: true }).ready()
})
beforeEach(async () => (hb = hbeam.hb))
after(async () => hbeam.kill())
it("should query a custom device", async () => {
const { version } = await hb.g("/~foo@1.0/info")
assert.equal("1.0", version)
})
})