0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[ERC8444] AIエージェントの提供機能と報酬付きの依頼をオンチェーンで見つける仕組み

0
Last updated at Posted at 2026-10-17

はじめに

初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。

LLMを使った仕様駆動開発を扱う書籍『LLMと進める仕様駆動開発』を発売予定です。
ハンズオンや、LLMによって開発がどう変わるのか、実践のTipsをまとめています。

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

AIエージェントが使える機能や報酬付きの依頼を探す時、説明文だけでは現在の所有者、支払い先、預けられた報酬まで確認できません。
ERC8444は、普段の検索結果へオンチェーンの根拠を結び付け、利用前に再確認できるようにする仕組みです。

2026年10月17日時点では Draft です。
この記事では、前提となる検索方式、全API、公式参考実装、テスト、安全性まで解説します。

公式仕様は以下です。

概要

旅行を計画するAIエージェントが、航空券を検索できる機能と、旅程作成を報酬付きで依頼している相手を探す場面を考えます。
検索結果に「予約API」、「報酬100 USDC」と書かれていても、その説明を現在の所有者が承認しているか、報酬が実際に預けられているかは文章だけでは分かりません。
古い所有者の支払い先が残っていれば、正しい機能を見つけても誤った相手へ支払う可能性があります。

Web上の公開情報を集め、ふつうの言葉でAI向けの機能や接続先を探せる共通方式を Agentic Resource Discovery、略して ARD と呼びます。
ERC8444はARDを置き換えません。
ARDの検索結果と、トークン、依頼に付けた報酬、AIエージェントの身元、検索サービスの運営者をオンチェーン情報で結び付けます。

提供者は、検索結果へ載せる説明文を作り、その内容から得たハッシュ値をオンチェーンへ記録します。
検索サービスは説明文を取得し、記録済みのハッシュ値、現在の所有者、価値の裏付け、公開元ドメインとの関係を照合します。
利用するAIエージェントは、検索の関連度とオンチェーンの根拠を分けて読み、候補を選びます。

AIエージェントが機能と報酬付きの依頼を探す場面

ARDでは、利用者側のAIエージェントが「BaseでUSDC払いの画像生成機能」のような、ふつうの言葉で書いた検索条件を送ります。
検索側は、機能、API、AIエージェントなどを表す entry を返します。
entry は、誰が公開したどの接続先かを検索結果として表すJSONです。

ERC8444は、提供する機能だけでなく、作業を依頼する側も検索対象にします。
報酬付きの依頼には、求める成果物の形式、報酬、期限、受入条件を載せられます。
依頼を引き受けるAIエージェントは、自分が提供できる成果物と条件が合う報酬付きの依頼を探せます。

オンチェーン情報を検索結果へ結び付ける役割

検索結果の説明とオンチェーン情報の接点になる対象を anchor と呼びます。
たとえば、機能を表すNFT、報酬を預けた依頼を表すNFT、AIエージェントを表すaddressがanchorになります。
anchorごとに、説明文のハッシュ値や公開名を保存するオンチェーン記録が record です。

説明文を含むARD entryを descriptor と呼びます。
現在の所有者はdescriptorのうち提供条件に関わる部分をハッシュ化し、その値をrecordへ登録します。
取得先がdescriptorを書き換えても、検索側はハッシュ不一致として検出できます。

anchorの種類ごとに、既存contractの読み方は異なります。
そこで、対象contractから「存在するか」、「現在誰が操作できるか」、「価値の裏付けがあるか」、「説明文と照合する値は何か」を同じ形で読む接続部品を adapter と呼びます。
adapterは値を保存し直さず、呼び出された時点の情報を対象contractから読みます。

取得した文書とオンチェーン情報を照合し、検索結果へ値を追加するソフトウェアが resolver です。
resolverがARDの検索APIまで公開する場合は gateway と呼びます。

検索から利用判断まで

検索開始から利用判断までは、以下の順で進みます。

  1. 提供者がdescriptorを公開し、現在の所有者が内容のハッシュ値をrecordへ登録します
  2. gatewayはrecordとadapterを読み、descriptorとドメイン上の公開文書を取得します
  3. resolverはハッシュ値、現在の所有者、公開ドメインとの関係、価値の裏付けを計算します
  4. gatewayは整合したentryだけを通常のARD検索結果へ載せます
  5. 利用側は、検索条件との近さ、状態、所有者の承認、価値の裏付けを別々に確認します

ERC8444でふつうの言葉による検索からオンチェーン情報を照合した検索結果を得る流れ

検索順位の点数は、検索条件に近いかを示す値であり、信用度ではありません。
また、所有者が変わった後に新しい所有者がdescriptorを承認していないrecordや、廃止されたrecordは検索結果から外れます。

一時停止中のrecordを検索へ載せるか、どのlevelで示すかは、現行仕様だけでは一意に確定できません。
参考gatewayは一時停止を数値上のlevel 0として扱いますが、これを規格要件とは断定できません。
利用側は status と所有者の承認状態を個別に確認し、一時停止中のofferを有効な候補として扱わない実装にします。

動機

ARDは、どの公開元がどの機能を案内しているかを検索する仕組みです。
一方、価格、支払い、報酬の預け入れ、信用情報までは定めません。
ERC8444は、既存ARD entryをそのまま利用しながら、価値に関わる事実を既存contractから読めるようにします。

発見だけでは読めない価値

「画像を生成できる」という説明から、呼び出し1回の価格や支払い先は確定できません。
「報酬100 USDCの画像作成依頼」という説明からも、100 USDCが預けられたままか、依頼が取り消されていないかは分かりません。

価値に関する情報を検索用registryへ複製すると、元contractの残高や状態とずれる可能性があります。
ERC8444は、recordへ説明文のcommitmentを置き、残高や所有者はadapter経由で元contractから都度読みます。
検索用のコピーを第二の正本にしない設計です。

domainと支払いaddressの分離

Webのdomainを管理する主体と、資金を受け取るaddressを管理する主体は同じとは限りません。
両者を同格の権限者にすると、domain側がaddress所有者の承認していない価格を公開したり、address側がdomain名を勝手に名乗ったりできます。

ERC8444では、資金やanchorを操作するaddressを価値情報の起点にします。
domainは、そのaddressを自分の公開文書で認めることで信頼関係を追加します。
秘密鍵を持たないdomain公開者も従来のARD entryを公開でき、その場合はオンチェーン根拠のない段階として検索できます。

報酬付きの依頼を見つける方向

従来のARD検索は、利用者が使いたい機能を探す方向が中心です。
ERC8444の task は、成果物を依頼し、受入条件を満たした相手へ支払う報酬を示す公開情報をentryとして表します。

依頼する側は、受け取れる成果物の形式、報酬額、期限、審査方式、応募条件を公開します。
提供側のAIエージェントは「自分の機能を誰が必要としているか」を検索できます。
ただし、成果物の提出、審査、報酬解放そのものはERC8444の範囲外です。

移転後の古い説明を止める理由

機能NFTを売却しても、recordへ保存されたdescriptorのハッシュ値は自動では変わりません。
そのまま配信すると、買い手が旧所有者のendpoint、利用条件、支払い先を支持しているように見えます。
受け取りを望まないaddressへNFTを送り付けるケースも同じです。

ERC8444は、descriptorを最後に登録したaddressと現在の所有者を比較します。
一致しない間は検索結果から外し、新しい所有者が同じハッシュ値または新しいハッシュ値を登録した後に再掲載します。

仕様

4種類のkind

検索対象が取引のどちら側にいるかを表す区分を kind と呼びます。
データ形式を表すARDの type とは別の値です。

kind 立場 anchor 現在操作できる主体 裏付けの条件
skill 機能の提供 実行可能な機能を表すtoken token owner package hashが存在する
task 報酬付きの依頼 報酬付きの依頼を表すtoken token owner 未取消、報酬あり、期限内
agent 取引主体 address そのaddress identity registryでactive
registry 検索先 運営addressと識別値 そのaddress 常に裏付けなし

skill、task、agent、registry の4種類はすべてのregistry instanceが対応します。
新しいJSON形式が増えてもkindは増やしません。
assertion、policy、market は将来用に予約されています。

kind IDは以下の式で作ります。

bytes32 kindId = keccak256("ard-onchain.kind.<name>");

AnchorとRecord

anchorは (chainId, target, id) の組です。
tokenをanchorにする場合はchain、token contract、token IDを示すCAIP-19形式、addressをanchorにする場合はchainとaddressを示すCAIP-10形式で表示します。
CAIP形式により、利用側はどのchainのどのcontractまたはaddressを確認すべきか特定できます。

record IDはanchorから以下の式で作ります。

recordId = keccak256(abi.encode(block.chainid, target, id));

同じanchorには1つのrecordだけを登録できます。
別kindを指定しても2件目は登録できません。
別chain上の同じtoken addressとIDは、別のanchorです。

ARDの公開名は urn:air:<publisher>:<namespace>:<name> 形式です。
このURNは別名であり、recordの同一性はanchorで比較します。
現在の所有者とpublisher domainの関係が確認でき、domainのmanifestにも同じURNが載る間だけcanonical nameとして使えます。

gatewayは自分のdomainで到達できるaliasを作れます。
複数gatewayが別のaliasを返しても、利用側は value:anchor を比較して同じrecordか判断します。

Descriptorの検証

descriptorは、表示名、接続先、説明、提供条件などを載せたARD entryです。
現在の所有者は、提供内容に関わる部分だけを固定順序のJSONへ変換してSHA-256を計算し、recordへ保存します。

Hashへ入る情報

descriptorのmemberは3種類に分かれます。

区分 主な値 hash対象 理由
公開側の情報 identifier、trustManifest、@context、@id 対象外 gatewayごとに別名や公開情報を持てるようにする
内容の情報 displayName、type、urlまたはdata、説明、提供条件 対象 所有者が何を提供または依頼したか固定する
resolverの計算結果 value:anchor、binding、level、status、adoptedなど 対象外 読み取り時点のchainとdomainから再計算する

内容の情報はRFC 8785のJSON Canonicalization Schemeで正規化します。
これは、同じJSON内容を空白やkey順に左右されない同一のbyte列へ変換する方式です。
そのUTF-8 byte列へSHA-256を適用した値が descriptorHash です。

ERC8444でpublisherが作る情報、hash対象、resolverが加える情報を分ける境界

descriptorURI は取得先のヒントであり、信頼の根拠ではありません。
取得できない場合やhashが一致しない場合、resolverは整合したrecordとして配信できません。

提供条件と報酬付きの依頼のJSON

所有者が書く追加情報には次があります。

member 読者が確認する内容
value:kind entryが機能提供、報酬付きの依頼、主体、検索先のどれか
value:controller 公開者が主張するcontroller。権限判定には使わない
value:terms 機能の単位、価格、通貨、供給量
value:settlement onchain、x402、宣言のみの支払い経路と支払い先
value:provenance packageと公開cardのdigest
value:credit 外部の信用情報を確認するregistryとlevel
value:dispute 紛争判断を確認するpolicyとregistry
value:eligibility taskへ応募できる主体の条件
value:registration agentのregistration file
value:indexes registryが索引するregistry instance

task のpayloadは application/ai-task+json を使います。
求める成果物形式を表す wants と、報酬額・通貨を表す bounty は必須です。
期限、件数、審査方式、応募条件は任意で追加できます。
オンチェーン情報がなくても有効なARD entryになるため、domainだけの報酬付きの依頼も公開できます。

Domain Binding

domainとaddressが互いを認めたかを示す関係が binding です。
address側のオンチェーン宣言と、domain側のWeb上の承認をresolverが照合します。

Onchain claim

addressは、対象domainを自分と結び付けたいという宣言をregistryへ保存します。
本人がtransactionを送る方法と、署名を第三者に提出してもらう方法があります。

domainは小文字ASCII、数字、-、. の1〜253 bytesで正規化します。
先頭・末尾の.と空labelは使えません。
国際化domainはpunycodeへ変換してから渡します。

同じdomainを複数addressがclaimできます。
registryは本当の所有者を決めず、各addressの宣言だけを保存します。

Domainのacknowledgment

domain側は /.well-known/ard.json にaddressを載せて承認します。
manifest上部の value:controllers に含めるか、entryの value:controller に書きます。

chainはHTTPS上のmanifestを読めません。
そのため、registry contractがverified判定を保存するのではなく、resolverがonchain claimとmanifestを合わせて計算します。

ERC8444でaddressのclaimとdomainの承認を照合してbindingを決める流れ

4つのbinding状態

bindingは、domainとaddressの間にどの宣言がそろったかを表します。
利用側は、この状態を読んでdomain名を信頼表示へ使えるか判断します。

状態 保存・公開されている条件 利用側の判断
unbound addressのclaimがない 関係なしとして扱う
claimed 1 addressだけがclaimし、domainは未承認 address側だけの主張として扱う
verified addressがclaimし、domainも承認 domainとaddressの相互確認済みとして扱う
contested 複数addressがclaimし、対象addressは未承認 競合中として扱う

recordにURNがない場合は none、現在のcontrollerがURNのpublisher domainをclaimしていない場合は third-party として表示します。
承認が消えるとcanonical URNはstaleになります。
後から承認が戻っても、承認がなかった期間に配信された内容まで遡って正当化しません。

LevelとAdoption

DormantからBackedまで

levelは、検索対象にどのオンチェーン根拠があるかを表します。
domainとのbindingや信用情報とは独立しています。

level 条件 利用側が確認できること
L0 recordがない domain公開者の宣言だけである
L1 statusがActiveのrecordがある anchorと現在のcontrollerをchainで読める
L2 L1に加えadapterがbackedを返し、必要なprovenanceも一致する 報酬、package、identityなどkind固有の裏付けがある

L2 でも価格の妥当性や提供品質まで保証しません。
利用側は (level, binding, credit) を別々に読みます。

一時停止と廃止

recordには、利用中、一時停止、廃止済みのどれかが保存されます。
一時停止は現在のcontrollerが解除できますが、廃止は後の所有者も解除できません。

保存状態 次に許される変更 検索時の扱い
Active SuspendedまたはRetired L1またはL2の候補になる
Suspended ActiveまたはRetired 仕様上の検索掲載とlevelに曖昧さが残るためstatusを明示確認する
Retired 変更不可 検索結果へ載せない

購入前にはstatusを確認します。
一時的に止めるだけなら廃止を選びません。

移転後のadoption

descriptorを最後にcommitしたaddressを attestor と呼びます。
attestorと現在のcontrollerが一致している状態が adopted、一致しない状態が unadopted です。

ERC8444でtoken移転後に検索から外れ新所有者の再commitで戻る流れ

unadoptedでもrecord、level、statusは残ります。
ただしresolverは検索結果と一覧から外します。
新しいcontrollerが updateDescriptor 関数で同じdescriptorHashを再登録すると、そのaddressがattestorになり再掲載できます。

IARDOnchainRegistry

IARDOnchainRegistry は、anchorごとのcommitment、domain claim、kindとadapterの対応を扱うinterfaceです。

Status

enum Status { None, Active, Suspended, Retired }

Status は、recordが存在するか、現在利用中か、一時停止か、廃止済みかを表すEnum型です。

値 保存状態 判定時の扱い
None recordが未登録 更新や状態変更を拒否する
Active 利用中 L1またはL2の判定へ進む
Suspended controllerが一時停止 利用候補から外す判断に使う
Retired 永続的に廃止 更新を拒否し検索へ載せない

Record

struct Record {
    bytes32 kind;
    address target;
    uint256 id;
    bytes32 descriptorHash;
    string descriptorURI;
    string urn;
    Status status;
    address attestor;
    uint64 updatedAt;
}

Record は、controllerが登録したdescriptorのcommitmentとanchorをまとめて保存する構造体です。

  • フィールド
field 保存する値
kind 4種類のどれとして読むか
target token contractまたは主体address
id token ID、agentの0、registryの非0識別値
descriptorHash 内容memberのSHA-256
descriptorURI descriptor取得先のヒント
urn controllerが提示したcanonical候補
status 利用中、一時停止、廃止の状態
attestor descriptorを最後にcommitしたcontroller
updatedAt 最終更新時刻

controller、backing、anchor hintは保存せず、adapterから毎回読みます。

Registered

event Registered(bytes32 indexed recordId, bytes32 indexed kind, address indexed target, uint256 id, address attestor, bytes32 descriptorHash, string descriptorURI, string urn);

anchorへ最初のrecordを登録する時に発行するイベントです。
indexerはこのログから候補を列挙できます。

  • フィールド
field 詳細
recordId anchorから作ったrecord識別子
kind adapterを選ぶkind ID
target anchorのcontractまたはaddress
id token IDまたはaddress anchorの識別値
attestor 登録時のcontroller
descriptorHash commitした内容hash
descriptorURI descriptor取得先
urn canonical候補

DescriptorUpdated

event DescriptorUpdated(bytes32 indexed recordId, bytes32 descriptorHash, string descriptorURI, string urn);

現在のcontrollerがdescriptor commitmentを更新する時に発行するイベントです。
同じhashの再commitも記録されますが、attestorの新しい値はrecordを再読取して確認します。

  • フィールド
field 詳細
recordId 更新したrecord
descriptorHash 更新後の内容hash
descriptorURI 更新後の取得先
urn 更新後のcanonical候補

StatusChanged

event StatusChanged(bytes32 indexed recordId, Status status);

recordの利用状態を変更する時に発行するイベントです。

  • フィールド
field 詳細
recordId 状態を変えたrecord
status 変更後の状態

DomainClaimed

event DomainClaimed(bytes32 indexed domainHash, address indexed controller, string domain);

addressのdomain claimを保存する時に発行するイベントです。

  • フィールド
field 詳細
domainHash 正規化済みdomainのkeccak256
controller claimしたaddress
domain 正規化済みdomain文字列

DomainReleased

event DomainReleased(bytes32 indexed domainHash, address indexed controller);

addressが自分のdomain claimを取り下げる時に発行するイベントです。

  • フィールド
field 詳細
domainHash 対象domainのhash
controller claimを取り下げたaddress

KindRegistered

event KindRegistered(bytes32 indexed kind, address adapter, string uri);

instance ownerがkindとadapterの対応を初めて登録する時に発行するイベントです。

  • フィールド
field 詳細
kind 登録したkind ID
adapter live情報を読むadapter address
uri adapter情報の参照先

OwnershipTransferred

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

registry instanceの管理者を変更する時に発行するイベントです。

  • フィールド
field 詳細
previousOwner 変更前のinstance owner
newOwner 変更後のinstance owner

register

function register(bytes32 kind, address target, uint256 id, bytes32 descriptorHash, string calldata descriptorURI, string calldata urn) external returns (bytes32 recordId);

anchorへ最初のrecordを登録する関数です。

  1. kindにadapterがあり、anchorが存在し、呼び出し元が現在のcontrollerであることを確認します
  2. anchorからrecord IDを作り、重複と空のdescriptor hashを拒否します
  3. Activeのrecordを保存し、呼び出し元をattestorとして Registered イベントを発行します
  • 引数
引数 詳細
kind adapterを選ぶkind ID
target token contractまたは主体address
id token IDまたはaddress anchorの識別値
descriptorHash 内容memberのSHA-256
descriptorURI descriptor取得先
urn canonical候補。空文字列も可能
  • 戻り値
戻り値 詳細
recordId chain ID、target、idから作った識別子
  • 実行条件

  • kindが登録済みであること。

  • anchorが存在し、呼び出し元が現在のcontrollerであること。

  • descriptorHashが0でなく、anchorにrecordが未登録であること。

updateDescriptor

function updateDescriptor(bytes32 recordId, bytes32 descriptorHash, string calldata descriptorURI, string calldata urn) external;

既存recordのdescriptor commitmentを更新する関数です。

  1. recordが存在し、廃止済みでないことを確認します
  2. adapterが返す現在のcontrollerと呼び出し元を比較します
  3. hash、URI、URN、attestor、更新時刻を保存し、 DescriptorUpdated イベントを発行します
  • 引数
引数 詳細
recordId 更新するrecord
descriptorHash 新しい内容hash。同じhashの再commitも可能
descriptorURI 新しい取得先
urn 新しいcanonical候補
  • 実行条件

  • recordが存在し、statusがRetiredでないこと。

  • 呼び出し元が現在のcontrollerであること。

  • descriptorHashが0でないこと。

同じhashを許すため、移転後の新しいcontrollerは内容を変更せずadoptできます。

setStatus

function setStatus(bytes32 recordId, Status status) external;

recordを一時停止、再開、または廃止する関数です。

  1. recordと現在のcontrollerを確認します
  2. ActiveとSuspendedの往復、または両状態からRetiredへの変更だけを許可します
  3. statusを保存し、 StatusChanged イベントを発行します
  • 引数
引数 詳細
recordId 状態を変更するrecord
status 変更後の状態
  • 実行条件

  • 呼び出し元が現在のcontrollerであること。

  • Retiredではないこと。

  • 許可された遷移であること。

この関数はattestorを変更しません。

claimDomain

function claimDomain(string calldata domain) external;

呼び出し元addressがdomainとの関係を主張する関数です。

  1. domainの文字、長さ、label境界を検証します
  2. 同じaddressによる重複claimと、実装上のclaim数上限を確認します
  3. claimを保存し、 DomainClaimed イベントを発行します
  • 引数
引数 詳細
domain 正規化済みのdomain文字列
  • 実行条件

  • domainが1〜253 bytesの正規形であること。

  • 呼び出し元が同じdomainを未claimであること。

claimDomainBySig

function claimDomainBySig(string calldata domain, address controller, uint256 nonce, uint256 deadline, bytes calldata signature) external;

controllerの署名を使い、第三者がdomain claimを提出する関数です。

  1. domain、期限、現在のnonceを確認します
  2. chain ID、registry address、controller、nonce、期限を含む型付きdigestを再計算します
  3. 署名を検証し、claimを保存してnonceを1増やします
  • 引数
引数 詳細
domain claimする正規化済みdomain
controller claim主体のaddress
nonce 署名を1度だけ使うための現在値
deadline 署名を受け付ける期限
signature controllerが作成した署名
  • 実行条件

  • deadlineを過ぎていないこと。

  • nonceが nonces(controller) と一致すること。

  • EOAまたはcontract accountの署名検証に成功すること。

署名対象をchainとregistryへ結び付け、別環境での再利用を防ぐ方式がEIP712です。
利用側は DOMAIN_SEPARATOR、nonce、deadlineを使って同じdigestを再計算できます。

EIP712については以下の記事を参考にしてください。

controllerがcontractの場合は、そのcontractへdigestと署名を渡し、所定の承認値が返るか確認します。
multisigやpolicy contractの署名承認を扱うこの方式がERC1271です。

ERC1271については以下の記事を参考にしてください。

releaseDomain

function releaseDomain(string calldata domain) external;

呼び出し元が自分のdomain claimを取り下げる関数です。

  1. domainを検証してhashを計算します
  2. 呼び出し元のclaimが存在することを確認します
  3. claimを削除し、 DomainReleased イベントを発行します
  • 引数
引数 詳細
domain claimを取り下げるdomain
  • 実行条件

  • domainが正規形であること。

  • 呼び出し元のclaimが存在すること。

registerKind

function registerKind(bytes32 kind, address adapter, string calldata uri) external;

kindとadapterの対応を1度だけ登録する関数です。

  1. 呼び出し元がinstance ownerで、adapterがzero addressでないことを確認します
  2. kindが未登録で、adapter自身が返すkindと一致することを確認します
  3. 対応を保存し、 KindRegistered イベントを発行します
  • 引数
引数 詳細
kind 登録するkind ID
adapter 読み取りを担当するcontract
uri adapter情報の参照先
  • 実行条件

  • instance ownerだけが呼べること。

  • kindが未登録であること。

  • adapterの報告するkindが一致すること。

kind tableはwrite-onceです。
誤ったadapterを登録しても置き換えられません。

transferOwnership

function transferOwnership(address newOwner) external;

registry instanceの管理権限を別addressへ移す関数です。

  1. 呼び出し元が現在のownerであることを確認します
  2. ownerをnewOwnerへ更新します
  3. OwnershipTransferred イベントを発行します
  • 引数
引数 詳細
newOwner 新しいinstance owner。zero addressなら管理を終了する
  • 実行条件

  • 現在のownerだけが呼べること。

zero addressへ移すと、以後kindを追加できません。

owner

function owner() external view returns (address);

kind登録とownership移転を実行できるinstance ownerを返す関数です。

  1. 保存されているowner addressを読み、その値を返します
  • 戻り値
戻り値 詳細
address 現在のowner。zero addressなら管理終了済み
  • 実行条件

  • 誰でも呼び出せます。

recordIdOf

function recordIdOf(address target, uint256 id) external view returns (bytes32);

現在のchain ID、target、idからrecord IDを計算する関数です。

  1. chain ID、target、idをABI encodeし、keccak256の結果を返します
  • 引数
引数 詳細
target anchorのcontractまたはaddress
id token IDまたはaddress anchorの識別値
  • 戻り値
戻り値 詳細
bytes32 計算したrecord ID
  • 実行条件

  • 誰でも任意のtargetとidを指定できます。

recordOf

function recordOf(bytes32 recordId) external view returns (Record memory);

record IDに対応する保存済みRecordを返す関数です。

  1. record IDをkeyに保存値を読み、Record構造体として返します
  • 引数
引数 詳細
recordId 読み取るrecord
  • 戻り値
戻り値 詳細
Record commitment、anchor、status、attestor、更新時刻

未登録のIDでは status がNoneの値を読み取れます。

  • 実行条件

  • 誰でも呼び出せます。

controllerOf

function controllerOf(bytes32 recordId) external view returns (address);

adapterへ問い合わせ、anchorを現在操作できるaddressを返す関数です。

  1. recordのkindとanchorを読み、対応adapterのcontrollerOfへ転送します
  • 引数
引数 詳細
recordId controllerを確認するrecord
  • 戻り値
戻り値 詳細
address 現在のcontroller。anchor消滅時はzero address
  • 実行条件

  • recordが存在すること。

保存値ではないため、token移転後は新しいownerを返します。

backed

function backed(bytes32 recordId) external view returns (bool);

adapterへ問い合わせ、kind固有の価値の裏付けが現在あるか返す関数です。

  1. recordのkindとanchorを読み、対応adapterのbackedへ転送します
  • 引数
引数 詳細
recordId 裏付けを確認するrecord
  • 戻り値
戻り値 詳細
bool 裏付けがあればtrue
  • 実行条件

  • recordが存在すること。

anchorHintOf

function anchorHintOf(bytes32 recordId) external view returns (bytes32);

descriptorのprovenanceと照合するkind固有の値をadapterから返す関数です。

  1. recordのkindとanchorを読み、対応adapterのanchorHintへ転送します
  • 引数
引数 詳細
recordId hintを確認するrecord
  • 戻り値
戻り値 詳細
bytes32 package hash、task hash、identity digest、または0
  • 実行条件

  • recordが存在すること。

claimsOf

function claimsOf(bytes32 domainHash) external view returns (address[] memory);

1つのdomainをclaimしている全addressを返す関数です。

  1. domainHashをkeyにclaimant一覧を読み、その配列を返します
  • 引数
引数 詳細
domainHash 正規化済みdomainから計算したhash
  • 戻り値
戻り値 詳細
addresses claim中のaddress一覧
  • 実行条件

  • 誰でも任意のdomainHashを指定できます。

resolverは一覧とdomain manifestを照合してcontested状態を計算します。

hasClaim

function hasClaim(bytes32 domainHash, address controller) external view returns (bool);

特定addressのdomain claimが保存されているか返す関数です。

  1. domainHashとcontrollerの組をclaim indexで調べ、存在結果を返します
  • 引数
引数 詳細
domainHash 対象domainのhash
controller 確認するaddress
  • 戻り値
戻り値 詳細
bool claimがあればtrue
  • 実行条件

  • 誰でも任意のdomainHashとcontrollerを指定できます。

domainHashOf

function domainHashOf(string calldata domain) external pure returns (bytes32);

渡された文字列のbyte列へkeccak256を適用する関数です。

  1. domainをbyte列へ変換し、keccak256の結果を返します
  • 引数
引数 詳細
domain hash化するdomain文字列
  • 戻り値
戻り値 詳細
bytes32 keccak256(bytes(domain)) の結果

このview自体はdomainの正規形を検証しません。
claim時の検証済み文字列と同じ形式を渡します。

  • 実行条件

  • 誰でも任意の文字列を指定できます。

kindAdapter

function kindAdapter(bytes32 kind) external view returns (address);

kindに固定されたadapter addressを返す関数です。

  1. kindをkeyにadapter tableを読み、そのaddressを返します
  • 引数
引数 詳細
kind 確認するkind ID
  • 戻り値
戻り値 詳細
address 登録済みadapter。未登録ならzero address
  • 実行条件

  • 誰でも任意のkind IDを指定できます。

nonces

function nonces(address controller) external view returns (uint256);

controllerが署名付きdomain claimで使うnonceを返す関数です。

  1. controllerをkeyにnonceを読み、その現在値を返します
  • 引数
引数 詳細
controller nonceを確認するaddress
  • 戻り値
戻り値 詳細
uint256 次に受け付けるnonce

成功した署名付きclaimごとに1増え、同じ署名の再利用を防ぎます。

  • 実行条件

  • 誰でも任意のcontrollerを指定できます。

DOMAIN_SEPARATOR

function DOMAIN_SEPARATOR() external view returns (bytes32);

署名対象を特定のchainとregistry instanceへ結び付けるEIP712 domain separatorを返す関数です。

  1. deployment時に固定したdomain separatorを読み、その値を返します
  • 戻り値
戻り値 詳細
bytes32 name、version、chain ID、verifying contractから作った値
  • 実行条件

  • 誰でも呼び出せます。

supportsInterface

function supportsInterface(bytes4 interfaceId) external view returns (bool);

対象contractが指定interfaceへの対応を宣言しているか返す関数です。

  1. interfaceIdをregistry interfaceとERC165のIDへ照合し、対応結果を返します
  • 引数
引数 詳細
interfaceId 対応を確認する4 bytesのID
  • 戻り値
戻り値 詳細
bool 対応していればtrue

呼び出すinterfaceを事前に選べるこの検出方法がERC165です。
ERC8444 registryは 0x881908a5 にtrueを返します。

  • 実行条件

  • 誰でも任意のinterfaceIdを指定できます。

ERC165については以下の記事を参考にしてください。

Custom errors

custom errorは、どの入力または保存状態を拒否したかを呼び出し元へ返します。

error NotOwner();
error ZeroAddress();
error KindExists(bytes32 kind);
error UnknownKind(bytes32 kind);
error AdapterKindMismatch(bytes32 expected, bytes32 reported);
error AnchorMissing();
error NotController();
error EmptyDescriptor();
error AlreadyRegistered(bytes32 recordId);
error UnknownRecord(bytes32 recordId);
error RecordRetired(bytes32 recordId);
error InvalidTransition(Status from, Status to);
error InvalidDomain();
error AlreadyClaimed();
error NotClaimed();
error TooManyClaims();
error SignatureExpired(uint256 deadline);
error BadNonce(uint256 expected, uint256 given);
error InvalidSignature();
error 拒否する状態 修正する内容
NotOwner instance owner以外の管理操作 owner accountから実行する
ZeroAddress adapterなどにzero addressを指定 有効なaddressを渡す
KindExists 同じkindを再登録 既存adapterを使う
UnknownKind 未登録kindでrecord操作 kindを先に登録する
AdapterKindMismatch adapterの報告値と登録kindが不一致 正しいadapterを指定する
AnchorMissing adapterがanchor不在を報告 targetとid、焼却状態を確認する
NotController 現在のcontroller以外が変更 adapterのlive controllerを確認する
EmptyDescriptor descriptorHashが0 内容から有効なhashを作る
AlreadyRegistered anchorにrecordが存在 既存recordを更新する
UnknownRecord record未登録 正しいrecord IDを使う
RecordRetired 廃止済みrecordの変更 変更を中止する
InvalidTransition 許可外のstatus遷移 ActiveとSuspendedの往復かRetiredへの遷移を使う
InvalidDomain domain形式が不正 小文字ASCIIの正規形へ直す
AlreadyClaimed 同じaddressの重複claim 既存claimを使う
NotClaimed 存在しないclaimのrelease claim状況を確認する
TooManyClaims 実装上のclaim数上限超過 上限内になるまで待つ
SignatureExpired deadline超過 新しい期限で署名し直す
BadNonce 保存nonceと提出nonceが不一致 現在nonceで署名し直す
InvalidSignature EOAまたはERC1271検証に失敗 signer、digest、署名形式を確認する

IARDKindAdapter

IARDKindAdapter は、異なるanchor contractからregistryが必要とする5種類の値を同じ形で読むinterfaceです。
adapterのviewは失敗をrevertせず、false、zero address、0で返します。

kind

function kind() external view returns (bytes32);

adapterが担当するkind IDを返す関数です。

  1. adapterへ固定されたkind IDを読み、その値を返します
  • 戻り値
戻り値 詳細
bytes32 keccak256("ard-onchain.kind.<name>") の値

registryはkind登録時に期待値と比較します。

  • 実行条件

  • 誰でも呼び出せます。

exists

function exists(address target, uint256 id) external view returns (bool);

指定anchorが現在存在し、そのkindとして読めるか返す関数です。

  1. targetのinterfaceとidの存在を読み、失敗をfalseへ変えて返します
  • 引数
引数 詳細
target 対象contractまたはaddress
id token IDまたはaddress anchorの識別値
  • 戻り値
戻り値 詳細
bool 存在すればtrue。外部call失敗時はfalse
  • 実行条件

  • 誰でも任意のtargetとidを指定できます。

controllerOf

function controllerOf(address target, uint256 id) external view returns (address);

anchorを現在操作できるaddressを返す関数です。

  1. kind固有のownerまたは主体addressを読み、失敗をzero addressへ変えて返します
  • 引数
引数 詳細
target 対象contractまたはaddress
id 対象ID
  • 戻り値
戻り値 詳細
address token ownerまたは主体address。読取失敗時はzero address
  • 実行条件

  • 誰でも任意のtargetとidを指定できます。

backed

function backed(address target, uint256 id) external view returns (bool);

anchorの価値を裏付けるkind固有の状態が現在成立しているか返す関数です。

  1. package、escrow、期限、identityのうちkind固有の条件を読み、boolを返します
  • 引数
引数 詳細
target 対象contractまたはaddress
id 対象ID
  • 戻り値
戻り値 詳細
bool package、escrow、identityの条件が成立すればtrue
  • 実行条件

  • 誰でも任意のtargetとidを指定できます。

anchorHint

function anchorHint(address target, uint256 id) external view returns (bytes32);

descriptorのprovenanceと照合する値を対象contractから返す関数です。

  1. kind固有のpackage hash、task hash、identity digestを読み、失敗を0へ変えて返します
  • 引数
引数 詳細
target 対象contractまたはaddress
id 対象ID
  • 戻り値
戻り値 詳細
bytes32 package hash、task hash、identity digest、または0
  • 実行条件

  • 誰でも任意のtargetとidを指定できます。

ResolverとGateway

resolverは以下の順で検索用entryを作ります。

  1. recordとadapterのlive viewを読みます
  2. descriptorURIから文書を取得し、内容memberのhashを照合します
  3. anchor、gateway alias、binding、canonical URN、level、statusを計算します
  4. attestorと現在のcontrollerを比較してadoptedを計算します
  5. 内容memberへ公開側memberと計算済みmemberを加えて配信します

descriptorを取得できない、hashが違う、unadopted、Retiredのrecordは検索結果へ載せません。
unadopted recordは、anchorやrecord IDを指定するinspection endpointで value:adopted: false として表示できます。

gatewayはARDのsearch endpointへ auto、referrals、none のfederation modeを実装します。
auto は必要に応じて他registryも検索し、referrals は照会先を返し、none は自分のindexだけを使うモードです。
gateway自身を説明する application/ai-registry+json のentryも /.well-known/ard.json で公開します。

補足

検索関連度とTrust

検索scoreは、利用側が送った検索条件とentryの内容がどれだけ近いかを順位付けする値です。
高いscoreでも、descriptorが現在のcontrollerに採用されている、domainがverified、報酬がbackedという意味にはなりません。

利用側は少なくとも value:anchor、value:descriptorHash、value:level、value:binding、value:status、value:adopted を別々に表示します。
creditも外部registryへの参照であり、levelへ合算しません。

名前と同一性

同じrecordが、controllerのcanonical URN、gateway Aのalias、gateway Bのaliasで返る場合があります。
名前の一致ではなく value:anchor の一致で同一recordと判断します。

canonical URNは、publisher domainとのverified bindingとmanifest上の同一identifierがそろう間だけ有効です。
条件を失ったURNはstaleとして表示し、anchor自体は変えません。

価格表示とSettlement

value:terms は、call、epoch、license、copyの単位と価格を人やagentへ示します。
value:settlement は、支払いrail、payee、asset、endpointを示します。

onchain はanchored contractの支払い先を使います。
x402 と declared はdescriptor内のpayeeを使うため、unadopted descriptorを配信しないことが特に重要です。
ERC8444は価格の正しさ、実際の決済、escrow解放を実行しません。

Cross chainと可用性

registryは自分と同じchainのanchorだけを検証します。
別chainの同じtokenは別recordであり、複数chainを同一対象として結ぶ方法は将来のprofileに委ねられています。

descriptorやdomain manifestはoffchainで取得します。
規格は取得サイズ、timeout、cache TTL、confirmation数、reorg後の回復、production SLAを定めていません。
運用者はlast sync、参照block、取得失敗、stale cacheを外部から確認できるようにします。

テストケース

固定値を再計算するvector

公式vectorは、独立実装が同じ値を作れるか確認するための固定入力と期待値です。

対象 例 期待値
skill kind keccak256("ard-onchain.kind.skill") 0x75357da1...8ca7b0bb
record ID chain 1、target 0x11...11、id 42 0x0d08c6fa...047a6db1
domain hash alice.example 0xd94dcba6...2b0f7a7
registry descriptor JCS化した内容member SHA-256 0xb5a38766...32445b6
registry interface 全function selectorのXOR 0x881908a5
adapter interface 全function selectorのXOR 0x67bde273

署名vectorでは、Sepoliaのchain ID、registry address、controller、nonce 0、deadlineを使った claimDomainBySig が成功し、nonceが1になります。
別chainや別registry、期限切れ、使用済みnonceでは同じ署名を再利用できません。

Recordとclaimの失敗条件

公式test suiteは、4 kindの登録に加え、unknown kind、空hash、存在しないanchor、controller以外、同じanchorの二重登録を拒否することを確認します。
token移転後もrecordのcontrollerは新ownerへ変わり、attestorは旧ownerのまま残り、同じhashの再commit後に一致することも確認します。

domainでは、不正形式、重複claim、期限切れ、nonce不一致、署名再利用、EOA署名、ERC1271署名を検証します。
taskでは、報酬あり、取消済み、期限切れを分け、agent identityではactive、stale、retiredを分けます。

Gatewayのconformance

参考実装の記録ではFoundry 44 tests、gateway e2e 20 steps、ARD conformanceが成功しています。
gateway e2eは、descriptor hash不一致、domain binding、検索、filter、facet、pagination、transfer後のunadopted、再commit後の再掲載を確認します。

これらは参考実装の測定結果であり、規格上の最低test数ではありません。
Suspendedの検索掲載を検証するe2e caseはなく、その挙動は未解決のままです。

互換性

既存ARD Entry

ERC8444の追加memberはARDの拡張可能なJSON schemaへ載ります。
value namespaceを知らないregistryも、通常のdisplayName、type、url、dataなどを持つentryとしてindexできます。
既存のdomain-only entryは変更せずL0として残ります。

既存Contractへの接続

skill、tender、identityの既存contractを変更する必要はありません。
adapterが既存viewを読み、ERC8444 registryは対象contractへ書き込みません。

agent identity registryがないchainでは、agent adapterの backed を常にfalseにできます。
その場合もL1のagent recordとして検索できますが、L2にはなりません。

関連規格から読む値

agent entryはregistration fileのURIをdescriptorへ載せ、adapterはidentity registryから稼働状態を読みます。
利用側はURIで対象agentの登録情報を特定し、identityがactiveな場合だけbackedとして扱います。
このregistration fileとidentity registryの役割を定める関連規格がERC8004です。

ERC8004については以下の記事を参考にしてください。

descriptorは、紛争判断に使うpolicy IDとdecision registryを value:dispute で参照できます。
利用側はその2値から判断記録の正本を開き、ERC8444 recordへ投票結果を複製しません。
複数主体による判断手続きを定める関連規格がERC8436です。

ERC8436については以下の記事を参考にしてください。

DraftとVersionの境界

ERC8444は2026年10月17日時点でDraftです。
前提のARDはv0.91 Proposalであり、schemaとJSON-LD contextは参考実装へ固定版として含まれます。

実装時は、EIPページの版、ARD schemaの版、deploymentのregistry address、4 adapter addressを記録します。
参考実装のtest成功は、Final化や第三者production環境での採用を保証しません。

参考実装

公式資料には、オンチェーンkernel、4 adapter、署名helper、Node.js gateway、example descriptor、test vectorが含まれます。
ここでは、規格が要求する責任と参考実装固有の選択を分けて読みます。

ERC8444参考実装でkernelの保存値、adapterのlive読取、resolverの計算結果を分ける構造

Kernelの保存境界

ARDOnchainRegistry.sol は、record、kindとadapterの対応、domain claim、claim index、署名nonceをmappingへ保存します。
controller、escrow残高、identity状態は保存しません。
更新のたびにadapterへ問い合わせるため、token移転後に古いcontrollerを権限判定へ使いません。

kind tableはwrite-onceで、kernelはupgradeableではありません。
instance ownerが4 kindを登録した後にownershipをzero addressへ移せば、kind追加権限も終了します。
参考実装のruntime sizeは9,939 bytesですが、これは測定値であり規格上限ではありません。

4つのAdapter

SkillAdapter.sol はERC165でskill kernel対応を確認し、ERC721のownerとpackage hashを読みます。
TaskAdapter.sol はowner、取消状態、escrow残高、settleBy、task hashを読み、報酬が残る期限内の依頼だけをbackedとします。

AgentAdapter.sol はaddress自身をcontrollerとし、設定されたidentity registryのactive状態とself-declared document digestを読みます。
RegistryAdapter.sol は非zero addressと非zero discriminatorを確認し、address自身をcontrollerとして返しますが、backedは常にfalseです。

外部callが失敗した時、adapterはrevertせずzero値へ変換します。
この動作により、anchor contractの故障や焼却がregistry全体の一覧処理を止めません。

署名検証

SignatureChecker.sol は、controllerがcontractなら最初にERC1271の isValidSignature をstaticcallします。
戻り値がmagic valueでなければ拒否します。

EOAでは65 bytesのECDSA署名だけを受け付け、v、low-s、zero signerを確認してrecoverします。
malformed responseや外部call失敗はfalseとなり、claim全体がrevertします。

Gatewayの同期処理

参考gatewayのindexerは Registered イベントからrecord IDを列挙し、各recordの保存値とadapterのlive viewを再読取します。
resolverはdescriptor、hash、binding、level、adoptionを計算します。
serverは全件の解決に成功した時だけ、配信用in-memory indexとlastSyncを置き換えます。

既定の同期間隔は15秒です。
RPC同期に失敗すると直前のindexが残り得ます。
confirmation数、reorg回復、cache TTL、descriptor取得サイズとtimeout、production SLAは規格にも参考実装の契約にも固定されていません。

検索のscore、filter、facet、paginationは参考gateway固有の実装です。
ARDのpageSize上限は100、参考kernelの1 domainあたりclaim上限は64ですが、両者を規格一般の容量上限として扱いません。

Sepoliaでの移転と再掲載

公式evidenceは、旧holderがskill recordを登録し、検索結果に載ることを示します。
tokenを新holderへ移すと、adapterのcontrollerは新holderになりますが、recordのattestorは旧holderのままです。
gatewayはunadoptedとして検索から外します。

新holderが同じdescriptor hashを再commitするとattestorが更新され、再び検索結果へ載ります。
この結果は移転とadoptionの流れを実ネットワーク上で確認した証拠ですが、reorg時の回復や長期運用の可用性までは示しません。

セキュリティ

Canonical URNとDomain

verified bindingがないURNをcanonicalとして表示しません。
domainの所有者やmanifestが変わって承認が消えたら、以前のcanonical URNをstaleへ移します。

gateway aliasはgateway自身のdomain内に限定されます。
表示名や説明文はpublisherが自由に書けるため、利用画面ではanchor、binding、levelを合わせて示します。
claimまたはacknowledgmentの片方だけでは、domainが現在の価値情報を支持している根拠になりません。
検索画面はcanonical URNとgateway aliasを同じ信頼度で見せず、bindingの状態と確認時点を併記します。

Descriptor差し替え

descriptor URIの配信者がJSONを書き換えても、content hash不一致を検出できます。
ただし、hostが文書を配信しない可用性問題、内容自体の虚偽、publisherが書く価格の妥当性はhashで解決しません。

resolverが不正な結果を返す可能性もあります。
利用側は検索結果のanchorとdescriptorHashをchainで再確認するか、別gatewayと照合します。
同期時刻や参照blockが不明なgatewayでは、古い結果として扱います。

移転と古い支払い先

token移転後はattestorとcurrent controllerが不一致になります。
resolverはそのrecordを検索から外し、旧所有者のendpoint、terms、x402 payeeを新所有者のofferとして配信しません。

inspection endpointから直接読むclientも同じ比較を行います。
levelが残っていてもadoptedでなければ利用しません。
この比較を省くと、移転済みのtokenに紐づくrecordを、旧controllerが今も支持していると取り違えます。
一時停止中のrecordも、levelだけでは利用可否を決めず、status と value:adopted を個別に確認します。

署名とReplay

署名付きclaimは、chain ID、registry address、controller、nonce、deadlineをdigestへ含めます。
第三者がtransactionを先に送っても、claimは署名者addressへ保存されます。
domain側のacknowledgmentがなければverifiedにもなりません。

秘密鍵管理だけでなく、contract accountが返すERC1271の判定条件も監査対象です。
nonceとdeadlineをUI上で確認できるようにし、意図しない長期署名を避けます。

AdapterとInstance owner

adapterはanchorの存在、controller、backingを決める信頼境界です。
deploymentはregistryと4 adapterのaddress、code hash、対応kindを公開します。

kind登録はwrite-onceなので、誤ったadapterを登録すると同じinstanceでは修正できません。
登録前にinterface ID、kind戻り値、外部call失敗時のzero値、対象contractの意味を確認します。
4 kindを固定した後はownershipを維持する理由を明示するか、zero addressへ移して追加権限を終了します。

最後に

ERC8444は、通常のARD検索を保ったまま、機能、報酬付きの依頼、AIエージェント、検索先をオンチェーンのanchorへ結び付けます。
実装時は、検索score、binding、level、status、adoptionを混ぜず、descriptorのhashと現在のcontrollerを利用直前に確認することが重要です。

他でも色々記事を書いているのでぜひよろしければ読んでいってください!

LLMを使った仕様駆動開発を扱う書籍『LLMと進める仕様駆動開発』を発売予定です。
ハンズオンや、LLMによって開発がどう変わるのか、実践のTipsをまとめています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?