はじめに
初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
ERC8217は、ERC8004で登録したAIエージェントと、既存のNFTやトークンを結び付ける提案です。
外部トークンの所有者が変わると、結び付いたエージェントの制御者も同じ移転に追従できるようにします。
概要
ERC8004とは
ERC8004では、AIエージェントの識別情報を管理するレジストリが、各エージェントをNFTとして登録します。
このレジストリを Identity Registry と呼び、NFTのトークンIDをエージェントの agentId として使います。
登録したエージェントの詳しい情報は、agentURI が指す登録ファイルから確認できます。
一方、レジストリは追加情報をオンチェーンの metadata にキーと値の組み合わせで記録できます。
ERC8217が利用するのは、この agentId と metadata です。
ERC8004については以下の記事を参考にしてください。
ERC8217が追加する仕組み
既存のプロジェクトNFTやキャラクターNFTは、ERC8004のエージェントとは別のトークンコントラクトで管理されています。
ERC8217は、agentId と外部トークンの組み合わせを、チェーンごとに1つ用意する専用コントラクトへ記録します。
規格ではこのコントラクトを binding contract と呼びます。
以下、バインディングコントラクトと表記します。
例えば、agentId = 17 のエージェントを、あるERC721コントラクトの tokenId = 42 に結び付ける場面を考えます。
エージェント側の metadata には、バインディングコントラクトの20バイトのアドレスだけを書き込みます。
そしてクライアントは、そのコントラクトの bindingOf(17) を呼び出し、standard = ERC721、トークンコントラクトのアドレス、tokenId = 42 を読み出します。
外部トークンがAliceからBobへ送付されても、agentId = 17 が tokenId = 42 を指す関係は変わりません。
その代わり、バインディングコントラクトが採用する制御条件に従い、現在の所有者であるBobをエージェントの制御者として確認できます。
ただし、所有権や残高をどのように認可へ使うかはERC8217の対象外であり、バインディングコントラクトが定めます。
動機
ERC8004の登録自体もNFTです。
既存のプロジェクトNFTを持つ人にエージェントを制御させる場合、どの登録NFTをどのプロジェクトNFTへ結び付けたかを記録する必要があります。
その記録方法がプロジェクトごとに異なると、ウォレット、マーケットプレイス、インデクサーは、プロジェクトごとのデータ形式と確認方法を個別に組み込むことになります。
ERC8217がない場合、あるプロジェクトでは metadata へトークンコントラクトを直接書き、別のプロジェクトでは独自の管理コントラクトを置くかもしれません。
すると、クライアントは metadata を読んだだけでは、外部トークンによる制御が使われているのか、どのコントラクトへ問い合わせるのかを判断できません。
そこでERC8217は、agent-binding という共通の項目名と、20バイトのアドレス形式を固定します。
共通化するのは、バインディングコントラクトを見つけ、bindingOf から standard、tokenContract、tokenId を読むところまでです。
認可の条件まで1つに固定すると、ERC721の所有権と、ERC1155やERC6909の残高を同じ方法で判定できません。
そのため、発見方法はERC8217が固定し、現在の制御者を決める処理はバインディングコントラクトへ残します。
仕様
ERC8217の仕様は、登録時と検証時で役割が分かれます。
エージェントを登録するレジストリやアダプターは、agent-binding へ正規のバインディングコントラクトを書き込みます。
バインディングコントラクトは初回登録時に Binding を保存し、その後は standard、tokenContract、tokenId を変更しません。
クライアントは bindingOf(agentId) から、この3つの値を読み出します。
metadataとBinding
agent-binding は、ERC8004の metadata で使う予約項目です。
値には、チェーンごとに1つだけ使う正規のバインディングコントラクトのEVMアドレスを、ちょうど20バイトで書き込みます。
abi.encodePacked(bindingContract)
bindingContract はEVMアドレスなので、余分なデータや32バイトへの埋め合わせを加えません。
例えば、アドレスが以下であれば、metadata の値も同じ20バイトです。
0x9c4e8f2a1b7d6e3c0a5f8d2b9e1c4a7f3d6e8b0c
この20バイトから、トークン規格、トークンコントラクト、トークンIDを復元するわけではありません。
クライアントは20バイトを呼び出し先のアドレスとして解釈し、そのコントラクトの bindingOf(agentId) から3つの値を取得します。
metadata は問い合わせ先を示し、Binding は standard、tokenContract、tokenId を示します。
クライアントは、20バイトのアドレスと外部トークンの情報を取り違えずに読み出せます。
IERCAgentBindings
バインディングコントラクトは、少なくとも以下のインターフェースを公開します。
登録用関数、管理機能、URI更新、ウォレットとの結び付け、アップグレード用の管理機能を追加しても構いませんが、それらはERC8217の検証対象に含まれません。
pragma solidity ^0.8.24;
interface IERCAgentBindings {
enum TokenStandard {
ERC721,
ERC1155,
ERC6909
}
struct Binding {
TokenStandard standard;
address tokenContract;
uint256 tokenId;
}
event AgentBound(
uint256 indexed agentId,
TokenStandard indexed standard,
address indexed tokenContract,
uint256 tokenId,
address registeredBy
);
function bindingOf(uint256 agentId) external view returns (Binding memory);
}
このインターフェースは、Binding を読む bindingOf と、初回登録を追跡する AgentBound イベントだけを検証用に定めています。
つまり、クライアントは実装固有の登録方法を知らなくても、登録後の結果を同じ呼び出しで確認できます。
TokenStandard
enum TokenStandard {
ERC721,
ERC1155,
ERC6909
}
TokenStandard は、結び付け先のトークン規格を区別するEnum型です。
Binding.standard の値を見れば、tokenContract と tokenId をどのトークン規格として解釈するかが分かります。
| 値 | 対象 |
|---|---|
ERC721 |
1つのトークンIDに1つの所有者を記録するNFT |
ERC1155 |
同じコントラクトで複数種類のトークンと残高を管理するトークン |
ERC6909 |
1つのコントラクトで複数のトークンIDと残高を管理する軽量なトークン |
Enum型の各値はトークンの種類を特定するだけで、誰を制御者とみなすかまでは決めません。
例えばERC1155では、1単位以上の保有を条件にするのか、指定数量以上を求めるのかをバインディングコントラクトが決めます。
Binding
struct Binding {
TokenStandard standard;
address tokenContract;
uint256 tokenId;
}
Binding は、1つの agentId へ結び付けた外部トークンを表す構造体です。
バインディングコントラクトが初回登録時に保存し、クライアントが bindingOf(agentId) から読み出します。
- フィールド
| フィールド | 詳細 |
|---|---|
standard |
tokenContract と tokenId を解釈するトークン規格です。 |
tokenContract |
外部トークンを管理するコントラクトのアドレスです。 |
tokenId |
外部トークンコントラクトの中で、結び付け対象を特定するIDです。 |
例えば standard = ERC721、tokenContract = 0x111...1111、tokenId = 42 なら、agentId はそのERC721の42番へ結び付いています。
ERC8217に準拠するバインディングコントラクトは、この3つの値を初回登録後に変更しません。
AgentBound
event AgentBound(
uint256 indexed agentId,
TokenStandard indexed standard,
address indexed tokenContract,
uint256 tokenId,
address registeredBy
);
AgentBound イベントは、ある agentId の Binding を初めて保存した時に発行されます。
インデクサーは、インデックス付きの agentId、standard、tokenContract で対象を絞り込み、どの外部トークンが登録されたかを追跡できます。
- フィールド
| フィールド | 詳細 |
|---|---|
agentId |
結び付けるERC8004のエージェントIDです。イベント検索に使えるようインデックスが付きます。 |
standard |
外部トークンの規格です。イベント検索に使えるようインデックスが付きます。 |
tokenContract |
外部トークンコントラクトのアドレスです。イベント検索に使えるようインデックスが付きます。 |
tokenId |
外部トークンコントラクト内のトークンIDです。 |
registeredBy |
Binding の登録処理を呼び出したアドレスです。 |
イベントログからは、誰がどの agentId をどのトークンへ結び付けたかを追えます。
ただし、クライアントはイベントログではなく、bindingOf(agentId) が返す Binding を使って検証します。
イベントを取りこぼしても、この関数を呼び出せば現在保存されている Binding を確認できます。
bindingOf
function bindingOf(uint256 agentId) external view returns (Binding memory);
bindingOf は、指定したエージェントに保存された Binding を返す参照関数です。
クライアントは metadata から見つけたバインディングコントラクトに対して呼び出します。
-
agentIdのBindingを読み出すバインディングコントラクトは、引数の
agentIdに結び付いたBindingをコントラクトストレージから読み出す。 -
Bindingを返すstandard、tokenContract、tokenIdを含む構造体を呼び出し元へ返す。
- 引数
| 引数 | 詳細 |
|---|---|
agentId |
結び付けた外部トークンを確認したいERC8004のエージェントIDです。 |
- 戻り値
| 戻り値 | 詳細 |
|---|---|
Binding memory |
トークン規格、トークンコントラクト、トークンIDをまとめた Binding です。 |
-
実行条件
-
呼び出し元の権限条件は定められていません。
-
metadataから取得した正規のバインディングコントラクトへ呼び出します。 -
未登録の
agentIdに対する返り値やrevert条件は、このインターフェースでは定められていません。
最後の条件は、バインディングコントラクトごとに異なります。
クライアントは呼び出しの成功だけでなく、返された Binding をバインディングコントラクトの仕様に従って検証します。
登録時に守る7つの条件
agentId と外部トークンをバインディングコントラクトへ初めて登録する時は、以下の7つの条件を満たす必要があります。
登録後は、agent-binding のアドレスと、Binding に保存した standard、tokenContract、tokenId を変更できません。
-
予約項目
agent-bindingを予約し、信頼できない呼び出し元が任意の値で上書きできないようにします。 -
20バイトの値
metadataには正規のバインディングコントラクトのアドレスを、ちょうど20バイトで書き込みます。 -
呼び出し先との一致
metadataのアドレスは、そのagentIdに対してbindingOfを提供するコントラクトと一致させます。 -
チェーンごとの正規コントラクト
同じチェーンでは、正規のバインディングコントラクトを1つだけ使います。
-
初回イベント
ある
agentIdのBindingを初めて保存する時にAgentBoundイベントを発行します。 -
登録したアドレスを変更しない
agent-bindingへ書き込んだバインディングコントラクトのアドレスを変更しません。 -
Bindingの3つの値を変更しないbindingOf(agentId)が返すstandard、tokenContract、tokenIdを変更しません。
agent-binding のアドレスと Binding の3つの値が変わらないため、クライアントやインデクサーは1度検証した Binding をキャッシュできます。
ただし、外部トークンの所有者や残高は変化するので、現在の制御者を判断する時は外部トークン側の値を改めて確認します。
検証手順
クライアントは、metadata とバインディングコントラクトを分けて検証します。
どこか1段階でも失敗した場合、クライアントはエージェントと外部トークンの結び付きを確認できないものとして処理を止めます。
-
agent-bindingを読み出すクライアントは、ERC8004のレジストリから対象
agentIdのagent-bindingを取得する。 -
20バイトのアドレスとして解釈する
値の長さが20バイトであることを確認し、バインディングコントラクトのEVMアドレスへ変換する。
項目がない場合や長さが異なる場合は、未検証として処理を止める。 -
bindingOf(agentId)を呼び出す取得したアドレスへ
agentIdを渡し、Bindingを読み出す。
呼び出しが失敗する場合は、未検証として処理を止める。 -
制御条件を確認する
返された
standard、tokenContract、tokenIdを使い、バインディングコントラクトが定める認可処理へ進む。
例えばERC721の所有権を使うバインディングコントラクトなら、外部トークンコントラクトのownerOf(tokenId)から現在の所有者を確認する。
4番目は、Binding の検証後に実際の制御者を求めるための処理です。
ERC8217が固定するのは3番目までであり、所有権や残高から制御者を決める規則はバインディングコントラクトに残ります。
補足
ERC8217は、metadata を小さくするためだけにバインディングコントラクトを置くわけではありません。
外部トークンの識別と、そのトークンを認可へ使う規則を分けることで、クライアントが確認すべきコントラクトを明示します。
バインディングコントラクトを保存する理由
トークンコントラクトとトークンIDだけでは、誰がエージェントを制御できるかを決められません。
例えば同じERC1155トークンでも、1単位以上を持つアドレスを制御者とするコントラクトと、10単位以上を持つアドレスだけを制御者とするコントラクトでは、認可結果が異なります。
そこで metadata には、認可処理を実装したバインディングコントラクトのアドレスを保存します。
クライアントは bindingOf の返り値に加え、そのコントラクトが所有権や残高をどう判定するかを確認できます。
同じトークンを参照していても、認可規則が異なるコントラクトを別々に評価できます。
metadataへ複製しない情報
トークン規格、トークンコントラクト、トークンIDは、バインディングコントラクトの Binding へ保存します。
metadata には、そのコントラクトの20バイトのアドレスだけを書き込みます。
もし両方へ3項目を保存し、片方だけを更新すると、metadata と bindingOf の返り値が食い違います。
ERC8217では、トークン規格、トークンコントラクト、トークンIDを bindingOf の返り値だけから読み出します。
さらに Binding は変更できないため、1度確認した3つの値を後から別のトークンへ差し替えられません。
互換性
ERC8217は、既存の登録レジストリや外部トークンの動作を変更しません。
追加するのは、バインディングコントラクトのアドレスを保存する agent-binding と、Binding を返す IERCAgentBindings です。
ERC8004との関係
ERC8004のレジストリは、エージェントごとに agentId と metadata を管理します。
ERC8217に対応するレジストリやアダプターは、既存の metadata に agent-binding を書き込むため、新しいエージェント識別子を作りません。
一方、既存のERC8004レジストリやアダプターは、agent-binding に対応しなくても構いません。
agent-binding に対応するレジストリやアダプターは、20バイトの共通形式を読み、同じ手順でバインディングコントラクトを見つけられます。
外部トークンとの関係
ERC721は、トークンIDごとに1つの所有者を記録するNFTの規格です。
バインディングコントラクトは、その所有者をエージェントの制御者として使えます。
ERC721については以下の記事を参考にしてください。
ERC1155は、1つのコントラクトで複数種類のトークンと保有数量を管理します。
そのため、バインディングコントラクトは、指定したトークンIDの残高を制御条件へ使えます。
ERC1155については以下の記事を参考にしてください。
ERC6909も、1つのコントラクトで複数のトークンIDと残高を管理します。
ただし、何単位を持てば制御できるか、複数の保有者が同時に条件を満たした時にどうするかは、ERC8217ではなくバインディングコントラクトが決めます。
つまり、ERC8217は3つのトークン規格を結び付け先として識別しますが、各規格の所有権、残高、送付の動作は変更しません。
外部トークンの所有者や残高が変わると、バインディングコントラクトの認可結果だけが変わり、Binding 自体は同じ値を返し続けます。
テスト
テストでは、metadata の20バイトと、bindingOf が返す3項目を分けて確認します。
agent-binding だけからトークン情報を読み取る処理は、ERC8217の仕様と一致しません。
20バイトのmetadata
バインディングコントラクトを以下のアドレスとします。
bindingContract = 0x9c4e8f2a1b7d6e3c0a5f8d2b9e1c4a7f3d6e8b0c
agent-binding へ保存する値は、接頭辞の 0x を除いて40桁の16進数、つまり20バイトです。
0x9c4e8f2a1b7d6e3c0a5f8d2b9e1c4a7f3d6e8b0c
このテストでは、保存値がアドレスと完全に一致することを確認します。
TokenStandard、外部トークンコントラクト、トークンIDを連結した値は不合格です。
また、20バイトより短い値や、32バイトへ埋め合わせた値も不合格として扱います。
bindingOfの返り値
同じバインディングコントラクトに対して、例えば bindingOf(17) を呼び出します。
ERC721の42番へ結び付けた場合の返り値は、以下の内容です。
Binding({
standard: TokenStandard.ERC721,
tokenContract: 0x1111111111111111111111111111111111111111,
tokenId: 42
})
このテストでは、standard、tokenContract、tokenId が初回登録時の値と一致することを確認します。
さらに、登録後に更新処理を呼び出しても、bindingOf(17) の返り値と agent-binding のアドレスが変わらないことを確認します。
なお、提案のテスト例にはERC1155の tokenId = 5 や、ERC721の tokenId = 0x1234 も示されています。
これらの値も Binding から返され、metadata には入りません。
セキュリティ
agent-binding の20バイトを正しく読み取れても、現在の制御者まで確定したことにはなりません。
クライアントは参照先のコントラクトと、外部トークン側の所有権または残高を分けて確認します。
metadataだけでは足りない理由
metadata が示すのは、bindingOf(agentId) を呼ぶコントラクトのアドレスだけです。
外部トークンは bindingOf の返り値から確認し、制御条件はバインディングコントラクトが所有権や残高を調べる処理から確認します。
例えば Binding がERC721の42番を返したとしても、現在の所有者アドレスは Binding に含まれません。
所有権を制御条件にするバインディングコントラクトでは、外部トークンコントラクトの ownerOf(42) を呼び出して初めて、現在の制御者を判断できます。
そのため、metadata のデコードだけで署名や資産操作を許可してはいけません。
正規コントラクトとアップグレードのリスク
チェーンごとに正規のバインディングコントラクトを1つにすると、クライアントとインデクサーは評価・監視する対象を1つへ絞れます。
しかし、そのコントラクトが誤った Binding を返せば、そこへ登録されたすべての agentId を正しく検証できません。
クライアントは、バインディングコントラクトのコード、監査結果、運用実績、アップグレードできる構成かどうかを確認します。
コードをアップグレードできるバインディングコントラクトでも、変更後に agent-binding のアドレスと登録済みの Binding を同じ値に保つ必要があります。
管理権限を使って登録済みの standard、tokenContract、tokenId を変更できるコントラクトは、ERC8217の条件を満たしません。
また、同じチェーンに複数のバインディングコントラクトを置くと、クライアントは各コントラクトを個別に評価し、インデクサーは各 AgentBound イベントを監視することになります。
ERC8217は、信頼と監視の基準が分散しないよう、チェーンごとに1つの正規コントラクトを使います。
形式の境界
agent-binding は20バイトのEVMアドレスだけを表します。
チェーンIDを含まないため、クライアントは、どのチェーンのERC8004レジストリから metadata を読んだかという呼び出し元の情報と組み合わせて解釈します。
また、この形式では非EVMチェーンのアドレスを表現できません。
非EVMのトークンへ結び付ける形式や、異なるチェーンをまたぐ結び付けはERC8217の対象外です。
エージェントを登録するレジストリやアダプターは、agent-binding を予約項目として保護し、信頼できない呼び出し元による上書きを拒否します。
クライアント側では、値が20バイトでも正規のバインディングコントラクトとは限らないため、そのチェーンで採用されたコントラクトかを確認してから bindingOf を信頼します。
ERC8217の公式仕様は以下です。
最後に
ERC8217は、ERC8004の metadata へバインディングコントラクトの20バイトのアドレスを記録し、bindingOf(agentId) からトークン規格、トークンコントラクト、トークンIDを読み出す仕組みです。
Binding を変更できない値として残す一方で、現在の制御者は外部トークンの所有者や残高から判断します。
これにより、登録情報を書き換えずに、外部トークンの送付へエージェントの制御を追従させられます。
他でも色々記事を書いているのでぜひよろしければ読んでいってください!




