EIP-6381: Public Non-Fungible Token Emote Repository

Apologies if I’ve mentioned this before, but what if you track reactions as ERC-5114 badges? Note that these soulbound badges aren’t removable, which would be different from your current proposal.

That’s just too different honestly. Besides not being removable, you may have thousands of reactions using the same emoji on a token, which can be tracked by a simple counter in this proposal. With the badges approach it would be too expensive to do this.

What if I suggest allowing burning badges to @MicahZoltu? It’s a small amount of mutability, in exchange for use cases like these.

If the emoji are tracked as a simple counter, how do you control who can remove one? Couldn’t anyone remove the reaction I added?

We use two simple mappings to track the emotes given by the address and the number of emotes the token has received: EIPs/assets/eip-6381/contracts/Emotable.sol at 2bdcdb61baf8ba6210096c06f0e265d8622d4d7f · ethereum/EIPs · GitHub

This ensures that the emoter can emote and undo emote and that the change is reflected on the token.

That seems roughly equivalent to what you’d need to do to keep track of badges. You could use keccak256(abi.encode(msg.sender, tokenId, emoji)) as the badge’s tokenId.

Here’s an untested implementation:

// SPDX-License-Identifier: CC0-1.0

pragma solidity ^0.8.17;

interface IERC5114 {
	event Mint(uint256 indexed badgeId, address indexed nftAddress, uint256 indexed nftTokenId);

	function ownerOf(uint256 badgeId) external view returns (address nftAddress, uint256 nftTokenId);

	function collectionUri() external pure returns (string memory collectionUri);

	function badgeUri(uint256 badgeId) external view returns (string memory badgeUri);

	function metadataFormat() external pure returns (string memory format);
}

contract Reactions is IERC5114 {
    event Reacted(
        address indexed emoter,
        address indexed nftAddress,
        uint256 indexed nftTokenId,
        bytes4 emoji,
        bool enabled
    );

    struct Reaction {
        address operator;
        address nftAddress;
        uint256 nftTokenId;
        bytes4 codepoint;
        bool enabled;
    }

    mapping (uint256 => Reaction) private _reactions;

    //      (nftAddress =>         (nftTokenId =>        (codepoint => count  )))
    mapping (address    => mapping (uint256    => mapping(bytes4    => uint256))) private _counts;

    /*
     * IERC5114 Implementation
     */
    string constant public metadataFormat = "TODO";
    string constant public collectionUri = "TODO";

    function ownerOf(uint256 badgeId) external view returns (address nftAddress, uint256 nftTokenId) {
        Reaction storage reaction = _reactions[badgeId];
        require(address(0) != reaction.operator);
        return (reaction.nftAddress, reaction.nftTokenId);
    }

    function badgeUri(uint256 badgeId) external view returns (string memory) {
        require(address(0) != _reactions[badgeId].operator);
        return "TODO";
    }

    /*
     * IToggleable Implementation
     */
    function isEnabled(uint256 badgeId) external view returns (bool) {
        Reaction storage reaction = _reactions[badgeId];
        require(address(0) != reaction.operator);
        return reaction.enabled;
    }

    /*
     * Reactions Implementation
     */

    function id(
        address operator,
        address nftAddress,
        uint256 nftTokenId,
        bytes4 codepoint
    ) public pure returns (uint256) {
        return uint256(keccak256(abi.encode(operator, nftAddress, nftTokenId, codepoint)));
    }

    function react(
        address nftAddress,
        uint256 nftTokenId,
        bytes4 codepoint,
        bool enabled
    ) external returns (uint256) {
        uint256 badgeId = id(msg.sender, nftAddress, nftTokenId, codepoint);
        Reaction storage reaction = _reactions[badgeId];

        if (reaction.enabled == enabled) {
            return badgeId;
        }

        if (address(0) == reaction.operator) {
            reaction.operator = msg.sender;
            reaction.nftAddress = nftAddress;
            reaction.nftTokenId = nftTokenId;
            reaction.codepoint = codepoint;

            emit Mint(badgeId, nftAddress, nftTokenId);
        }

        if (enabled) {
            _counts[nftAddress][nftTokenId][codepoint] += 1;
        } else {
            _counts[nftAddress][nftTokenId][codepoint] -= 1;
        }

        reaction.enabled = enabled;
        emit Reacted(
            msg.sender,
            nftAddress,
            nftTokenId,
            codepoint,
            enabled
        );

        return badgeId;
    }

    function reactionCountOf(
        address nftAddress,
        uint256 nftTokenId,
        bytes4 codepoint
    ) public view returns (uint256) {
        return _counts[nftAddress][nftTokenId][codepoint];
    }
}

Any mutability (including burning) makes it so you can not aggressively cache badges, which was a design goal of 5114. That being said, one could create another EIP for mutable badges where the owner can burn them. This EIP could share essentially the same interface as 5114, so anyone supporting one would likely support the other as well. I think there is value in calling those two types of badges different things however, which is why I think they should be different EIPs (with different numbers and titles).

There are so many differences:

  1. They are meant to be non removable, confirmed by the author.
  2. Badges are sent to any contract, which can be a nice feature, but our proposal is meant to be an extension, so badges are tracked within the contract itself.
  3. We don’t need collectionUri nor metadataFormat. For badgeUri we could force it to return the id of the emoji I guess, but it feels really hacky IMO.
  4. There’s no way to get the count of reactions for a specific emoji on a token. It is reasonable in general since an indexer could take care of this. But it is important on our EIP since we intend implementations to vary the behavior based on this. e.g:
  • An NFT of a plant returns a dying plant image unless it has a certain number of :cloud_with_rain: reactions. This plays really well with ERC-5773.
  • An NFT of a person returns a blushed version after it gets 100 :heart: emojis. Goes also well with 5773.
  • An NFT can be transferable only after receiving 10 :unlock: reactions, playing nice with ERC-6454

Finally, when building extensions of ERC721, we’ve found contract size to be a huge limitation. You hit the 24kb limit easily when you stack many together. So we try to keep them as minimal as possible. I understand your purpose with this and it’s really valuable, but in this case forcing it into use a somehow related one would be harmful for us.

Very true. The example I posted uses non-removable badges. I’d be very open to making a burnable badge standard if necessary.

What benefits come with putting the reactions in the same contract?

Assuming someone made a generic badge viewing dapp, or wallets build in native support for badges, then the reactions/emoji badges would just show up (and collectionUri could have a nice description of what a reaction is for.) metadataFormat is just an implementation detail.

See reactionCountOf from my earlier example.

Ha, I love it! That’ll be quite fun. I think you could still implement this by querying the badge contract in your tokenURI?

Isn’t this a great argument against your point 2? If the reactions/emoji live in their own contract, there’s no code size increase to the ERC-721 contract.

It seems like such a close fit to me, but it’s totally fine if you don’t agree!

Thanks again, I really enjoy this discussions :grin:

What benefits come with putting the reactions in the same contract?

I don’t have to keep track of an external contract where emojis would be stored. My main problem with tracking them in an external contract is that there could actually be many! Each marketplace might have their own for instance, so I’d have to either choose or aggregate, which feels less usable.

Assuming someone made a generic badge viewing dapp, or wallets build in native support for badges, then the reactions/emoji badges would just show up (and collectionUri could have a nice description of what a reaction is for.) metadataFormat is just an implementation detail.

It still feels forced and extra unneeded effort IMO. Emojis are powerful, widely used and can be achieved with as little as what we’re proposing.

See reactionCountOf from my earlier example.

The thing is, that’s not part of the 5114’s interface, and we do need it to be there if we want to use it as intended. It would be a show stopper.

Isn’t this a great argument against your point 2? If the reactions/emoji live in their own contract, there’s no code size increase to the ERC-721 contract.

It is :sweat_smile: I realized as I was writing it. But weighing pros and cons we find local storage better for this use case, mostly for the reason pointed in the answer about benefits.

That’s a great point :thinking: What if the standard includes the deployment transaction so there can be exactly one emoji contract at a well-known address?

For sure! I’m more suggesting we define IEmotable as:

interface IEmotable is IERC5114 { ... }

Or in other words, add whatever extra methods/events you need on top of IERC5114.

I would use something like GitHub - Zoltu/deterministic-deployment-proxy: An Ethereum proxy contract that can be used for deploying contracts to a deterministic address on any chain. personally, which is a simplification around keyless execution. I agree with this general idea though. Have a single “reaction registry” (this would be standardized) that allows anyone to react to any token (5114 or 721 or other, anything with the form (address, id)). This means you don’t need to build tokens with support for reactions built-in, it can be added on to any existing token. For example, people could react to CryptoKitties or Bored Apes once the standard is finished.

We will review your suggestions @SamWilsn and @MicahZoltu, they look promising.
Thanks!

We decided to switch for the recommended approach of a single contract with a predictable address across all chains. :partying_face: but not reusing 5114. We’ll need a few days to adapt, will keep you posted.
Thanks!

1 Like

Thank you once again for the constructive comments @SamWilsn and @MicahZoltu!

We have now published the updated version of the EIP that aims to standardise a Public Non-Fungible Token Emote Repository that is deployed to a pre-determined address (while the updated proposal is already published, we are still calculating the desired address, so I’ll post it here as soon as we have it along with the list of test networks we deployed the repository to).

1 Like

Couple further questions:

  • It’ll be expensive to emote on mainnet. Is there any way we can emote on a cheaper chain for an NFT on mainnet? Is that even worthwhile?
  • How does this handle emoji built from several codepoints, like :+1:t6:? That one is at least two pieces (:+1: and 🏿.)

I think it’s looking great so far!

2 Likes

It isn’t that expensive (and any reaction that is not the first reaction with a given emoji costs less than the first one), but we designed the repository in a way that it can be deployed to any EVM compatible chain at the same address.

I just ran the gas profiler and these are the results:

·------------------------------------------------------------------------------------|---------------------------|-------------|-----------------------------·
|                                Solc version: 0.8.18                                ·  Optimizer enabled: true  ·  Runs: 200  ·  Block limit: 30000000 gas  │
·····················································································|···························|·············|······························
|  Methods                                                                           ·               50 gwei/gas               ·       1865.56 usd/eth       │
················································|····································|·············|·············|·············|···············|··············
|  Contract                                     ·  Method                            ·  Min        ·  Max        ·  Avg        ·  # calls      ·  usd (avg)  │
················································|····································|·············|·············|·············|···············|··············
|  RMRKEmoteTrackerMock                         ·  emote                             ·      24874  ·      70211  ·      57994  ·           17  ·       5.41  │
················································|····································|·············|·············|·············|···············|··············

It doesn’t intend to. There could be an extension to support it, but currently only the base emojis are supported. We see this as beneficial if you are using the emotes to drive the evolution of the token. Having to only process one emoji (thumbs up) and not 5 emojis (variations of thumbs up) maintains the simplicity of using the repository.

1 Like

We are happy to announce that the initial implementation of the Emote Repository is now live on Görli, Sepolia, Mumbai and MoonbaseAlpha test networks @ 0x311073569e12F7770719497CD3B3Aa2dB0a0C3D9.

2 Likes

Hashcash for emotes. They actually mean something if someone had to pay a little bit for them. If they are free, they are meaningless.

2 Likes

Hi there, just to clarify this EIP is meant to be an extension to ERC721 and ERC1155?

because the title in ERC-6381: Public Non-Fungible Token Emote Repository is different from the one here " Emotable Extension for Non-Fungible Tokens"

1 Like