Skip to content

Commit

Permalink
Clarify standards text
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDaub committed Apr 10, 2022
1 parent 82dc89a commit eaa0b25
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 41 deletions.
46 changes: 20 additions & 26 deletions EIPS/eip-4973.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
eip: 4973
title: Non-Transferrable Non-Fungible Tokens (Soulbound Tokens)
description: A standard interface for non-transferrable non-fungible tokens, also known as "soulbound tokens" (short "SBT").
title: Non-Transferrable Non-Fungible Tokens (Soulbound Tokens or "Badges")
description: A standard interface for non-transferrable non-fungible tokens, also known as "soulbound tokens" (short "SBT") or "badges".
author: Nicola Greco, Tim Daubenschütz (TimDaub)
discussions-to: https://ethereum-magicians.org/t/eip-4966-non-transferrable-non-fungible-tokens-soulbound-tokens/8825
status: Draft
Expand All @@ -17,41 +17,34 @@ The following standard allows for the implementation of a standard API for SBTs

## Motivation

`ERC4973` tokens inhert their metadata model from [`ERC721`](./eip-721.md)'s metadata. Wallets can differentiate between `ERC4973` and transferrable [`ERC721`](./eip-721.md) tokens through [`ERC165`](./eip-165.md)'s `supportsInterface(bytes4 interfaceID);` method.
In various GitHub issues, pull requests and on Ethereum's governance forums, its community has expressed a need for non-transferrable, non-fungible tokens. Sometimes those "NTTs" are referred to as "soulbound tokens" ("SBTs") in referrence to popular MMORPGs as World of Warcraft where items, upon looting, bound with the player's soul and hence couldn't subsequently transferred to other characters anymore. Othertimes, in the context of using them as credentials, NTTs have been called "badges."

When a wallet detects support for `IERC721Metadata` (`interfaceID`: `0x5b5e139f`), it can additonal check if transfer functionality via

- `IERC721` (`interfaceID`: `0x80ac58cd`); or
- soulbound properties via `IERC4973` (`interfaceID`: `0xad5ec850`)

are supported.

Since `ERC4973` supports `ERC721Metadata`, if implemented correctly, [`ERC721`](./eip-721.md) wallets should be able to display `ERC4973` tokens without the need for any modifications from developers. [`ERC721`](./eip-721.md)'s transfer functionality could be hidden from the UI if `interfaceID` `0x80ac58cd` isn't supported or `ERC4973`'s `interfaceID` is detected.
The purpose of this document is to make NTTs a reality on Ethereum by creating consensus around a **maximally backwards-compatible** but otherwise **minimal** interface definition.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

**Every `ERC4973` compliant contract must implement the `ERC4973`, `ERC721Metadata` and [`ERC165`](./eip-165.md) interfaces** (subject to "caveats" below):
`ERC4973` tokens must define the interface function of [`ERC165`](./eip-165.md) "Standard Interface Definition." An `ERC4973` must support the [`ERC721`](./eip-721.md)'s `ERC721Metadata` interface with the hexadecimal identifier `0x5b5e139f`. It must not, however, support [`ERC721`](./eip-721.md)'s tracking and transferring functionality with the interface identifier `0x80ac58cd`. Neither [`ERC721`](./eip-721.md)'s `ERC721TokenReceiver` with the interface identifier `0x150b7a02` must be supported.

```solidity
pragma solidity ^0.8.6;
/// @title ERC-4973 Non-Transferrable Non-Fungible Token Standard
/// @title Non-Transferrable Non-Fungible Tokens (Soulbound Tokens or "Badges")
/// @dev See https://eips.ethereum.org/EIPS/eip-4973
/// Note: the ERC-165 identifier for this interface is 0xad5ec850.
interface IERC4973 /* is ERC165, ERC721Metadata */ {
/// @dev This emits when bond of any SBT is established by any mechanism.
/// This event emits when SBTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of SBTs
/// may be created and assigned without emitting Bond.
event Bond(address indexed _from, address indexed _to, uint256 indexed _tokenId);
/// @notice Find the address bound to an ERC4973 soulbound token (short "SBT")
/// @dev This emits when transfer of any soulbound token is established by any
/// mechanism. This event emits when SBTs are created (`from` == 0) and
/// destroyed (`to` == 0). Exception: during contract creation, any number of
/// SBTs may be created and assigned without emitting Transfer.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
/// @notice Find the address bound to an ERC4973 soulbound token
/// @dev SBTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an SBT
/// @return The address of the owner bound to the SBT
function boundTo(uint256 _tokenId) external view returns (address);
function ownerOf(uint256 _tokenId) external view returns (address);
}
interface ERC165 {
Expand Down Expand Up @@ -86,18 +79,19 @@ For "ERC721 Metadata JSON Schema", see [`ERC721`](./eip-721.md).

## Rationale

Since 2018, the Ethereum community has been seeking consensus for a standard for non-transferrable non-fungible token.
To understand this standard's benefits over other similar proposals, it's important to understand the design philosophy it was built upon. `ERC4973` shall be maximally backwards-compatible but still only expose a minimal and simple to implement interface definition.

As [`ERC721`](./eip-721.md) tokens have seen wide-spread adoption with wallet providers and marketplaces, using its `ERC721Metadata` interface with [`ERC165`](./eip-165.md) for feature-detection, potentially allows implementers to support `ERC4973` tokens out of the box.

For `ERC4973`, we propose:
If an implementer of [`ERC721`](./eip-721.md) properly built [`ERC165`](./eip-165.md)'s `function supportsInterface(bytes4 interfaceID` function, already by recognizing that [`ERC721`](./eip-721.md)'s track and transfer interface component with the identifier `0x80ac58cd`, transferring of a token shouldn't be suggested as a user interface option.

- `event Bond(address indexed _from, address indexed _to, uint256 indexed _tokenId)`, essentially `ERC721`'s Transfer event but renamed to "Bond".
- `function boundTo(uint256 _tokenId) external view returns (address)`, essentially `ERC721`'s `ownerOf(uint256 _tokenId)` but renamed to `boundTo`.
Still, however, since `ERC4973` supports [`ERC721`](./eip-721.md)'s `ERC721Metadata` extension, a "soulbound" token should be displayed in wallets and marketplaces with no changes needed.

The benefit of using the `ERC721Metadata` interface is that implementers (e.g. wallets) of `ERC4973` can identify transfer and tracking functionality using the [`ERC165`](./eip-165.md) `supportsInterface(bytes4 interfaceID)` function.
Although other possible implementations of soulbound tokens are possible, e.g. by having all transfer functions revert, `ERC4973` is superior as it supports fine-grained feature detection through [`ERC165`](./eip-165.md).

## Backwards Compatibility

We have adopted the [`ERC165`](./eip-165.md) and `ERC721Metadata` functions. `ERC4973`'s `boundTo` function is the pendant to [`ERC721`](./eip-721.md)'s `ownerOf` function.
We have adopted the [`ERC165`](./eip-165.md) and `ERC721Metadata` functions purposefully to create a high-degree of backwards compatibility with [`ERC721`](./eip-721.md)' implementers. Additionally, we've deliberately used [`ERC721`](./eip-721md`) terminology and definitions such as `event Transfer(...)` and `function ownerOf(...)` to minimize the effort for `ERC4973` implementers already familiar with e.g. [`ERC20`](./eip-20.md) or [`ERC721`](./eip-721.md).

## Reference Implementation

Expand Down
10 changes: 5 additions & 5 deletions assets/eip-4973/src/ERC4973.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ abstract contract ERC4973 is ERC165, IERC721Metadata, IERC4973 {
return _bonds[tokenId] != address(0);
}

function boundTo(uint256 tokenId) public view virtual returns (address) {
function ownerOf(uint256 tokenId) public view virtual returns (address) {
address owner = _bonds[tokenId];
require(owner != address(0), "boundTo: token doesn't exist");
require(owner != address(0), "ownerOf: token doesn't exist");
return owner;
}

Expand All @@ -63,16 +63,16 @@ abstract contract ERC4973 is ERC165, IERC721Metadata, IERC4973 {
_bonds[tokenId] = msg.sender;
_tokenURIs[tokenId] = uri;
_tokenIds.increment();
emit Bond(address(0), msg.sender, tokenId);
emit Transfer(address(0), msg.sender, tokenId);
return tokenId;
}

function _burn(uint256 tokenId) internal virtual {
address owner = boundTo(tokenId);
address owner = ownerOf(tokenId);

delete _bonds[tokenId];
delete _tokenURIs[tokenId];

emit Bond(owner, address(0), tokenId);
emit Transfer(owner, address(0), tokenId);
}
}
4 changes: 2 additions & 2 deletions assets/eip-4973/src/ERC4973.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ contract ERC4973Test is DSTest {
string memory tokenURI = "https://example.com/metadata.json";
uint256 tokenId = sbt.mint(tokenURI);
assertEq(sbt.tokenURI(tokenId), tokenURI);
assertEq(sbt.boundTo(tokenId), address(this));
assertEq(sbt.ownerOf(tokenId), address(this));
sbt.burn(tokenId);
}

Expand All @@ -66,7 +66,7 @@ contract ERC4973Test is DSTest {
}

function testFailGetBonderOfNonExistentTokenId() public view {
sbt.boundTo(1337);
sbt.ownerOf(1337);
}
}

19 changes: 11 additions & 8 deletions assets/eip-4973/src/interfaces/IERC4973.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.6;

interface IERC4973{
/// @dev This emits when bond of any SBT is established by any mechanism.
/// This event emits when SBTs are created (`from` == 0) and destroyed
/// (`to` == 0). Exception: during contract creation, any number of SBTs
/// may be created and assigned without emitting Bond.
event Bond(address indexed _from, address indexed _to, uint256 indexed _tokenId);
/// @notice Find the address bound to an ERC1238 soulbound token (short "SBT")
/// @title Non-Transferrable Non-Fungible Tokens (Soulbound Tokens or "Badges")
/// @dev See https://eips.ethereum.org/EIPS/eip-4973
/// Note: the ERC-165 identifier for this interface is 0xad5ec850.
interface IERC4973 /* is ERC165, ERC721Metadata */ {
/// @dev This emits when transfer of any soulbound token is established by any
/// mechanism. This event emits when SBTs are created (`from` == 0) and
/// destroyed (`to` == 0). Exception: during contract creation, any number of
/// SBTs may be created and assigned without emitting Transfer.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
/// @notice Find the address bound to an ERC4973 soulbound token
/// @dev SBTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param _tokenId The identifier for an SBT
/// @return The address of the owner bound to the SBT
function boundTo(uint256 _tokenId) external view returns (address);
function ownerOf(uint256 _tokenId) external view returns (address);
}

0 comments on commit eaa0b25

Please sign in to comment.