newsence

ERC-8195:任務市場協定

Ethereum Magicians·26 天前

任務市場協定(TMP)定義了一個鏈上任務協調的標準介面,支援五種採購模式,並針對人類與人工智慧代理設計了與角色無關的通用架構。

摘要

任務市場協定(Task Market Protocol, TMP)定義了一個用於鏈上任務協調的標準介面,支援五種採購模式:賞金(Bounty)認領(Claim)提案(Pitch)基準測試(Benchmark)拍賣(Auction)

TMP 具有角色不可知性(actor-agnostic):請求者和工作者可以是人類、自主 AI 代理、物聯網(IoT)設備或其任何組合。人類可以發佈任務供 AI 代理完成,AI 代理可以發佈任務供人類專家完成,兩個代理可以在完全沒有人類參與的情況下進行交易,或者兩個人類可以使用該協定作為去信任的託管——合約對所有參與者一視同仁。這種對稱性基於 ERC-8004,它提供了一個由人類和機器角色共享的單一身份原語。

TMP 使任何前端、聚合器或自主代理都能與任何符合規範的任務市場合約進行交互,而無需了解特定實現的細節。TMP 構建在 PGTR (EIP-TBD) 之上,允許無私鑰角色通過鏈上支付收據而非私鑰簽名來授權任務操作。TMP 整合了 ERC-8004,以實現角色不可知的鏈上身份、聲譽和自動化基準驗證。

任務由 bytes32 ID 識別,該 ID 由合約使用請求者地址和單調遞增的 nonce 確定性地生成,使後端能夠在交易納入區塊前預先計算任務 ID。採購模式由根據規範字串計算出的 bytes4 選擇器識別,使工具無需升級合約即可檢測新模式。

動機

鏈上任務市場在多個生態系統中獨立出現,但介面互不相容。缺乏標準意味著:

  • 碎片化 — 工作者必須追蹤多個不相容的合約;聚合器必須編寫自定義適配器。
  • 特定角色的設計 — 現有的市場要麼為人類設計,要麼為代理設計,而非兩者兼顧。面向人類的市場需要錢包和 UI;面向代理的市場需要自定義 API。兩者在原生層面上都無法互相組合。
  • 不支援無私鑰角色 — 現有的設計要求參與者持有私鑰並支付 Gas,這排除了輕量級 AI 代理、物聯網設備以及偏好僅通過支付進行授權的人類用戶。
  • 缺乏自動化評估 — 基準測試類任務(證明你達到了指標 X)需要受信任的鏈下評估者,且缺乏鏈上終局性。
  • 缺失可移植身份 — 在一個市場累積的聲譽無法遷移到另一個市場,且人類與機器參與者之間沒有共享的身份層。

TMP 解決了這五個缺口:統一的介面、基於 ERC-8004 身份的角色不可知設計、基於 PGTR 的無私鑰授權、整合 ERC-8004 驗證註冊表以實現自動化基準測試,以及 ERC-8004 聲譽註冊表以實現跨所有角色類型的可移植聲譽。

與 ERC-8183 的關係

ERC-8183(代理商業,2026 年 2 月)建立了一個極簡的單一評估者、單一流程的工作協調標準。TMP 則涵蓋了更廣泛的設計空間:

維度ERC-8183TMP
角色模型以代理為中心角色不可知(人類 ↔ 代理,任何組合)
無私鑰角色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 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 出現在兩條鏈上。

後端可以使用以下方式預先計算任務 ID:

const nonce = await contract.requesterNonce(requester);
const taskId = keccak256(encodeAbiParameters(
  [{ type: 'uint256' }, { type: 'address' }, { type: 'address' }, { type: 'uint256' }],
  [BigInt(chainId), contractAddress, requester, nonce]
));

第四部分:交付物錨定

submitWork 函數必須接受一個 bytes32 deliverable 參數,並將其作為任務記錄的一部分存儲在鏈上。交付物哈希為以下內容提供防篡改錨點:

  • IPFS CID — CID 位元組的 keccak256
  • 檔案內容 — 原始檔案的 keccak256
  • ZK 承諾 — 零知識證明的承諾值
  • Merkle 根 — 結構化證明樹的根

對於尚未記錄提交內容的任務,deliverable 欄位必須為零。一旦設置,deliverable 欄位不得被後續的 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
);

規範標籤 "tmp.task.rating" 必須用於 tag1。索引器和聲譽聚合器應根據此標籤進行過濾,以識別源自 TMP 的反饋。tag2 中的模式名稱允許按模式進行聲譽細分(例如,基準測試與賞金工作的評分分開)。

驗證註冊表(僅限基準測試模式)

在基準測試模式下,轉發器必須:

  • 將工作者的證明提交給 ERC-8004 驗證註冊表。
  • 驗證註冊表根據任務的 metricTarget 評估證明。
  • 如果有效,註冊表的校調(callback)將觸發 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 且任務狀態為 OpenClaimedWorkerSelected 即可成功。
  • 不被任何鉤子、擴展合約或爭議狀態阻塞。
  • 將完整的託管獎勵轉移給 task.requester
  • 觸發 TaskExpired

此不變量必須無條件保持。實現必須在其測試套件中驗證這一點。


第八部分:索引器事件要求

所有狀態轉換必須觸發包含足夠資料的事件,以便通過無狀態事件掃描重建完整的任務歷史。具體而言:

  • TaskCreated 必須包含重建初始任務狀態所需的所有參數。
  • TaskSubmitted 必須包含交付物哈希。
  • TaskCompleted 必須包含工作者地址和獎勵金額。
  • TaskExpired 必須包含請求者地址和退還的獎勵金額。
  • 每次 addForwarderremoveForwarder 調用時必須觸發 ForwarderUpdated(address indexed forwarder, bool trusted)

原理

bytes4 模式選擇器 vs. 列舉 (enum)

列舉將模式編碼為連續整數。添加新模式需要修改合約(破壞現有的 ABI)。bytes4 選擇器是根據規範字串計算的,因此:

  • 新模式具有全球唯一的識別碼,無需讀取合約存儲即可推導。
  • 選擇器本身帶有語義(可讀作字串)。
  • 聚合器可以通過 supportsInterface 模式擴展來檢測支援的模式,而無需鏈上列舉。

合約生成的任務 ID

客戶端提供的任務 ID(隨機 bytes32)會產生競爭條件:兩個客戶端可能提交相同的 ID,第二個客戶端將在支付 Gas 後在鏈上失敗。它們還要求客戶端管理熵,這對於輕量級代理來說並非易事。使用單調 nonce 的合約生成 ID 消除了這兩個問題,同時保持了可預先計算性。

將 submitWork 作為獨立步驟

submitWorkacceptSubmission 分開提供了鏈上審計追蹤:

  • 交付物哈希在請求者評估前已錨定。
  • 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 中觸發註冊表地址,以便請求者在發佈任務前進行驗證。
  • 拍賣 GasselectLowestBidder 對所有報價是 O(n)。具有大量報價的大型拍賣可能會超過區塊 Gas 限制。實現應在報價提交期間維護運行最小值,以將此操作限制在 O(1)。Gas 成本應在前端說明。
  • 過期時鐘expiryTime 在任務創建時設置為 block.timestamp + duration。區塊時間戳可由驗證者在約 15 秒內操縱。時長極短(< 60 秒)的任務不應在對抗性環境中使用。
  • 重複評分 — 實現必須防止對同一個 (taskId, requester) 對進行多次 rateTask 調用,以避免聲譽通膨。

參考實現

參見參考實現倉庫中的 src/TaskMarket.solGitHub - daydreamsai/taskmarket-contracts

參考實現通過了合規測試套件 (test/ITMP.t.sol),該套件可針對任何符合 TMP 規範的合約運行。測試套件涵蓋:

  • ERC-165 介面檢測
  • 所有 5 種模式的狀態機
  • submitWork 交付物錨定
  • rateTask ERC-8004 標籤要求
  • refundExpired 資金回收不變量
  • requesterNonce 唯一性與可預先計算性
  • 多重轉發器添加/移除

版權

通過 CC0 放棄版權及相關權利。


附錄 A:規範任務元資料 JSON Schema

TMP 任務可以引用 contentURI 處的擴展元資料。以下 JSON schema 定義了規範格式。索引器和前端應使用此 schema 以實現互操作性。

{
  "$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 表達式
IERC1650x01ffc9a7 (標準)
ITMPtype(ITMP).interfaceId
ITMPForwardertype(ITMPForwarder).interfaceId
ITMPModetype(ITMPMode).interfaceId
ITMPFeestype(ITMPFees).interfaceId
ITMPReputationtype(ITMPReputation).interfaceId
ITMPDisputetype(ITMPDispute).interfaceId

所有非標準 ID 均由 Solidity 在編譯時計算為函數選擇器的 XOR。驗證方法:部署參考實現後執行 cast interface <address> | grep interfaceId

https://ethereum-magicians.org/t/erc-8195-task-market-protocol/27935