⛓️

Rust HyperBEAM チュートリアル完全ガイド

に公開

HyperBEAM M3 ベータ版を使用した Rust デバイスの構築: mini-Roam API (Vol. 1)


この記事は、
https://blog.decent.land/rust-hb-tutorial/
をベースにチュートリアルしたものを日本語で書いたものです。

著者: ラニ・エルフセイニ
公開日: 2025年6月16日


はじめに

ar.io のroam(パーマウェブコンテンツを見つけるためのツール)にヒントを得て、roamのミニバージョンを例に、カスタムRustデバイスを構築する新しいHyperBEAM開発者向けのガイドを作成しました。

HyperBEAMの最大の強みの1つは、ほぼすべてのコードをノード内にネイティブにラップし、合理的なREST標準とaoネットワークとの相互運用性を実現できることです。

このチュートリアルでは、最初から最後まで解説します。最後には、HyperBEAMノード内で動作するカスタムRustデバイスが完成します。これは、任意のデバイスをHyperBEAMに組み込むための基盤となるはずです。

前提条件

HyperBEAMノードを実行するには、以下のソフトウェアが必要です:

  • Rust
  • rebar3
  • Erlang OTP27
  • その他の依存関係

注意: インストールに必要なすべての前提条件とセットアップ方法については、hyperbeam.ar.ioにあるHyperBEAMの公式ドキュメントを参照してください。

インストールと初期化

1. リポジトリのクローンと実行

git clone -b edge https://github.com/permaweb/HyperBEAM
cd HyperBEAM
rebar3 compile
rebar3 shell

ノードはhttp://localhost:8734/で実行されるはずです。

2. トラブルシューティング

動作しない場合は(特にUbuntuを使用している場合)、以下の手順を試してください:

erl -pa _build/default/lib/*/ebin

Erlangシェルで以下のコマンドを実行:

application:ensure_all_started(hb).

my_device Rustデバイス: ~roam@1.0 API

1. Rustクレートの初期化

cd native
cargo new my_device --lib 
cd my_device
touch .gitignore

2. Cargo.tomlの設定

[package]
name = "my_device"
version = "0.1.0"
edition = "2024"

[lib]
name = "my_device"
path = "src/lib.rs" 
crate-type = ["cdylib"]

[dependencies]
anyhow = "1.0.98"
rustler = "0.29.1"
ureq = {version = "3.0.11", features = ["json"]}
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

3. .gitignoreの設定

/target
.env

4. 基本的なNIF関数の実装

lib.rsに以下のコードを追加:

use rustler::NifResult;

mod atoms {
    rustler::atoms! {
        ok,
    }
}

#[rustler::nif]
fn hello() -> NifResult<String> {
    Ok("Hello world!".to_string())
}

rustler::init!(
    "my_device_nif",
    [hello]
);

ミニロームAPI機能の実装

1. コアディレクトリの作成

mkdir src/core
cd src/core
touch arweave.rs mod.rs

2. モジュールの設定

mod.rsに以下を追加:

pub mod arweave;

lib.rsのヘッダーに以下を追加:

pub mod core;

3. Arweaveクエリ機能の実装

arweave.rsに以下のコードを追加:

use anyhow::Error;
use serde_json::Value;
use ureq;

pub fn query_permaweb() -> Result<String, Error> {
    let url = "https://arweave-search.goldsky.com/graphql/graphql";

    let query = r#"
        query {
          transactions(
            first: 1
            sort: HEIGHT_DESC
            tags: [
              {
                name: "Content-Type"
                values: [
                  "image/png"
                  "image/jpeg"
                  "image/webp"
                  "image/gif"
                  "image/svg+xml"
                  "image/avif"
                ]
                op: EQ
                match: EXACT
              }
            ]
          ) {
            edges {
              node {
                id
                tags {
                  name
                  value
                }
                block {
                  id
                  height
                  timestamp
                  previous
                }
              }
            }
          }
        }
    "#;

    let body = serde_json::json!({ "query": query });

    let response = ureq::post(url)
        .header("Content-Type", "application/json")
        .header("Accept", "application/json")
        .header("DNT", "1")
        .header("Origin", "https://arweave-search.goldsky.com")
        .send_json(body)?;

    let json: Value = response.into_body().read_json()?;

    let tx_id = &json["data"]["transactions"]["edges"][0]["node"]["id"];
    let tx_id = tx_id.as_str().unwrap_or("No ID found").to_string();

    Ok(tx_id)
}

4. テストの追加

lib.rsの末尾に以下のテストコードを追加:

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_query_permaweb() {
        let id = query_permaweb().unwrap();
        println!("TXID: {:?}", id);
        assert_eq!(id.len(), 43);
    }
}

5. DirtyCpuスケジューラの実装

lib.rsに以下の関数を追加:

#[rustler::nif(schedule = "DirtyCpu")]
fn query() -> NifResult<String> {
    query_permaweb().map_err(|err| Error::Term(Box::new(err.to_string())))
}

rustler::init!("my_device_nif", [hello, query]);

ビルドスクリプトの作成

ルートディレクトリにbuild.shを作成:

#!/bin/bash
set -e  # Exit on any error

# Function to build and copy NIF
build_nif() {
    local nif_name=$1
    echo "Building ${nif_name}..."
    cd native/${nif_name}
    cargo build --release
    mkdir -p ../../priv/crates/${nif_name}
    cp target/release/lib${nif_name}.so ../../priv/crates/${nif_name}/${nif_name}.so
    cd ../..
}

# Main script
echo "Starting deployment..."

# Build all NIFs
build_nif "my_device"

echo "All NIFs built and copied successfully"

実行権限を付与して実行:

chmod +x build.sh
./build.sh

Erlangインターフェースの実装

1. 必要なモジュールの作成

cd src
touch my_device_nif.erl dev_roam.erl

2. my_device_nif.erlの実装

-module(my_device_nif).
-export([hello/0, query/0]).
-on_load(init/0).

-define(NOT_LOADED, not_loaded(?LINE)).

-spec hello() -> binary().
hello() ->
    ?NOT_LOADED.

-spec query() -> binary().
query() ->
    ?NOT_LOADED.

init() ->
    {ok, Cwd} = file:get_cwd(),
    io:format("Current directory: ~p~n", [Cwd]),
        
    PrivDir = case code:priv_dir(hb) of
        {error, _} -> "priv";
        Dir -> Dir
    end,
    
    NifPath = filename:join([PrivDir, "crates", "my_device", "my_device"]),
    io:format("NIF path: ~p~n", [NifPath]),
    
    NifSoPath = NifPath ++ ".so",
    io:format("NIF .so exists: ~p~n", [filelib:is_file(NifSoPath)]),
    
    Result = erlang:load_nif(NifPath, 0),
    io:format("Load result: ~p~n", [Result]),
    
    Result.

not_loaded(Line) ->
    erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).

3. dev_roam.erlの実装

-module(dev_roam).
-export([info/1, info/3, query_permaweb/3, hello/3]).

info(_) ->
    #{
        <<"default">> => dev_message,
        handlers => #{
            <<"info">> => fun info/3,
            <<"query_permaweb">> => fun query_permaweb/3,
            <<"hello">> => fun hello/3
        }
    }.

info(_Msg1, _Msg2, _Opts) ->
    InfoBody = #{
        <<"description">> => <<"Roam device for interacting with my_device_nif">>,
        <<"version">> => <<"1.0">>,
        <<"paths">> => #{
            <<"info">> => <<"Get device info">>,
            <<"query_permaweb">> => <<"Query the Arweave permaweb">>,
            <<"hello">> => <<"Simple hello world test">>
        }
    },
    {ok, #{<<"status">> => 200, <<"body">> => InfoBody}}.

hello(_Msg1, _Msg2, _Opts) ->
    try
        Result = my_device_nif:hello(),
        {ok, #{<<"status">> => 200, <<"body">> => Result}}
    catch
        error:Error:Stack ->
            io:format("~nError: ~p~n", [Error]),
            io:format("Stack: ~p~n", [Stack]),
            {error, #{
                <<"status">> => 500,
                <<"body">> => #{
                    <<"error">> => <<"Failed to call hello">>,
                    <<"details">> => Error
                }
            }}
    end.

query_permaweb(_Msg1, _Msg2, _Opts) ->
    try
        Result = my_device_nif:query(),
        {ok, #{<<"status">> => 200, <<"body">> => Result}}
    catch
        error:Error:Stack ->
            io:format("~nError: ~p~n", [Error]),
            io:format("Stack: ~p~n", [Stack]),
            {error, #{
                <<"status">> => 500,
                <<"body">> => #{
                    <<"error">> => <<"Failed to query permaweb">>,
                    <<"details">> => Error
                }
            }}
    end.

4. 設定ファイルの更新

rebar3.configに以下を追加:

{cargo_opts, [
    {src_dir, "native/dev_snp_nif"},
    {src_dir, "native/my_device"}
]}.

hb_opts.erlpreloaded_devicesリストに以下を追加:

#{<<"name">> => <<"roam@1.0">>, <<"module">> => dev_roam}

5. コンパイルと実行

rebar3 compile
rebar3 shell

動作確認

ブラウザで以下のURLにアクセスして動作確認ができます:

  • http://localhost:8734/~roam@1.0/query_permaweb
  • http://localhost:8734/~roam@1.0/hello

まとめ

このチュートリアルでは、roam.ar.ioに類似した機能を持つ基本的なAPIを構築しました。次のシリーズでは以下の内容を扱います:

  1. HyperBEAM内UIの開発方法
  2. GQLのさらなるカスタマイズ
  3. ブラウザ内データレンダリング
  4. クラウドでのHyperBEAMノードのホスティング方法

参考リソース

このデモのソースコードは以下のリポジトリで入手できます:
load_hb/edge-roam-demo

Discussion