DCIPs/EIPS/eip-4973.md

198 lines
12 KiB
Markdown

---
eip: 4973
title: Account-bound Tokens
description: An interface for non-transferrable NFTs binding to an Ethereum account like a legendary World of Warcraft item binds to a character.
author: Tim Daubenschütz (@TimDaub)
discussions-to: https://ethereum-magicians.org/t/eip-4973-non-transferrable-non-fungible-tokens-soulbound-tokens-or-badges/8825
status: Review
type: Standards Track
category: ERC
created: 2022-04-01
requires: 165, 712, 721, 1271
---
## Abstract
Proposes a standard API for account-bound Tokens (ABT) within smart contracts. An ABT is a non-fungible token bound to a single account. ABTs don't implement a canonical interface for transfers. This EIP defines basic functionality to mint, assign, revoke and track ABTs.
## Motivation
In the popular MMORPG World of Warcraft, its game designers intentionally took some items out of the world's auction house market system to prevent them from having a publicly-discovered price and limit their accessibility.
Vanilla WoW's "Thunderfury, Blessed Blade of the Windseeker" was one such legendary item, and it required a forty-person raid, among other sub-tasks, to slay the firelord "Ragnaros" to gain the "Essence of the Firelord," a material needed to craft the sword once.
Upon voluntary pickup, the sword permanently **binds** to a character's "soul," making it impossible to trade, sell or even swap it between a player's characters.
In other words, "Thunderfury"'s price was the aggregate of all social costs related to completing the difficult quest line with friends and guild members. Other players spotting Thunderfuries could be sure their owner had slain "Ragnaros," the blistering firelord.
World of Warcraft players could **trash** legendary and soulbound items like the Thunderfury to permanently remove them from their account. It was their choice to visibly **equip** or **unequip** an item and hence show their achievements to everyone.
The Ethereum community has expressed a need for non-transferrable, non-fungible, and socially-priced tokens similar to WoW's soulbound items. Popular contracts implicitly implement account-bound interaction rights today. A principled standardization helps interoperability and improves on-chain data indexing.
The purpose of this document is to make ABTs a reality on Ethereum by creating consensus around a **maximally backward-compatible** but otherwise **minimal** interface definition.
## Specification
### Solidity Interface
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.
ABTs _must_ implement the interfaces:
- [EIP-165](./eip-165.md)'s `ERC165` (`0x01ffc9a7`)
- [EIP-721](./eip-721.md)'s `ERC721Metadata` (`0x5b5e139f`)
ABTs _must not_ implement the interfaces:
- [EIP-721](./eip-721.md)'s `ERC721` (`0x80ac58cd`)
An ABT receiver must be able to always call `function unequip(address _tokenId)` to take their ABT off-chain.
```solidity
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.6;
/// @title Account-bound tokens
/// @dev See https://eips.ethereum.org/EIPS/eip-4973
/// Note: the ERC-165 identifier for this interface is 0xeb72bb7c
interface IERC4973 {
/// @dev This emits when ownership of any ABT changes by any mechanism.
/// This event emits when ABTs are given or equipped and unequipped
/// (`to` == 0).
event Transfer(
address indexed from, address indexed to, uint256 indexed tokenId
);
/// @notice Count all ABTs assigned to an owner
/// @dev ABTs assigned to the zero address are considered invalid, and this
/// function throws for queries about the zero address.
/// @param owner An address for whom to query the balance
/// @return The number of ABTs owned by `address owner`, possibly zero
function balanceOf(address owner) external view returns (uint256);
/// @notice Find the address bound to an ERC4973 account-bound token
/// @dev ABTs assigned to zero address are considered invalid, and queries
/// about them do throw.
/// @param tokenId The identifier for an ABT.
/// @return The address of the owner bound to the ABT.
function ownerOf(uint256 tokenId) external view returns (address);
/// @notice Removes the `uint256 tokenId` from an account. At any time, an
/// ABT receiver must be able to disassociate themselves from an ABT
/// publicly through calling this function. After successfully executing this
/// function, given the parameters for calling `function give` or
/// `function take` a token must be re-equipable.
/// @dev Must emit a `event Transfer` with the `address to` field pointing to
/// the zero address.
/// @param tokenId The identifier for an ABT.
function unequip(uint256 tokenId) external;
/// @notice Creates and transfers the ownership of an ABT from the
/// transaction's `msg.sender` to `address to`.
/// @dev Throws unless `bytes signature` represents a signature of the
// EIP-712 structured data hash
/// `Agreement(address active,address passive,bytes metadata)` expressing
/// `address to`'s explicit agreement to be publicly associated with
/// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be
/// generated by type-casting the `bytes32` EIP-712 structured data hash to a
/// `uint256`. If `bytes signature` is empty or `address to` is a contract,
/// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must
/// be made to `address to`. A successful execution must result in the
/// `event Transfer(msg.sender, to, tokenId)`. Once an ABT exists as an
/// `uint256 tokenId` in the contract, `function give(...)` must throw.
/// @param to The receiver of the ABT.
/// @param metadata The metadata that will be associated to the ABT.
/// @param signature A signature of the EIP-712 structured data hash
/// `Agreement(address active,address passive,bytes metadata)` signed by
/// `address to`.
/// @return A unique `uint256 tokenId` generated by type-casting the `bytes32`
/// EIP-712 structured data hash to a `uint256`.
function give(address to, bytes calldata metadata, bytes calldata signature)
external
returns (uint256);
/// @notice Creates and transfers the ownership of an ABT from an
/// `address from` to the transaction's `msg.sender`.
/// @dev Throws unless `bytes signature` represents a signature of the
/// EIP-712 structured data hash
/// `Agreement(address active,address passive,bytes metadata)` expressing
/// `address from`'s explicit agreement to be publicly associated with
/// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be
/// generated by type-casting the `bytes32` EIP-712 structured data hash to a
/// `uint256`. If `bytes signature` is empty or `address from` is a contract,
/// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must
/// be made to `address from`. A successful execution must result in the
/// emission of an `event Transfer(from, msg.sender, tokenId)`. Once an ABT
/// exists as an `uint256 tokenId` in the contract, `function take(...)` must
/// throw.
/// @param from The origin of the ABT.
/// @param metadata The metadata that will be associated to the ABT.
/// @param signature A signature of the EIP-712 structured data hash
/// `Agreement(address active,address passive,bytes metadata)` signed by
/// `address from`.
/// @return A unique `uint256 tokenId` generated by type-casting the `bytes32`
/// EIP-712 structured data hash to a `uint256`.
function take(address from, bytes calldata metadata, bytes calldata signature)
external
returns (uint256);
/// @notice Decodes the opaque metadata bytestring of an ABT into the token
/// URI that will be associated with it once it is created on chain.
/// @param metadata The metadata that will be associated to an ABT.
/// @return A URI that represents the metadata.
function decodeURI(bytes calldata metadata) external returns (string memory);
}
```
See [EIP-721](./eip-721.md) for a definition of its metadata JSON Schema.
### [EIP-712](./eip-712.md) Typed Structured Data Hashing and Bytearray Signature Creation
To invoke `function give(...)` and `function take(...)` a bytearray signature must be created using [EIP-712](./eip-712.md). A tested reference implementation in Node.js is attached at [../assets/eip-4973/sdk/src/index.mjs](../assets/eip-4973/sdk/src/index.mjs), [../assets/eip-4973/sdk/test/index_test.mjs](../assets/eip-4973/sdk/test/index_test.mjs) and [../assets/eip-4973/package.json](../assets/eip-4973/package.json). In Solidity, this bytearray signature can be created as follows:
```solidity
bytes32 r = 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90;
bytes32 s = 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064;
uint8 v = 27;
bytes memory signature = abi.encodePacked(r, s, v);
```
## Rationale
### Interface
ABTs shall be maximally backward-compatible but still only expose a minimal and simple to implement interface definition.
As [EIP-721](./eip-721.md) tokens have seen widespread adoption with wallet providers and marketplaces, using its `ERC721Metadata` interface with [EIP-165](./eip-165.md) for feature-detection potentially allows implementers to support ABTs out of the box.
If an implementer of [EIP-721](./eip-721.md) properly built [EIP-165](./eip-165.md)'s `function supportsInterface(bytes4 interfaceID)` function, already by recognizing that [EIP-721](./eip-721.md)'s track and transfer interface component with the identifier `0x80ac58cd` is not implemented, transferring of a token should not be suggested as a user interface option.
Still, since ABTs support [EIP-721](./eip-721.md)'s `ERC721Metadata` extension, wallets and marketplaces should display an account-bound token with no changes needed.
Although other implementations of account-bound tokens are possible, e.g., by having all transfer functions revert, ABTs are superior as it supports feature detection through [EIP-165](./eip-165.md).
We expose `function unequip(address _tokenId)` and require it to be callable at any time by an ABT's owner as it ensures an owner's right to publicly disassociate themselves from what has been issued towards their account.
### Exception handling
Given the non-transferable between accounts property of ABTs, if a user's keys to an account or a contract get compromised or rotated, a user may lose the ability to associate themselves with the token. In some cases, this can be the desired effect. Therefore, ABT implementers should build re-issuance and revocation processes to enable recourse. We recommend implementing strictly decentralized, permissionless, and censorship-resistant re-issuance processes.
But this document is deliberately abstaining from offering a standardized form of exception handling in cases where user keys are compromised or rotated.
In cases where implementers want to make account-bound tokens shareable among different accounts, e.g., to avoid losing access when keys get compromised, we suggest issuing the account-bound token towards a contract's account that implements a multi-signature functionality.
### Provenance Indexing
ABTs can be indexed by tracking the emission of `event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)`. As with [EIP-721](./eip-721.md), transfers between two accounts are represented by `address from` and `address to` being non-zero addresses. Unequipping a token is represented through emitting a transfer with `address to` being set to the zero address. Mint operations where `address from` is set to zero don't exist. To avoid being spoofed by maliciously-implemented `event Transfer` emitting contracts, an indexer should ensure that the transaction's sender is equal to `event Transfer`'s `from` value.
## Backwards Compatibility
We have adopted the [EIP-165](./eip-165.md) and `ERC721Metadata` functions purposefully to create a high degree of backward compatibility with [EIP-721](./eip-721.md). We have deliberately used [EIP-721](./eip-721.md) terminology such as `function ownerOf(...)`, `function balanceOf(...)` to minimize the effort of familiarization for ABT implementers already familiar with, e.g., [EIP-20](./eip-20.md) or [EIP-721](./eip-721.md). For indexers, we've re-used the widely-implemented `event Transfer` event signature.
## Reference Implementation
You can find an implementation of this standard in [../assets/eip-4973](../assets/eip-4973/ERC4973-flat.sol).
## Security Considerations
There are no security considerations related directly to the implementation of this standard.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).