
ERC-8216:適用於 ERC-6551 帳戶的插槽式裝備標準
本文介紹了 ERC-8216,這是一個用於管理 ERC-6551 代幣綁定帳戶中插槽式裝備的標準介面,旨在實現永久性的鏈上身份、來源證明以及可組合的角色系統。
我一直在為 6551 帳戶開發裝備介面,我並非要定義它應該或不應該用於什麼,但其核心問題顯而易見,足以圍繞它編寫一個標準。九個函數、三個事件、一個結構體。沒有目錄基礎設施,沒有框架依賴,僅僅是原語。
摘要
一個用於管理 ERC-6551 代幣綁定帳戶(TBA)內插槽式裝備的標準介面(IERC6551Equipment)。代幣(ERC-721 或 ERC-1155)可以裝備到應用程式定義的插槽中,作為結構化裝備清單(loadout)進行查詢,並可選擇永久鎖定,從而實現鏈上身份、溯源和可組合的角色系統。
ERC-165 介面 ID:0xd38f0891
動機
6551 賦予了 NFT 錢包功能,但並未規定錢包內應該裝什麼,導致每個想要實現角色裝備清單的專案最終都必須從頭編寫自己的插槽邏輯,且彼此互不相容。在經歷了看似無限次的迭代過程後,我一直在對這個框架進行壓力測試,現在是時候將其正式化為一個標準了;互操作性只有在每個人的裝備介面都相容的情況下才有效,這正是本標準要解決的問題。
本標準支援的案例:
遊戲:具有可裝備道具插槽的角色,其身份特徵在鑄造時永久鎖定,而時尚物品則可自由更換。
社交/身份:具有鎖定驗證憑證和可更換顯示徽章的個人檔案。
藝術:具有鎖定底層和可更換框架或配件的可組合 NFT。
市場:透過單次 getLoadout() 調用實現通用的裝備展示,區分永久特徵與可交易裝備。
規範
介面
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.24;
interface IERC6551Equipment {
struct SlotEntry {
bytes32 slotId;
address tokenContract;
uint256 tokenId;
uint256 amount;
bool locked;
}
event Equipped(bytes32 indexed slotId, address indexed tokenContract, uint256 indexed tokenId, uint256 amount);
event Unequipped(bytes32 indexed slotId, address indexed tokenContract, uint256 indexed tokenId, uint256 amount);
event SlotLocked(bytes32 indexed slotId, address indexed tokenContract, uint256 tokenId);
function equip(bytes32 slotId, address tokenContract, uint256 tokenId, uint256 amount) external;
function unequip(bytes32 slotId) external;
function lockSlot(bytes32 slotId) external;
function equipBatch(bytes32[] calldata slotIds, address[] calldata tokenContracts, uint256[] calldata tokenIds, uint256[] calldata amounts) external;
function lockSlots(bytes32[] calldata slotIds) external;
function getEquipped(bytes32 slotId) external view returns (address tokenContract, uint256 tokenId, uint256 amount);
function getLoadout() external view returns (SlotEntry[] memory entries);
function isSlotOccupied(bytes32 slotId) external view returns (bool);
function isSlotLocked(bytes32 slotId) external view returns (bool);
}
插槽識別碼
插槽為 bytes32 類型:應用程式透過 keccak256("slot.head")、keccak256("slot.weapon") 等定義自己的分類體系,只要符合上下文即可。沒有中央註冊表,也不對應該存在哪些插槽預設立場;本標準定義的是機制而非分類法。
在多個上下文中共享同一個 TBA 的應用程式「應該」(SHOULD)為插槽設定命名空間以避免衝突,例如 keccak256("myapp.slot.head") 與 keccak256("otherapp.slot.head")。
行為
equip(裝備):如果調用者不是有效簽署者、插槽已被佔用、插槽已鎖定或數量為零,則「必須」(MUST)回退(revert)。在外部轉帳前「必須」更新狀態(遵循 CEI 模式)。「必須」透過 safeTransferFrom 將代幣轉入 TBA。 「必須」發出 Equipped 事件。
unequip(卸除):如果調用者不是有效簽署者、插槽為空或插槽已鎖定,則「必須」回退。在外部轉帳前「必須」刪除狀態(遵循 CEI 模式)。「必須」將代幣轉回給調用者。「必須」發出 Unequipped 事件。
lockSlot(鎖定插槽):如果調用者不是有效簽署者、插槽為空或已鎖定,則「必須」回退。「必須」將插槽設定為永久鎖定。這是不可逆的,沒有 unlockSlot 函數。「必須」發出 SlotLocked 事件。
equipBatch(批量裝備):如果陣列長度不匹配或任何單個裝備操作會導致回退,則「必須」回退。採用全有或全無(all-or-nothing)語義。「必須」為每個插槽發出 Equipped 事件。
lockSlots(批量鎖定):如果任何單個鎖定操作會導致回退,則「必須」回退。採用全有或全無語義。「必須」為每個插槽發出 SlotLocked 事件。
getEquipped:返回給定插槽的代幣合約、代幣 ID 和數量。如果為空則返回 (address(0), 0, 0)。
getLoadout:為每個已佔用的插槽返回一個 SlotEntry 結構體陣列。鎖定狀態透過 bool locked 包含在內。如果未裝備任何物品則返回空陣列。
isSlotOccupied / isSlotLocked:布林值查詢。鎖定的插槽始終是被佔用的,也就是說,你不能鎖定一個空插槽。
所有權轉移語義
當父級 NFT 轉移給新所有者時:
所有已裝備的物品保留在 TBA 中
未鎖定的插槽可由新所有者修改(裝備/卸除)
鎖定的插槽將永遠保持鎖定:新所有者繼承這些鎖定
新所有者「可以」在空插槽中裝備新物品,並鎖定額外的未鎖定插槽
也就是說,角色的身份(鎖定的特徵)會隨著交易而移動,而新所有者可以自定義角色的裝備(未鎖定的插槽)。
原理
在迭代過程中保留下來的設計決策及其重要性:
鎖定是永久性的:這是我非常在意的一點。lockSlot() 是不可逆的,沒有解鎖、沒有時間鎖、沒有管理員覆蓋。如果 isSlotLocked() 返回 true,它將永遠返回 true,跨越所有權轉移,跨越時間。新所有者繼承鎖定。這就是重點:可證明的鏈上不可變身份。你在鑄造時鎖定角色的核心特徵,它們就永遠成為了該角色的一部分。如果你想要可更換的東西,就不要鎖定它。
CEI 是強制性的而非建議:equip() 和 unequip() 會觸發 safeTransferFrom,進而調用接收者回調;規範要求在外部調用之前更新狀態。我們在自己的實現中經歷了發現並修復此問題的過程,因此認為將其在規範中列為「必須」(MUST)而非建議是非常重要的。
getLoadout() 單次調用返回所有內容:所有佔用的插槽、鎖定狀態都包含在結構體的 bool locked 中。前端和市場無需進行 N 次單獨的查詢即可獲得完整視圖。這是一個刻意的結構體變更,將 locked 加入 SlotEntry,以便一次調用即可提供完整角色資訊。
批量操作的存在是因為鑄造流程的需求:裝備 12 個特徵並鎖定 5 個身份插槽不應該花費 17 筆交易。equipBatch() + lockSlots() 將其減少到 2 筆。全有或全無語義與 1155 的模式一致。
刻意不存在的功能及其原因:
沒有 unlockSlot():永久性是功能而非限制。
沒有 swap():先卸除再裝備,如果需要原子性可透過 execute() 進行多重調用(multicall)。
沒有 getLockedSlots():getLoadout() 已涵蓋,事件處理索引。
不對應該存在哪些插槽或其含義預設立場。
先前技術
我了解來自 RMRK 的 ERC-6220(利用可裝備部件的可組合 NFT)。那是不同的方法和範疇。6220 直接擴展了 ERC-721,需要一個帶有預定義部件的目錄(Catalog)系統、可裝備地址白名單,以及 RMRK 套件中的 5 個以上介面;它是為視覺可組合性設計的,即從組件部分組裝渲染圖像。本 ERC 專門擴展了 ERC-6551,除了 TBA 本身之外不需要額外的基礎設施,並專注於所有權的可組合性:代幣物理性地轉移到角色的錢包中。在 6220 或我找到的其他地方都不存在的關鍵原語是永久性的插槽鎖定。
關於 6551 上的裝備問題曾在原始 ERC-6551 討論串中被提及(第 128 樓),當時有人建議將 6220 作為解決方案。但 6220 並不擴展 6551:它直接擴展 721 並需要 RMRK 目錄基礎設施。本 ERC 是該問題的原生答案。
EIP-4973(帳戶綁定代幣)使用了「裝備/卸除」術語,但在不同的語境下:顯示或隱藏靈魂綁定憑證,而非將可轉移代幣放入具名插槽。4973 使代幣不可移動。本 ERC 使插槽不可移動:不同的原語,不同的使用案例。
ERC-7635(多功能代幣)提出了一種內建插槽的新代幣標準,但完全取代了現有的代幣模型。本 ERC 在現有的 721 + 1155 + 6551 生態系統中運作:沒有新代幣類型,只是為已部署的內容提供介面。
ERC-998(自上而下的可組合 NFT)早於 6551,處理父子嵌套,但未定義具名插槽、鎖定語義或批量操作。ERC-4883(可組合 SVG NFT)僅限於視覺,沒有插槽邏輯。
如果我遺漏了涵蓋此精確領域(即 TBA 的插槽式裝備且具備永久鎖定功能)的先前技術,我真心希望能了解。
向後相容性
沒有向後相容性問題。透過新功能擴展了 ERC-6551 代幣綁定帳戶。現有的 TBA 不受影響。實現此介面的帳戶與 ERC-721 和 ERC-1155 代幣標準完全相容。
參考實現
包含 27 個 Foundry 測試,涵蓋核心流程、鎖定、批量操作、所有權轉移、CEI 驗證和 ERC-165。已部署在 Base Sepolia 上,並在 phantoma.io 有一個使用該介面的線上前端:展示了帶有鎖定狀態顯示的 getLoadout() 渲染、跨注入式和嵌入式錢包提供商的裝備管理,以及 supportsInterface 檢測。
安全考量
檢查-效果-交互(CEI):equip()、unequip() 和 equipBatch() 涉及透過 safeTransferFrom 進行外部調用,這會觸發接收者回調。實作「必須」在執行任何外部代幣轉帳之前更新插槽狀態。
不可逆鎖定:lockSlot() 是永久性的且無法撤銷。前端「應該」呈現清晰的確認對話框。鎖定在插槽中的代幣將永久提交給 TBA。
批量原子性:equipBatch() 和 lockSlots() 使用全有或全無語義。實作「必須」在處理前驗證所有陣列長度是否匹配。
所有權轉移:當父級 NFT 轉移時,新所有者獲得對所有未鎖定插槽的控制權,但繼承所有鎖定。買家「應該」在購買前檢查裝備清單。
ERC-165 檢測:市場和合約「應該」調用 supportsInterface(0xd38f0891) 以在進行裝備相關調用前驗證裝備支援。
版權
透過 CC0 放棄版權及相關權利。
1 則貼文 - 1 位參與者
[閱讀完整主題](https://ethereum-magicians.org/t/erc-8216-slot-based-equipment-for-erc-6551-accounts/28139)