WAO - Wizard AO SDKの紹介
原文: wao
WAO SDKは、洗練された構文拡張とシームレスなメッセージパイプ機能によって、Arweave/AO開発を効率化し、快適なコーディング体験を提供します。GraphQL操作も非常に簡単に行えます。
さらに、aoconnect
の代替としてそのまま使用できる機能を備えており、AOユニットをメモリ上でエミュレートすることで、Luaスクリプトのテストをメインネットより1000倍速く実行可能です。arlocal や ao-localnet を使用したテストと比べても100倍速くなっています。
Quick Start
WAOは現在も積極的に開発が進められています。使用にあたってはご自身の判断でお願いします。
インストール
yarn add wao
aoconnect
代替としてそのまま使える
テスト用の aoconnect
をWAO connectに置き換えることで、すべての処理がメモリ上で実行され、待ち時間ゼロでテストが1000倍高速になります。APIは完全に同一のため、コードの他の部分を変更する必要はありません。
//import { spawn, message, dryrun, assign, result } from "@permaweb/aoconnect"
import { connect, acc } from "wao/test";
const { spawn, message, dryrun, assign, result } = connect();
Setting up a Project
テスト用のAOプロジェクトを手動でセットアップするのはとても簡単です。
mkdir wao-test && cd wao-test
yarn init && yarn add wao
package.json
に test
と test-only
のコマンドを追加してください。
{
"scripts": {
"test": "node --experimental-wasm-memory64",
"test-only": "node --experimental-wasm-memory64 --test-only"
}
}
test
とtest.js
ファイルを作成してください。
mkdir test && touch test/test.js
テストを書く
test.js
にシンプルなテストを書きます。
import assert from "assert";
import { describe, it } from "node:test";
import { connect, acc } from "wao/test";
const { spawn, message, dryrun } = connect();
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,
module: "Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM",
scheduler: "_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA",
});
// 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!");
});
});
テストごとにランダムなArweaveウォレットを生成すると時間がかかり、テストの実行速度が低下する点に注意してください。そのため、Wao connectではあらかじめ生成されたアカウントをテスト用に提供しており、テストを何千回も実行する場合でも大幅な時間短縮が可能です。
acc[0] = { jwk, addr, signer }
テストを実行します。
yarn test test/test.js
WAO SDKを使用する
WAOには洗練されたシンタックスシュガーが備わっており、AOプロジェクトの開発が格段に楽しくなります。
同じテストは以下のように記述できます。
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", false), "Hello, World!");
});
});
AO
クラスはインメモリテスト専用ではなく、本番コードにも使用できます。異なるパスからインポートするだけで利用可能です。
import { AR, AO, GQL } from "wao";
出力の絞り込み(Cherry-Picking Outputs)
複数のメッセージがspawnされるような処理結果から、特定のデータだけを取り出したい場面はよくあります。そのためには、すべての戻りメッセージを走査し、さらに各メッセージのタグやデータをチェックする必要があり、かなりのコード量になります。
AO
にはこれを簡略化するための get
パラメータが用意されています。
以下のLuaハンドラーを例に見てみましょう。
local json = require('json')
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)
Handlers.add("Hello3", "Hello3", function (msg)
msg.reply({ Profile = json.encode({ 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: { obj: { firstname: "Name", age: "Age" } },
});
assert.deepEqual(out5, { firstname: "Bob", age: "30" });
// shortcut if keys don't include name, data, from, json
const out6 = await p.d("Hello2", { get: { firstname: "Name", age: "Age" } });
assert.deepEqual(out6, { firstname: "Bob", age: "30" });
// await p.d("Hello2", { get: { name: "Name", age: "Age" } }) doesn't work
// handle tag as json
const out7 = await p.d("Hello3", {
get: { prof: { name: "Profile", json: true } },
});
assert.deepEqual(out7, { prof: { Name: "Bob", Age: "30" } });
メッセージの成功判定(Determining Message Success)
メッセージが成功したかどうかを判定するには、多くの場合、非同期で送られる一連のメッセージを追跡し、最終的に得られたタグやデータを検証する必要があります。
これは実際には非常に複雑な処理であり、多くのコードを書くことになります。
幸いにも、AO
にはこの作業を大幅に簡素化する check
パラメータが用意されています。check
はメッセージを追跡し、指定された条件が満たされたかどうかをLazy Evaluation(遅延評価)で判断します。
// 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!")
receive()
を使った非同期メッセージの追跡
AOS2では便利な関数 receive()
が導入されており、別のプロセスにメッセージを送信し、その返信を同じハンドラー内で受け取ることができます。
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)
Target
プロセスからの返信によってトリガーされる別のメッセージの一部として、2番目の返信が返されるため、arconnect
の result
関数だけでは最終的な返信を取得することはできません。
そのためには、プロセスの results
を繰り返し確認したり、メッセージチェーンをたどって問題の原因を調べる必要があります。
AO
の get
および check
は、こうした複雑な処理をバックグラウンドで短絡的に(short-circuit)かつ遅延評価で自動的に処理してくれます。適切な timeout
(ミリ秒)を指定することを推奨します。
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");
複雑なAO開発をより簡単にするための強力なテクニックが、まだまだたくさん用意されています。
詳細はこの後のAPIリファレンスセクションをご覧ください!
ログ出力(Logging)
WAOはAOSのコアモジュールコードをホットパッチしており、ao.log
を使うと自動的にJSの console.log
に転送され、ログがそのままターミナルに表示されます。Luaのテーブルは自動的にJSONオブジェクトに変換されます。
この仕組みは本番コードには一切影響せず、テスト時のみモジュールに対してホットパッチが適用されます。これにより、複雑なデバッグ作業も非常に簡単になります。
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)
エラーハンドラー内でエラーが発生した場合でもログを取得できるため、原因の特定が非常に容易になります。
Wasmメモリのフォーク(Fork Wasm Memory)
Wasmメモリを新しいプロセスにフォーク(複製)することができます。これはテスト用のチェックポイントを作成する際に便利です。
この機能はインメモリテスト環境でのみ動作します。
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");
また、メインネット上のプロセスのメモリをCUエンドポイント(GET /state/{pid}
)から取得し、それをテスト用にフォークすることも可能です。
WeaveDrive
WeaveDrive 拡張機能はWAO上で完全にエミュレートされています。AO
から attest
および avail
関数を使用することができます。
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");
});
});
ローカル永続サーバー(Local Persistent Server)
ローカルで永続ストレージ付きのWAOサーバーを起動することができ、これによりフロントエンドアプリなどの外部コンポーネントと接続することが可能になります。
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
この場合、各ユニットのポートは以下のように割り当てられます:
- AR ⇒
5000
- MU ⇒
5002
- SU ⇒
5003
- CU ⇒
5004
WAOユニットにはWAO SDKまたはAOConnectを使って接続できますが、WAO SDKを使用した場合は以下のタグが自動的に設定されます:
- AOS2.0.1 モジュール:
Do_Uc2Sju_ffp6Ev0AnLVdPtot15rvMjP-a9VVaA5fM
- スケジューラー:
_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA
- オーソリティ:
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!");
});
});
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
HyperBEAM
HyperBEAMとの統合は、現時点では非常に実験的な段階にあります。
WAOをクローンし、依存関係をインストールしてください。
git clone https://github.com/weavedb/wao.git && cd wao && yarn
CUは http://localhost:4004 で起動している必要があります。
Hyperbeamのセットアップガイド に従って、最新版を正しくビルド済みであることを確認してください。
ただし、ここではまだ rebar3 shell
を実行しないでください。
代わりに、WAOのルートディレクトリから以下のコマンドでHyperBEAMノードを起動します。
yarn hb PATH_TO_HB_DIR
HyperBEAMは http://localhost:10001 で起動するはずです。
WAOサーバーをローカルCUとして使用しながら、HyperBEAMリクエストのテストが可能です。
import assert from "assert";
import { after, describe, it } from "node:test";
import { acc } from "wao/test";
import { HB } from "wao";
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)
`;
describe("Hyperbeam", function () {
it("should interact with a hyperbeam node", async () => {
const hb = await new HB({ url: "http://localhost:10001" }).init(acc[0].jwk);
const metrics = await hb.metrics();
const info = await hb.info();
const process = await hb.process();
const slot = await hb.schedule({ process, data });
const r = await hb.compute({ process, slot });
const slot2 = await hb.schedule({ process, action: "Inc" });
const r2 = await hb.compute({ process, slot: slot2 });
assert.equal(r2.Messages[0].Data, "Count: 1");
const r3 = await hb.dryrun({ process, action: "Get" });
assert.equal(r3.Messages[0].Data, "Count: 1");
});
});
API Reference
AO
- Instantiate
- deploy
- msg
- dry
- res
- ress
- asgn
- load
- eval
- spwn
- aoconnect Functions
- postModule
- postScheduler
- attest
- avail
- wait
- var
インスタンス化(Instantiate)
AOはARと同様の方法で初期化できます。
import { AO } from "wao";
const ao = await new AO().init(jwk || arweaveWallet);
ARの設定を渡したい場合は ar
を使用してください。ao.ar
が自動的に利用可能になります。
const ao = await new AO({ ar: { port: 4000 } }).init(jwk || arweaveWallet);
const addr = ao.ar.addr;
await ao.ar.post({ data, tags });
AOのコア機能
deploy
プロセスをspawnし、Luaソースを取得してスクリプトを評価します。src
にはLuaスクリプトのArweaveトランザクションIDを指定します。
const { err, res, pid, p } = await ao.deploy({ data, tags, src, fills });
src
の代わりに、Luaスクリプトそのものを src_data
として直接渡すことも可能です。
const { err, res, pid, p } = await ao.deploy({ data, tags, src_data, fills });
boot
オプションを使用すると、Eval
アクションの代わりに On-Boot
タグを用いてプロセスを初期化できます。
このとき true
を設定すれば src_data
を使用しますし、既存スクリプトのtxidを指定することも可能です。true
を使う場合は、data
を未定義にしておく必要があり、src_data
が spawn
の中で data
を埋めます。
const { err, res, pid, p } = await ao.deploy({
boot: true,
tags,
src_data,
fills,
});
fills
は、src
で指定されたLuaソーススクリプト内のプレースホルダーを置き換えるために使用されます。
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" };
最終的には、以下のようなLuaスクリプトが生成されます。
local replace_me = 'hello'
local replace_me_again = 'world'
local replace_me_with_hello_again = 'hello'
複数のスクリプトがある場合は、loads
を使用し、それぞれの src
および fills
を渡してください。
await ao.deploy({
tags,
loads: [
{ src, fills },
{ src: src2, fills: fills2 },
],
});
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);
msg
messageを送信します。
const { err, mid, res, out } = await ao.msg({
pid,
data,
act,
tags,
check,
get,
mode,
limit,
});
check
は、res
内の Messages
に含まれる Tags
をチェックすることでメッセージ呼び出しの成功可否を判定します。
check
または get
で from
を使用する場合は、mode
を gql
に設定する必要があります。mode
のデフォルトは aoconnect
であり、これは aoconnect.results
関数を使って結果を追跡しますが、どのプロセスからの結果かを判別することができません。
一方 gql
モードは、AOやArweaveのメインネットと併用した場合、ブロックファイナリティの遅延により一部の結果を取得できないことがあります。
limit
は、check
に対して何件のトランザクションや結果を取得するかを指定します。
const check = { Status: "Success" }; // Statusタグが"Success"であれば成功
const check2 = { Status: true }; // Statusタグが存在すれば成功
文字列またはブール値を指定した場合は、Tags
ではなく Data
フィールドをチェックします。
const check3 = "Success"; // Dataが"Success"なら成功
const check4 = true; // Dataが存在すれば成功
const check5 = /ok/; // Dataが"ok"を含んでいれば成功
const check6 = (n) => +n > 10; // Dataが10より大きければ成功
const check7 = { json: { a: 3 } }; // DataがJSONでaが3なら成功
const check8 = { json: { a: 3, b: 4 }, eq: true }; // JSONが完全一致なら成功
const check9 = { data: true, tags: { Status: true, Balance: (n) => +n > 10 } };
const check10 = { data: true, from: PID }; // メッセージ送信元のプロセスを指定
配列を使用すれば、複数の条件を同時にチェックできます。
const check11 = ["Success", { Age: "30" }]; // DataがSuccessかつAgeタグが30
const check12 = [
{ data: "Success", from: PID },
{ Age: "30", from: PID2 },
];
get
を使えば、指定したデータを 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
const get10 = {
data: true,
json: true,
match: (val, index, res) => val.Age < 10,
};
check
と get
は、非同期メッセージを追跡しながらタグやデータを遅延評価(Lazy Evaluation)します。条件が満たされた時点で、それ以上のメッセージは追跡しません。
AOS 2.0 で追加された receive()
を使用すると、result
関数から取得できるのは receive()
関数までにspawnされたメッセージのみです。
しかしWAOは receive()
以降にspawnされたメッセージも自動的に追跡し、check
条件が満たされたかどうかを判断します。
たとえば、次のようなハンドラーを考えてみましょう。
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)
この場合、aoconnect
の result
関数では最初の "Hello, World!"
は取得できますが、2回目の "Hello, " .. name .. "!"
は取得できません。
dry
メッセージをArweaveに書き込まずに実行します(ドライラン)。
const { err, res, out } = await ao.dry({ pid, data, action, tags, check, get });
res
既存の mid
(メッセージID)を対象に、msg と同様の処理を行います。
const { err, res, out } = await ao.res({ pid, mid, check, get });
ress
あるプロセスから複数の結果を取得します。メッセージが多い場合はページネーション用に next()
が返されます。
const { err, out: msgs, res, next } = await ao.ress({ pid, limit, asc, from });
if (next) {
const { out: msgs2 } = await next();
}
-
pid
:プロセスID -
limit
:取得件数 -
asc
:デフォルトでは降順。昇順にする場合はasc=true
-
from
:取得開始カーソル
asgn
既存のメッセージを指定プロセスに割り当てます。
const { err, mid, res, out } = await ao.asgn({ pid, mid, check, get });
load
ArweaveからLuaソーススクリプトを取得し、プロセス上で評価(eval)します。
const { err, res, mid } = await ao.load({ src, fills, pid });
eval
Luaスクリプトを指定プロセス上で評価(eval)します。
const { err, res, mid } = await ao.eval({ pid, data });
spwn
プロセスをspawnします。module
と scheduler
は省略された場合、自動で設定されます。
const { err, res, pid } = await ao.spwn({ module, scheduler, tags, data });
aoconnect の関数群
従来の aoconnect 関数である message
、spawn
、result
、assign
、dryrun
も利用可能です。
また、createDataItemSigner
は 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 });
高度な機能(Advanced Functions)
postModule
data
にはWASMバイナリを指定します。overwrite
を指定すると、AOインスタンスのデフォルトモジュールを置き換えます。
const { err, id: module } = await ao.postModule({ data, jwk, tags, overwrite });
postScheduler
scheduler
を返す Scheduler-Location
を jwk
のアドレスで投稿します。
const { err, scheduler } = await ao.postScheduler({
url,
jwk,
tags,
overwrite,
});
attest
WeaveDrive用にArweaveトランザクションを証明(attest)します。
const { err, res, id } = await ao.attest({ id, tags, jwk });
avail
ArweaveトランザクションをWeaveDrive用に利用可能状態にします。
const { err, res, id } = await ao.avail({ ids, tags, jwk });
wait
spwn
の後、プロセスが利用可能になるまで待機します。主に deploy
の内部処理で使用されます。
const { err } = await ao.wait({ pid });
var
dryrun
を使って現在のステートからLua変数を読み取ります。
const { pid } = await ao.deploy({
src_data: `Table = { String = "Hello", Array = { "str", 3, true } }`,
});
const table = await ao.var({ pid, data: "Table" });
出力から装飾的なタグを除去し、Luaテーブルを自動的にJSONに変換します。変換の無効化は json
および pretty
オプションで制御可能です。
const table = await ao.var({ pid, data: "Table", json: false, pretty: true });
Process
Process
クラスを使えば、さらに簡潔な構文で操作できます。
インスタンス化
const p = ao.p(pid);
or
const { p, pid } = await ao.deploy({ data, tags, src, fills });
msg
最初の引数は Action
、2 番目の引数は Tags
、3 番目の引数はその他のオプションです。
const { mid, res, out, err } = await p.msg(
"Action",
{ Tag1: "value1", Tag2: "value2" },
{ get: true, check: { TagA: "valueA" }, jwk }
);
3 番目の引数のデフォルトは { get: true }
で、JSON デコードされた Data
を返します。
const { mid, out } = await p.msg("Action", { Tag1: "value1", Tag2: "value2" });
3 番目のパラメータがオブジェクトでない場合、デフォルトで get
になります。
const { mid, out } = await p.msg("Action", { Tag1: "value1" }, "TagA");
これは次と同じ意味です。
const { mid, out } = await p.msg("Action", { Tag1: "value1" }, "TagA");
タグを渡す必要がない場合、2 番目の引数は省略できます。
const { mid, out } = await p.msg("Action", { check: "success!" }}
m
m
では out
のみを取得できます。これは最も簡潔な形式です。
const out = await p.m("Action", { Tag1: "value1", Tag2: "value2" });
これはテスト中によく見られるパターンです。aoconnect
で同じことを行うには、特に 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);
dry
const { mid, out } = await p.dry("Action", { Tag1: "value1", Tag2: "value2" });
d
const out = await p.d("Action", { Tag1: "value1", Tag2: "value2" });
res
const { err, res, out } = await p.res({ mid, check, get });
r
const out = await p.r({ mid, check, get });
v
v
は dryrun
を使用して Lua 変数を取得するための var のショートカットです。
const { p } = await ao.deploy({
src_data: `Table = { String = "Hello", Array = { "str", 3, true } }`,
});
const table = await p.v("Table"); // { String: "Hello", Array: [ "str", 3, true ] }
自動 JSON コンバージョンを無効にし、整形出力を有効にするには、第 2 引数と第 3 引数を使用します。
const table = await p.v("Table", false, true);
関数のパイプ処理(Function Piping)
pipe
多くの関数は { err, res, out, pid, mid, id }
の形式で値を返すため、これらを pipe
でチェーンすることができます。pipe
により複数メッセージの実行が簡単になります。
以下は deploy
が内部でどのように pipe
を使っているかの例です。いずれかの関数でエラーが発生すると即座に中断されます。
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 });
bind
AO
以外のインスタンスから関数を使う場合は bind
を使用します。
const fns = [{ fn: "post", bind: this.ar, args: { data, tags } }];
then
then
を使うことで、関数間で値を受け渡すことができます。たとえば、前の関数の結果を次の関数の引数として渡すのは一般的な操作です。
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 });
then
が値を返した場合、pipe
はその値で即座に終了します。また、err
を使ってエラーとして中断することも可能です。
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
には便利なパラメータがたくさんあります。
-
res
: 前の処理からのres
(結果) -
args
: 次の関数に渡すためのargs
(引数) -
out
:pipe
シーケンス全体の最終的なout
(出力) -
inp
: 前の処理からのout
(出力) -
_
:pipe
の最後にトップレベルのフィールドとして返したい値をこの_
フィールドに格納することで返却される -
pid
: もし前の関数がpid
(例:deploy
)を返していれば、それが渡される -
mid
: もし前の関数がmid
(例:msg
)を返していれば、それが渡される -
id
: もし前の関数がid
(例:post
)を返していれば、それが渡される
then
は簡易的なハッシュマップ(オブジェクト)として記述することも可能です。
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
err
は then
と同じ構造(シグネチャ)を持ちます。err
が値を返すと、その値を使って pipe
は Error
をスローします。
const fns = [
{
fn: "msg",
args: { pid, tags },
err: ({ inp }) => {
if (!inp.done) return "something went wrong!";
},
},
];
const val = await ao.pipe({ jwk, fns });
cb
cb
は各関数の実行後に、pipe
の現在の進行状況を報告するために使用できます。
await ao.pipe({
jwk,
fns,
cb: ({ i, fns, inp }) => {
console.log(`${i} / ${fns.length} functions executed`);
},
});
AR
AR
は、Arweave の基盤となるストレージレイヤーの操作およびウォレット接続を管理します。
- Instantiate
- Set or Generate Wallet
- toAddr
- mine
- balance | toAR | toWinston
- transfer
- checkWallet
- post
- tx
- data
- bundle
Instantiate
import { AR } from "wao";
const ar = new AR();
host
、port
、protocol
を設定することで、https://arweave.net
以外の特定の gateway にアクセスすることができます。
const ar = new AR({ host: "localhost", port: 4000, protocol: "http" });
ローカルの gateway を使用する場合は、port
だけを設定すればよく、他の設定項目は自動的に補完されます。
const ar = new AR({ port: 4000 });
AO
クラスは内部で自動的に AR
をインスタンス化します。
import { AO } from "wao";
const ao = new AO();
const ar = ao.ar;
ウォレットの設定または生成
AR
は、JWK ウォレットまたは ArConnect を使って初期化できます。
const ar = await new AR().init(jwk || arweaveWallet);
新しいウォレットを生成することも可能です。ArLocal を使用している場合は、同時に AR をミントすることもできます。
const { jwk, addr, pub, balance } = await ar.gen("100"); // 100 AR をミント
これら 3 つの方法のいずれかでウォレットを設定すると、別のウォレットでそのインスタンスを使用することはできません。別のウォレットで使用したい場合は、再度初期化する必要があります。これは、ブラウザ側で接続されているアクティブアドレスが知らないうちに変更され、誤ったウォレットでトランザクションを実行してしまうのを防ぐためです。
init
や gen
を呼び出さずにそのまま進むことも可能です。この場合、AR
は必要に応じてランダムなウォレットを自動生成し、複数のウォレットの使用も許可されます。これは、AO に対して dryrun
を呼び出すだけのケースで便利です。AO では dryrun
にも署名が必要ですが、読み取り専用の呼び出しのたびにブラウザ拡張ウォレットを起動してユーザーに負担をかけたくない場合に役立ちます。
ウォレットが設定されると、ar.jwk
および ar.addr
が利用可能になります。
トークン関連メソッド
toAddr
jwk
を対応するアドレスに変換します。
const addr = await ar.toAddr(jwk);
mine
保留中のブロックをマイニングします(ArLocal 専用)。
await ar.mine();
balance | toAR | toWinston
指定したアドレスの現在の残高を AR 単位で取得します。addr
を省略すると、自身のアドレス(ar.addr
)が使用されます。
const balance_AR = await ar.balance(); // 自分の残高を取得
const balance_Winston = ar.toWinston(balance_AR);
const balance_AR2 = ar.toAR(balance_Winston);
const balance_AR3 = await ar.balance(addr); // 任意のアドレスの残高を取得
transfer
AR トークンを送信します。amount
はシンプルにするため AR 単位で指定します(winston ではありません)。
const { id } = await ar.transfer(amount, to);
第 3 引数として送信元の jwk
を指定することも可能です。指定しない場合は ar.jwk
が使用されます。
const { id } = await ar.transfer(amount, to, jwk);
多くの書き込み系関数では、jwk
は最後の引数として、または { data, tags, jwk }
のようなオブジェクトのフィールドとして指定できます。
checkWallet
checkWallet
は主に内部で使用されますが、init
によってウォレットが設定されていれば this.jwk
を返します。そうでなければ、使用するためのランダムウォレットを生成して返します。
このようなパターンは多くの箇所で利用されています。init
でウォレットが設定されており、ユーザーが渡す jwk
が異なる場合、checkWallet
は誤ったウォレットによる操作を防ぐためにエラーを出します。一方、init
または gen
でウォレットが設定されておらず、jwk
も渡されない場合は、ランダムウォレットを生成して返します。
some_class_method({ jwk }){
let err = null
;({ err, jwk } = await ar.checkWallet({ jwk }))
if(!err){
// do something with the jwk
}
}
ストレージ関連メソッド
post
データを Arweave に投稿します。
const { err, id } = await ar.post({ data, tags });
tags
は配列ではなく、簡潔に書くためにハッシュマップ(オブジェクト)として指定します。
const tags = { "Content-Type": "text/markdown", Type: "blog-post" };
同じタグ名を複数使用する必要がある場合は、値に配列を使うことで対応できます。
const tags = { Name: ["name-tag-1", "name-tag-2"] };
tx
指定したトランザクション ID のトランザクションを取得します。
const tx = await ar.tx(txid);
data
指定したトランザクション ID のデータを取得します。
const data = await ar.data(txid, true); // 文字列として取得する場合は true を指定
bundle
ANS-104 に準拠した DataItem をバンドルします。
const { err, id } = await ar.bundle(dataitems);
dataitems
は[ [ 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
GQL
はArwave GraphQLの操作を簡素化し、ブロックやトランザクションをクエリします。
インスタンス化
エンドポイント url
で GQL クラスをインスタンス化できます。
import { GQL } from "wao";
const gql = new GQL({ url: "https://arweave.net/graphql" }); // the default url
AR
クラスは GQL
を内部的に自動生成します。
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;
Txs
最新のトランザクションを取得します。
const txs = await gql.txs();
asc
トランザクションを昇順で取得します。
const txs = await gql.txs({ asc: true });
first
最初の X 件のトランザクションを取得します。
const txs = await gql.txs({ first: 3 });
after
ページネーションのために、特定のトランザクション以降のトランザクションを取得します。cursor
を渡して指定します。
const txs = await gql.txs({ first: 3 });
const txs2 = await gql.txs({ first: 3, after: txs[2].cursor });
next
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
は null
になります。
block
特定のブロック高の範囲内にあるトランザクションを取得します。
const txs = await gql.txs({ block: { min: 0, max: 10 } });
or
const txs = await gql.txs({ block: [0, 10] });
Ymin
または max
のどちらか一方のみを指定することも可能です。
トランザクション ID 指定
トランザクション ID を指定してトランザクションを取得します。
const txs = await gql.txs({ id: TXID });
または
const txs = await gql.txs({ ids: [TXID1, TXID2, TXID3] });
by Recipients
受信者(recipients)を指定してトランザクションを取得します。
const txs = await gql.txs({ recipient: ADDR });
or
const txs = await gql.txs({ recipients: [ADDR1, ADDR2, ADDR3] });
by Owners
オーナー(owners)を指定してトランザクションを取得します。
const txs = await gql.txs({ owner: ADDR });
or
const txs = await gql.txs({ owners: [ADDR1, ADDR2, ADDR3] });
by Tags
タグに一致するトランザクションを取得します。
const txs = await gql.txs({ tags: { Name: "Bob", Age: "30" } });
fields
返却されるフィールドを選択することができます。
const txs = await gql.txs({ fields: ["id", "recipient"] });
ネストされたオブジェクトに対しては、次のように指定できます。
const txs = await gql.txs({ fields: ["id", { owner: ["address", "key"] }] });
また、ハッシュマップ形式でもフィールドを指定可能です。
const txs = await gql.txs({
fields: { id: true, owner: { address: true, key: true } },
});
false
を指定すると、それ以外のフィールドが返されます。
const txs = await gql.txs({
fields: { id: true, { block: { previous: false } } }
})
たとえば、上記の例では block
内の previous
を除外し、id
、timestamp
、height
が返されます。
GraphQL クエリにおいて、トランザクションで利用可能な全フィールドは以下の通りです。
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 }
}`;
Blocks
最新のブロックを取得します。
const blocks = await gql.blocks();
asc
ブロックを昇順で取得します。
const blocks = await gql.blocks({ asc: true });
first
最初の X 件のブロックを取得します。
const blocks = await gql.blocks({ first: 3 });
after
特定のブロック以降のブロックを取得してページネーションを行うには、cursor
を渡して指定します。
const blocks = await gql.blocks({ first: 3 });
const blocks2 = await gql.blocks({ first: 3, after: blocks[2].cursor });
by Block IDs
ブロック ID によってブロックを取得します。
const blocks = await gql.blocks({ id: BLCID });
or
const blocks = await gql.blocks({ ids: [BLKID1, BLKID2, BLKID3] });
height
ブロック高の範囲によってブロックを取得します。
const blocks = await gql.blocks({ height: { min: 0, max: 10 } });
または
const blocks = await gql.blocks({ height: [0, 10] });
next
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
は null
になります。
fields
const blocks = await gql.blocks({
fields: ["id", "timestamp", "height", "previous"],
});
GraphQL クエリにおいて、ブロックで利用可能な全フィールドは以下の通りです。
const block_fields = `{ id timestamp height previous }`;
ArMem
ArMem
は「Arweave in memory(インメモリ上の Arweave)」の略で、Arweave ノードおよび AO ユニットをメモリ上でエミュレートするためのクラスです。これは WAO のテストフレームワーク内で内部的に使用されます。ArMem
をインスタンス化し、他のクラス間で受け渡すことで複数のエミュレータを制御できます。
インスタンス化
wao/test
から WAO の connect
や AO
をインスタンス化すると、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
ArMem
を自分でインスタンス化し、それを他のクラスに渡すこともできます。
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 })
同じ ArMem
インスタンスを渡さない場合、2 つの AO インスタンスは異なる環境を持つことになります。
import { AO } = "wao/test"
const ao = new AO() // ao.mem
const ao2 = new AO() // ao2.mem
ao.mem
と ao2.mem
は接続されておらず、異なるネットワーク上に存在しています。
import { AO } = "wao/test"
const ao = new AO()
const ao2 = new AO({ mem: ao.mem })
これにより、2 つのインスタンスが接続されます。
HB
HB
は、Hyperbeam ノードとのインタラクションを管理します。
インスタンス化
import { HB } = "wao"
const hb = await new HB({ url: "http://localhost:10000" }).init(jwk)
metrics
ノードのメトリクスを取得します。
const metrics = await hb.metrics();
info
ノード情報を取得します。
const info = await hb.info();
const operator = info.address;
messages
あるプロセス上のメッセージを取得します。次のメッセージは message.next
を使って取得できます。
const msgs = await hb.messages({ target, from, to })
for(const item of msgs.edges){
const { cursor: slot, node: { assignment, message } } = item
}
if(msgs.next) const msgs2 = await msg.next()
-
target
: プロセス ID -
from
|to
: スロット番号を指定します。HB ではメッセージは txid ではなく整数(スロット)で識別されます。0
はプロセスを生成したType=Process
メッセージです。to
は上限値として含まれます。両方とも省略可能です。
process
プロセスを生成(spawn)する操作に相当します。
const pid = await hb.process({ tags, data });
schedule
メッセージを送信する操作に相当します。
const slot = await hb.schedule({ tags, data, process: pid, action });
compute
結果を取得する操作に相当します。
const res = await hb.compute({ process: pid, slot });
const { Messages, Spawns, Assignments, Output } = res;
dryrun
const res = await hb.dryrun({ tags, data, process: pid, action });
const { Messages, Spawns, Assignments, Output } = res;
get
署名付きの HTTP リクエストを GET
メソッドで HyperBEAM に送信します。
const res = await hb.get({ device, path });
post
署名付きの HTTP リクエストを POST
メソッドで HyperBEAM に送信します。
const res = await hb.post({ device, path, tags });
request
署名付きの HTTP リクエストを HyperBEAM に送信します。
const res = await hb.request({ device, path, tags, method });

Empowering your applications with a high-performance, scalable, decentralized, and reliable database solution 👉 linktr.ee/weavedb
Discussion