草案 ERC:任務市場協定 (TMP) 鏈上任務協作標準
任務市場協定定義了一個用於鏈上任務協作的標準介面,支援五種採購模式,並透過 ERC-8004 實現人類與 AI 代理之間的統一身份識別。
摘要
任務市場協議(Task Market Protocol, TMP)定義了一個用於鏈上任務協調的標準介面,支援五種採購模式:賞金 (Bounty)、認領 (Claim)、提案 (Pitch)、基準測試 (Benchmark) 和 拍賣 (Auction)。
TMP 是角色無關 (actor-agnostic) 的:請求者和工作者可以是人類、自主 AI 代理、物聯網 (IoT) 設備或其任何組合。人類可以發布任務讓 AI 代理完成,AI 代理可以為人類專家發布任務,兩個代理可以在完全沒有人類參與的情況下進行交易,或者兩個人類可以使用該協議作為去信任的託管——合約對所有參與者一視同仁。這種對稱性基於 ERC-8004,它提供了一個由人類和機器角色共享的單一身份原語。
TMP 使任何前端、聚合器或自主代理都能與任何符合標準的任務市場合約進行交互,而無需了解特定實現的知識。TMP 構建於 PGTR (EIP-待定) 之上,允許無私鑰角色透過鏈上支付收據而非私鑰簽名來授權任務操作。TMP 整合了 ERC-8004 以實現角色無關的鏈上身份、聲譽和自動化基準驗證。
任務由合約使用請求者地址和單調遞增的 nonce 確定性生成的 bytes32 ID 標識,使後端能夠在交易納入區塊前預先計算任務 ID。採購模式由根據規範字串計算的 bytes4 選擇器標識,使工具無需升級合約即可檢測新模式。
動機
鏈上任務市場已在多個生態系統中獨立出現,但介面互不相容。標準的缺失意味著:
- 碎片化 — 工作者必須追蹤多個不相容的合約;聚合器必須編寫自定義適配器。
- 特定角色的設計 — 現有市場多為人類或代理其中之一設計,而非兩者兼顧。面向人類的市場需要錢包和 UI;面向代理的市場需要自定義 API。兩者在原生層面上都無法互相組合。
- 不支援無私鑰角色 — 現有設計要求參與者持有私鑰並支付 Gas,排除了輕量級 AI 代理、物聯網設備以及偏好僅透過支付授權的人類用戶。
- 缺乏自動化評估 — 基準測試類任務(證明達到了指標 X)需要受信任的鏈外評估者,且缺乏鏈上最終性。
- 缺失可移植身份 — 在一個市場累積的聲譽無法遷移到另一個市場,且人類與機器參與者之間沒有共享的身份層。
TMP 解決了這五個缺口:統一介面、基於 ERC-8004 身份的角色無關設計、基於 PGTR 的無私鑰授權、整合 ERC-8004 驗證註冊表以實現自動化基準測試,以及整合 ERC-8004 聲譽註冊表以實現跨所有角色類型的可移植聲譽。
與 ERC-8183 的關係
ERC-8183(代理商業,2026 年 2 月)建立了一個最小化的單一評估者、單一流程的工作協調標準。TMP 則涵蓋了更廣泛的設計空間:
| 維度 | ERC-8183 | TMP |
|---|---|---|
| 角色模型 | 以代理為中心 | 角色無關(人類 ↔ 代理,任何組合) |
| 無私鑰角色 | ERC-2771(基於簽名) | PGTR(基於支付收據) |
| 協調模式 | 1 種線性流程 | 5 種模式 |
| 去信任評估 | 選配 | 基準測試 + ERC-8004 驗證註冊表 |
| ERC-8004 整合 | 建議 | 規範性(包含所有 3 個註冊表) |
| 質押 | 無 | 認領模式質押/沒收 |
| 多重轉發器 | 單一 | mapping(address => bool) |
ERC-8183 支援的每個工作流都可以在 TMP 中表達(賞金模式,請求者作為評估者)。這兩個標準是互補的;最小化部署可能偏好 ERC-8183,而多模式或代理原生部署應使用 TMP。
規範
關鍵詞 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY” 和 “OPTIONAL” 應按照 RFC 2119 中的描述進行解釋。
第一部分:核心介面 (ITMP)
模式常量
TMP 模式由 bytes4 常量標識,該常量計算為規範模式名稱字串的 Keccak256 哈希的前 4 個位元組。使用選擇器而非列舉 (enum) 值允許在不修改現有合約的情況下部署新模式並由工具檢測。
bytes4 constant TMP_BOUNTY = bytes4(keccak256("TMP.mode.bounty"));
bytes4 constant TMP_CLAIM = bytes4(keccak256("TMP.mode.claim"));
bytes4 constant TMP_PITCH = bytes4(keccak256("TMP.mode.pitch"));
bytes4 constant TMP_BENCHMARK = bytes4(keccak256("TMP.mode.benchmark"));
bytes4 constant TMP_AUCTION = bytes4(keccak256("TMP.mode.auction"));
確切的 32 位元值請參見附錄 B。
任務狀態
enum TaskStatus {
Open, // 任務正在接受工作
Claimed, // 工作者已鎖定任務(認領 / 拍賣模式)
WorkerSelected, // 請求者已選擇工作者(提案模式)
PendingApproval, // 工作已提交,等待請求者接受(賞金 / 基準測試模式)
Accepted, // 工作已接受;報酬已發放給工作者
Expired, // 任務過期未被接受;獎勵退還給請求者
Cancelled // 任務在工作開始前由請求者取消
}
核心列舉中刻意去除了 Disputed(爭議中)。爭議解決是在 ITMPDispute 中定義的可選擴展。核心協議實現不需要支援爭議。
數據結構
/// @notice 由 getTask() 返回的規範任務表示。
struct TaskInfo {
bytes32 id; // 合約生成的任務標識符
address requester; // 發布任務的主體(創建時的 pgtrSender)
uint256 reward; // 託管的支付代幣數量(基本單位)
uint256 expiryTime; // 任務可能過期的 Unix 時間戳
bytes4 mode; // 採購模式(TMP_BOUNTY, TMP_CLAIM 等)
TaskStatus status; // 當前生命週期狀態
address worker; // 工作者地址(若尚未選擇則為零地址)
bytes32 deliverable; // 工作者提交的交付物哈希(若無則為零)
bytes32 contentHash; // 可選:鏈外任務描述的 keccak256
string contentURI; // 可選:指向擴展任務元數據的 URI
}
/// @notice 工作者地址的累計統計數據。
struct WorkerStats {
uint256 tasksCompleted; // 已接受的任務(報酬已發放給此工作者)
uint256 tasksAttempted; // 工作者提交工作或認領的任務
uint256 totalEarned; // 累計收到的支付代幣數量(基本單位)
uint256 avgRating; // 平均評分 0–100(縮放值;若未評分則為 0)
uint256 ratingCount; // 收到的評分數量
}
ITMP 介面
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "./ITMPForwarder.sol";
/// @title ITMP — 任務市場協議核心介面
/// @notice 符合 TMP 標準的合約必須實現此介面中的所有函數和事件
/// 且必須在 supportsInterface(type(ITMP).interfaceId) 中返回 true。
interface ITMP is IERC165 {
// ─── 事件 ────────────────────────────────────────────────────────────────
/// @notice 當新任務創建時觸發。
event TaskCreated(
bytes32 indexed taskId,
address indexed requester,
uint256 reward,
bytes4 indexed mode,
uint256 expiryTime
);
/// @notice 當工作者提交交付物哈希時觸發。
event TaskSubmitted(
bytes32 indexed taskId,
address indexed worker,
bytes32 deliverable
);
/// @notice 當請求者接受提交且報酬發放時觸發。
event TaskCompleted(
bytes32 indexed taskId,
address indexed worker,
uint256 reward
);
/// @notice 當任務過期且獎勵退還給請求者時觸發。
event TaskExpired(bytes32 indexed taskId, address indexed requester, uint256 reward);
// ─── 任務生命週期 ─────────────────────────────────────────────────────────
/// @notice 創建新任務並託管獎勵。
/// @dev 由受信任的 PGTR 轉發器調用。`requester` 是轉發器上的 pgtrSender。
/// 任務 ID 必須按以下方式生成:
/// keccak256(abi.encode(block.chainid, address(this), requester, requesterNonce[requester]++))
/// 這使得 ID 是確定性的,且在交易納入區塊前可預先計算。
/// @param requester 發布任務的主體(由轉發器設置為 pgtrSender)。
/// @param reward 要託管的支付代幣數量(從轉發器/伺服器轉移)。
/// @param duration 任務從當前區塊時間戳起算的壽命(秒)。
/// @param mode 採購模式選擇器(TMP_BOUNTY, TMP_CLAIM 等)。
/// @param pitchDeadline 提案提交截止秒數(0 = 任務時長;僅限提案模式)。
/// @param bidDeadline 競標截止秒數(0 = 任務時長;僅限拍賣模式)。
/// @return taskId 合約生成的任務標識符。
function createTask(
address requester,
uint256 reward,
uint256 duration,
bytes4 mode,
uint256 pitchDeadline,
uint256 bidDeadline
) external returns (bytes32 taskId);
/// @notice 記錄工作者提交的交付物哈希。
/// @dev 由受信任的 PGTR 轉發器調用。`worker` 是轉發器上的 pgtrSender。
/// 狀態轉換取決於模式(見狀態機章節)。
/// 交付物哈希必須存儲在鏈上並可透過 getTask() 檢索。
/// @param taskId 任務標識符。
/// @param worker 提交工作的人(由轉發器設置為 pgtrSender)。
/// @param deliverable 工作成果的內容哈希(例如:文件的 keccak256、IPFS CID 或 ZK 承諾)。不得為零。
function submitWork(bytes32 taskId, address worker, bytes32 deliverable) external;
/// @notice 接受工作者的提交並釋放託管的獎勵。
/// @dev 由受信任的 PGTR 轉發器調用。`requester` 必須等於任務請求者。
/// 觸發 TaskCompleted。在 TaskStatus.Expired 之後不得調用。
/// @param taskId 任務標識符。
/// @param requester 授權接受的請求者。
/// @param worker 工作被接受的工作者。
function acceptSubmission(bytes32 taskId, address requester, address worker) external;
/// @notice 為完成的任務評分並在 ERC-8004 聲譽註冊表中記錄反饋。
/// @dev 必須僅在 TaskStatus.Accepted 之後可調用。
/// 必須調用 IReputationRegistry.giveFeedback 並帶入:
/// tag1 = "tmp.task.rating"
/// tag2 = 規範模式名稱(例如 "tmp.mode.bounty")
/// @param taskId 任務標識符。
/// @param requester 評分請求者(必須是任務請求者)。
/// @param rating 分數 0–100。
/// @param workerAgentId 被評分工作者的 ERC-8004 代理 ID。
/// @param feedbackURI 指向結構化反饋數據的 URI。
/// @param feedbackHash feedbackURI 處反饋數據的 keccak256。
function rateTask(
bytes32 taskId,
address requester,
uint8 rating,
uint256 workerAgentId,
string calldata feedbackURI,
bytes32 feedbackHash
) external;
/// @notice 過期後將託管的獎勵退還給請求者。
/// @dev 在 task.expiryTime 過後且狀態為 Open、Claimed 或 WorkerSelected 時,必須可由任何人調用。
/// 必須繞過所有鉤子 (hooks) 和擴展合約。
/// 即使 ITMPDispute 或其他擴展處於活動狀態,此不變量也必須保持。
/// 觸發 TaskExpired。
function refundExpired(bytes32 taskId) external;
// ─── 視圖函數 ─────────────────────────────────────────────────────────
/// @notice 返回完整的任務資訊。
function getTask(bytes32 taskId) external view returns (TaskInfo memory);
/// @notice 返回工作者地址的累計統計數據。
function getWorkerStats(address worker) external view returns (WorkerStats memory);
/// @notice 返回用於生成任務 ID 的當前請求者 nonce。
function requesterNonce(address requester) external view returns (uint256);
/// @notice 如果 addr 是受信任的 PGTR 轉發器,則返回 true。
function isTrustedForwarder(address addr) external view returns (bool);
}
第二部分:模式狀態機(規範性)
以下狀態圖定義了每種模式的所有有效狀態轉換。
* 表示由 PGTR 轉發器轉發的鏈外操作;submitWork* 在認領/提案/拍賣模式中記錄鏈上交付物哈希,但不改變狀態。
賞金模式 (Bounty Mode)
任何工作者皆可提交。第一個被接受的提交獲勝。
Open
--[submitWork]--> PendingApproval
--[expire]------> Expired
PendingApproval
--[acceptSubmission]--> Accepted
--[expire]-----------> Expired (保留交付物)
認領模式 (Claim Mode)
工作者透過質押鎖定任務。失敗時質押會被沒收。
Open
--[claimTask*]---> Claimed
--[expire]-------> Expired
Claimed
--[submitWork*]---> Claimed (記錄交付物哈希;狀態不變)
--[acceptSubmission]--> Accepted
--[forfeitClaim*]---> Open (沒收質押;任務重新開放)
--[expire]---------> Expired (若有配置,質押退還給工作者)
提案模式 (Pitch Mode)
請求者在工作開始前從提案中選擇一名工作者。
Open
--[submitPitch*]----> Open (提案記錄在鏈外;狀態不變)
--[selectWorker]----> WorkerSelected
--[expire]----------> Expired
WorkerSelected
--[submitWork*]----> WorkerSelected (記錄交付物哈希;狀態不變)
--[acceptSubmission]--> Accepted
--[expire]----------> Expired
基準測試模式 (Benchmark Mode)
工作由 ERC-8004 驗證註冊表自動驗證。
Open
--[submitWork]--> PendingApproval
--[expire]------> Expired
PendingApproval
--[acceptSubmission (透過驗證註冊表)]--> Accepted
--[expire]-----> Expired (保留交付物)
在基準測試模式中,evaluatorFor(taskId) 返回 ERC-8004 驗證註冊表地址。轉發器必須在鏈外向驗證註冊表提交證明;如果證明有效,註冊表會調用 TMP 合約上的 acceptSubmission。
拍賣模式 (Auction Mode)
工作者提交密封報價;最低出價者贏得工作權。
Open
--[submitBid*]-------> Open (記錄報價;狀態不變)
--[selectLowestBidder]--> Claimed (鎖定獲勝出價者)
--[expire]-----------> Expired
Claimed (鎖定獲勝者)
--[submitWork*]----> Claimed (記錄交付物哈希;狀態不變)
--[acceptSubmission]--> Accepted
--[expire]----------> Expired
注意:selectLowestBidder 對所有報價執行 O(n) 掃描。實現應在 submitBid 期間維護一個運行中的最小值,以實現 O(1) 選擇。
第三部分:任務 ID 生成
任務 ID 必須由合約按以下方式生成:
taskId = keccak256(abi.encode(
block.chainid,
address(this),
requester,
requesterNonce[requester]++
));
此方案保證:
- 唯一性 — 鏈 ID + 合約地址 + 請求者 + 單調 nonce 不會發生衝突。
- 可預先計算性 — 後端可以在提交交易前透過讀取
requesterNonce[requester]來預測 ID。 - 無需鏈外隨機性 — 不需要伺服器端隨機位元組。
- 跨鏈消歧 — 鏈 ID 防止相同的 ID 出現在兩條鏈上。
第四部分:交付物錨定
submitWork 函數必須接受一個 bytes32 deliverable 參數,並將其作為任務記錄的一部分存儲在鏈上。交付物哈希為以下內容提供防篡改錨點:
- IPFS CIDs — CID 位元組的 keccak256
- 文件內容 — 原始文件的 keccak256
- ZK 承諾 — 來自零知識證明的承諾值
- Merkle 根 — 結構化證明樹的根
對於尚未記錄提交的任務,交付物欄位必須為零。一旦設置,交付物欄位不得被後續的 submitWork 調用覆蓋。
第五部分:ERC-8004 整合(規範性)
ERC-8004 是 TMP 的角色無關身份層。人類和機器參與者(請求者和工作者)都表示為 ERC-8004 角色。這種共享的身份原語使得人類對代理、代理對人類、代理對代理以及人類對人類的任務交互能夠被協議和聲譽索引器統一對待。
支援聲譽的 TMP 合約必須整合所有三個 ERC-8004 註冊表:
身份註冊表 (Identity Registry)
請求者和工作者是 ERC-8004 角色——無論是人類還是機器。rateTask 中的 workerAgentId 參數必須對應於 ERC-8004 身份註冊表中的有效角色 ID。任何註冊了 ERC-8004 身份的參與者(人類或 AI 代理)都參與同一個聲譽圖譜。
聲譽註冊表 (Reputation Registry)
當調用 rateTask 時,合約必須調用:
IReputationRegistry.giveFeedback(
workerAgentId,
int128(rating),
0, // valueDecimals
"tmp.task.rating", // tag1 — 規範 TMP 標籤
_modeName(task.mode), // tag2 — 例如 "tmp.mode.bounty"
feedbackURI,
feedbackHash
);
tag1 必須使用規範標籤 "tmp.task.rating"。索引器和聲譽聚合器應根據此標籤過濾以識別源自 TMP 的反饋。tag2 中的模式名稱允許按模式進行聲譽細分(例如,基準測試與賞金工作的評分分開)。
驗證註冊表(僅限基準測試模式)
在基準測試模式中,轉發器必須:
- 將工作者的證明提交給 ERC-8004 驗證註冊表。
- 驗證註冊表根據任務的
metricTarget評估證明。 - 如果有效,註冊表的回調會觸發 TMP 合約上的
acceptSubmission。
在基準測試模式下,TMP 合約不得接受來自任意地址的 acceptSubmission 調用——僅限來自驗證註冊表(由 evaluatorFor(taskId) 返回)。
第六部分:可選擴展介面
TMP 實現可以實現以下可選擴展介面。調用者在調用擴展函數前應透過 ERC-165 檢查。
ITMPMode
interface ITMPMode is IERC165 {
/// @notice 返回負責評估任務完成情況的地址。
/// 賞金/認領:返回請求者
/// 基準測試:返回 ERC-8004 驗證註冊表
/// 提案/拍賣:返回請求者(或指定的仲裁員)
function evaluatorFor(bytes32 taskId) external view returns (address evaluator);
}
ITMPFees
interface ITMPFees is IERC165 {
/// @notice 任務完成時從獎勵中扣除的平台費用(以基點為單位)。
function defaultFeeBps() external view returns (uint16);
/// @notice 接收平台費用的地址。
function feeRecipient() external view returns (address);
/// @notice 自部署以來累計收取的費用。
function totalFeesCollected() external view returns (uint256);
/// @notice 給定獎勵將被扣除的實際費用金額。
function feeForTask(uint256 reward) external view returns (uint256);
}
ITMPReputation
interface ITMPReputation is IERC165 {
/// @notice 用於提交反饋的 ERC-8004 聲譽註冊表。
function reputationRegistry() external view returns (address);
event ReputationRegistryUpdated(address indexed registry);
}
ITMPDispute
interface ITMPDispute is IERC165 {
enum DisputeStatus { None, Open, Resolved }
/// @notice 返回任務的爭議狀態。
function disputeStatus(bytes32 taskId) external view returns (DisputeStatus);
/// @notice 返回任務的仲裁員(若無爭議則為零地址)。
function arbitratorFor(bytes32 taskId) external view returns (address);
/// @notice 為處於 PendingApproval 或 Accepted 狀態的任務發起爭議。
function openDispute(bytes32 taskId, address initiator, string calldata reason) external;
/// @notice 解決爭議。僅可由 arbitratorFor(taskId) 調用。
/// workerShare + requesterShare 必須等於 100。
function resolveDispute(bytes32 taskId, uint8 workerShare, uint8 requesterShare) external;
event DisputeOpened(bytes32 indexed taskId, address indexed initiator);
event DisputeResolved(bytes32 indexed taskId, uint8 workerShare, uint8 requesterShare);
}
規範性要求:ITMPDispute 實現不得干擾 refundExpired。已達到 expiryTime 的任務即使存在開啟的爭議也必須是可退還的。此不變量保護請求者免受惡意發起爭議的騷擾。
第七部分:資金回收不變量(規範性)
refundExpired 必須:
- 可由任何地址調用(無需權限)。
- 只要
block.timestamp > task.expiryTime且任務狀態為 Open、Claimed 或 WorkerSelected,就必須成功。 - 不被任何鉤子、擴展合約或爭議狀態阻斷。
- 將完整的託管獎勵轉移給
task.requester。 - 觸發
TaskExpired。
此不變量必須無條件保持。實現必須在其測試套件中驗證這一點。
第八部分:索引器事件要求
所有狀態轉換必須觸發包含足夠數據的事件,以便從無狀態事件掃描中重建完整的任務歷史。具體而言:
TaskCreated必須包含重建初始任務狀態所需的所有參數。TaskSubmitted必須包含交付物哈希。TaskCompleted必須包含工作者地址和獎勵金額。TaskExpired必須包含請求者地址和退還的獎勵金額。- 每次調用
addForwarder和removeForwarder時必須觸發ForwarderUpdated(address indexed forwarder, bool trusted)。
原理
bytes4 模式選擇器 vs. 列舉 (enum)
列舉將模式編碼為連續整數。添加新模式需要修改合約(破壞現有 ABI)。bytes4 選擇器是從規範字串計算得出的,因此:
- 新模式具有全局唯一的標識符,無需讀取合約存儲即可推導。
- 選擇器本身帶有語義(可讀作字串)。
- 聚合器可以透過
supportsInterface模式擴展來檢測支援的模式,而無需鏈上列舉。
合約生成的任務 ID
客戶端提供的任務 ID(隨機 bytes32)會產生競爭條件:兩個客戶端可能提交相同的 ID,第二個客戶端在支付 Gas 後會在鏈上失敗。它們還要求客戶端管理熵,這對於輕量級代理來說並非易事。使用單調 nonce 的合約生成 ID 消除了這兩個問題,同時保持了可預先計算性。
submitWork 作為獨立步驟
將 submitWork 與 acceptSubmission 分開提供了鏈上審計追蹤:
- 交付物哈希在請求者評估之前就被錨定。
- ZK 證明、IPFS CID 和其他內容定址引用被不可篡改地記錄。
- 爭議擁有確切提交內容和時間的鏈上記錄。
認領/提案/拍賣模式中的鏈外提交
在認領、提案和拍賣模式中,工作者已經鎖定在任務中(沒有競爭)。這些模式下的 submitWork 在鏈上記錄交付物哈希但不改變狀態,因為相關的門檻(接受)是請求者的顯式操作。無論何種模式都觸發 TaskSubmitted 可提供一致的索引器行為。
PGTR 優於 ERC-2771
參見 EIP-PGTR §與 ERC-2771 的關係。
向後相容性
此 ERC 引入了一個新介面。現有的任務市場合約不受影響,除非它們選擇實現 ITMP。合約可以將 supportsInterface(type(ITMP).interfaceId) 作為增量升級添加,前提是底層函數簽名與 ITMP 介面匹配。
安全考量
- 支付原子性 — 所有支付操作(創建時託管、接受時釋放、過期時退還)必須是原子的。帶有外部代幣調用的部分狀態更新必須使用
ReentrancyGuard或同等機制。 - 轉發器信任 —
trustedForwarders映射是一個特權集合。添加惡意轉發器允許代表任何請求者任意創建任務和接受工作。addForwarder必須受onlyOwner或同等治理保護。 - 質押抵押品 — 在認領模式中,質押金額應設置得足夠高,使惡意行為(認領後放棄)在經濟上不具吸引力。
stakeBps是獎勵的百分比;實現應強制執行最低絕對質押量。 - 基準測試預言機信任 — 基準測試模式中的驗證註冊表被信任能公平地評估證明。請求者應使用經過審計、信譽良好的註冊表實現。TMP 合約應在
TaskCreated中觸發註冊表地址,以便請求者在發布任務前進行驗證。 - 拍賣 Gas —
selectLowestBidder對所有報價是 O(n)。報價較多的大型拍賣可能會超過區塊 Gas 限制。實現應在報價提交期間維護運行中的最小值,以將此限制在 O(1)。Gas 成本應在前端說明。 - 過期時鐘 —
expiryTime在任務創建時設置為block.timestamp + duration。區塊時間戳可由驗證者在約 15 秒內操縱。持續時間極短(< 60 秒)的任務不應在對抗性環境中使用。 - 重複評分 — 實現必須防止對同一個
(taskId, requester)對進行多次rateTask調用,以避免聲譽通膨。
參考實現
參見參考實現存儲庫中的 src/TaskMarket.sol。
參考實現通過了一個合規性測試套件 (test/ITMP.t.sol),該套件可針對任何符合 TMP 標準的合約運行。測試套件涵蓋:
- ERC-165 介面檢測
- 所有 5 種模式的狀態機
submitWork交付物錨定rateTaskERC-8004 標籤要求refundExpired資金回收不變量requesterNonce唯一性和可預先計算性- 多重轉發器添加/移除
版權
透過 CC0 放棄版權及相關權利。
附錄 A:規範任務元數據 JSON 模式
TMP 任務可以在 contentURI 處引用擴展元數據。以下 JSON 模式定義了規範格式。索引器和前端應使用此模式以實現互操作性。
{
"$schema": "https://json-schema.org/draft/2020-12",
"title": "TMP Task Metadata",
"type": "object",
"required": ["title", "description", "mode", "reward"],
"properties": {
"title": {
"type": "string",
"description": "簡短的人類可讀任務標題"
},
"description": {
"type": "string",
"description": "完整的任務描述"
},
"mode": {
"type": "string",
"enum": ["bounty", "claim", "pitch", "benchmark", "auction"],
"description": "採購模式"
},
"reward": {
"type": "string",
"description": "USDC 基本單位的獎勵金額(字串形式以避免精度損失)"
},
"tags": {
"type": "array",
"items": { "type": "string" },
"description": "可搜索的標籤"
},
"benchmark": {
"type": "object",
"description": "僅存在於基準測試模式任務",
"properties": {
"metricDescription": { "type": "string" },
"metricTarget": { "type": "string" },
"validatorAddress": {
"type": "string",
"description": "ERC-8004 驗證註冊表地址"
},
"proofType": {
"type": "string",
"description": "預期的證明格式(例如 'zk-snark', 'benchmark-result-json')"
}
},
"required": ["metricDescription", "metricTarget", "validatorAddress", "proofType"]
},
"auction": {
"type": "object",
"description": "僅存在於拍賣模式任務",
"properties": {
"auctionType": {
"type": "string",
"enum": ["dutch", "english", "reverse_dutch", "reverse_english"]
},
"maxPrice": { "type": "string" },
"floorPrice": { "type": "string" },
"startPrice": { "type": "string" },
"bidDeadline": {
"type": "integer",
"description": "競標截止的 Unix 時間戳"
}
}
},
"createdAt": {
"type": "integer",
"description": "任務創建的 Unix 時間戳"
},
"chainId": {
"type": "integer",
"description": "部署任務合約的 EVM 鏈 ID"
},
"contractAddress": {
"type": "string",
"description": "TMP 合約地址(帶校驗和)"
}
}
}
附錄 B:模式選擇器值
規範的 bytes4 模式選擇器,計算為 bytes4(keccak256("<string>")):
| 模式 | 輸入字串 | bytes4 值 |
|---|---|---|
| 賞金 (Bounty) | "TMP.mode.bounty" | bytes4(keccak256("TMP.mode.bounty")) |
| 認領 (Claim) | "TMP.mode.claim" | bytes4(keccak256("TMP.mode.claim")) |
| 提案 (Pitch) | "TMP.mode.pitch" | bytes4(keccak256("TMP.mode.pitch")) |
| 基準測試 (Benchmark) | "TMP.mode.benchmark" | bytes4(keccak256("TMP.mode.benchmark")) |
| 拍賣 (Auction) | "TMP.mode.auction" | bytes4(keccak256("TMP.mode.auction")) |
要驗證確切的 32 位元值,請運行:
cd packages/contracts
forge script -vvv --sig "run()" - <<'EOF'
import "forge-std/Script.sol";
import "../src/interfaces/ITMPMode.sol";
contract Check is Script {
function run() external view {
console.logBytes4(TMP_BOUNTY);
console.logBytes4(TMP_CLAIM);
console.logBytes4(TMP_PITCH);
console.logBytes4(TMP_BENCHMARK);
console.logBytes4(TMP_AUCTION);
}
}
EOF
附錄 C:ERC-165 介面 ID
| 介面 | Solidity 表達式 |
|---|---|
| IERC165 | 0x01ffc9a7 (標準) |
| ITMP | type(ITMP).interfaceId |
| ITMPForwarder | type(ITMPForwarder).interfaceId |
| ITMPMode | type(ITMPMode).interfaceId |
| ITMPFees | type(ITMPFees).interfaceId |
| ITMPReputation | type(ITMPReputation).interfaceId |
| ITMPDispute | type(ITMPDispute).interfaceId |
所有非標準 ID 均由 Solidity 在編譯時計算為函數選擇器的 XOR。驗證方法:部署參考實現後執行 cast interface <address> | grep interfaceId。