メインコンテンツまでスキップ

エージェントを登録する

このガイドでは、MIZUHIKIエージェントレジストリ にAIエージェントを登録する手順を説明します。エージェントの所有者・取引ウォレット・指示を記録する ERC-8004 エージェントNFTをミントし、MIZUHIKI ID を通じて所有者がKYC済みエンティティであることを検証します。

テストネット限定

エージェントレジストリは、開発およびテスト用として Awajiテストネット にデプロイされています。実際の資金を扱うエージェントは登録しないでください。

ブラウザから登録する​

セットアップ不要で、このページから直接エージェントを登録し、稼働中のレジストリを閲覧できます。「登録」フォームはブラウザ内で ERC-8004 エージェント登録JSONを構築し、ご自身のウォレット(MetaMaskなど)でAwajiテストネットに正式に登録します。

「ダッシュボード」タブはレジストリをインデックスし、各エージェントの所有者KYCステータスをベリファイアからライブで表示します。

レジストリを読み込み中…

プログラムで実行したい場合は、以降のガイドで viem を使った同じフローを説明します。

レジストリの仕組み​

MizuhikiAgentIdentityRegistry は ERC-721 コントラクト(Mizuhiki Agent / MAGENT)です。各エージェントは、以下を紐付けるトークンです。

  • 所有者(Owner) — エージェントNFTを保有するアドレスです(ownerOf(agentId))。NFTトークンを転送すると、エージェントも新しいアカウントに移転します(標準的なERC-721の所有権に準拠)。
  • 取引ウォレット(Trading wallet) — エージェントが取引に使用するアドレスです。所有者とは別にバインドされ、鍵を管理していることを証明する EIP-712メッセージへの署名 が必要です(setAgentWallet)。エージェントを転送すると、取引ウォレットは自動的に解除されます。
  • 指示およびメタデータ(Instructions & metadata) — setMetadata / getMetadata による任意のオンチェーン key → bytes エントリ。agentURI はオプションのオフチェーン引数です(ERC-721の tokenURI に相当)。

KYC情報はレジストリには保存されません。エージェントがKYC済みエンティティに所有されているかを確認するには、ownerOf(agentId) を読み取り、そのアドレスを MIZUHIKI Verified SBT と照合します。詳細は後述の所有者のKYCステータスを検証するをご覧ください。

デプロイメント​

以下のコントラクトはすべて MIZUHIKI Testnet Awaji にデプロイされています — チェーンID 6497Click to Copy、RPC https://rpc.awaji.mizuhiki.ioClick to Copy。レジストリはERC-721上に構築された ERC-8004 (Trustless Agents) です。

コントラクトアドレス
エージェントレジストリ — MizuhikiAgentIdentityRegistry0xBF1a6870dfeeB66B25951b1DdBcB936C6D2954F7Click to Copy
エージェントベリファイア — MizuhikiAgentVerifier0x92566308971c7780Fa83eE77Ef00B87ec5D6fD48Click to Copy
KYC SBT — ベリファイアに登録済み0x7828549D695777Bb01567aE7954F7586243bd294Click to Copy

このガイドでは エージェントレジストリ を使用します。Blockscoutで検証済みコントラクトを見る ↗

前提条件​

  • Node.js v22以降 — node --version で確認してください。
  • 所有者ウォレット — ガス代用のテストネット MIZU をファウセットから入手してください。このウォレットがエージェントNFTを所有します。
  • 取引ウォレット — エージェントが取引を実行するアドレス。ステップ4でバインディングメッセージに署名するため、その秘密鍵が必要です。

依存関係をインストールします:

npm install viem

1. クライアントをセットアップする​

Awajiチェーンを定義し、所有者アカウント用にパブリッククライアント(読み取り)とウォレットクライアント(書き込み)を作成します。

config.js
import { createPublicClient, createWalletClient, http, defineChain } from "viem";
import { privateKeyToAccount } from "viem/accounts";

export const awaji = defineChain({
id: 6497,
name: "MIZUHIKI Testnet Awaji",
nativeCurrency: { decimals: 18, name: "Mizu", symbol: "MIZU" },
rpcUrls: { default: { http: ["https://rpc.awaji.mizuhiki.io"] } },
blockExplorers: { default: { name: "Explorer", url: "https://awaji.blockscout.com" } },
});

export const REGISTRY = "0xBF1a6870dfeeB66B25951b1DdBcB936C6D2954F7";

// Owner account — owns the Agent NFT and sends the transactions
export const owner = privateKeyToAccount(process.env.OWNER_PRIVATE_KEY);

export const publicClient = createPublicClient({ chain: awaji, transport: http() });
export const ownerClient = createWalletClient({ account: owner, chain: awaji, transport: http() });

このガイドで使用する関数:

abi.js
export const registryAbi = [
{ type: "function", name: "register", stateMutability: "nonpayable",
inputs: [
{ name: "agentURI", type: "string" },
{ name: "metadata", type: "tuple[]", components: [
{ name: "metadataKey", type: "string" },
{ name: "metadataValue", type: "bytes" },
]},
],
outputs: [{ name: "agentId", type: "uint256" }] },
{ type: "function", name: "ownerOf", stateMutability: "view",
inputs: [{ name: "tokenId", type: "uint256" }], outputs: [{ type: "address" }] },
{ type: "function", name: "walletNonce", stateMutability: "view",
inputs: [{ type: "uint256" }], outputs: [{ type: "uint256" }] },
{ type: "function", name: "setAgentWallet", stateMutability: "nonpayable",
inputs: [
{ name: "agentId", type: "uint256" }, { name: "newWallet", type: "address" },
{ name: "deadline", type: "uint256" }, { name: "signature", type: "bytes" },
], outputs: [] },
{ type: "function", name: "getAgentWallet", stateMutability: "view",
inputs: [{ name: "agentId", type: "uint256" }], outputs: [{ type: "address" }] },
{ type: "function", name: "setMetadata", stateMutability: "nonpayable",
inputs: [
{ name: "agentId", type: "uint256" }, { name: "metadataKey", type: "string" },
{ name: "metadataValue", type: "bytes" },
], outputs: [] },
{ type: "function", name: "getMetadata", stateMutability: "view",
inputs: [{ name: "agentId", type: "uint256" }, { name: "metadataKey", type: "string" }],
outputs: [{ type: "bytes" }] },
{ type: "event", name: "Registered",
inputs: [
{ name: "agentId", type: "uint256", indexed: true },
{ name: "agentURI", type: "string", indexed: false },
{ name: "owner", type: "address", indexed: true },
] },
];

2. エージェントを登録する​

register を呼び出すと、新しいエージェントNFTが所有者にミントされ、連番の agentId が割り当てられます。オフチェーンの agentURI と、初期のオンチェーンメタデータエントリを渡すことができます。各メタデータの値は bytes 型のため、文字列は stringToHex でエンコードしてください。

予約済みキー

agentWallet は予約済みのメタデータキーです — setAgentWallet(ステップ4)を通じてのみ管理され、register や setMetadata では設定できません。

register.js
import { stringToHex, parseEventLogs } from "viem";
import { awaji, REGISTRY, owner, publicClient, ownerClient } from "./config.js";
import { registryAbi } from "./abi.js";

const hash = await ownerClient.writeContract({
address: REGISTRY,
abi: registryAbi,
functionName: "register",
args: [
"https://example.com/agents/the-best-trader.json", // off-chain metadata (optional, can be "")
[
{ metadataKey: "name", metadataValue: stringToHex("The Best Trader") },
{ metadataKey: "instructions", metadataValue: stringToHex("Do not trade on sports.") },
],
],
});

const receipt = await publicClient.waitForTransactionReceipt({ hash });

// Read the assigned agentId from the Registered event
const [registered] = parseEventLogs({ abi: registryAbi, eventName: "Registered", logs: receipt.logs });
const agentId = registered.args.agentId;
console.log(`✓ Registered agent #${agentId} owned by ${registered.args.owner}`);
よりシンプルな登録

初期のオンチェーンメタデータが不要な場合は、register() および register(string agentURI) のオーバーロードも利用できます。

3. エージェント指示を保存する​

指示やその他のメタデータは、いつでも更新可能なオンチェーンの key → bytes エントリです。setMetadata で設定し、getMetadata で読み取ります。

instructions.js
import { stringToHex, hexToString } from "viem";
import { REGISTRY, publicClient, ownerClient } from "./config.js";
import { registryAbi } from "./abi.js";

const agentId = 1n; // the id from step 2

// Update the instructions
await ownerClient.writeContract({
address: REGISTRY,
abi: registryAbi,
functionName: "setMetadata",
args: [agentId, "instructions", stringToHex("Do not trade on sports or politics.")],
});

// Read them back
const raw = await publicClient.readContract({
address: REGISTRY, abi: registryAbi, functionName: "getMetadata",
args: [agentId, "instructions"],
});
console.log("Instructions:", hexToString(raw));

4. 取引ウォレットをバインドする​

取引ウォレットは所有者とは別にバインドされます。取引ウォレットが、鍵を管理していることを証明するEIP-712の SetAgentWallet メッセージに署名します。 所有者(エージェントNFTの所有者)は、このEIP-712 SetAgentWallet 署名を添えて setAgentWallet トランザクションを送信します。

bind-wallet.js
import { privateKeyToAccount } from "viem/accounts";
import { awaji, REGISTRY, publicClient, ownerClient } from "./config.js";
import { registryAbi } from "./abi.js";

const agentId = 1n;

// The wallet the agent will trade from
const trading = privateKeyToAccount(process.env.TRADING_PRIVATE_KEY);

// Per-agent nonce gives replay protection
const nonce = await publicClient.readContract({
address: REGISTRY, abi: registryAbi, functionName: "walletNonce", args: [agentId],
});

const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // valid for 1 hour

// The trading wallet signs the EIP-712 typed data
const signature = await trading.signTypedData({
domain: {
name: "MizuhikiAgentIdentityRegistry",
version: "1",
chainId: awaji.id,
verifyingContract: REGISTRY,
},
types: {
SetAgentWallet: [
{ name: "agentId", type: "uint256" },
{ name: "newWallet", type: "address" },
{ name: "deadline", type: "uint256" },
{ name: "nonce", type: "uint256" },
],
},
primaryType: "SetAgentWallet",
message: { agentId, newWallet: trading.address, deadline, nonce },
});

// The owner submits the binding transaction
const hash = await ownerClient.writeContract({
address: REGISTRY,
abi: registryAbi,
functionName: "setAgentWallet",
args: [agentId, trading.address, deadline, signature],
});
await publicClient.waitForTransactionReceipt({ hash });

const wallet = await publicClient.readContract({
address: REGISTRY, abi: registryAbi, functionName: "getAgentWallet", args: [agentId],
});
console.log(`✓ Agent #${agentId} now trades from ${wallet}`);
なぜ署名が必要か

取引ウォレットの署名を必須とすることで、所有者が実際には管理していないアドレスをバインドすることを防ぎます。同じ理由から、エージェントNFTを転送するとバインド済みウォレットは解除され、新しい所有者が再バインドする必要があります。

5. 所有者のKYCステータスを検証する​

エージェントがKYC済みエンティティに所有されていることを確認するには、その所有者を読み取り、そのアドレスを MIZUHIKI Verified SBT と照合します。SBTは balanceOf(address) と isVerified(address) の両方を公開しています。

verify-kyc.js
import { REGISTRY, publicClient } from "./config.js";
import { registryAbi } from "./abi.js";

// MIZUHIKI Verified SBT — see the compliance docs for the canonical address & interface
const MIZUHIKI_VERIFIED_SBT = "0x7828549D695777Bb01567aE7954F7586243bd294";

const agentId = 1n;

const owner = await publicClient.readContract({
address: REGISTRY, abi: registryAbi, functionName: "ownerOf", args: [agentId],
});

const isVerified = await publicClient.readContract({
address: MIZUHIKI_VERIFIED_SBT,
abi: [{ type: "function", name: "isVerified", stateMutability: "view",
inputs: [{ name: "account", type: "address" }], outputs: [{ type: "bool" }] }],
functionName: "isVerified",
args: [owner],
});

console.log(`Agent #${agentId} owner ${owner} — KYC'd: ${isVerified ? "✓" : "✗"}`);
正規のSBTアドレスとオンチェーンでのアクセス制御

Verified SBTのアドレスと検証インターフェース(balanceOf / isVerified)は、コンプライアンス対応dAppを作成するおよびMIZUHIKI Verifiedアドレスに記載されています。正確な情報源としてそちらのページを参照し、ご自身のコントラクトからは balanceOf(owner) > 0 を必須条件としてアクセスを制御してください。

コントラクトリファレンス​

関数用途
register() · register(agentURI) · register(agentURI, metadata[])新しいエージェントNFTをミントし、agentId を返します。
ownerOf(agentId)エージェントの所有者(ERC-721)。KYCチェックに使用します。
setAgentWallet(agentId, newWallet, deadline, signature)取引ウォレットをバインドします(newWallet によるEIP-712署名が必要)。
getAgentWallet(agentId) · unsetAgentWallet(agentId)バインド済み取引ウォレットの読み取り / 解除。
setMetadata(agentId, key, value) · getMetadata(agentId, key)オンチェーンメタデータの設定 / 読み取り(例:instructions)。
setAgentURI(agentId, newURI) · tokenURI(agentId)オフチェーンメタデータURIの設定 / 読み取り。
walletNonce(agentId)setAgentWallet のリプレイ攻撃を防ぐエージェントごとのノンス。

イベント: Registered(agentId, agentURI, owner)、AgentWalletSet(agentId, newWallet)、MetadataSet(agentId, metadataKey, metadataValue)、URIUpdated(agentId, newURI, updatedBy)。

次のステップ​