
ERC-8216:適用於 ERC-6551 代幣綁定帳戶的插槽式裝備標準
本提案為 ERC-6551 帳戶引入了一套管理可裝備物品的標準介面,允許將代幣組織到應用程式定義的插槽中,並提供可選的永久鎖定機制,以實現鏈上身份識別。
我一直在為 6551 帳戶開發裝備介面,這裡並非要定義它應該或不應該用於什麼,而是核心問題顯而易見,足以圍繞其編寫一個標準。九個函數、三個事件、一個結構體。沒有目錄基礎設施,沒有框架依賴,僅僅是原語(primitive)。
摘要
一個用於管理 ERC-6551 代幣綁定帳戶內插槽式裝備的標準介面 (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)」回滾。在外部轉移之前「必須」更新狀態(遵循 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 中。
未鎖定的插槽可由新所有者修改(裝備/卸下)。
鎖定的插槽永遠保持鎖定:新所有者繼承這些鎖定。
新所有者「可以(CAN)」在空插槽中裝備新物品,並鎖定額外的未鎖定插槽。
即角色的身份(鎖定的特徵)隨交易轉移,而新所有者可以自定義角色的裝備(未鎖定的插槽)。
原理
在迭代過程中保留下來的設計決策及其重要性:
鎖定是永久性的:這是我非常在意的一點。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 檢測。
代碼庫: GitHub - LordThisDrip/erc-equipment
安全考量
檢查-效果-交互(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-token-bound-accounts/28139)