EIP-6381: Public Non-Fungible Token Emote Repository

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];
    }
}