Skip to content

Commit

Permalink
Merge a31ff62 into d099627
Browse files Browse the repository at this point in the history
  • Loading branch information
TimDaub authored Aug 1, 2022
2 parents d099627 + a31ff62 commit 31e9f3a
Show file tree
Hide file tree
Showing 2 changed files with 943 additions and 52 deletions.
98 changes: 73 additions & 25 deletions EIPS/eip-4973.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ In the popular MMORPG World of Warcraft, its game designers intentionally took s

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.
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.
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.

Expand All @@ -52,37 +52,85 @@ 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 0x5164cf47.
interface IERC4973 /* is ERC165, ERC721Metadata */ {
/// @dev This emits when a new token is created and bound to an account by
/// any mechanism.
/// Note: For a reliable `from` parameter, retrieve the transaction's
/// authenticated `from` field.
event Attest(address indexed to, uint256 indexed tokenId);
/// @dev This emits when an existing ABT is revoked from an account and
/// destroyed by any mechanism.
/// Note: For a reliable `from` parameter, retrieve the transaction's
/// authenticated `from` field.
event Revoke(address indexed to, uint256 indexed tokenId);
/// Note: the ERC-165 identifier for this interface is 0x5164cf47
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 `owner`, possibly zero
/// @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
/// @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 Destroys `tokenId`. At any time, an ABT receiver must be able to
/// disassociate themselves from an ABT publicly through calling this
/// function.
/// @dev Must emit a `event Revoke` with the `address to` field pointing to
/// @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 burn(uint256 tokenId) external;
/// @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 an EIP-2089 Compact
/// Signature of the EIP-712 structured data hash
/// `Agreement(address active,address passive,string tokenURI)` expressing
/// `address to`'s explicit agreement to be publicly associated with
/// `msg.sender` and `string tokenURI`. A unique `uint256 tokenId` must be
/// generated by type-casting the `bytes32` EIP-712 structured data hash to a
/// `uint256`. A successful execution must result in the emission of an
/// `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 uri A distinct Uniform Resource Identifier (URI) for a given ABT.
/// @param signature A EIP-2089-compatible Compact Signature of the EIP-712
/// structured data hash
/// `Agreement(address active,address passive,string tokenURI)` 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,
string calldata uri,
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 an EIP-2089 Compact
/// Signature of the EIP-712 structured data hash
/// `Agreement(address active,address passive,string tokenURI)` expressing
/// `address from`'s explicit agreement to be publicly associated with
/// `msg.sender` and `string tokenURI`. A unique `uint256 tokenId` must be
/// generated by type-casting the `bytes32` EIP-712 structured data hash to a
/// `uint256`. A successful execution must result in the emission of an
/// `event Transfer(msg.sender, to, tokenId)`. Once an ABT exists as an
/// `uint256 tokenId` in the contract, `function take(...)` must throw.
/// @param from The origin of the ABT.
/// @param uri A distinct Uniform Resource Identifier (URI) for a given ABT.
/// @param signature A EIP-2089-compatible Compact Signature of the EIP-712
/// structured data hash
/// `Agreement(address active,address passive,string tokenURI)` 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,
string calldata uri,
bytes calldata signature
) external returns (uint256);
}
```

Expand Down Expand Up @@ -114,11 +162,11 @@ In cases where implementers want to make account-bound tokens shareable among di

### Provenance Indexing

ABTs can be indexed by tracking the emission of `event Attest` and `event Revoke`. To guarantee reliable and implementation-independent indexable information, neither `event Attest` nor `event Revoke` include a `from` argument to depict the transaction sender. Instead, as a `from` property wouldn't be authenticated and hence opens a security vector, we omit it and advise indexers to substitute it with the transaction-level `from` field which gets authenticated through Ethereum's transaction signature validation prior to inclusion in a block.
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(...)` or `function balanceOf(...)` to minimize the effort of familiarization for `EIP-4973` implementers already familiar with, e.g., [`EIP-20`](./eip-20.md) or [`EIP-721`](./eip-721.md).
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 `EIP-4973` 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

Expand Down
Loading

0 comments on commit 31e9f3a

Please sign in to comment.