跳到主要内容

部署、集成与规范演进

30 秒导读: 前三章拆的是三个合约各自的原理。这一章把它们串成一个能真跑的系统: 谁先部署、谁把地址喂给谁;怎么让同一套合约在多条链上落到同一个地址;线上到底部署了哪些; 一个纯前端 demo 怎么直连合约——以及这个 demo 为什么落后于当前代码。最后用仓库里保留的 legacy/ 旧合约,对照出这套标准三年里改了什么。

三个合约本身见前三章,这里不重复: 身份注册表 · 声誉注册表 · 验证注册表


1. 为什么部署要讲顺序(零基础)

三个合约不是平级并列,而是一主两从

  • IdentityRegistry 是根。它管"谁是 agent"(agent 即 NFT,见第 1 章)。
  • ReputationRegistryValidationRegistry 都要回头问身份注册表"这个 agentId 存在吗", 所以它俩的构造函数各需要一个参数:身份注册表的地址。

一句话直觉:先有户籍处,才能开评价处和复核处——后两个开张时必须知道户籍处开在哪。

这条依赖决定了部署脚本里雷打不动的三步顺序:先 Identity,拿到它的地址,再把地址分别注入 Reputation 和 Validation 的构造函数。


2. 一把梭部署:Deploy.s.sol

这是最常用的脚本:一次交易广播里按顺序把三个合约都部署好。

主流程(script/Deploy.s.sol:26,合约 Deploy.run)就是把上面那句"先根后从"翻译成代码:

// script/Deploy.s.sol:33-43 —— 顺序依赖的三步
IdentityRegistry identityRegistry = new IdentityRegistry(); // 1 无依赖
ReputationRegistry reputationRegistry = new ReputationRegistry(address(identityRegistry)); // 2 注入地址
ValidationRegistry validationRegistry = new ValidationRegistry(address(identityRegistry)); // 3 注入地址

identityRegistry 先被 new 出来,它的 address(...) 才能当参数传给后两个。私钥从环境变量 读(vm.envUint("PRIVATE_KEY")Deploy.s.sol:27),中间用 vm.startBroadcast / stopBroadcast 把这几步打包成真实上链交易。

部署流向:

环境变量 PRIVATE_KEY


┌──────────────────┐
│ IdentityRegistry │ ① 先部署,无依赖
└────────┬─────────┘
│ address(identityRegistry)
┌────┴──────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Reputation │ │ Validation │ ②③ 构造函数吃进身份注册表地址
│ Registry │ │ Registry │
└──────────────┘ └──────────────┘

用法(脚本头部注释 Deploy.s.sol:15 给的原样命令):

forge script script/Deploy.s.sol:Deploy --rpc-url <RPC_URL> --broadcast --verify

3. 只部署一个:Deploy*Only 变体

同一个文件里还并排放了三个"单发"合约,供分步/补部署场景(比如身份注册表已在链上、 只想追加一个声誉注册表):

变体干什么依赖来源源码锚点
DeployIdentityOnly只部署身份注册表script/Deploy.s.sol:67
DeployReputationOnly只部署声誉注册表从 env 读已有身份地址script/Deploy.s.sol:84
DeployValidationOnly只部署验证注册表从 env 读已有身份地址script/Deploy.s.sol:102

关键差别在"从属"两个变体:它们不再自己 new 身份注册表,而是从环境变量读一个已存在的地址:

// script/Deploy.s.sol:87 —— DeployReputationOnly.run
address identityRegistryAddress = vm.envAddress("IDENTITY_REGISTRY");
ReputationRegistry reputationRegistry = new ReputationRegistry(identityRegistryAddress);

也就是说:一把梭脚本里"根→从"的依赖是代码内连线;单发脚本里同样的依赖被外移到 IDENTITY_REGISTRY 环境变量。忘了设这个变量,DeployReputationOnly / DeployValidationOnly 就会因 vm.envAddress 取不到值而失败。


4. 多链同址:DeployDeterministic.s.sol

4.1 要解决的小问题

普通 new(CREATE)算出的合约地址依赖部署者地址 + nonce。同一个人在不同链上部署,nonce 往往不同,于是同一份合约在 Ethereum、Base、Optimism 上落到三个不同地址——集成方要为每条链 单独记地址,很烦。

4.2 思路:CREATE2 + 固定 SALT

CREATE2 让地址只由 部署者地址 + SALT + 合约字节码 决定,不含 nonce。只要这三样一致, 地址就在所有链上一致。脚本把 SALT 钉成一个常量:

// script/DeployDeterministic.s.sol:22 —— 固定盐
bytes32 constant SALT = keccak256("ERC8004_JAN2026_UPDATE_V1");

部署时给每个 new 挂上 {salt: SALT}DeployDeterministic.s.sol:35/40/45):

IdentityRegistry identityRegistry = new IdentityRegistry{salt: SALT}();
ReputationRegistry reputationRegistry = new ReputationRegistry{salt: SALT}(address(identityRegistry));

于是:同一个部署者 + 同一个 SALT,在任何链上都得到同一组地址(脚本末尾日志 :60-61 也把这句话打出来了)。想换一组地址?注释 :21 明说改 SALT 即可。

4.3 两种部署脚本怎么选

维度Deploy.s.solDeployDeterministic.s.sol
地址算法CREATE(含 nonce)CREATE2(含 SALT,不含 nonce)
多链是否同址
顺序依赖一样:先 Identity 再注入两从一样
单发变体Deploy*Only 三个无,只有一把梭 run

注意:CREATE2 只保证同址,不改变"先根后从"的依赖——两从注册表的地址仍取决于当次算出的 身份注册表地址,所以三者必须在同一次运行里、用同一个 SALT 一起部署,跨链才对得齐。


5. 线上真部署了什么:deployments.json

当前只有一条链有 Jan 2026 规范版(v1.2.0)的记录——Ethereum Sepolia 测试网deployments.json:2-33):

合约Sepolia 地址已验证
IdentityRegistry0xf66e7CBdAE1Cb710fee7732E4e1f173624e137A7
ReputationRegistry0x6E2a285294B5c74CB76d76AB77C1ef15c2A9E407
ValidationRegistry0xC26171A3c4e1d958cEA196A5e84B7418C58DCA2C
  • 部署者 0x9B4Cef62a0ce1671ccFEFA6a6D8cBFa165c49831,部署日 2026-01-28,Solidity 0.8.19
  • ReputationRegistry 的 verified: false 带了原因(deployments.json:26):因 via-IR 编译, 区块浏览器验证暂缺——合约能用,只是源码没在 Etherscan 上核对绿标。
  • 文件里还留了一个 legacy 段(deployments.json:51-75),记着被取代的 v1.1.0(2026-01-09)在 Sepolia 和 Base Sepolia 的旧地址——即"部署记录自己也在做版本对照"。

6. web/:纯前端 demo,以及它的"滞后"

6.1 它是什么

web/ 是一个没有后端的浏览器 demo:用 ethers.js(v5)直接连合约做注册和查询。核心两点:

  • 合约地址不写死在 JS 逻辑里,而是从 web/config.js 的全局对象 window.CONTRACT_ADDRESSES 读入(web/app.js:77loadContractAddresses)。
  • 读操作走只读 RPC provider,不需要钱包(web/app.js:347/528new ethers.providers.JsonRpcProvider); 写操作(注册)才要连 MetaMask。

6.2 关键问题:demo 落后于当前合约(inferred 标注)

这个 demo 对的是旧接口和旧地址,和当前 src/ 的 Jan 2026 版对不上。两处硬证据:

证据一,ABI 是 v0.x 接口。 web/app.js:56-63identityRegistryABI 里写的是:

// web/app.js:57-60 —— 旧接口签名
"function newAgent(string memory agentDomain, address agentAddress) external returns (uint256)",
"function getAgent(uint256 agentId) external view returns (tuple(uint256 agentId, string agentDomain, address agentAddress))",
"function resolveByDomain(string memory agentDomain) external view returns (...)",

而当前 src/IdentityRegistry.sol 早已是 ERC-721 那套:入口是 register(...)src/IdentityRegistry.sol:85/102/111 三个重载),身份信息进了 tokenURI没有 newAgent / agentDomain / getAgent 这些符号。前端调用会直接对不上函数选择子。

证据二,config 地址指向 legacy 合约。 web/config.js:6 里 Sepolia 的 identityRegistry 是 0x127C86a24F46033E77C347258354ee4C739b139C——这个值和 legacy/README.md:21 表格里的 v0.x legacy 地址一字不差,而不是 deployments.json 里 Jan 2026 版的 0xf66e7C...

结论(inferred): 整个 web/ demo 内部是自洽的——旧 ABI 配旧地址,指向的是 v0.x legacy 部署——但它整体滞后于当前 src/deployments.json,属演示未随规范更新。这不是我们的推断 硬猜:CHANGELOG_V1.md:199 的 Future Work 明写着 "Web interface update for v1.0 (currently supports v0.3)",即维护者自己记录了"前端还停在 v0.3"。


7. 用 legacy/ 对照规范演进

legacy/ 目录完整保留了旧版三合约(legacy/src/ 下三个 .sol + 各自 test/script/), 定位就是对照物legacy/README.md:7 明说这些已弃用、别再拿去新部署,看当前实现请回根 src/

7.1 三个阶段的时间线

把散落在 legacy 代码、CHANGELOG_V1.md 迁移片段、当前 src/ 里的信息拼起来,声誉注册表这条线 走过三个阶段(其他两个注册表同期演进,见下表):

v0.x (legacy/) v1.0 (2025-10) Jan 2026 v1.2 (src/)
预授权 acceptFeedback → 签名 giveFeedback → 直接 giveFeedback
无 score / 无 tag uint8 score int128 value
bytes32 tag1/tag2 + uint8 valueDecimals
bytes feedbackAuth(签名) string tag1/tag2
(预授权 + 签名双双移除)

7.2 关键字段的演进(三注册表)

关切v0.x(legacy/src/v1.0(CHANGELOG_V1.md 迁移片段)Jan 2026 v1.2(src/
身份自建 domain/address 映射,newAgent()ERC-721 + URIStorage,register()同 v1.0,新增 unsetAgentWallet()
声誉·授权预授权acceptFeedback 先授权EIP-191/ERC-1271 签名随反馈带上无需授权:任何人可直接 giveFeedback
声誉·评分无评分字段uint8 scoreint128 value + uint8 valueDecimals(带符号定点)
声誉·标签无标签字段bytes32 tag1/tag2string tag1/tag2(人类可读)
验证·载体哈希键 dataHash(bytes32) + uint8 responseURI 化 + tagURI 化,getValidationStatusresponseHash
验证·时效有过期槽(time-bounded)移除过期无过期

逐格锚点(都可 grep 符号名核对):

  • v0.x 预授权:legacy/src/ReputationRegistry.sol:45acceptFeedback)、:25_feedbackAuthorizations 映射)、:80AuthFeedback 事件)。
  • v0.x 哈希验证:legacy/src/ValidationRegistry.sol:48validationRequest(...,bytes32 dataHash))、 :106validationResponse(bytes32 dataHash, uint8 response))。
  • v1.0 签名反馈:CHANGELOG_V1.md:127-135giveFeedback(uint8 score, bytes32 tag1, bytes32 tag2, ..., bytes feedbackAuth),以及 :31 记的"移除 acceptFeedback,改签名授权"。
  • Jan 2026 现状:src/ReputationRegistry.sol:21-24 的合约头注释亲口列出 "REMOVED feedbackAuth pre-authorization / REMOVED signature verification / NEW 直接提交、string tags、int128 value + valueDecimals";函数签名见 :90giveFeedback)。

7.3 两个容易混淆的点

  • "预授权"和"签名"是两代不同机制,都被 Jan 2026 砍掉。 v0.x 靠链上预授权;v1.0 换成随反馈 携带的密码学签名;Jan 2026 两者全不要,改为任何人直接提交,反 Sybil/刷分挪到链下过滤src/ReputationRegistry.sol:30 注释亲述)。当前合约头把这两代旧机制并列标 REMOVED,读代码时别 误以为它俩是同一样东西。
  • bytes32 tag → stringuint8 score → int128+valueDecimals 发生在 v1.0 → Jan 2026 这一跳, 不是 v0.x → v1.0——因为 v0.x 的声誉合约根本没有 score/tag 字段。要看这两个旧类型的真身,得看 CHANGELOG_V1.md 里的 v1.0 迁移片段,而非 legacy/src/

8. 边界与局限(诚实)

  • CREATE2 同址 ≠ 跨链自动可用。 脚本只保证地址一致,不做任何跨链消息同步;每条链仍是独立 部署、独立状态。
  • 线上仅 Sepolia 有 Jan 2026 版。 deployments.json 当前只记了 Ethereum Sepolia 一条链的 v1.2; 多链同址是脚本能力,不代表已经在多链上跑过。
  • ReputationRegistry 未通过区块浏览器验证。 via-IR 编译导致 Etherscan 验证暂缺 (deployments.json:26),链上可用但源码无绿标。
  • web demo 不能用来试当前合约。 它的 ABI 和地址都停在 v0.x(见 §6.2);想连 Jan 2026 版需自行 换 ABI 与 config.js 地址。
  • legacy 合约仅测试网留存、不再维护legacy/README.md:44-45),只作对照,勿用于新部署。

9. 代码地图(导航索引)

主题文件路径符号 / 锚点
一把梭部署(顺序依赖)script/Deploy.s.solDeploy.run(:26),三步 new(:33/38/43)
单发·仅身份script/Deploy.s.solDeployIdentityOnly(:67)
单发·仅声誉(读 env 地址)script/Deploy.s.solDeployReputationOnly + vm.envAddress("IDENTITY_REGISTRY")(:84/87)
单发·仅验证(读 env 地址)script/Deploy.s.solDeployValidationOnly(:102)
多链同址部署script/DeployDeterministic.s.solSALT 常量(:22)、DeployDeterministic.run + {salt: SALT}(:24/35/40/45)
线上地址(Sepolia v1.2)deployments.jsonerc8004_jan2026_spec_update.networks.ethereum_sepolia(:11-33)
旧部署地址对照deployments.jsonlegacy.erc8004_jan2026_update_v1_1(:51-75)
前端直连合约web/app.jsTrustlessAgentsAppidentityRegistryABI(:56-63)、JsonRpcProvider(:347/528)
前端地址配置(指向 legacy)web/config.jswindow.CONTRACT_ADDRESSES(:4-25)
旧版声誉:预授权legacy/src/ReputationRegistry.solacceptFeedback(:45)、AuthFeedback(:80)
旧版验证:哈希+时效legacy/src/ValidationRegistry.solvalidationRequest(...,bytes32 dataHash)(:48)、validationResponse(:106)
演进事实来源CHANGELOG_V1.md / legacy/README.mdv1.0 迁移片段(:113-150)、Key Differences(:25-38)
当前声誉合约(对照现状)src/ReputationRegistry.sol合约头 REMOVED/NEW 清单(:20-31)、giveFeedback(:90)