Skip to content

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.

/package.json
{
  "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.

/.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).

/test/hyperbeam.test.js
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.

/test/hb.test.js
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 process
  • schedule : schedule a message to a process
  • compute : compute the result of a slot
  • messages : 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.

/HyperBEAM/src/dev_foo.erl
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)
  })
})