ERC-8191:鏈上定期循環支付標準
本提案介紹了 ERC-8191,這是一個針對以太坊及 EVM 相容鏈的 Solidity 標準介面,旨在透過守護者閘控的拉取支付模型,實現去信任化且自動化的鏈上定期循環支付。
摘要
本提案定義了一個標準的 Solidity 介面 —— ISubscription —— 用於以太坊及 EVM 相容鏈上的鏈上定期付款。它引入了一種拉取式付款(pull-payment)模型,具備完整的訂閱生命週期管理(創建、暫停、恢復、取消)、守護者門控(keeper-gated)的付款收集、原生 ETH 與 ERC-20 支援,以及為商家提供的選用回調介面。目前已提供測試覆蓋率 >95% 的參考實現。
動機
定期付款是數位經濟的基礎原語。儘管如此,目前尚不存在一個已定稿的 ERC 標準來實現去信任化、自動化的鏈上訂閱。
先前的兩次嘗試均未成功:
ERC-1337 (2018):需要鏈下元交易(meta-transaction)中繼者,引入了中心化與信任假設。從未編寫過測試套件。目前處於停滯狀態。
ERC-5643:僅限於基於 NFT 的會員資格。缺乏拉取式付款機制,沒有狀態管理(暫停、寬限期、重試)。這只是大問題的局部解決方案。
其結果是,現今每個構建定期付款的協議都在重新發明自己的邏輯 —— 這些邏輯不具備組合性,無法通過共享標準進行審計,且與任何共享工具層都不相容。
本提案旨在永久解決這一問題。
規範
核心類型
struct SubscriptionTerms {
address token; // ERC-20 地址,原生 ETH 則為 address(0)
uint256 amount; // 每個週期的付款金額
uint48 interval; // 兩次付款之間的秒數
uint48 trialPeriod; // 首次付款前的免費試用秒數;0 = 無
uint256 maxPayments; // 最大付款次數;0 = 無限制
uint256 originChainId;
uint256 paymentChainId;
}
enum Status { Active, Paused, Cancelled, Expired, PastDue }
介面
interface ISubscription {
function subscribe(address merchant, SubscriptionTerms calldata terms)
external returns (bytes32 subId);
function collectPayment(bytes32 subId) external returns (bool);
function cancelSubscription(bytes32 subId) external;
function pauseSubscription(bytes32 subId) external;
function resumeSubscription(bytes32 subId) external;
function getStatus(bytes32 subId) external view returns (Status);
function nextPaymentDue(bytes32 subId) external view returns (uint256);
function getTerms(bytes32 subId) external view returns (SubscriptionTerms memory);
function getSubscriber(bytes32 subId) external view returns (address);
function getMerchant(bytes32 subId) external view returns (address);
function getPaymentCount(bytes32 subId) external view returns (uint256);
}
關鍵設計決策
collectPayment() 由守護者(keeper)門控。 訂閱者直接拉取(推播模型)無法實現自動化。而無限制的收集則會引入騷擾(griefing)風險。雙層 KeeperRegistry(由所有者設置的全域守護者 + 商家自我管理的專屬守護者)在去中心化與活性(liveness)之間取得了平衡。
PastDue(逾期)是在 getStatus() 中動態計算的。 這避免了單純為了更新狀態而需要守護者發送交易 —— 任何 view 調用都能反映正確的狀態。
bytes32 訂閱 ID 的推導方式為 keccak256(subscriber, merchant, block.timestamp, block.chainid, nonce) —— 具有確定性、跨鏈可移植性,且無需順序計數器即可防止碰撞。
address(0) 代表原生 ETH —— 與廣泛的慣例一致(例如 Uniswap, 1inch)。採用託管模式分離訂閱者與商家的 ETH 餘額,以消除重入攻擊向量。
使用 uint48 表示 interval 和 trialPeriod —— 可與其他結構體欄位共用同一個存儲插槽,支援長達約 890 萬年(對於任何實際用例而言實際上是無限的)。
選用擴展
以下擴展介面設計為可組合且透過 ERC-165 選擇性加入,遵循 ERC-721/ERC-721Enumerable 的先例:
ISubscriptionTrial —— 運行時試用查詢(isInTrial(), trialEndsAt(), extendTrial())
ISubscriptionTiered —— 分級定價(Tier 結構體, createTier(), upgradeTier(), downgradeTier())
ISubscriptionDiscovery —— 帶有鏈下元數據 URI 的極簡鏈上商家註冊表
ISubscriptionHook —— 針對付款失敗的催款(dunning)回調(由鏈下自動化調用,而非由 SubscriptionManager 調用)
選用商家回調
interface ISubscriptionReceiver {
function onPaymentCollected(bytes32 subId, uint256 amount, address token)
external returns (bytes4);
function onSubscriptionCancelled(bytes32 subId)
external returns (bytes4);
}
商家透過實現此介面並返回函數選擇器(function selector)來選擇加入。在參考實現中,回調被封裝在 try/catch 中 —— 商家回調的 revert 永遠不會阻礙付款收集。
向後相容性
此 ERC 引入了一個新介面,不修改也不與任何現有標準衝突。它與 ERC-20、ERC-165 和 ERC-7683(跨鏈意圖)相容。ERC-5643 的實現不需要遷移;本標準可以與之共存。
參考實現
完整的 Foundry 參考實現可在以下網址取得:github.com/cadence-protocol/cadence-protocol
src/interfaces/ISubscription.sol — 完整介面
src/SubscriptionManager.sol — 參考實現(ERC-20 + ETH, 託管, 試用, 守護者門控, 接收者回調)
src/KeeperRegistry.sol — 帶有黑名單的雙層守護者授權
test/ — 90 多個單元測試、模糊測試和不變量測試(分支覆蓋率 >95%)
docs/rationale.md — 針對上述所有決策的詳細設計原由
社群提問
collectPayment() 訪問模型:KeeperRegistry 是正確的抽象嗎?還是應該完全交由實現者決定,不在介面中設置鏈上門控?
跨鏈:SubscriptionTerms 中的 originChainId / paymentChainId 預見了 ERC-7683 意圖。將其編碼在結構體中是正確的方法嗎?還是跨鏈應該作為一個獨立的擴展?
PastDue 作為計算狀態:對於不存儲在狀態中的狀態值是否有任何異議?
擴展介面粒度:四個擴展介面(試用、分級、發現、鉤子)的劃分是否合適?還是應該合併其中一些?
期待聽到任何感興趣的人的反饋。
— Cadence Protocol 團隊
[更新] 正式的 ERC 草案已提交:https://github.com/ethereum/ERCs/pull/1595
1 則貼文 - 1 位參與者
[閱讀完整主題](https://ethereum-magicians.org/t/erc-8191-onchain-recurring-payments/27946)