WAO SDK streamlines Arweave/AO development with elegant syntax enhancements and seamless message piping for enjoyable coding experiences. GraphQL operations are also made super easy.
Additionally, it includes a drop-in replacement for aoconnect
, allowing the testing of lua scripts 1000x faster than the mainnet by emulating AO units in memory. It's even 100x faster than testing with arlocal and ao-localnet.
WAO is still actively being developed; please use it at your discretion.
yarn add wao
By replacing aoconnect
with WAO connect, everything runs in memory with zero latency and your tests are executed 1000x faster. The APIs are identical. So, there's no need to change anything else in your code.
//import { spawn, message, dryrun, assign, result } from "@permaweb/aoconnect"
import { connect } from "wao/test"
const { spawn, message, dryrun, assign, result } = connect()
It's super easy to set up a test AO project manually.
mkdir wao-test && cd wao-test
yarn init && yarn add wao
Add test
and test-only
commands to your package.json
.
{
"scripts": {
"test": "node --experimental-wasm-memory64",
"test-only": "node --experimental-wasm-memory64 --test-only"
}
}
Create test
directory and test.js
file.
mkdir test && touch test/test.js
Write a simple test in test.js
.
import assert from "assert"
import { describe, it } from "node:test"
import { connect } from "wao/test"
const { acc, spawn, message, dryrun } = connect()
// import { wait } from "wao/utils"
const signer = acc[0].signer
const src_data = `
Handlers.add("Hello", "Hello", function (msg)
msg.reply({ Data = "Hello, World!" })
end)
`
describe("WAO", function () {
it("should spawn a process and send messages", async () => {
const pid = await spawn({ signer })
// on mainnet, you need to wait here till the process becomes available.
// WAO automatically handles it. No need with in-memory tests.
// await wait({ pid })
await message({
process: pid,
tags: [{ name: "Action", value: "Eval" }],
data: src_data,
signer,
})
const res = await dryrun({
process: pid,
tags: [{ name: "Action", value: "Hello" }],
signer,
})
assert.equal(res.Messages[0].Data, "Hello, World!")
})
})
Note that generating random Arweave wallets for every test takes time and slows down your test executions, so Wao connect provides pre-generated accounts for your tests, which saves hours if you are to run your tests thousands of times.
acc[0] = { jwk, addr, signer }
Run the test.
yarn test test/test.js
WAO comes with elegant syntactic sugar and makes writing AO projects an absolute joy.
The same test can be written as follows.
import assert from "assert"
import { describe, it } from "node:test"
import { AO, acc } from "wao/test"
const src_data = `
Handlers.add("Hello", "Hello", function (msg)
msg.reply({ Data = "Hello, World!" })
end)
`
describe("WAO", function () {
it("should spawn a process and send messages", async () => {
const ao = await new AO().init(acc[0])
const { p } = await ao.deploy({ src_data })
assert.equal(await p.d("Hello", "Hello, World!")
})
})
The AO
class is not only for in-memory tests, but also for production code. You just need to import from a different path.
import { AR, AO, GQL } from "wao"
You often need to pick a specific piece of data from returned results with multiple spawned messages. You need to go through all the returned messages and further go through tags and data to find it. That's too much code to write. AO
comes with get
parameter to simplify it.
Consider the following Lua handlers.
Handlers.add("Hello", "Hello", function (msg)
msg.reply({ Data = json.encode({ Name = "Bob" }) })
end)
Handlers.add("Hello2", "Hello2", function (msg)
msg.reply({ Data = "Hello, World!", Name = "Bob", Age = "30" })
end)
// by default it extracts JSON decoded Data
const out = await p.d("Hello")
assert.deepEqual(out, { Name: "Bob" })
// equivalent
const out2 = await p.d("Hello", { get: true })
assert.deepEqual(out2, { Name: "Bob" })
// get string Data
const out3 = await p.d("Hello2", { get: false })
assert.equal(out3, "Hello, World!")
// get a tag
const out4 = await p.d("Hello2", { get: "Age" })
assert.equal(out4, "30")
// get multiple tags
const out5 = await p.d("Hello2", { get: { name: "Name", age: "Age" } })
assert.deepEqual(out5, { name: "Bob", age: "30" })
To determine if your message is successful, you often need to track down a chain of asynchronous messages and examine resulted tags and data. This is actually a fairy complex operation and too much code to write. Luckily for you, AO
comes with check
parameter to extremely simplify it. check
tracks down messages and lazy-evaluates if your check
conditions are met.
// check if Data exists
await p.m("Hello2", { check: true })
// check if Data is a certain value
await p.m("Hello2", { check: "Hello, World! })
// check if a tag exists
await p.m("Hello2", { check: "Name" })
// check if tags are certain values
await p.m("Hello2", { check: { Name: "Bob", Age: "30" } })
// it throws an Error if the conditions are not met
try{
await p.m("Hello2", { check: { Name: "Bob", Age: "20" } })
}catch(e){
console.log("something went wrong!")
}
// check if Name is Bob and Age exists, then get Age
const age = await p.m("Hello2", { check: { Name: "Bob", Age: true }, get : "Age" })
assert.equal(age, "30", "Bob is not 30 yo!")
AOS2 introduced a handy function receive()
to send a message to another process and receive a reply in the same handler.
Handlers.add("Hello3", "Hello3", function (msg)
msg.reply({ Data = "How old are you?" })
local age = Send({
Target = msg.To, Action = "Get-Age", Name = msg.Who
}).receive().Data
msg.reply({ Data = "got your age!", Name = msg.Who, Age = age })
end)
Since the second reply will be a part of another message triggerd by the Target
process reply, you cannot get the final reply simply with the arconnect result
function. You need to keep pinging the process results
or track down the chain of messages to examine what went wrong. The AO get
and check
automatically handle this complex operation in a lazy short-circuit manner in the background for you. A proper timeout
(ms) should be specified.
const age = await p.m(
"Hello3",
{ Who: "Bob", To: DB_PROCESS_ID }, // second argument can be tags
{ get: "Age", check: "got your age!", timeout: 5000 }
)
assert.equal(age, "30")
There are so many more powerful tricks you can utilize to make complex AO development easier.
Read on to the API reference section to find out!
WAO hot-patches the core AOS module code so ao.log
automatically is forwarded to JS console.log
and whatever you log will be directly displayed in your terminal. Lua tables will be auto-converted to JSON objects. It doesn't affect your production code, it only hot-paches the module during testing. This makes complex debugging so easy.
Handlers.add("Hello4", "Hello4", function (msg)
ao.log("Hello, Wordl!") -- will be displayed in the terminal
ao.log({ Hello = "World!" }) -- will be auto-converted to JSON
-- passing multiple values
ao.log("Hi", 3, true, [ 1, 2, 3 ], { Hello = "World!" })
end)
You can get logs even when an error occurs in the handler, which is extremely handy to identify the error causes.
You can fork wasm memory to a new process. This could come in handy to create checkpoints for tests.
It only works with in-memory testing.
const src_counter = `
local count = 0
Handlers.add("Add", "Add", function (msg)
count = count + tonumber(msg.Plus)
end)
Handlers.add("Get", "Get", function (msg)
msg.reply({ Data = tostring(count) })
end)
`
const ao = await new AO().init(acc[0])
const { p, pid } = await ao.deploy({ boot: true, src_data: src_counter })
await p.m("Add", { Plus: 3 })
assert.equal(await p.d("Get"), "3")
const ao2 = await new AO().init(acc[0])
// pass the exisiting wasm memory to a new process
const { p: p2 } = await ao2.spwn({ memory: ao.mem.env[pid].memory })
assert.equal(await p2.d("Get"), "3")
await p2.m("Add", { Plus: 2 })
assert.equal(await p2.d("Get"), "5")
You can also get mainnet process memory from the CU endpoint (GET /state/{pid}
) and fork it for tests.
The WeaveDrive extension is fully emulated with WAO. You can use attest
and avail
functions from AO
.
import { blueprint, AO, acc } from "wao/test"
const attestor = acc[0]
const handler = `
apm.install('@rakis/WeaveDrive')
Drive = require('@rakis/WeaveDrive')
Handlers.add("Get", "Get", function (msg)
msg.reply({ Data = Drive.getData(msg.id) })
end)`
describe("WeaveDrive", () => {
it("should load Arweave tx data", async () => {
const ao = await new AO().init(attestor)
const { p } = await ao.deploy({
tags: { Extension: "WeaveDrive", Attestor: attestor.addr },
loads: [ await blueprint("apm"), handler ],
})
const { id } = await ao.ar.post({ data: "Hello" })
await ao.attest({ id })
assert.equal(await p.d("Get", { id }), "Hello")
})
})
You can run a local WAO server with persistent storage, which enables connections with outside components such as frontend apps.
npx wao
port
: Arweave port, the ports of AO units are based on this port (default to4000
)- AR: localhost:4000
- MU: localhost:4002
- SU: localhost:4003
- CU: localhost:4004
db
: a directory to store data (default to.cache
)reset
: to reset the database
npx wao --port 5000 --db .custom_cache_dir --reset
In this case, the ports will be, AR => 5000
, MU => 5002
, SU => 5003
, CU => 5004
.
You can use WAO SDK or AOConnect to connect with the WAO units, but the following tags will be automatically set with WAO SDK.
- AOS2.0.1 Module:
Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM
- Scheduler:
_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA
- Authority:
eNaLJLsMiWCSWvQKNbk_YT-9ydeWl9lrWwXxLVp9kcg
import { describe, it } from "node:test"
import assert from "assert"
import { AO } from "wao"
const src_data = `
Handlers.add("Hello", "Hello", function (msg)
msg.reply({ Data = "Hello, World!" })
end)`
describe("WAO Server", ()=>{
it("should connect with WAO SDK", async ()=>{
const ao = await new AO(4000).init(YOUR_JWK)
const { p } = await ao.deploy({ src_data })
assert.equal(await p.d("Hello"), "Hello, World!")
})
})
With AOConnect,
import { describe, it } from "node:test"
import assert from "assert"
import { connect, createDataItemSigner } from "@permaweb/aoconnect"
const { spawn, message, dryrun, assign, result } = connect({
MU_URL: `http://localhost:4002`,
CU_URL: `http://localhost:4003`,
GATEWAY_URL: `http://localhost:4000`
})
const src_data = `
Handlers.add("Hello", "Hello", function (msg)
msg.reply({ Data = "Hello, World!" })
end)`
describe("WAO Server", () => {
it("should connect with WAO SDK", async () => {
const pid = await spawn({
module: "Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM",
scheduler: "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA",
tags: [
{
name: "Authority",
value: "eNaLJLsMiWCSWvQKNbk_YT-9ydeWl9lrWwXxLVp9kcg",
},
],
signer: createDataItemSigner(YOUR_JWK),
})
// wait till the process becomes available
const mid = await message({
process: pid,
tags: [{ name: "Action", value: "Eval" }],
data: src_data,
signer: createDataItemSigner(acc[0].jwk),
})
console.log(await result({ process: pid, message: mid }))
const res = await dryrun({
process: pid,
data: "",
tags: [{ name: "Action", value: "Hello" }],
anchor: "1234",
})
assert.equal(res.Messages[0].Data, "Hello, World!")
})
})
Connecting with the AOS terminal,
aos \
--gateway-url http://localhost:4000 \
--cu-url http://localhost:4004 \
--mu-url http://localhost:4002 \
--tag-name Authority \
--tag-value eNaLJLsMiWCSWvQKNbk_YT-9ydeWl9lrWwXxLVp9kcg
- Instantiate
- deploy
- msg
- dry
- asgn
- load
- eval
- spwn
- aoconnect Functions
- postModule
- postScheduler
- attest
- avail
- wait
You can initialize AO in the same way as AR.
import { AO } from "wao"
const ao = await new AO().init(jwk || arweaveWallet)
If you need to pass AR settings, use ar
. ao.ar
will be automatically available.
const ao = await new AO({ ar: { port: 4000 }}).init(jwk || arweaveWallet)
const addr = ao.ar.addr
await ao.ar.post({ data, tags })
Spawn a process, get a Lua source, and eval the script. src
is an Arweave txid of the Lua script.
const { err, res, pid, p } = await ao.deploy({ data, tags, src, fills })
You can directly pass the Lua script with src_data
instead of src
.
const { err, res, pid, p } = await ao.deploy({ data, tags, src_data, fills })
boot
will use On-Boot
tag to initialize the process instead of Eval
action. You can set either true
to use src_data
, or set a txid of an existing script. In case of true
, data
should be undefined so the src_data
can fill it with spawn
.
const { err, res, pid, p } = await ao.deploy({ boot: true, tags, src_data, fills })
fills
replace the Lua source script from src
.
local replace_me = '<REPLACE_ME>'
local replace_me_again = '<REPLACE_ME_AGAIN>'
local replace_me_with_hello_again = '<REPLACE_ME>'
const fills = { REPLACE_ME: "hello", REPLACE_ME_AGAIN: "world" }
This will end up in the following lua script.
local replace_me = 'hello'
local replace_me_again = 'world'
local replace_me_with_hello_again = 'hello'
In case you have multiple scripts, use loads
and pass src
and fills
respectively.
await ao.deploy({ tags, loads: [ { src, fills }, { src: src2, fills: fills2 } ] })
You can also pass an array of string data to loads
.
const num = `num = 0`
const inc = `Handlers.add("Inc", "Inc", function () num = num + 1 end)`
const get = `Handlers.add("Get", "Get", function (m) m.reply({ Data = num }) end)`
const { p } = await ao.deploy({ tags, loads: [ num, inc, get ] })
await p.m("Inc")
assert.equal(await p.d("Get"), 1)
Send a message.
const { err, mid, res, out } = await ao.msg({
data, action, tags, check, get, mode, limit
})
check
determins if the message call is successful by checking through Tags
in Messages
in res
.
When using from
either in check
or get
, mode
needs to be set gql
. mode
defaults to aoconnect
which uses the aoconnect.results
function to track down results, which cannot tell where results come from. gql
mode doesn't sometimes catch all results if used with AO/Arweave mainnet since there are lags due to the block finality time. limit
specifies how many transactions or results to fetch for the check
.
const check = { Status : "Success" } // succeeds if Status tag is "Success"
const check2 = { Status : true } // succeeds if Status tag exists
Assigning either a string or boolean value checks Data
field instead of Tags
.
const check3 = "Success" // succeeds if Data field is "Success"
const check4 = true // succeeds if Data field exists
const check5 = /ok/ // succeeds if Data field is string containing "ok"
const check6 = (n)=> +n > 10 // succeeds if Data field is bigger than 10
const check7 = { json: { a: 3 } } // succeeds if Data field is JSON and a is 3
const check8 = { json: { a: 3, b: 4 }, eq: true } // deep equal JSON
const check9 = { data: true, tags: { Status: true, Balance: (n)=> +n > 10 } }
const check10 = { data: true, from: PID } // specify message sender process
Use an array to check multiple conditions.
const check11 = ["Success", { Age: "30" }] // Data is Success and Age tag is 30
const check12 = [{ data: "Success", from: PID }, { Age: "30", from: PID2 }]
get
will return specified data via out
.
const get = "ID" // returns the value of "ID" tag
const get2 = { name: "Profile", json: true } // "Profile" tag with JSON.parse()
const get3 = { data: true, json: true } // returns Data field with JSON.parse()
const get4 = true // => { data: true, json: true }
const get5 = false // => { data: true, json: false }
const get6 = { obj: { age: "Age", who: "Name" }} // => { age: 30, who: "Bob" }
const get7 = { age: "Age", who: "Name" } // same as get6
const get8 = { name: "Profile", json: true, from: PID } // specify sender process
const get9 = { age: { name: "Age", from: PID }, who: "Name" } // another example
check
and get
lazy-evaluate tags and data by tracking down async messages. As soon as the conditions are met, they won't track further messages. With receive()
added with AOS 2.0, you can only get spawned messages up to the receive()
function from result
. But WAO automatically tracks down further messages and determines if check conditions are met beyond the receive()
function.
For example, consider the following handler.
Handlers.add("Hello", "Hello", function (msg)
msg.reply({ Data = "Hello, World!" })
local name = Send({ Target = msg.to, Action = "Reply" }).receive().Data
msg.reply({ Data = "Hello, " .. name .. "!" })
end)
you can only get the first Hello, World!
, but not the second "Hello, " .. name .. "!"
from the aoconnect result
function.
Dryrun a message without writing to Arweave.
const { err, res, out } = await ao.dry({ data, action, tags, check, get })
Assign an existing message to a process.
const { err, mid, res, out } = await ao.asgn({ pid, mid, check, get })
Get a Lua source script from Arweave and eval it on a process.
const { err, res, mid } = await ao.load({ src, fills, pid })
Eval a Lua script on a process.
const { err, res, mid } = await ao.eval({ pid, data })
Spawn a process. module
and scheduler
are auto-set if omitted.
const { err, res, pid } = await ao.spwn({ module, scheduler, tags, data })
The original aoconnect functions message
| spawn
| result
| assign
| dryrun
are also available.
createDataItemSigner
is available as toSigner
.
const signer = ao.toSigner(jwk)
const process = await ao.spawn({ module, scheduler, signer, tags, data })
const message = await ao.message({ process, signer, tags, data })
const result = await ao.result({ process, message })
data
should be wasm binary. overwrite
to replace the default module set to the AO instance.
const { err, id: module } = await ao.postModule({ data, jwk, tags, overwrite })
This will post Scheduler-Location
with the jwk
address as the returning scheduler
.
const { err, scheduler } = await ao.postScheduler({ url, jwk, tags, overwrite })
Attest Arweave transactions for WeaveDrive.
const { err, res, id } = await ao.attest({ id, tags, jwk })
Make Arweave transactions available for WeaveDrive.
const { err, res, id } = await ao.avail({ ids, tags, jwk })
wait
until the process becomes available after spwn
. This is mostly used internally with deploy
.
const { err } = await ao.wait({ pid })
You can go for even more concise syntax with Process
class.
const p = ao.p(pid)
or
const { p, pid } = await ao.deploy({ data, tags, src, fills })
The first argument is Action
, the second argument is Tags
, and the third argument is the rest of the options.
const { mid, res, out, err } = await p.msg(
"Action",
{ Tag1: "value1", Tag2: "value2" },
{ get: true, check: { TagA: "valueA" }, jwk }
)
The default third argument is { get: true }
to return the JSON decoded Data
.
const { mid, out } = await p.msg("Action", { Tag1: "value1", Tag2: "value2" })
The third parameter defaults to get
if it's not an object.
const { mid, out } = await p.msg("Action", { Tag1: "value1" }, "TagA")
is equivalent to
const { mid, out } = await p.msg("Action", { Tag1: "value1" }, "TagA")
You can omit the second argument if there is no tag to pass to.
const { mid, out } = await p.msg("Action", { check: "success!" }}
You can only get out
with m
. This is the most extreme form.
const out = await p.m("Action", { Tag1: "value1", Tag2: "value2" })
This is a quite common pattern during testing. Doing the same with aoconnect
requires an enormous amount of code, especially if it involves async/await receive()
.
const { p } = await ao.deploy({ tags, src_data, fills })
const out = await p.m("Action", { Tag1: "value1", Tag2: "value2" }) // get Data
assert.equal(out, EXPECTED_JSON)
const { mid, out } = await p.dry("Action", { Tag1: "value1", Tag2: "value2" })
const out = await p.d("Action", { Tag1: "value1", Tag2: "value2" })
Most functions return in the format of { err, res, out, pid, mid, id }
, and these function can be chained with pipe
, which makes executing multiple messages a breeze.
For example, the following is how deploy
uses pipe
internally. The execution will be immediately aborted if any of the functions in fns
produces an error.
let fns = [
{
fn: "spwn",
args: { module, scheduler, tags, data },
then: { "args.pid": "pid" },
},
{ fn: "wait", then: { "args.pid": "pid" } },
{ fn: "load", args: { src, fills }, then: { "args.pid": "pid" } }
]
const { err, res, out, pid } = await this.pipe({ jwk, fns })
If the function comes from other instances rather than AO
, use bind
.
const fns = [{ fn: "post", bind: this.ar, args: { data, tags }}]
You can pass values between functions with then
. For instance, passing the result from the previous functions to the next function's arguments is a common operation.
const fns = [
{ fn: "post", bind: ao.ar, args: { data, tags }, then: ({ id, args, out })=>{
args.tags.TxId = id // adding TxId tag to `msg` args
out.txid = id // `out` will be returned at last with `pipe`
}},
{ fn: "msg", args: { pid, tags }},
]
const { out: { txid } } = await ao.pipe({ fns, jwk })
If then
returns a value, pipe
will immediately return with that single value. You can also use err
to abort pipe
with an error.
const fns = [
{ fn: "msg", args: { pid, tags }, then: ({ inp })=>{
if(inp.done) return inp.val
}},
{ fn: "msg", args: { pid, tags }, err: ({ inp })=>{
if(!inp.done) return "something went wrong"
}},
]
const val = await ao.pipe({ jwk, fns })
then
has many useful parameters.
res
:res
from the previous resultargs
:args
for the next functionout
: the finalout
result from thepipe
sequenceinp
:out
from the previous result_
: if values are assigned to the_
fields,pipe
returns them as top-level fields in the endpid
:pid
will be passed if any previous functions returnpid
( e.g.deploy
)mid
:mid
will be passed if any previous functions returnmid
( e.g.msg
)id
:id
will be passed if any previous functions returnid
( e.g.post
)
then
can be a simplified hashmap object.
let fns = [
{
fn: "msg",
args: { tags },
then: { "args.mid": "mid", "out.key": "inp.a", "_.val": "inp.b" },
},
{ fn: "some_func", args: {} } // args.mid will be set from the previous `then`
]
const { out: { key }, val } = await ao.pipe({ jwk, fns })
err
has the same signature as then
. If err
returns a value, pipe
will throw an Error
with that value.
const fns = [
{ fn: "msg", args: { pid, tags }, err: ({ inp })=>{
if(!inp.done) return "something went wrong!"
}}
]
const val = await ao.pipe({ jwk, fns })
cb
can report the current progress of pipe
after every function execution.
await ao.pipe({ jwk, fns, cb: ({ i, fns, inp })=>{
console.log(`${i} / ${fns.length} functions executed`)
}})
AR
handles operations on the base Arweave Storage layer as well as wallet connections.
- Instantiate
- Set or Generate Wallet
- toAddr
- mine
- balance | toAR | toWinston
- transfer
- checkWallet
- post
- tx
- data
- bundle
import { AR } from "wao"
const ar = new AR()
host
, port
, and protocol
can be set to access a specific gateway rather than https://arweave.net
.
const ar = new AR({ host: "localhost", port: 4000, protocol: "http" })
In the case of local gateways, you can only set port
and the rest will be automatically figured out.
const ar = new AR({ port: 4000 })
AO
class auto-instantiates AR
internally.
import { AO } from "wao"
const ao = new AO()
const ar = ao.ar
You can initialize AR with a wallet JWK or ArConnect.
const ar = await new AR().init(jwk || arweaveWallet)
Or you can generate a new wallet. In case of ArLocal, you can mint AR at the same time.
const { jwk, addr, pub, balance } = await ar.gen("100") // mint 100 AR
Once a wallet is set in one of these 3 ways, you cannot use the instance with another wallet unless you re-initialize it with another wallet. This is to prevent executing transactions with the wrong wallet when the browser connected active address has been changed unknowingly.
You can go on without calling init
or gen
, in this case, AR generates a random wallet when needed, and also using different wallets will be allowed. This is useful, if you are only calling dryrun
with AO, since AO requires a signature for dryrun
too, but you don't want to bother the user by triggering the browser extension wallet for read only calls.
Once a wallet is set, ar.jwk
and ar.addr
will be available.
Convert a jwk to the corresponding address.
const addr = await ar.toAddr(jwk)
Mine pending blocks (only for arlocal).
await ar.mine()
Get the current balance of the specified address in AR. addr
will be ar.addr
if omitted.
const balance_AR = await ar.balance() // get own balance
const balance_Winston = ar.toWinston(balance_AR)
const balance_AR2 = ar.toAR(balance_Winston)
const balance_AR3 = await ar.balance(addr) // specify wallet address
Transfer AR token. amount
is in AR, not in winston for simplicity.
const { id } = await ar.transfer(amount, to)
You can set a jwk to the 3rd parameter as a sender. Otherwise, the sender is ar.jwk
.
const { id } = await ar.transfer(amount, to, jwk)
For most write functions, jwk
can be specified as the last parameter or a field like { data, tags, jwk }
.
checkWallet
is mostly used internally, but it returns this.jwk
if a wallet has been assigned with init
, or else it generates a random wallet to use. The following pattern is used in many places. With this pattern, if a wallet is set with init
and the jwk
the user is passing is different, checkWallet
produces an error to prevent the wrong wallet. If no wallet has been set with init
or gen
and the jwk
is not passed, it generates and returns a random wallet.
some_class_method({ jwk }){
let err = null
;({ err, jwk } = await ar.checkWallet({ jwk }))
if(!err){
// do something with the jwk
}
}
Post a data to Arweave.
const { err, id } = await ar.post({ data, tags })
tags
are not an Array but a hash map Object for brevity.
const tags = { "Content-Type": "text/markdown", Type: "blog-post" }
If you must use the same name for multiple tags, the value can be an Array.
const tags = { Name: [ "name-tag-1", "name-tag-2" ] }
Get a transaction.
const tx = await ar.tx(txid)
Get a data.
const data = await ar.data(txid, true) // true if string
Bundle ANS-104 dataitems.
const { err, id } = await ar.bundle(dataitems)
dataitems
are [ [ data, tags ], [ data, tags ], [ data, tags ] ]
.
const { err, id } = await ar.bundle([
[ "this is text", { "Content-Type": "text/plain" }],
[ "# this is markdown", { "Content-Type": "text/markdown" }],
[ png_image, { "Content-Type": "image/png" }]
])
GQL
simplifies the Arwave GraphQL operations to query blocks and transactions.
You can instantiate the GQL class with an endpoint url
.
import { GQL } from "wao"
const gql = new GQL({ url: "https://arweave.net/graphql" }) // the default url
AR
class auto-instantiates GQL
internally.
import { AO } from "wao"
const ao = new AO()
const gql = ao.ar.gql
import { AR } from "wao"
const ar = new AR()
const gql = ar.gql
Get latest transactions.
const txs = await gql.txs()
Get transactions in ascending order.
const txs = await gql.txs({ asc: true })
Get the firxt X transactions.
const txs = await gql.txs({ first: 3 })
Get transactions after a specific one to paginate. Pass a cursor
.
const txs = await gql.txs({ first: 3 })
const txs2 = await gql.txs({ first: 3, after: txs[2].cursor })
Easier pagination with next
.
const { next, data: txs0_2 } = await gql.txs({ first: 3, next: true })
const { next: next2, data: txs3_5 } = await next()
const { next: next3, data: txs6_8 } = await next2()
res.next
will be null
if there's no more transactions to paginate.
Get transactions within a block height range.
const txs = await gql.txs({ block: { min: 0, max: 10 } })
or
const txs = await gql.txs({ block: [0, 10] })
You can also specify only min
or max
.
Get transactions by transaction ids.
const txs = await gql.txs({ id: TXID })
or
const txs = await gql.txs({ ids: [ TXID1, TXID2, TXID3 ] })
Get transactions by recipients.
const txs = await gql.txs({ recipient: ADDR })
or
const txs = await gql.txs({ recipients: [ ADDR1, ADDR2, ADDR3 ] })
Get transactions by owners.
const txs = await gql.txs({ owner: ADDR })
or
const txs = await gql.txs({ owners: [ ADDR1, ADDR2, ADDR3 ] })
Get transactions that match tags.
const txs = await gql.txs({ tags: { Name: "Bob", Age: "30" } })
Choose fields to be returned.
const txs = await gql.txs({ fields: ["id", "recipient"] })
For nested objects,
const txs = await gql.txs({ fields: ["id", { owner: ["address", "key"] }] })
You can use a hashmap to specify fields too.
const txs = await gql.txs({
fields: { id: true, { owner: { address: true, key: true } } }
})
If you assign false
, the other fields will be returned.
const txs = await gql.txs({
fields: { id: true, { block: { previous: false } } }
})
For example, the above will exclude previous
from block
and return id
, timestamp
and height
.
The entire available fields for transactions as in a graphql query are as follows.
const tx_fields = `{
id
anchor
signature
recipient
owner { address key }
fee { winston ar }
quantity { winston ar }
data { size type }
tags { name value }
block { id timestamp height previous }
parent { id }
bundledIn { id }
}`
Get latest blocks.
const blocks = await gql.blocks()
Get blocks in ascending order.
const blocks = await gql.blocks({ asc: true })
Get the firxt X blocks.
const blocks = await gql.blocks({ first: 3 })
Get blocks after a specific one to paginate. Pass a cursor
.
const blocks = await gql.blocks({ first: 3 })
const blocks2 = await gql.blocks({ first: 3, after: blocks[2].cursor })
Get blocks by block ids.
const blocks = await gql.blocks({ id: BLCID })
or
const blocks = await gql.blocks({ ids: [ BLKID1, BLKID2, BLKID3 ] })
Get blocks within a block height range.
const blocks = await gql.blocks({ height: { min: 0, max: 10 } })
or
const blocks = await gql.blocks({ height: [0, 10] })
Easier pagination with next
.
const { next, data: blocks0_2 } = await gql.blocks({ first: 3, next: true })
const { next: next2, data: blocks3_5 } = await next()
const { next: next3, data: blocks6_8 } = await next2()
res.next
will be null
if there's no more blocks to paginate.
const blocks = await gql.blocks({
fields: ["id", "timestamp", "height", "previous"]
})
The entire available fields for blocks as in a graphql query are as follows.
const block_fields = `{ id timestamp height previous }`
ArMem
stands for Arweave in memory and is a class to emulate an Arweave node and AO units in memory, which is internally used in the WAO testing framework. You can instantiate ArMem
and control multiple emulators by passing it between other classes.
When you instantiate WAO connect
or AO
from wao/test
, it automatically and internally instantiates ArMem
.
import { connect } from "wao/test"
const { spawn, message, dryrun, assign, result, mem } = connect() // aoconnect APIs
import { AO } from "wao/test"
const ao = new AO() // ao.mem
You can instantiate ArMem
and pass it to other classes.
import { ArMem, AO, AR, connect } = "wao/test"
const mem = new ArMem()
const { spawn, message, dryrun, assign, result } = connect(mem)
const ao = new AO({ mem })
const ar = new AR({ mem })
If you don't pass the same ArMem
instance, the two AO instances will have different environments.
import { AO } = "wao/test"
const ao = new AO() // ao.mem
const ao2 = new AO() // ao2.mem
ao.mem
and ao2.mem
are not connected. They are on different networks.
import { AO } = "wao/test"
const ao = new AO()
const ao2 = new AO({ mem: ao.mem })
This will connect the two.