Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-5727: 半同质化灵魂绑定代币

灵魂绑定代币(也称为徽章或帐户绑定代币)的接口,该代币可以是同质化的也可以是非同质化的。

Authors Austin Zhu (@AustinZhu), Terry Chen <terry.chen@phaneroz.io>
Created 2022-09-28
Discussion Link https://ethereum-magicians.org/t/eip-5727-semi-fungible-soulbound-token/11086
Requires EIP-165, EIP-712, EIP-721, EIP-3525, EIP-4906, EIP-5192, EIP-5484

摘要

灵魂绑定代币(SBT)的接口,这是一种不可转让的代币,代表个人的身份、凭证、隶属关系和声誉。

我们的接口可以有组织地处理同质化和非同质化代币的组合。它提供了一组核心方法,可用于管理灵魂绑定代币的生命周期,以及一组丰富的扩展,支持 DAO 治理、委派、代币到期和帐户恢复。

此接口旨在为灵魂绑定代币系统的开发提供灵活且可扩展的框架。

动机

当前的 Web3 生态系统主要集中在金融化的、可转让的代币上。然而,越来越需要不可转让的代币来代表独特的个人属性和权利。以太坊社区内创建此类代币的现有尝试缺乏必要的灵活性和可扩展性。我们的接口解决了这一差距,为 SBT 提供了一种通用且全面的解决方案。

我们的接口可用于表示不可转让的所有权,并为常见用例提供功能,包括但不限于:

  • 生命周期管理:强大的工具,用于铸造、撤销和管理 SBT 的订阅和到期。
  • DAO 治理和委托:支持社区驱动的决策和用于 SBT 管理的运营委托。
  • 帐户恢复:用于帐户恢复和密钥轮换的高级机制,确保安全性和连续性。
  • 代币的多功能性:支持同质化和非同质化 SBT,满足各种用例,如会员卡和忠诚度计划。
  • 代币分组:创新的基于插槽的系统,用于组织 SBT,非常适合复杂的奖励结构,包括凭证、积分和徽章。
  • 可声明的 SBT:简化了 SBT 的分发,用于空投、赠品和推荐计划。

此接口不仅丰富了 Web3 的前景,而且为更加分散和个性化的数字社会铺平了道路。

规范

本文档中的关键词“必须”、“禁止”、“需要”、“应该”、“不应该”、“推荐”、“可以”和“可选”应按照 RFC 2119 中的描述进行解释。

代币由其tokenId标识,它是一个 256 位无符号整数。代币还可以有一个值来表示其面额。

插槽由其slotId标识,它是一个 256 位无符号整数。插槽用于将同质化和非同质化代币组合在一起,从而使代币成为半同质化的。一个代币一次只能属于一个插槽。

核心

核心方法用于管理 SBT 的生命周期。 所有半同质化 SBT 实现都必须支持它们。

/**
 * @title ERC5727 Soulbound Token Interface
 * @dev The core interface of the ERC5727 standard.
 * @title ERC5727 灵魂绑定代币接口
 * @dev ERC5727 标准的核心接口。
 */
interface IERC5727 is IERC3525, IERC5192, IERC5484, IERC4906 {
    /**
     * @dev MUST emit when a token is revoked.
     * @param from The address of the owner
     * @param tokenId The token id
     * @dev 必须在代币被撤销时发出。
     * @param from 所有者的地址
     * @param tokenId 代币 ID
     */
    event Revoked(address indexed from, uint256 indexed tokenId);

    /**
     * @dev MUST emit when a token is verified.
     * @param by The address that initiated the verification
     * @param tokenId The token id
     * @param result The result of the verification
     * @dev 必须在代币被验证时发出。
     * @param by 发起验证的地址
     * @param tokenId 代币 ID
     * @param result 验证结果
     */
    event Verified(address indexed by, uint256 indexed tokenId, bool result);

    /**
     * @notice Get the verifier of a token.
     * @dev MUST revert if the `tokenId` does not exist
     * @param tokenId the token for which to query the verifier
     * @return The address of the verifier of `tokenId`
     * @notice 获取代币的验证者。
     * @dev 如果 `tokenId` 不存在,则必须恢复
     * @param tokenId 要查询验证者的代币
     * @return `tokenId` 的验证者的地址
     */
    function verifierOf(uint256 tokenId) external view returns (address);

    /**
     * @notice Get the issuer of a token.
     * @dev MUST revert if the `tokenId` does not exist
     * @param tokenId the token for which to query the issuer
     * @return The address of the issuer of `tokenId`
     * @notice 获取代币的发行者。
     * @dev 如果 `tokenId` 不存在,则必须恢复
     * @param tokenId 要查询发行者的代币
     * @return `tokenId` 的发行者的地址
     */
    function issuerOf(uint256 tokenId) external view returns (address);

    /**
     * @notice Issue a token in a specified slot to an address.
     * @dev MUST revert if the `to` address is the zero address.
     *      MUST revert if the `verifier` address is the zero address.
     * @param to The address to issue the token to
     * @param tokenId The token id
     * @param slot The slot to issue the token in
     * @param burnAuth The burn authorization of the token
     * @param verifier The address of the verifier
     * @param data Additional data used to issue the token
     * @notice 向地址中的指定插槽发行代币。
     * @dev 如果 `to` 地址为零地址,则必须恢复。
     *      如果 `verifier` 地址为零地址,则必须恢复。
     * @param to 要向其发行代币的地址
     * @param tokenId 代币 ID
     * @param slot 要在其中发行代币的插槽
     * @param burnAuth 代币的销毁授权
     * @param verifier 验证者的地址
     * @param data 用于发行代币的附加数据
     */
    function issue(
        address to,
        uint256 tokenId,
        uint256 slot,
        BurnAuth burnAuth,
        address verifier,
        bytes calldata data
    ) external payable;

    /**
     * @notice Issue credit to a token.
     * @dev MUST revert if the `tokenId` does not exist.
     * @param tokenId The token id
     * @param amount The amount of the credit
     * @param data The additional data used to issue the credit
     * @notice 向代币发行信用。
     * @dev 如果 `tokenId` 不存在,则必须恢复。
     * @param tokenId 代币 ID
     * @param amount 信用额度
     * @param data 用于发行信用的附加数据
     */
    function issue(
        uint256 tokenId,
        uint256 amount,
        bytes calldata data
    ) external payable;

    /**
     * @notice Revoke a token from an address.
     * @dev MUST revert if the `tokenId` does not exist.
     * @param tokenId The token id
     * @param data The additional data used to revoke the token
     * @notice 从地址撤销代币。
     * @dev 如果 `tokenId` 不存在,则必须恢复。
     * @param tokenId 代币 ID
     * @param data 用于撤销代币的附加数据
     */
    function revoke(uint256 tokenId, bytes calldata data) external payable;

    /**
     * @notice Revoke credit from a token.
     * @dev MUST revert if the `tokenId` does not exist.
     * @param tokenId The token id
     * @param amount The amount of the credit
     * @param data The additional data used to revoke the credit
     * @notice 从代币撤销信用。
     * @dev 如果 `tokenId` 不存在,则必须恢复。
     * @param tokenId 代币 ID
     * @param amount 信用额度
     * @param data 用于撤销信用的附加数据
     */
    function revoke(
        uint256 tokenId,
        uint256 amount,
        bytes calldata data
    ) external payable;

    /**
     * @notice Verify if a token is valid.
     * @dev MUST revert if the `tokenId` does not exist.
     * @param tokenId The token id
     * @param data The additional data used to verify the token
     * @return A boolean indicating whether the token is successfully verified
     * @notice 验证代币是否有效。
     * @dev 如果 `tokenId` 不存在,则必须恢复。
     * @param tokenId 代币 ID
     * @param data 用于验证代币的附加数据
     * @return 一个布尔值,指示代币是否已成功验证
     */
    function verify(
        uint256 tokenId,
        bytes calldata data
    ) external returns (bool);
}

扩展

以下所有扩展对于 ERC-5727 实现都是可选的。 实现可以选择实现部分、全部或不实现。

可枚举

此扩展提供了枚举所有者的代币的方法。 建议与核心接口一起实现。

/**
 * @title ERC5727 Soulbound Token Enumerable Interface
 * @dev This extension allows querying the tokens of a owner.
 * @title ERC5727 灵魂绑定代币可枚举接口
 * @dev 此扩展允许查询所有者的代币。
 */
interface IERC5727Enumerable is IERC3525SlotEnumerable, IERC5727 {
    /**
     * @notice Get the number of slots of a owner.
     * @param owner The owner whose number of slots is queried for
     * @return The number of slots of the `owner`
     * @notice 获取所有者的插槽数量。
     * @param owner 要查询其插槽数量的所有者
     * @return `owner` 的插槽数量
     */
    function slotCountOfOwner(address owner) external view returns (uint256);

    /**
     * @notice Get the slot with `index` of the `owner`.
     * @dev MUST revert if the `index` exceed the number of slots of the `owner`.
     * @param owner The owner whose slot is queried for.
     * @param index The index of the slot queried for
     * @return The slot is queried for
     * @notice 获取带有 `owner` 的 `index` 的插槽。
     * @dev 如果 `index` 超过 `owner` 的插槽数量,则必须恢复。
     * @param owner 要查询其插槽的所有者。
     * @param index 要查询的插槽的索引
     * @return 查询的插槽
     */
    function slotOfOwnerByIndex(
        address owner,
        uint256 index
    ) external view returns (uint256);

    /**
     * @notice Get the balance of a owner in a slot.
     * @dev MUST revert if the slot does not exist.
     * @param owner The owner whose balance is queried for
     * @param slot The slot whose balance is queried for
     * @return The balance of the `owner` in the `slot`
     * @notice 获取所有者在插槽中的余额。
     * @dev 如果插槽不存在,则必须恢复。
     * @param owner 要查询其余额的所有者
     * @param slot 要查询其余额的插槽
     * @return `owner` 在 `slot` 中的余额
     */
    function ownerBalanceInSlot(
        address owner,
        uint256 slot
    ) external view returns (uint256);
}

元数据

此扩展提供了获取代币、插槽和合约本身元数据的方法。 如果需要指定代币、插槽和合约(即 SBT 集合)的外观和属性,建议实现此扩展。

/**
 * @title ERC5727 Soulbound Token Metadata Interface
 * @dev This extension allows querying the metadata of soulbound tokens.
 * @title ERC5727 灵魂绑定代币元数据接口
 * @dev 此扩展允许查询灵魂绑定代币的元数据。
 */
interface IERC5727Metadata is IERC3525Metadata, IERC5727 {

}

治理

此扩展提供了通过投票管理铸造和撤销权限的方法。 如果希望依靠一组选民来决定特定 SBT 的发行,这将非常有用。

/**
 * @title ERC5727 Soulbound Token Governance Interface
 * @dev This extension allows issuing of tokens by community voting.
 * @title ERC5727 灵魂绑定代币治理接口
 * @dev 此扩展允许通过社区投票发行代币。
 */
interface IERC5727Governance is IERC5727 {
    enum ApprovalStatus {
        Pending,
        Approved,
        Rejected,
        Removed
    }

    /**
     * @notice Emitted when a token issuance approval is changed.
     * @param approvalId The id of the approval
     * @param creator The creator of the approval, zero address if the approval is removed
     * @param status The status of the approval
     * @notice 当代币发行批准发生更改时发出。
     * @param approvalId 批准的 ID
     * @param creator 批准的创建者,如果批准被移除,则为零地址
     * @param status 批准的状态
     */
    event ApprovalUpdate(
        uint256 indexed approvalId,
        address indexed creator,
        ApprovalStatus status
    );

    /**
     * @notice Emitted when a voter approves an approval.
     * @param voter The voter who approves the approval
     * @param approvalId The id of the approval
     * @notice 当选民批准批准时发出。
     * @param voter 批准批准的选民
     * @param approvalId 批准的 ID
     */
    event Approve(
        address indexed voter,
        uint256 indexed approvalId,
        bool approve
    );

    /**
     * @notice Create an approval of issuing a token.
     * @dev MUST revert if the caller is not a voter.
     *      MUST revert if the `to` address is the zero address.
     * @param to The owner which the token to mint to
     * @param tokenId The id of the token to mint
     * @param amount The amount of the token to mint
     * @param slot The slot of the token to mint
     * @param burnAuth The burn authorization of the token to mint
     * @param data The additional data used to mint the token
     * @notice 创建发行代币的批准。
     * @dev 如果调用者不是选民,则必须恢复。
     *      如果 `to` 地址为零地址,则必须恢复。
     * @param to 要向其铸造代币的所有者
     * @param tokenId 要铸造的代币的 ID
     * @param amount 要铸造的代币的数量
     * @param slot 要铸造的代币的插槽
     * @param burnAuth 要铸造的代币的销毁授权
     * @param data 用于铸造代币的附加数据
     */
    function requestApproval(
        address to,
        uint256 tokenId,
        uint256 amount,
        uint256 slot,
        BurnAuth burnAuth,
        address verifier,
        bytes calldata data
    ) external;

    /**
     * @notice Remove `approvalId` approval request.
     * @dev MUST revert if the caller is not the creator of the approval request.
     *      MUST revert if the approval request is already approved or rejected or non-existent.
     * @param approvalId The approval to remove
     * @notice 移除 `approvalId` 批准请求。
     * @dev 如果调用者不是批准请求的创建者,则必须恢复。
     *      如果批准请求已被批准或拒绝或不存在,则必须恢复。
     * @param approvalId 要移除的批准
     */
    function removeApprovalRequest(uint256 approvalId) external;

    /**
     * @notice Approve `approvalId` approval request.
     * @dev MUST revert if the caller is not a voter.
     *     MUST revert if the approval request is already approved or rejected or non-existent.
     * @param approvalId The approval to approve
     * @param approve True if the approval is approved, false if the approval is rejected
     * @param data The additional data used to approve the approval (e.g. the signature, voting power)
     * @notice 批准 `approvalId` 批准请求。
     * @dev 如果调用者不是选民,则必须恢复。
     *     如果批准请求已被批准或拒绝或不存在,则必须恢复。
     * @param approvalId 要批准的批准
     * @param approve 如果批准被批准,则为 True,如果批准被拒绝,则为 False
     * @param data 用于批准批准的附加数据(例如签名、投票权)
     */
    function voteApproval(
        uint256 approvalId,
        bool approve,
        bytes calldata data
    ) external;

    /**
     * @notice Get the URI of the approval.
     * @dev MUST revert if the `approvalId` does not exist.
     * @param approvalId The approval whose URI is queried for
     * @return The URI of the approval
     * @notice 获取批准的 URI。
     * @dev 如果 `approvalId` 不存在,则必须恢复。
     * @param approvalId 要查询其 URI 的批准
     * @return 批准的 URI
     */
    function approvalURI(
        uint256 approvalId
    ) external view returns (string memory);
}

委托

此扩展提供了在插槽中将铸造权委托(取消委托)给(从)操作员的方法。 如果希望允许操作员代表您在特定插槽中铸造代币,这将非常有用。

/**
 * @title ERC5727 Soulbound Token Delegate Interface
 * @dev This extension allows delegation of issuing and revocation of tokens to an operator.
 * @title ERC5727 灵魂绑定代币委托接口
 * @dev 此扩展允许将代币的发行和撤销委托给操作员。
 */
interface IERC5727Delegate is IERC5727 {
    /**
     * @notice Emitted when a token issuance is delegated to an operator.
     * @param operator The owner to which the issuing right is delegated
     * @param slot The slot to issue the token in
     * @notice 当代币发行被委托给操作员时发出。
     * @param operator 向其委托发行权的所有者
     * @param slot 要在其中发行代币的插槽
     */
    event Delegate(address indexed operator, uint256 indexed slot);

    /**
     * @notice Emitted when a token issuance is revoked from an operator.
     * @param operator The owner to which the issuing right is delegated
     * @param slot The slot to issue the token in
     * @notice 当从操作员处撤销代币发行时发出。
     * @param operator 向其委托发行权的所有者
     * @param slot 要在其中发行代币的插槽
     */
    event UnDelegate(address indexed operator, uint256 indexed slot);

    /**
     * @notice Delegate rights to `operator` for a slot.
     * @dev MUST revert if the caller does not have the right to delegate.
     *      MUST revert if the `operator` address is the zero address.
     *      MUST revert if the `slot` is not a valid slot.
     * @param operator The owner to which the issuing right is delegated
     * @param slot The slot to issue the token in
     * @notice 将插槽的权利委托给 `operator`。
     * @dev 如果调用者没有委托的权利,则必须恢复。
     *      如果 `operator` 地址为零地址,则必须恢复。
     *      如果 `slot` 不是有效插槽,则必须恢复。
     * @param operator 向其委托发行权的所有者
     * @param slot 要在其中发行代币的插槽
     */
    function delegate(address operator, uint256 slot) external;

    /**
     * @notice Revoke rights from `operator` for a slot.
     * @dev MUST revert if the caller does not have the right to delegate.
     *      MUST revert if the `operator` address is the zero address.
     *      MUST revert if the `slot` is not a valid slot.
     * @param operator The owner to which the issuing right is delegated
     * @param slot The slot to issue the token in
     * @notice 从 `operator` 处撤销插槽的权利。
     * @dev 如果调用者没有委托的权利,则必须恢复。
     *      如果 `operator` 地址为零地址,则必须恢复。
     *      如果 `slot` 不是有效插槽,则必须恢复。
     * @param operator 向其委托发行权的所有者
     * @param slot 要在其中发行代币的插槽
     */

    function undelegate(address operator, uint256 slot) external;

    /**
     * @notice Check if an operator has the permission to issue or revoke tokens in a slot.
     * @param operator The operator to check
     * @param slot The slot to check
     * @notice 检查操作员是否具有在插槽中发行或撤销代币的权限。
     * @param operator 要检查的操作员
     * @param slot 要检查的插槽
     */
    function isOperatorFor(
        address operator,
        uint256 slot
    ) external view returns (bool);
}

恢复

此扩展提供了从无效所有者处恢复代币的方法。 建议使用此扩展,以便用户能够在某些情况下从受损或旧钱包中检索其代币。 签名方案应与 EIP-712 兼容,以提高可读性和可用性。

/**
 * @title ERC5727 Soulbound Token Recovery Interface
 * @dev This extension allows recovering soulbound tokens from an address provided its signature.
 * @title ERC5727 灵魂绑定代币恢复接口
 * @dev 此扩展允许从提供其签名的地址中恢复灵魂绑定代币。
 */
interface IERC5727Recovery is IERC5727 {
    /**
     * @notice Emitted when the tokens of `owner` are recovered.
     * @param from The owner whose tokens are recovered
     * @param to The new owner of the tokens
     * @notice 当 `owner` 的代币被恢复时发出。
     * @param from 其代币被恢复的所有者
     * @param to 代币的新所有者
     */
    event Recovered(address indexed from, address indexed to);

    /**
     * @notice Recover the tokens of `owner` with `signature`.
     * @dev MUST revert if the signature is invalid.
     * @param owner The owner whose tokens are recovered
     * @param signature The signature signed by the `owner`
     * @notice 使用 `signature` 恢复 `owner` 的代币。
     * @dev 如果签名无效,则必须恢复。
     * @param owner 其代币被恢复的所有者
     * @param signature 由 `owner` 签名的签名
     */
    function recover(address owner, bytes memory signature) external;
}

可过期

此扩展提供了管理代币到期的方法。 如果希望在一段时间后使代币过期/失效,这将非常有用。

/**
 * @title ERC5727 Soulbound Token Expirable Interface
 * @dev This extension allows soulbound tokens to be expirable and renewable.
 * @title ERC5727 灵魂绑定代币可过期接口
 * @dev 此扩展允许灵魂绑定代币可过期和可续订。
 */
interface IERC5727Expirable is IERC5727, IERC5643 {
    /**
     * @notice Set the expiry date of a token.
     * @dev MUST revert if the `tokenId` token does not exist.
     *      MUST revert if the `date` is in the past.
     * @param tokenId The token whose expiry date is set
     * @param expiration The expire date to set
     * @param isRenewable Whether the token is renewable
     * @notice 设置代币的到期日期。
     * @dev 如果 `tokenId` 代币不存在,则必须恢复。
     *      如果 `date` 在过去,则必须恢复。
     * @param tokenId 要设置其到期日期的代币
     * @param expiration 要设置的到期日期
     * @param isRenewable 代币是否可续订
     */
    function setExpiration(
        uint256 tokenId,
        uint64 expiration,
        bool isRenewable
    ) external;
}

理由

代币存储模型

我们采用半同质化代币存储模型,该模型旨在支持同质化和非同质化代币,灵感来自半同质化代币标准。 我们发现这种模型比 ERC-1155 中使用的模型更适合 SBT 的表示。

首先,每个插槽都可以用于表示不同类别的 SBT。 例如,DAO 可以在一个 SBT 集合中拥有会员 SBT、角色徽章、声誉等。

其次,与 ERC-1155 中每个同质化代币单元完全相同不同的是,我们的接口可以帮助区分相似的代币。 这是合理的,因为从不同实体获得的凭证分数不仅在价值上有所不同,而且在效果、有效期、来源等方面也有所不同。 但是,它们仍然共享同一个插槽,因为它们都有助于个人的信誉、会员资格等。

恢复机制

为了防止 SBT 的丢失,我们提出了一种恢复机制,允许用户通过提供由其所有者地址签名的签名来恢复其代币。 此机制的灵感来自 ERC-1271

由于 SBT 绑定到一个地址,并且旨在表示该地址的身份,因此无法将其拆分为几部分。 因此,每次恢复都应被视为所有者的所有代币的转移。 这就是我们使用 recover 函数而不是 transferFromsafeTransferFrom 的原因。

向后兼容性

此 EIP 提出了一个新的代币接口,该接口与 ERC-721ERC-3525ERC-4906ERC-5192ERC-5484 兼容。

此 EIP 也与 ERC-165 兼容。

测试用例

我们的示例实现包括使用 Hardhat 编写的测试用例。

参考实现

您可以在此处找到我们的参考实现。

安全考虑

此 EIP 不涉及代币的常规转移,因此通常不会出现与代币转移相关的安全问题。

但是,用户应该意识到使用恢复机制的安全风险。 如果用户丢失了他的/她的私钥,则他/她的所有灵魂绑定代币将面临潜在的盗窃风险。 攻击者可以创建一个签名并恢复受害者的所有 SBT。 因此,用户应始终确保其私钥的安全。 我们建议开发人员实施一种需要多个签名才能恢复 SBT 的恢复机制。

版权

版权和相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Austin Zhu (@AustinZhu), Terry Chen <terry.chen@phaneroz.io>, "ERC-5727: 半同质化灵魂绑定代币 [DRAFT]," Ethereum Improvement Proposals, no. 5727, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5727.