EIP-4973 - Account-bound Tokens

The key nuance of this sentence:

It is not something that needs to be solved with an EIP that removes all ability to use the existing and properly standardized tokens. The ability to standardize the process without significant feature loss is possible.

I don’t understand sorry, mind elaborating?

I noticed that this EIP uses the same Transfer event as EIP-721, and uses the EIP721Metadata interface. I assume this is hoping to be compatible with existing NFT indexers out of the box.

However, this EIP states that the contract must not implement EIP-721, which means that the contract will not advertise EIP-712 compatibility through EIP-165.

This is going to be an issue for some indexers.

The origin of the issue is in the fact that indexing parameters in an event changes the way the event data is encoded but not the topic0 of the event. topic0 is to events what bytes4 function selectors are to functions. The consequence of that is that EIP-20’s Transfer and EIP-721’s Transfer have different data encoding but similar topic0.

So when you listen for Transfer event all over the blockchain, you’ll get EIP-20 and EIP-721 transfers at the same time.

What (some) indexers do is that whenever they see such an event, they try to determine if it comes from an EIP-721 contract. This is done using EIP-165 (and cached). An example of this can be seen in an EIP-721 indexing subgraph.

export function fetchERC721(address: Address): ERC721Contract | null {
	let erc721   = IERC721.bind(address)

	// Try load entry
	let contract = ERC721Contract.load(address)
	if (contract != null) {
		return contract

	// Detect using ERC165
	let detectionId      = address.concat(Bytes.fromHexString('80ac58cd')) // Address + ERC721
	let detectionAccount = Account.load(detectionId)

	// On missing cache
	if (detectionAccount == null) {
		detectionAccount = new Account(detectionId)
		let introspection_01ffc9a7 = supportsInterface(erc721, '01ffc9a7') // ERC165
		let introspection_80ac58cd = supportsInterface(erc721, '80ac58cd') // ERC721
		let introspection_00000000 = supportsInterface(erc721, '00000000', false)
		let isERC721               = introspection_01ffc9a7 && introspection_80ac58cd && introspection_00000000
		detectionAccount.asERC721  = isERC721 ? address : null

	// If an ERC721, build entry
	if (detectionAccount.asERC721) {
		contract                  = new ERC721Contract(address)
		let try_name              = erc721.try_name()
		let try_symbol            = erc721.try_symbol()
		contract.name             = try_name.reverted   ? '' : try_name.value
		contract.symbol           = try_symbol.reverted ? '' : try_symbol.value
		contract.supportsMetadata = supportsInterface(erc721, '5b5e139f') // ERC721Metadata
		contract.asAccount        = address

		let account               = fetchAccount(address)
		account.asERC721          = address

	return contract

This function will return null if the address is not an EIP-721 contract.

By not including that interfaceId, EIP-4973 contracts will be disregarded by this indexing systems (and probably others) which will assume its an EIP-20 contract.

1 Like

Good point, I’ll follow up by implementing EIP-5192 for soulbinding in EIP-4973.

Updates to the specification and reference implementation

  • Removed EIP-2098 support as EIP-1271 expects a “naively” concatenated signature and not an EIP-2098-style compact signature. This was pushed upstream with @frangio removing support for EIP-2098 in the SignatureChecker library.
  • Change string tokenURI in structure data hash and give and take inputs to a more generic bytes metadata. PR: Use opaque metadata by frangio · Pull Request #52 · attestate/ERC4973 · GitHub
  • Start using forge fmt for all Solidity code formatting

PRs involved in this update:


We are experimenting with implement soulbinding via ERC5192 in ERC4973. Here’s the a draft pull request: Create compatibility with IERC721 and IERC5192 by TimDaub · Pull Request #58 · attestate/ERC4973 · GitHub

For ERC4973 users, this is a big interface change. Please give us feedback.
I think the benefits are that it’ll streamline development. ERC4973 defines a generalization of signature-based allow lists. ERC5192 is about lockable NFTs. Composed, they create Soulbound tokens.

1 Like

I like that this EIP is converging using signatures to prove the token acceptance from the 2 sides of the agreement. That something i’ve myself implemented to build the ticketing/credentials protocol Rouge.

But I feel that the EIP is trying to drive to much how this should be implemented and organized. Why not remove give and take from the specs and replace by having just a mandatory event Bound having 1 or two signatures (from issuer and receiver of the token) - of course also metadata & tokenId -. In case the event is emitted by an issuer’s tx, its signature is superfluous and can be null, and respectively if the event is emitted by a receiver tx, its signature can be null.

Wallet should only be able to tell if the token had approval from both sides to be bound, so only one event should be enough.

I also feel that unequip should be removed from the specs and replace by a symmetric event Unbound with the same double approval mechanism above.