// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.18; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "../Utilities/MutableERC2771ContextUpgradeable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../Interfaces/IRulesManager.sol"; import "../Interfaces/ICreditManager.sol"; import "../Interfaces/ISmartAssetBase.sol"; /** * @title MessageHub. * @author Arianee - Dynamic NFTs for real-world use cases and consumer engagement (www.arianee.org). * @notice The MessageHub contract is part of the Arianee protocol. Its permits to send messages to specific tokens according to `RulesManager` conditions. * @dev See: https://docs.arianee.org */ contract MessageHub is OwnableUpgradeable, MutableERC2771ContextUpgradeable, UUPSUpgradeable { /** * @notice Rules manager contract used for event authorization. * Rules are managed by globally by token contract owner and on a per-token basis by token owners. */ IRulesManager public rulesManager; /** * @notice Credit manager contract used for credit consumption. */ ICreditManager public creditManager; /** * @notice Message data. * @param tokenId ID of the token. * @param messageId ID of the message. * @param imprint Imprint of the message. * @param sender Address of the sender. * @param receiver Address of the receiver. * @param isRead A flag indicating if the message has been read. * @param creatorProvider Address of the creator provider. * @param walletProvider Address of the wallet provider. */ struct Message { uint256 tokenId; uint256 messageId; bytes32 imprint; address sender; address receiver; bool isRead; uint256 rewardBatchIndex; } /** * @notice Mapping from token address to message ID <> message data. */ mapping(address => mapping(uint256 => Message)) public messages; /** * @notice Mapping from receiver address to message IDs. */ mapping(address => uint256[]) public receiverToMessageIds; /** * This emits when a message is sent. * @param tokenAddress address of the target token contract. * @param tokenId ID of the token. * @param messageId ID of the message. * @param imprint Imprint of the message. * @param sender Address of the sender. * @param receiver Address of the receiver. */ event MessageSent( address indexed tokenAddress, uint256 indexed tokenId, uint256 messageId, bytes32 imprint, address sender, address indexed receiver ); /** * @notice This emits when a message is read by the receiver. * @param tokenAddress address of the target token contract. * @param messageId ID of the message. * @param receiver Address of the receiver. * @param sender Address of the sender. */ event MessageRead( address indexed tokenAddress, uint256 indexed messageId, address sender, address indexed receiver ); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @notice Initializer of the contract, used instead of constructor to let the proxy pattern works. * @param rulesManagerAddress_ address of the rules manager contract. * @param creditManagerAddress_ address of the credit manager contract. */ function initialize(address rulesManagerAddress_, address creditManagerAddress_, address trustedForwarder_) public initializer { __Ownable_init_unchained(); __MutableERC2771ContextUpgradeable_init_unchained(trustedForwarder_); __UUPSUpgradeable_init(); rulesManager = IRulesManager(rulesManagerAddress_); creditManager = ICreditManager(creditManagerAddress_); } /** * @notice Check if the `sender` is allowed to send a message to the target token. * @dev See {IRulesManager-isAllowedMsgSender}. */ modifier isAllowedMsgSender(address tokenAddress, uint256 tokenId, address sender) { require(rulesManager.isAllowedMsgSender(tokenAddress, tokenId, sender), "MessageHub: Not allowed to send message to this token"); _; } /** * @notice Send a message to a token owner. * @param tokenAddress address of the target token contract. * @param tokenId ID of the target token. * @param messageId ID of the message. * @param imprint Imprint of the message. * @param creatorProvider address of the creator provider. */ function sendMessage( address tokenAddress, uint256 tokenId, uint256 messageId, bytes32 imprint, address creatorProvider ) external isAllowedMsgSender(tokenAddress, tokenId, _msgSender()) { address tokenOwner = IERC721(tokenAddress).ownerOf(tokenId); require(messages[tokenAddress][messageId].sender == address(0), "MessageHub: This messageId already exists"); uint256 _batchIndex = creditManager.consumeCredit(_msgSender(), creatorProvider); Message memory message = Message({ tokenId: tokenId, messageId: messageId, imprint: imprint, sender: _msgSender(), receiver: tokenOwner, isRead: false, rewardBatchIndex: _batchIndex }); messages[tokenAddress][messageId] = message; receiverToMessageIds[tokenOwner].push(messageId); emit MessageSent(tokenAddress, tokenId, messageId, imprint, _msgSender(), tokenOwner); // TODO: RulesManager } /** * @notice Mark a message as read. * NOTE: This function can only be called by the receiver of the target message. * @param tokenAddress address of the target token contract. * @param messageId ID of the message. */ function markMessageAsRead(address tokenAddress, uint256 messageId, address walletProvider) public { Message storage message = messages[tokenAddress][messageId]; require(message.sender != address(0), "MessageHub: This message does not exist"); require(_msgSender() == message.receiver, "MessageHub: Only the receiver can mark the message as read"); require(message.isRead == false, "MessageHub: This message was already read"); message.isRead = true; creditManager.rewardClaim( message.rewardBatchIndex, walletProvider, message.receiver ); emit MessageRead(tokenAddress, messageId, message.receiver, message.sender); } /** * @dev See {UUPSUpgradeable-_authorizeUpgrade}. */ function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} /** * @dev See {MutableERC2771ContextUpgradeable-_msgSender}. */ function _msgSender() internal view override(MutableERC2771ContextUpgradeable, ContextUpgradeable) returns (address sender) { return MutableERC2771ContextUpgradeable._msgSender(); } /** * @dev See {MutableERC2771ContextUpgradeable-_msgData}. */ function _msgData() internal view override(MutableERC2771ContextUpgradeable, ContextUpgradeable) returns (bytes calldata ret) { return MutableERC2771ContextUpgradeable._msgData(); } }