ERC-5114 Soulbound badge
Last Call · NFTs & multi-token · created 2022-05-30
Creates a badge token that is bound to a specific NFT the moment it is issued and can never be transferred afterward. This lets the history or credentials accumulated by an NFT be preserved as a permanent record.
Why it is needed
Most tokens that represent credentials or history on-chain can be transferred to another address. If transfer is possible, badges such as completion certificates or participation records can be sold or handed off to someone else, making it hard to trust that the record truly reflects what that holder accomplished. Also, if the descriptive data attached to a badge lives at an ordinary web address, the issuer can later change or delete its content, which could be used as a means of censoring or manipulating a specific user. This standard binds a badge to a specific NFT the moment it is issued and prevents it from being moved afterward, guaranteeing that badges accumulated on one NFT cannot be split off or merged into another collection. Badge information is also served only through content-addressed means, where the address changes if the content changes, so that even the issuer cannot alter the content after the fact.
What to implement
event Mint(uint256 indexed badgeId, address indexed nftAddress, uint256 indexed nftTokenId);: An event emitted whenever a new badge is minted. It must not be emitted twice for the same badgeId.function ownerOf(uint256 badgeId) external view returns (address nftAddress, uint256 nftTokenId);: Returns the contract address and token ID of the NFT this badge is bound to. It must revert if called before minting and must always return the same value after minting.function collectionUri() external pure returns (string collectionUri);: Returns a URI containing information about the entire badge collection. It must be an immutable, content addressable address.function badgeUri(uint256 badgeId) external view returns (string badgeUri);: Returns a censorship resistant information URI for an individual badge. This data takes precedence over the data from collectionUri.function metadataFormat() external pure returns (string format);: Returns a string indicating the format of the data returned by badgeUri and collectionUri.
In depth
Badges are bound to NFTs, not addresses
A badge's owner is an NFT, not a wallet address. ownerOf(badgeId) returns the nftAddress and nftTokenId of the bound NFT, and must throw for a badge that has not yet been minted. After minting, every call must always return the same value, and that value must match what was recorded in the original Mint event. The Mint event must not be emitted twice for the same badgeId. Compatible souls are any asset identifiable as [address, id].
What non-transferability guarantees
The requirement that a badge can never move guarantees two things simultaneously: that a soulbound badge collection bound to a single NFT cannot be split (non-separability) or merged with another collection (non-mergeability), and that users can aggressively cache binding lookup results.
Metadata must be an immutable, content addressable URI
The URIs returned by collectionUri() and badgeUri(badgeId) must both be immutable and content addressable. The spec gives the example that ipfs:// is acceptable while http:// is not. Any external links referenced by that content must follow the same rule. The data from the two URIs is merged, with badgeUri's data taking precedence over collectionUri's data. This requirement exists to prevent issuers from censoring or manipulating specific users by changing or deleting remote content. Not only can owners not transfer a badge, issuers also cannot reclaim or alter ownership.
Data format is deliberately left unspecified
The format of the data the URIs point to, and how the two data sets are merged, are deliberately excluded from this standard, to leave room for future development as a separate standard. All this standard specifies is the immutability constraint, which preserves the purpose of soulbound badges regardless of data format. Instead, metadataFormat() returns a string such as 'EIP-ABCD' or 'soulbound-schema-version-4' indicating the format and version, so callers can parse the data directly without having to guess the format by inspecting it.
Recommendation to use an interface detection standard
Implementers of this standard should also rely on an interface detection standard so that callers can easily check whether a given contract implements this interface. This standard represents a new token type and is not intended to be backward compatible with existing tokens other than existing soul assets identifiable by [address, id].
Things to watch out for
- Implementations must be verified directly: Badges that claim to implement this standard must be carefully checked to confirm they actually follow the rules. A contract can appear compliant from its external API alone while hiding a transfer mechanism that simply isn't used yet.
- Badges are bound to personas, not people: Soulbound badges are bound to a persona, not a human being. A persona is the acting entity that accumulates badges, and it can be a group of multiple people, or it can be transferred to another person or group. You must not assume that a single fixed human stands behind a persona.
- Badge links can form loops: A soulbound badge can be bound to another soulbound badge. If every badge in a chain were created at the same time, a loop could in theory form, so software that traverses such links must have a strategy to break out when it detects a loop.