diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..85f5562 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +node_modules +artifacts +cache +coverage diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..209d55a --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + env: { + browser: false, + es2021: true, + mocha: true, + node: true, + }, + extends: ['standard', 'plugin:prettier/recommended', 'plugin:node/recommended'], + parserOptions: { + ecmaVersion: 12, + }, + overrides: [ + { + files: ['hardhat.config.js'], + globals: { task: true }, + }, + { + files: ['scripts/**'], + rules: { 'no-process-exit': 'off' }, + }, + { + files: ['hardhat.config.js', 'scripts/**', 'test/**'], + rules: { 'node/no-unpublished-require': 'off' }, + }, + ], +}; diff --git a/contracts/PSUser.sol b/contracts/PSUser.sol new file mode 100644 index 0000000..4b9f29d --- /dev/null +++ b/contracts/PSUser.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; +pragma experimental ABIEncoderV2; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import "point-contract-manager/contracts/IIdentity.sol"; + +contract PSUser is Initializable, UUPSUpgradeable, OwnableUpgradeable { + + using EnumerableSet for EnumerableSet.AddressSet; + + address private _identityContractAddr; + string private _identityHandle; + + struct Connections { + EnumerableSet.AddressSet following; + EnumerableSet.AddressSet followers; + EnumerableSet.AddressSet blocked; + } + + mapping(address => Connections) private _connectionsByUser; + + modifier onlyMutuals (address _user) { + require(isFollowing(msg.sender, _user), "ERROR_NOT_MUTUAL"); + require(isFollowing(_user, msg.sender), "ERROR_NOT_MUTUAL"); + _; + } + + modifier onlyFollowers (address _user) { + require((msg.sender == _user) || isFollowing(msg.sender, _user), "ERROR_NOT_FOLLOWING"); + _; + } + + modifier notBlocked (address _user) { + require(!isBlocked(msg.sender, _user), "ERROR_USER_BLOCKED"); + require(!isBlocked(_user, msg.sender), "ERROR_USER_BLOCKED"); + _; + } + + modifier onlyDeployer { + require( + IIdentity(_identityContractAddr).isIdentityDeployer( + _identityHandle, + msg.sender + ), + "ERROR_NOT_DEPLOYER" + ); + _; + } + + enum FollowAction { + Follow, + UnFollow, + Block, + UnBlock + } + + event FollowEvent(address indexed from, address indexed to, FollowAction action); + + function initialize( + address identityContractAddr, + string calldata identityHandle + ) public initializer onlyProxy { + __Ownable_init(); + __UUPSUpgradeable_init(); + _identityContractAddr = identityContractAddr; + _identityHandle = identityHandle; + } + + function _authorizeUpgrade(address) internal onlyDeployer view override { + } + + function followUser(address _user) public notBlocked(_user) returns (bool) { + bool result = + EnumerableSet.add(_connectionsByUser[msg.sender].following, _user) + && + EnumerableSet.add(_connectionsByUser[_user].followers, msg.sender); + + emit FollowEvent(msg.sender, _user, FollowAction.Follow); + return result; + } + + function unfollowUser(address _user) public returns (bool) { + bool result = + EnumerableSet.remove(_connectionsByUser[msg.sender].following, _user) + && + EnumerableSet.remove(_connectionsByUser[_user].followers, msg.sender); + emit FollowEvent(msg.sender, _user, FollowAction.UnFollow); + return result; + } + + function isFollowing(address _owner, address _user) public view returns (bool) { + return EnumerableSet.contains(_connectionsByUser[_owner].following, _user); + } + + function followingList(address _user) public onlyFollowers(_user) view returns (address[] memory) { + return EnumerableSet.values(_connectionsByUser[_user].following); + } + + function followingCount(address _user) public view returns (uint256) { + return EnumerableSet.length(_connectionsByUser[_user].following); + } + + function followersList(address _user) public onlyFollowers(_user) view returns (address[] memory) { + return EnumerableSet.values(_connectionsByUser[_user].followers); + } + + function followersCount(address _user) public view returns (uint256) { + return EnumerableSet.length(_connectionsByUser[_user].followers); + } + + function blockUser(address _user) public returns (bool) { + EnumerableSet.remove(_connectionsByUser[msg.sender].following, _user); + EnumerableSet.remove(_connectionsByUser[msg.sender].followers, _user); + EnumerableSet.remove(_connectionsByUser[_user].following, msg.sender); + EnumerableSet.remove(_connectionsByUser[_user].followers, msg.sender); + bool result = + EnumerableSet.add(_connectionsByUser[msg.sender].blocked, _user); + emit FollowEvent(msg.sender, _user, FollowAction.Block); + return result; + } + + function unBlockUser(address _user) public returns (bool) { + bool result = + EnumerableSet.remove(_connectionsByUser[msg.sender].blocked, _user); + emit FollowEvent(msg.sender, _user, FollowAction.UnBlock); + return result; + } + + function isBlocked(address _owner, address _user) public view returns (bool) { + return EnumerableSet.contains(_connectionsByUser[_owner].blocked, _user); + } + + function blockList() public view returns (address[] memory) { + return EnumerableSet.values(_connectionsByUser[msg.sender].blocked); + } + +} \ No newline at end of file diff --git a/contracts/PointSocial.sol b/contracts/PointSocial.sol index 21cba9b..daa947a 100644 --- a/contracts/PointSocial.sol +++ b/contracts/PointSocial.sol @@ -3,14 +3,18 @@ pragma solidity >=0.8.0; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts/utils/Counters.sol"; +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "point-contract-manager/contracts/IIdentity.sol"; -contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { +contract SocialStorage { + using Counters for Counters.Counter; + Counters.Counter internal _postIds; Counters.Counter internal _commentIds; Counters.Counter internal _likeIds; @@ -64,8 +68,18 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { event ProfileChange(address indexed from, uint256 indexed date); - address private _identityContractAddr; - string private _identityHandle; + event MultipliersChanged( + address indexed from, + uint256 timestamp, + uint256 likesWeightMultiplier, + uint256 dislikesWeightWultiplier, + uint256 ageWeightMultiplier, + uint256 initialWeight, + uint256 followWeight + ); + + address internal _identityContractAddr; + string internal _identityHandle; // posts uint256[] public postIds; @@ -81,7 +95,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { mapping(uint256 => uint256[]) public likeIdsByPost; mapping(uint256 => Like) public likeById; - address private _migrator; + address internal _migrator; mapping(address => Profile) public profileByOwner; mapping(uint256 => bool) public postIsFlagged; @@ -107,8 +121,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { Counters.Counter internal _dislikeIds; mapping(uint256 => uint256[]) public dislikeIdsByPost; mapping(address => uint256[]) public dislikeIdsByUser; - mapping(address => mapping(uint256 => uint256)) - public dislikeIdByUserAndPost; + mapping(address => mapping(uint256 => uint256)) public dislikeIdByUserAndPost; mapping(uint256 => Dislike) public dislikeById; struct PostWithMetadata { @@ -122,34 +135,81 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 dislikesCount; bool liked; bool disliked; + int256 weight; + bool flagged; + } + + uint256 public likesWeightMultiplier; + uint256 public dislikesWeightWultiplier; + uint256 public ageWeightMultiplier; + uint256 public weightThreshold; + uint256 public initialWeight; + uint256 public followWeight; + + //mapping(bytes32 => address) internal _contractExtensions; + /* + * Follow layout storage + */ + using EnumerableSet for EnumerableSet.AddressSet; + + struct FollowConnections { + EnumerableSet.AddressSet following; + EnumerableSet.AddressSet followers; + EnumerableSet.AddressSet blocked; } + mapping(address => FollowConnections) internal _followConnectionsByUser; + + enum FollowAction { + Follow, + UnFollow, + Block, + UnBlock + } + + event FollowEvent( + address indexed from, + address indexed to, + FollowAction action); + +} + +contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable, SocialStorage { + using Counters for Counters.Counter; + modifier postExists(uint256 _postId) { - require(postById[_postId].from != address(0), "Post does not exist"); + require(postById[_postId].from != address(0), "ERROR_POST_DOES_NOT_EXISTS"); + _; + } + + modifier onlyDeployer() { + require(IIdentity(_identityContractAddr).isIdentityDeployer(_identityHandle, msg.sender), + "ERROR_NOT_DEPLOYER" + ); + _; + } + + modifier isContract(address _contract) { + uint size; + assembly { size := extcodesize(_contract) } + require(size > 0, "ERROR_NO_CONTRACT"); _; } function initialize( address identityContractAddr, string calldata identityHandle - ) public initializer onlyProxy { + ) external initializer onlyProxy { __Ownable_init(); __UUPSUpgradeable_init(); _identityContractAddr = identityContractAddr; _identityHandle = identityHandle; + // _contractExtensions["PSFollow"] = address(new PSFollow()); } - function _authorizeUpgrade(address) internal view override { - require( - IIdentity(_identityContractAddr).isIdentityDeployer( - _identityHandle, - msg.sender - ), - "You are not a deployer of this identity" - ); - } + function _authorizeUpgrade(address) internal view override onlyDeployer {} - function addMigrator(address migrator) public onlyOwner { + function addMigrator(address migrator) external onlyOwner { require(_migrator == address(0), "Access Denied"); _migrator = migrator; emit StateChange( @@ -161,12 +221,42 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { ); } - function isDeployer() public view returns (bool) { - return IIdentity(_identityContractAddr).isIdentityDeployer(_identityHandle, msg.sender); + function isDeployer() external view returns (bool) { + return + IIdentity(_identityContractAddr).isIdentityDeployer( + _identityHandle, + msg.sender + ); + } + + function setWeights( + uint256 _likesWeightMultiplier, + uint256 _dislikesWeightWultiplier, + uint256 _ageWeightMultiplier, + uint256 _weightThreshold, + uint256 _initialWeight, + uint256 _followWeight + ) external onlyDeployer { + likesWeightMultiplier = _likesWeightMultiplier; + dislikesWeightWultiplier = _dislikesWeightWultiplier; + ageWeightMultiplier = _ageWeightMultiplier; + weightThreshold = _weightThreshold; + initialWeight = _initialWeight; + followWeight = _followWeight; + + emit MultipliersChanged( + msg.sender, + block.timestamp, + likesWeightMultiplier, + dislikesWeightWultiplier, + ageWeightMultiplier, + initialWeight, + followWeight + ); } // Post data functions - function addPost(bytes32 contents, bytes32 image) public { + function addPost(bytes32 contents, bytes32 image) external { _postIds.increment(); uint256 newPostId = _postIds.current(); Post memory _post = Post( @@ -195,8 +285,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { uint256 postId, bytes32 contents, bytes32 image - ) public { - require(postById[postId].createdAt != 0, "ERROR_POST_DOES_NOT_EXISTS"); + ) external postExists(postId) { require( msg.sender == postById[postId].from, "ERROR_CANNOT_EDIT_OTHERS_POSTS" @@ -214,8 +303,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { ); } - function deletePost(uint256 postId) public { - require(postById[postId].createdAt != 0, "ERROR_POST_DOES_NOT_EXISTS"); + function deletePost(uint256 postId) external postExists(postId) { require( msg.sender == postById[postId].from, "ERROR_CANNOT_DELETE_OTHERS_POSTS" @@ -236,25 +324,118 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { ); } - function flagPost(uint256 postId) public { - require(IIdentity(_identityContractAddr).isIdentityDeployer(_identityHandle, msg.sender), - "ERROR_PERMISSION_DENIED"); - require(postById[postId].createdAt != 0, "ERROR_POST_DOES_NOT_EXISTS"); + function _inArray(uint256 _number, uint256[] memory _array) + internal + pure + returns (bool) + { + uint256 length = _array.length; + for (uint256 i = 0; i < length; ) { + if (_array[i] == _number) { + return true; + } + unchecked { + i++; + } + } + return false; + } + function flagPost(uint256 postId) external postExists(postId) onlyDeployer { postIsFlagged[postId] = !postIsFlagged[postId]; - - emit StateChange(postId, msg.sender, block.timestamp, Component.Post, Action.Flag); + emit StateChange( + postId, + msg.sender, + block.timestamp, + Component.Post, + Action.Flag + ); } - function getAllPosts() public view returns (PostWithMetadata[] memory) { - PostWithMetadata[] memory postsWithMetadata = new PostWithMetadata[](postIds.length); + function getAllPosts() external view returns (PostWithMetadata[] memory) { + PostWithMetadata[] memory postsWithMetadata = new PostWithMetadata[]( + postIds.length + ); for (uint256 i = 0; i < postIds.length; i++) { postsWithMetadata[i] = _getPostWithMetadata(postIds[i]); } return postsWithMetadata; } - function getAllPostsLength() public view returns (uint256) { + /** + * @notice Validate that a post must be shown or not + * @dev Validate that a post must be shown or not + * @param _post - Post to be validated with metadata + * @param _postIdsToFilter - Already seen post ids + * @param _newerThanTimestamp - newest post seen timestamp + */ + function _validPostToBeShown( + PostWithMetadata memory _post, + uint256[] memory _postIdsToFilter, + uint256 _newerThanTimestamp + ) public view returns (bool) { + // Conditions: + // 1. CreatedAt must be different than 0 + // 2. Weight must be equal or higher than weightThreshold (if set) + // 3. Post must not have been seen before (not include on post ids array) + // 4. Must be newer than timestamp (if set) + uint256 ageWeight = (block.timestamp - _post.createdAt) * + ageWeightMultiplier; + + return + _post.createdAt != 0 && + (weightThreshold == 0 || + (_post.weight + int256(ageWeight)) >= + int256(weightThreshold)) && + !_inArray(_post.id, _postIdsToFilter) && + (_newerThanTimestamp == 0 || + _post.createdAt >= _newerThanTimestamp); + } + + function _filterPosts( + uint256[] memory _ids, + uint256[] memory _idsToFilter, + uint256 _maxQty, + uint256 _newerThanTimestamp + ) internal view returns (PostWithMetadata[] memory) { + uint256 length = _ids.length; + + PostWithMetadata[] memory _filteredArray = new PostWithMetadata[]( + _maxQty + ); + + uint256 insertedLength = 0; + for (uint256 i = length; i > 0; i--) { + if (insertedLength >= _maxQty) { + break; + } + + PostWithMetadata memory _post = _getPostWithMetadata(_ids[i - 1]); + bool _blocked = isBlocked(msg.sender, _post.from) || isBlocked(_post.from, msg.sender); + + if (!_blocked && _validPostToBeShown(_post, _idsToFilter, _newerThanTimestamp)) { + _filteredArray[insertedLength] = _post; + unchecked { + insertedLength++; + } + } + } + + PostWithMetadata[] memory _toReturnArray = new PostWithMetadata[]( + insertedLength + ); + + for (uint256 j = 0; j < insertedLength; ) { + _toReturnArray[j] = _filteredArray[j]; + unchecked { + j++; + } + } + + return _toReturnArray; + } + + function getAllPostsLength() external view returns (uint256) { uint256 length = 0; for (uint256 i = 0; i < postIds.length; i++) { if (postById[postIds[i]].createdAt > 0) { @@ -264,37 +445,43 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { return length; } - function getPaginatedPosts(uint256 cursor, uint256 howMany) - public - view - returns (PostWithMetadata[] memory) - { - uint256 length = howMany; - if (length > postIds.length - cursor) { - length = postIds.length - cursor; - } + function getPaginatedPosts( + uint256 howMany, + uint256[] memory _viewedPostsIds + ) external view returns (PostWithMetadata[] memory) { + return _filterPosts(postIds, _viewedPostsIds, howMany, 0); + } - PostWithMetadata[] memory postsWithMetadata = new PostWithMetadata[](length); - for (uint256 i = length; i > 0; i--) { - postsWithMetadata[length - i] = _getPostWithMetadata(postIds[postIds.length - cursor - i]); - } - return postsWithMetadata; + function getNewPosts( + uint256 _qty, + uint256[] memory _viewedPostsIds, + uint256 _newerThanTimestamp + ) external view returns (PostWithMetadata[] memory) { + return + _filterPosts(postIds, _viewedPostsIds, _qty, _newerThanTimestamp); } - function getAllPostsByOwner(address owner) - public + function getAllPostsByOwner(address owner, uint256[] memory _viewedPostsIds) + external view returns (PostWithMetadata[] memory) { - PostWithMetadata[] memory postsWithMetadata = new PostWithMetadata[](postIdsByOwner[owner].length); - for (uint256 i = 0; i < postIdsByOwner[owner].length; i++) { - postsWithMetadata[i] = _getPostWithMetadata(postIdsByOwner[owner][i]); + if (isBlocked(msg.sender, owner) || isBlocked(owner, msg.sender)) { + return new PostWithMetadata[](0); + } + else { + return + _filterPosts( + postIdsByOwner[owner], + _viewedPostsIds, + postIdsByOwner[owner].length, + 0 + ); } - return postsWithMetadata; } function getAllPostsByOwnerLength(address owner) - public + external view returns (uint256) { @@ -307,8 +494,23 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { return length; } - function _getPostWithMetadata(uint256 _postId) internal view returns (PostWithMetadata memory) { + function _getPostWithMetadata(uint256 _postId) + internal + view + returns (PostWithMetadata memory) + { Post memory post = postById[_postId]; + uint256 dislikesCount = getPostDislikesQty(post.id); + uint256 likesWeight = post.likesCount * likesWeightMultiplier; + uint256 weightPunishment = (dislikesCount * dislikesWeightWultiplier) + + (block.timestamp - post.createdAt) * + ageWeightMultiplier; + uint256 follow = isFollowing(msg.sender, post.from)? followWeight : 0; + + int256 weight = int256(initialWeight) + + int256(likesWeight) - + int256(weightPunishment) + int256(follow); + PostWithMetadata memory postWithMetadata = PostWithMetadata( post.id, post.from, @@ -317,9 +519,11 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { post.createdAt, post.likesCount, post.commentsCount, - getPostDislikesQty(post.id), + dislikesCount, checkLikeToPost(post.id), - checkDislikeToPost(_postId) + checkDislikeToPost(_postId), + weight, + postIsFlagged[_postId] ); return postWithMetadata; } @@ -336,29 +540,21 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { function getPaginatedPostsByOwner( address owner, - uint256 cursor, - uint256 howMany - ) public view returns (PostWithMetadata[] memory) { - uint256 _ownerPostLength = postIdsByOwner[owner].length; - - uint256 length = howMany; - if (length > _ownerPostLength - cursor) { - length = _ownerPostLength - cursor; - } - - PostWithMetadata[] memory postsWithMetadata = new PostWithMetadata[](length); - for (uint256 i = length; i > 0; i--) { - postsWithMetadata[length - i] = _getPostWithMetadata(postIdsByOwner[owner][_ownerPostLength - cursor - i]); - } - return postsWithMetadata; + uint256 howMany, + uint256[] memory _viewedPostsIds + ) external view returns (PostWithMetadata[] memory) { + return _filterPosts(postIdsByOwner[owner], _viewedPostsIds, howMany, 0); } - function getPostById(uint256 id) public view returns (PostWithMetadata memory) { + function getPostById(uint256 id) + external + view + returns (PostWithMetadata memory) + { return _getPostWithMetadata(id); } - // Example: 1,"0x0000000000000000000000000000000000000000000068692066726f6d20706e" - function addCommentToPost(uint256 postId, bytes32 contents) public { + function addCommentToPost(uint256 postId, bytes32 contents) external { _commentIds.increment(); uint256 newCommentId = _commentIds.current(); Comment memory _comment = Comment( @@ -381,7 +577,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { ); } - function editCommentForPost(uint256 commentId, bytes32 contents) public { + function editCommentForPost(uint256 commentId, bytes32 contents) external { Comment storage comment = commentById[commentId]; require(comment.createdAt != 0, "ERROR_POST_DOES_NOT_EXISTS"); @@ -400,7 +596,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { ); } - function deleteCommentForPost(uint256 postId, uint256 commentId) public { + function deleteCommentForPost(uint256 postId, uint256 commentId) external { require( commentById[commentId].createdAt != 0, "ERROR_COMMENT_DOES_NOT_EXISTS" @@ -430,7 +626,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { } function getAllCommentsForPost(uint256 postId) - public + external view returns (Comment[] memory) { @@ -443,7 +639,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { return _comments; } - function getCommentById(uint256 id) public view returns (Comment memory) { + function getCommentById(uint256 id) external view returns (Comment memory) { return commentById[id]; } @@ -572,8 +768,8 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { } function _removeDislikeFromPost(uint256 _postId) internal { - uint256 dislikeId = dislikeIdByUserAndPost[msg.sender][_postId]; - dislikeById[dislikeId].active = false; + uint256 dislikeId = dislikeIdByUserAndPost[msg.sender][_postId]; + dislikeById[dislikeId].active = false; } /** @@ -582,11 +778,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { * @param _postId - The post id * @return Post's dislikes qty */ - function getPostDislikesQty(uint256 _postId) - public - view - returns (uint256) - { + function getPostDislikesQty(uint256 _postId) public view returns (uint256) { Dislike[] memory postDislikes = _getPostDislikes(_postId); return postDislikes.length; } @@ -636,13 +828,17 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { return false; } + /********************************************************************** + * User Profile Functions + ***********************************************************************/ + function setProfile( bytes32 name_, bytes32 location_, bytes32 about_, bytes32 avatar_, bytes32 banner_ - ) public { + ) external { profileByOwner[msg.sender].displayName = name_; profileByOwner[msg.sender].displayLocation = location_; profileByOwner[msg.sender].displayAbout = about_; @@ -655,7 +851,9 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { return profileByOwner[id_]; } - // Data Migrator Functions - only callable by _migrator + /********************************************************************** + * Data Migrator Functions - only callable by _migrator + ***********************************************************************/ function add( uint256 id, @@ -664,7 +862,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { bytes32 image, uint16 likesCount, uint256 createdAt - ) public { + ) external { require(msg.sender == _migrator, "Access Denied"); Post memory _post = Post({ @@ -697,7 +895,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { address author, bytes32 contents, uint256 createdAt - ) public { + ) external { require(msg.sender == _migrator, "Access Denied"); Comment memory _comment = Comment({ @@ -729,7 +927,7 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { bytes32 about, bytes32 avatar, bytes32 banner - ) public { + ) external { require(msg.sender == _migrator, "Access Denied"); profileByOwner[user].displayName = name; @@ -740,4 +938,81 @@ contract PointSocial is Initializable, UUPSUpgradeable, OwnableUpgradeable { emit ProfileChange(user, block.timestamp); } + + /********************************************************************** + * Follow functions + ***********************************************************************/ + + modifier onlyMutuals (address _user) { + require(isFollowing(msg.sender, _user), "ERROR_NOT_MUTUAL"); + require(isFollowing(_user, msg.sender), "ERROR_NOT_MUTUAL"); + _; + } + + modifier onlyFollowers (address _user) { + require((msg.sender == _user) || isFollowing(msg.sender, _user), "ERROR_NOT_FOLLOWING"); + _; + } + + modifier notBlocked (address _user) { + require(!isBlocked(msg.sender, _user), "ERROR_USER_BLOCKED"); + require(!isBlocked(_user, msg.sender), "ERROR_USER_BLOCKED"); + _; + } + + function followUser(address _user) public notBlocked(_user) { + EnumerableSet.add(_followConnectionsByUser[msg.sender].following, _user); + EnumerableSet.add(_followConnectionsByUser[_user].followers, msg.sender); + emit FollowEvent(msg.sender, _user, FollowAction.Follow); + } + + function unfollowUser(address _user) public { + EnumerableSet.remove(_followConnectionsByUser[msg.sender].following, _user); + EnumerableSet.remove(_followConnectionsByUser[_user].followers, msg.sender); + emit FollowEvent(msg.sender, _user, FollowAction.UnFollow); + } + + function isFollowing(address _owner, address _user) public view returns (bool) { + return EnumerableSet.contains(_followConnectionsByUser[_owner].following, _user); + } + + function followingList(address _user) public onlyFollowers(_user) view returns (address[] memory) { + return EnumerableSet.values(_followConnectionsByUser[_user].following); + } + + function followingCount(address _user) public view returns (uint256) { + return EnumerableSet.length(_followConnectionsByUser[_user].following); + } + + function followersList(address _user) public onlyFollowers(_user) view returns (address[] memory) { + return EnumerableSet.values(_followConnectionsByUser[_user].followers); + } + + function followersCount(address _user) public view returns (uint256) { + return EnumerableSet.length(_followConnectionsByUser[_user].followers); + } + + function blockUser(address _user) public { + EnumerableSet.remove(_followConnectionsByUser[msg.sender].following, _user); + EnumerableSet.remove(_followConnectionsByUser[msg.sender].followers, _user); + EnumerableSet.remove(_followConnectionsByUser[_user].following, msg.sender); + EnumerableSet.remove(_followConnectionsByUser[_user].followers, msg.sender); + EnumerableSet.add(_followConnectionsByUser[msg.sender].blocked, _user); + emit FollowEvent(msg.sender, _user, FollowAction.Block); + } + + function unBlockUser(address _user) public { + EnumerableSet.remove(_followConnectionsByUser[msg.sender].blocked, _user); + emit FollowEvent(msg.sender, _user, FollowAction.UnBlock); + } + + function isBlocked(address _owner, address _user) public view returns (bool) { + return EnumerableSet.contains(_followConnectionsByUser[_owner].blocked, _user); + } + + function blockList() public view returns (address[] memory) { + return EnumerableSet.values(_followConnectionsByUser[msg.sender].blocked); + } + + } diff --git a/hardhat.config.js b/hardhat.config.js deleted file mode 100644 index 81f1a86..0000000 --- a/hardhat.config.js +++ /dev/null @@ -1,43 +0,0 @@ -require("@nomiclabs/hardhat-waffle"); -require('@openzeppelin/hardhat-upgrades'); -require("solidity-coverage"); - -module.exports = { - solidity: { - compilers: [ - { - version: '0.8.0', - settings: { - optimizer: { - enabled: true, - runs: 1000 - } - } - }, - { - version: '0.8.4', - settings: { - optimizer: { - enabled: true, - runs: 1000 - } - } - }, - { - version: '0.8.7', - settings: { - optimizer: { - enabled: true, - runs: 1000 - } - } - }, - ] - }, - paths: { - artifacts:'./hardhat/build', - sources: './contracts', - tests: './tests/unit/smartcontracts', - cache: './hardhat/cache' - } -}; diff --git a/hardhat.config.ts b/hardhat.config.ts new file mode 100644 index 0000000..1a55f07 --- /dev/null +++ b/hardhat.config.ts @@ -0,0 +1,74 @@ +import '@nomiclabs/hardhat-waffle'; +import '@openzeppelin/hardhat-upgrades'; +import fs from 'fs'; +import os from 'os'; +import { hdkey } from 'ethereumjs-wallet'; +import { mnemonicToSeedSync } from 'bip39'; + +const keystorePath = `${os.homedir()}/.point/keystore/key.json`; + +const networks: Record = { + rinkeby: { + url: process.env.RINKEBY_URL || '', + accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], + }, +}; + +try { + if (fs.existsSync(keystorePath)) { + const keystore: any = JSON.parse( + fs.readFileSync(`${os.homedir()}/.point/keystore/key.json`).toString() + ); + + const wallet = hdkey.fromMasterSeed(mnemonicToSeedSync(keystore.phrase)).getWallet(); + const privateKey = wallet.getPrivateKey().toString('hex'); + + networks.ynet = { + url: 'http://ynet.point.space:44444', + accounts: [privateKey], + }; + } +} catch (err) {} + +export default { + solidity: { + compilers: [ + { + version: '0.8.0', + settings: { + optimizer: { + enabled: true, + runs: 1000, + }, + }, + }, + { + version: '0.8.4', + settings: { + optimizer: { + enabled: true, + runs: 1000, + }, + }, + }, + { + version: '0.8.7', + settings: { + optimizer: { + enabled: true, + runs: 1000, + }, + }, + }, + ], + }, + + networks, + + paths: { + artifacts: './hardhat/build', + sources: './contracts', + tests: './tests/unit/smartcontracts', + cache: './hardhat/cache', + }, +}; diff --git a/package-lock.json b/package-lock.json index d698c7b..56b23e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,17 +53,30 @@ "@nomiclabs/hardhat-waffle": "^2.0.3", "@openzeppelin/hardhat-upgrades": "^1.19.0", "@parcel/transformer-image": "^2.0.0-rc.0", + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.1", + "@types/node": "^18.0.3", "babel-jest": "^28.1.1", "chai": "^4.3.6", + "eslint": "^7.29.0", + "eslint-config-prettier": "^8.3.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-promise": "^5.1.0", "ethereum-waffle": "^3.4.4", "ethers": "^5.6.8", "hardhat": "^2.9.8", "identity-obj-proxy": "^3.0.0", "jest": "^26.6.0", "parcel": "^2.0.0-rc.0", + "prettier": "^2.7.1", "react-error-overlay": "^6.0.9", "react-test-renderer": "^17.0.2", - "solidity-coverage": "^0.7.21" + "solidity-coverage": "^0.7.21", + "ts-node": "^10.8.2", + "typescript": "^4.7.4" } }, "node_modules/@ampproject/remapping": { @@ -2004,6 +2017,28 @@ "node": ">=0.1.95" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -6368,6 +6403,30 @@ "node": ">=10.13.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "devOptional": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "devOptional": true + }, "node_modules/@typechain/ethers-v5": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz", @@ -6570,10 +6629,16 @@ "@types/node": "*" } }, + "node_modules/@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true + }, "node_modules/@types/node": { - "version": "17.0.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz", - "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==" + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", + "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" }, "node_modules/@types/node-fetch": { "version": "2.6.1", @@ -6778,51 +6843,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "dependencies": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^4.0.0", - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/experimental-utils": { "version": "4.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", @@ -6846,32 +6866,6 @@ "eslint": "*" } }, - "node_modules/@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "dependencies": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@typescript-eslint/scope-manager": { "version": "4.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", @@ -7217,9 +7211,9 @@ } }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "bin": { "acorn": "bin/acorn" }, @@ -7485,6 +7479,12 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -10072,6 +10072,12 @@ "sha.js": "^2.4.8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true + }, "node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -11877,36 +11883,42 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-react-app": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", - "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", - "dependencies": { - "confusing-browser-globals": "^1.0.10" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^4.0.0", - "@typescript-eslint/parser": "^4.0.0", - "babel-eslint": "^10.0.0", - "eslint": "^7.5.0", - "eslint-plugin-flowtype": "^5.2.0", - "eslint-plugin-import": "^2.22.0", - "eslint-plugin-jest": "^24.0.0", - "eslint-plugin-jsx-a11y": "^6.3.1", - "eslint-plugin-react": "^7.20.3", - "eslint-plugin-react-hooks": "^4.0.8", - "eslint-plugin-testing-library": "^3.9.0" - }, - "peerDependenciesMeta": { - "eslint-plugin-jest": { - "optional": true + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" }, - "eslint-plugin-testing-library": { - "optional": true + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "peerDependencies": { + "eslint": "^7.12.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1 || ^5.0.0" } }, "node_modules/eslint-import-resolver-node": { @@ -12023,19 +12035,47 @@ "node": ">=4" } }, - "node_modules/eslint-plugin-flowtype": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz", - "integrity": "sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==", + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, "dependencies": { - "lodash": "^4.17.15", - "string-natural-compare": "^3.0.1" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": "^7.1.0" + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" } }, "node_modules/eslint-plugin-import": { @@ -12104,26 +12144,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", - "dependencies": { - "@typescript-eslint/experimental-utils": "^4.0.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": ">= 4", - "eslint": ">=5" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - } - } - }, "node_modules/eslint-plugin-jsx-a11y": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", @@ -12161,6 +12181,92 @@ "node": ">=6.0" } }, + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-node/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", + "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "dev": true, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0" + } + }, "node_modules/eslint-plugin-react": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", @@ -12230,133 +12336,6 @@ "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-testing-library": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.2.tgz", - "integrity": "sha512-WAmOCt7EbF1XM8XfbCKAEzAPnShkNSwcIsAD2jHdsMUT9mZJPjLCG7pMzbcC8kK366NOuGip8HKLDC+Xk4yIdA==", - "dependencies": { - "@typescript-eslint/experimental-utils": "^3.10.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0", - "npm": ">=6" - }, - "peerDependencies": { - "eslint": "^5 || ^6 || ^7" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", - "dependencies": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "dependencies": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": "^8.10.0 || ^10.13.0 || >=11.10.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-testing-library/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -12494,6 +12473,17 @@ "node": ">= 4" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -12503,9 +12493,9 @@ } }, "node_modules/eslint/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -23609,9 +23599,9 @@ } }, "node_modules/globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", "dependencies": { "type-fest": "^0.20.2" }, @@ -25211,9 +25201,9 @@ } }, "node_modules/is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", "dependencies": { "has": "^1.0.3" }, @@ -27542,7 +27532,7 @@ "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=" + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==" }, "node_modules/lodash.uniq": { "version": "4.5.0", @@ -27679,6 +27669,12 @@ "node": ">=6" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -32964,9 +32960,9 @@ } }, "node_modules/prettier": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", - "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", "bin": { "prettier": "bin-prettier.js" }, @@ -32977,6 +32973,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/prettier-plugin-solidity": { "version": "1.0.0-beta.19", "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.19.tgz", @@ -33763,6 +33771,14 @@ } } }, + "node_modules/react-scripts/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, "node_modules/react-scripts/node_modules/@babel/core": { "version": "7.12.3", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", @@ -33855,6 +33871,100 @@ "node": ">= 8" } }, + "node_modules/react-scripts/node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "dependencies": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/parser": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "dependencies": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "debug": "^4.3.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/react-scripts/node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/react-scripts/node_modules/babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -33913,6 +34023,171 @@ "node": ">=8" } }, + "node_modules/react-scripts/node_modules/eslint-config-react-app": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", + "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", + "dependencies": { + "confusing-browser-globals": "^1.0.10" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0", + "@typescript-eslint/parser": "^4.0.0", + "babel-eslint": "^10.0.0", + "eslint": "^7.5.0", + "eslint-plugin-flowtype": "^5.2.0", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jest": "^24.0.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-react": "^7.20.3", + "eslint-plugin-react-hooks": "^4.0.8", + "eslint-plugin-testing-library": "^3.9.0" + }, + "peerDependenciesMeta": { + "eslint-plugin-jest": { + "optional": true + }, + "eslint-plugin-testing-library": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-flowtype": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz", + "integrity": "sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==", + "dependencies": { + "lodash": "^4.17.15", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.1.0" + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-jest": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", + "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", + "dependencies": { + "@typescript-eslint/experimental-utils": "^4.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": ">= 4", + "eslint": ">=5" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-testing-library": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.2.tgz", + "integrity": "sha512-WAmOCt7EbF1XM8XfbCKAEzAPnShkNSwcIsAD2jHdsMUT9mZJPjLCG7pMzbcC8kK366NOuGip8HKLDC+Xk4yIdA==", + "dependencies": { + "@typescript-eslint/experimental-utils": "^3.10.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^5 || ^6 || ^7" + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/react-scripts/node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "dependencies": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/react-scripts/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/react-scripts/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/react-scripts/node_modules/react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -37903,9 +38178,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -38527,6 +38802,67 @@ "node": ">=4" } }, + "node_modules/ts-node": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.2.tgz", + "integrity": "sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==", + "devOptional": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "devOptional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -38747,6 +39083,18 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/typical": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", @@ -39206,6 +39554,12 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true + }, "node_modules/v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -42209,6 +42563,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -43537,6 +43900,27 @@ "minimist": "^1.2.0" } }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -46691,6 +47075,30 @@ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "devOptional": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "devOptional": true + }, "@typechain/ethers-v5": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-2.0.0.tgz", @@ -46889,10 +47297,16 @@ "@types/node": "*" } }, + "@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true + }, "@types/node": { - "version": "17.0.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.14.tgz", - "integrity": "sha512-SbjLmERksKOGzWzPNuW7fJM7fk3YXVTFiZWB/Hs99gwhk+/dnrQRPBQjPW9aO+fi1tAffi9PrwFvsmOKmDTyng==" + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", + "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==" }, "@types/node-fetch": { "version": "2.6.1", @@ -47098,31 +47512,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" }, - "@typescript-eslint/eslint-plugin": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", - "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", - "requires": { - "@typescript-eslint/experimental-utils": "4.33.0", - "@typescript-eslint/scope-manager": "4.33.0", - "debug": "^4.3.1", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", - "regexpp": "^3.1.0", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - }, - "dependencies": { - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "@typescript-eslint/experimental-utils": { "version": "4.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz", @@ -47136,17 +47525,6 @@ "eslint-utils": "^3.0.0" } }, - "@typescript-eslint/parser": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", - "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", - "requires": { - "@typescript-eslint/scope-manager": "4.33.0", - "@typescript-eslint/types": "4.33.0", - "@typescript-eslint/typescript-estree": "4.33.0", - "debug": "^4.3.1" - } - }, "@typescript-eslint/scope-manager": { "version": "4.33.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz", @@ -47434,9 +47812,9 @@ } }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==" }, "acorn-globals": { "version": "6.0.0", @@ -47623,6 +48001,12 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -49699,6 +50083,12 @@ "sha.js": "^2.4.8" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -51139,15 +51529,23 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==" }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "requires": { "lru-cache": "^6.0.0" } @@ -51175,13 +51573,19 @@ } } }, - "eslint-config-react-app": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", - "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", - "requires": { - "confusing-browser-globals": "^1.0.10" - } + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-config-standard": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-16.0.3.tgz", + "integrity": "sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==", + "dev": true, + "requires": {} }, "eslint-import-resolver-node": { "version": "0.3.6", @@ -51274,13 +51678,31 @@ } } }, - "eslint-plugin-flowtype": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz", - "integrity": "sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==", + "eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, "requires": { - "lodash": "^4.17.15", - "string-natural-compare": "^3.0.1" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "eslint-plugin-import": { @@ -51336,14 +51758,6 @@ } } }, - "eslint-plugin-jest": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", - "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", - "requires": { - "@typescript-eslint/experimental-utils": "^4.0.1" - } - }, "eslint-plugin-jsx-a11y": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", @@ -51374,6 +51788,59 @@ } } }, + "eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-promise": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", + "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "dev": true, + "requires": {} + }, "eslint-plugin-react": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz", @@ -51425,77 +51892,6 @@ "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==", "requires": {} }, - "eslint-plugin-testing-library": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.2.tgz", - "integrity": "sha512-WAmOCt7EbF1XM8XfbCKAEzAPnShkNSwcIsAD2jHdsMUT9mZJPjLCG7pMzbcC8kK366NOuGip8HKLDC+Xk4yIdA==", - "requires": { - "@typescript-eslint/experimental-utils": "^3.10.1" - }, - "dependencies": { - "@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==" - }, - "@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "requires": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -60139,9 +60535,9 @@ } }, "globals": { - "version": "13.12.1", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", - "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", "requires": { "type-fest": "^0.20.2" } @@ -61344,9 +61740,9 @@ } }, "is-core-module": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", - "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", "requires": { "has": "^1.0.3" } @@ -63162,7 +63558,7 @@ "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=" + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==" }, "lodash.uniq": { "version": "4.5.0", @@ -63267,6 +63663,12 @@ "semver": "^5.6.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true + }, "makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -67353,9 +67755,18 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "prettier": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", - "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==" + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } }, "prettier-plugin-solidity": { "version": "1.0.0-beta.19", @@ -67970,6 +68381,14 @@ "workbox-webpack-plugin": "5.1.4" }, "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, "@babel/core": { "version": "7.12.3", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", @@ -68020,6 +68439,57 @@ } } }, + "@typescript-eslint/eslint-plugin": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz", + "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==", + "requires": { + "@typescript-eslint/experimental-utils": "4.33.0", + "@typescript-eslint/scope-manager": "4.33.0", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", + "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", + "requires": { + "@typescript-eslint/scope-manager": "4.33.0", + "@typescript-eslint/types": "4.33.0", + "@typescript-eslint/typescript-estree": "4.33.0", + "debug": "^4.3.1" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, "babel-jest": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", @@ -68060,6 +68530,88 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" }, + "eslint-config-react-app": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-6.0.0.tgz", + "integrity": "sha512-bpoAAC+YRfzq0dsTk+6v9aHm/uqnDwayNAXleMypGl6CpxI9oXXscVHo4fk3eJPIn+rsbtNetB4r/ZIidFIE8A==", + "requires": { + "confusing-browser-globals": "^1.0.10" + } + }, + "eslint-plugin-flowtype": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.10.0.tgz", + "integrity": "sha512-vcz32f+7TP+kvTUyMXZmCnNujBQZDNmcqPImw8b9PZ+16w1Qdm6ryRuYZYVaG9xRqqmAPr2Cs9FAX5gN+x/bjw==", + "requires": { + "lodash": "^4.17.15", + "string-natural-compare": "^3.0.1" + } + }, + "eslint-plugin-jest": { + "version": "24.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.7.0.tgz", + "integrity": "sha512-wUxdF2bAZiYSKBclsUMrYHH6WxiBreNjyDxbRv345TIvPeoCEgPNEn3Sa+ZrSqsf1Dl9SqqSREXMHExlMMu1DA==", + "requires": { + "@typescript-eslint/experimental-utils": "^4.0.1" + } + }, + "eslint-plugin-testing-library": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.10.2.tgz", + "integrity": "sha512-WAmOCt7EbF1XM8XfbCKAEzAPnShkNSwcIsAD2jHdsMUT9mZJPjLCG7pMzbcC8kK366NOuGip8HKLDC+Xk4yIdA==", + "requires": { + "@typescript-eslint/experimental-utils": "^3.10.1" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==" + }, + "@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "requires": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + } + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==" + } + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -71207,9 +71759,9 @@ }, "dependencies": { "ajv": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", - "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "requires": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -71680,6 +72232,41 @@ } } }, + "ts-node": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.2.tgz", + "integrity": "sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==", + "devOptional": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "devOptional": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true + } + } + }, "ts-pnp": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", @@ -71857,6 +72444,11 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + }, "typical": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", @@ -72213,6 +72805,12 @@ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true + }, "v8-to-istanbul": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", @@ -74747,6 +75345,12 @@ } } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index f584b81..e10bdcd 100644 --- a/package.json +++ b/package.json @@ -79,17 +79,30 @@ "@nomiclabs/hardhat-waffle": "^2.0.3", "@openzeppelin/hardhat-upgrades": "^1.19.0", "@parcel/transformer-image": "^2.0.0-rc.0", + "@types/chai": "^4.3.1", + "@types/mocha": "^9.1.1", + "@types/node": "^18.0.3", "babel-jest": "^28.1.1", "chai": "^4.3.6", + "eslint": "^7.29.0", + "eslint-config-prettier": "^8.3.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.0", + "eslint-plugin-promise": "^5.1.0", "ethereum-waffle": "^3.4.4", "ethers": "^5.6.8", "hardhat": "^2.9.8", "identity-obj-proxy": "^3.0.0", "jest": "^26.6.0", "parcel": "^2.0.0-rc.0", + "prettier": "^2.7.1", "react-error-overlay": "^6.0.9", "react-test-renderer": "^17.0.2", - "solidity-coverage": "^0.7.21" + "solidity-coverage": "^0.7.21", + "ts-node": "^10.8.2", + "typescript": "^4.7.4" }, "resolutions": { "react-error-overlay": "6.0.9" diff --git a/src/components/feed/Feed.jsx b/src/components/feed/Feed.jsx index 6079fec..cb9383b 100644 --- a/src/components/feed/Feed.jsx +++ b/src/components/feed/Feed.jsx @@ -1,274 +1,306 @@ -import "./feed.css"; -import { useState, useEffect } from "react"; +import './feed.css'; +import { useState, useEffect } from 'react'; import { useAppContext } from '../../context/AppContext'; -import useInView from 'react-cool-inview' +import useInView from 'react-cool-inview'; import { makeStyles } from '@material-ui/core/styles'; -import unionWith from "lodash/unionWith"; -import isEqual from "lodash/isEqual"; +import unionWith from 'lodash/unionWith'; +import isEqual from 'lodash/isEqual'; import { Box, Button, Snackbar, SnackbarContent, Typography } from '@material-ui/core'; import HourglassEmptyOutlinedIcon from '@material-ui/icons/HourglassEmptyOutlined'; import CircularProgressWithIcon from '../generic/CircularProgressWithIcon'; import InboxOutlinedIcon from '@material-ui/icons/InboxOutlined'; -import PostCard from "../post/PostCard"; +import PostCard from '../post/PostCard'; -import EventConstants from "../../events"; +import EventConstants from '../../events'; import PostManager from '../../services/PostManager'; -const NUM_POSTS_PER_CALL = 5; +import getPostData from '../../mappers/Post'; + +const NUM_POSTS_PER_PAGE = 5; +const NUM_POSTS_PER_CALL = 100; const useStyles = makeStyles((theme) => ({ root: { - padding: 0, - margin: 0, + padding: 0, + margin: 0, marginTop: '10px', - maxWidth: '900px' + maxWidth: '900px', }, observer: { display: 'flex', - justifyContent: 'center' + justifyContent: 'center', }, empty: { padding: theme.spacing(2, 2), - display: "flex", - flexDirection: "column", - alignItems:"center", - justifyContent: "center" + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', }, backdrop: { zIndex: theme.zIndex.drawer + 1, }, container: { - display: "flex", - height: "100%", - minHeight: "50vh", - flexDirection: "column", + display: 'flex', + height: '100%', + minHeight: '50vh', + flexDirection: 'column', }, separator: { - marginTop: "20px", - marginBottom: "20px", + marginTop: '20px', + marginBottom: '20px', }, extendedIcon: { marginRight: theme.spacing(1), - }, + }, })); -const Feed = ({ account, setAlert, setUpperLoading, canPost=false }) => { - const {observe} = useInView({ - onEnter: async({observe,unobserve}) => { - if(length === posts.length) return; +const Feed = ({ account, setAlert, setUpperLoading, canPost = false }) => { + const { observe } = useInView({ + onEnter: async ({ observe, unobserve }) => { + if (length === posts.length) return; unobserve(); await getPosts(); observe(); - } + }, }); const styles = useStyles(); - const [posts, setPosts] = useState([]) + const [posts, setPosts] = useState([]); + const [viewedPostIds, setViewedPostIds] = useState([]); + const [newestPost, setNewestPost] = useState(null); const [length, setLength] = useState(0); const [loading, setLoading] = useState(false); const [reload, setReload] = useState(false); const { walletAddress, events } = useAppContext(); - // sorts accending (newest first) - const compareByTimestamp = ( post1, post2 ) => { - if ( post1.createdAt < post2.createdAt ){ - return 1; - } - if ( post1.createdAt > post2.createdAt ){ - return -1; - } - return 0; - } - - useEffect(()=>{ + useEffect(() => { reloadPosts(); }, []); useEffect(() => { getEvents(); return () => { - events.listeners["PointSocial"]["StateChange"].removeListener("StateChange", handleEvents, { type: 'feed'}); - events.unsubscribe("PointSocial", "StateChange"); + events.listeners['PointSocial']['StateChange'].removeListener('StateChange', handleEvents, { + type: 'feed', + }); + events.unsubscribe('PointSocial', 'StateChange'); }; }, []); - const getEvents = async() => { + const getEvents = async () => { try { - (await events.subscribe("PointSocial", "StateChange")).on("StateChange", handleEvents, { type: 'feed'}); - } - catch(error) { + (await events.subscribe('PointSocial', 'StateChange')).on('StateChange', handleEvents, { + type: 'feed', + }); + } catch (error) { console.log(error.message); } - } + }; - const handleEvents = async(event) => { + const handleEvents = async (event) => { if (event) { if (event.component === EventConstants.Component.Feed) { - switch(event.action) { + switch (event.action) { case EventConstants.Action.Create: - if (event.from.toString().toLowerCase() === walletAddress.toLowerCase()) { - // Autoload own posts - await reloadPosts(); - } - else { - setReload(true); - } - break; + if (event.from.toString().toLowerCase() === walletAddress.toLowerCase()) { + // Add new owned post to top of the feed + await addPostToFeed(event.id); + } else { + setReload(true); + } + break; default: - break; + break; } - } - else if (event.component === EventConstants.Component.Post) { - switch(event.action) { + } else if (event.component === EventConstants.Component.Post) { + switch (event.action) { case EventConstants.Action.Delete: deletePost(event.id); - break; + break; default: - break; + break; } } } - } + }; - const getPostsLength = async() => { + const getPostsLength = async () => { try { setLoading(true); - const data = await (account? - PostManager.getAllPostsByOwnerLength(account) : - PostManager.getAllPostsLength()); + const data = await (account + ? PostManager.getAllPostsByOwnerLength(account) + : PostManager.getAllPostsLength()); setLength(Number(data)); - } - catch(error) { + } catch (error) { console.log(error.message); setAlert(error.message); } setLoading(false); - } + }; + + const addPostToFeed = async (id) => { + const newPost = getPostData(await PostManager.getPost(id)); + setPosts((posts) => { + return [newPost, ...posts]; + }); + }; const fetchPosts = async (onlyNew = false) => { try { setLoading(true); - - const data = await (account? - PostManager.getPaginatedPostsByOwner(account,onlyNew?0:posts.length,NUM_POSTS_PER_CALL) : - PostManager.getPaginatedPosts(onlyNew?0:posts.length,NUM_POSTS_PER_CALL)); - const newPosts = data.filter(r => (parseInt(r[4]) !== 0)) - .map(([id, from, contents, image, createdAt, likesCount, commentsCount, dislikesCount, liked, disliked]) => ( - { - id, - from, - contents, - image, - createdAt: createdAt*1000, - likesCount: parseInt(likesCount, 10), - dislikesCount: parseInt(dislikesCount, 10), - commentsCount: parseInt(commentsCount, 10), - liked, - disliked, - } - ) - ); + const data = await (account + ? PostManager.getPaginatedPostsByOwner(account, NUM_POSTS_PER_CALL, viewedPostIds) + : PostManager.getPaginatedPosts( + NUM_POSTS_PER_CALL, + viewedPostIds, + onlyNew && newestPost ? newestPost.createdAt / 1000 : 0 // get new posts only + )); - return await Promise.all(newPosts.map(async post => { - try { - post.isFlagged = await PostManager.isFlaggedPost(post.id); - } - catch(error) { - console.warn(error.message); - } - return post; - })); + const newPosts = data.map(getPostData); - } catch(error) { + return newPosts + .sort(({ weight: w1 }, { weight: w2 }) => w2 - w1) + .slice(0, NUM_POSTS_PER_PAGE); + } catch (error) { console.log(error.message); setAlert(error.message); - } - finally { + } finally { setLoading(false); } - } + }; - const getPosts = async (loadNew = false) => { + const getPosts = async (newPosts = false) => { try { setLoading(true); - const posts = await fetchPosts(loadNew); - setPosts(prev => { - const result = unionWith(prev, posts, isEqual); - result.sort(compareByTimestamp); - return result; + const posts = await fetchPosts(newPosts); + + // save posts + setPosts((prev) => { + if (newPosts) { + return unionWith(posts, prev, isEqual); + } + return unionWith(prev, posts, isEqual); }); - } - catch(error) { + + // save viewed posts + setViewedPostIds((prev) => { + posts.forEach(({ id }) => { + if (!prev.includes(id)) { + prev.push(id); + } + }); + return prev; + }); + + setNewestPost((current) => { + let newest = current; + posts.forEach((post) => { + if (!newest || post.id > newest.id) { + newest = post; + } + }); + return newest; + }); + } catch (error) { console.log(error); setAlert(error.message); - } - finally { + } finally { setLoading(false); } - } + }; + + const getNewPosts = async () => { + await Promise.all([getPostsLength(), getPosts(true)]); + setReload(false); + }; const reloadPosts = async () => { - await getPostsLength(); - await getPosts(true); + await Promise.all([getPostsLength(), getPosts()]); setReload(false); - } + }; const deletePost = async (postId) => { await getPostsLength(); - setPosts((posts) => posts.filter(post => post.id !== postId)); - } + setPosts((posts) => posts.filter((post) => post.id !== postId)); + }; return ( <> - - + } + action={ + <> + + + + } /> - -
+ +
- { - (length === 0)? - -
- - {`No posts yet.${ canPost? " Be the first!" : "" }`} - -
-
- : - posts.filter(post => post.createdAt > 0).map((post) => ( -
- -
+ {length === 0 ? ( + +
+ + + {`No posts yet.${canPost ? ' Be the first!' : ''}`} + +
+
+ ) : ( + posts + .filter((post) => post.createdAt > 0) + .map((post) => ( +
+ +
)) - } + )}
- { - loading && - } props={{color : "inherit"}} /> - } + {loading && ( + } + props={{ color: 'inherit' }} + /> + )}
-
+
); - -} -export default Feed +}; +export default Feed; diff --git a/src/components/post/PostCard.jsx b/src/components/post/PostCard.jsx index facccf2..19a6e03 100644 --- a/src/components/post/PostCard.jsx +++ b/src/components/post/PostCard.jsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { useEffect, useState, useRef, createRef } from "react"; +import { useEffect, useState, useRef, createRef } from 'react'; import { useAppContext } from '../../context/AppContext'; import { makeStyles } from '@material-ui/core/styles'; import { usePushingGutterStyles } from '@mui-treasury/styles/gutter/pushing'; @@ -8,10 +8,10 @@ import ModeCommentOutlinedIcon from '@material-ui/icons/ModeCommentOutlined'; import ThumbUpOutlinedIcon from '@material-ui/icons/ThumbUpOutlined'; import ThumbDownOutlinedIcon from '@material-ui/icons/ThumbDownOutlined'; -import { Link } from "wouter"; +import { Link } from 'wouter'; import RichTextField from '../generic/RichTextField'; -import CircularProgressWithIcon from "../../components/generic/CircularProgressWithIcon"; +import CircularProgressWithIcon from '../../components/generic/CircularProgressWithIcon'; import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined'; import EditOutlinedIcon from '@material-ui/icons/EditOutlined'; @@ -23,29 +23,30 @@ import VisibilityOutlinedIcon from '@material-ui/icons/VisibilityOutlined'; import AccountTreeOutlinedIcon from '@material-ui/icons/AccountTreeOutlined'; import LanguageOutlinedIcon from '@material-ui/icons/LanguageOutlined'; -import EventConstants from "../../events"; - -import { format } from "timeago.js"; - -import {Backdrop, - Button, - Card, - CardActions, - CardContent, - CardHeader, - CircularProgress, - Collapse, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - IconButton, - Menu, - MenuItem, - ListItemIcon, - Typography, - } from '@material-ui/core'; +import EventConstants from '../../events'; + +import { format } from 'timeago.js'; + +import { + Backdrop, + Button, + Card, + CardActions, + CardContent, + CardHeader, + CircularProgress, + Collapse, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + IconButton, + Menu, + MenuItem, + ListItemIcon, + Typography, +} from '@material-ui/core'; import Skeleton from '@material-ui/lab/Skeleton'; @@ -58,673 +59,775 @@ import CommentList from '../comments/CommentList'; import CardMediaSelector from '../generic/CardMediaSelector'; import CardMediaContainer from '../generic/CardMediaContainer'; -import point from "../../services/PointSDK"; +import point from '../../services/PointSDK'; import UserManager from '../../services/UserManager'; import PostManager from '../../services/PostManager'; const EMPTY = '0x0000000000000000000000000000000000000000000000000000000000000000'; const useStyles = makeStyles((theme) => ({ - backdrop: { - position: "absolute", - zIndex: theme.zIndex.drawer - 1, - opacity: 0.9 - }, - card: { - }, - avatar: { - cursor:'pointer' - }, - media: { - height: 0, - minHeight: '250px', - maxHeight: '300px', - paddingTop: '56.25%', // 16:9 - objectFit: 'contain', - backgroundSize: 'contain', - }, - editor: { - height: '250px', - minHeight: '250px', - maxHeight: '300px', - objectFit: 'contain', - backgroundSize: 'contain', - backgroundColor: '#ccc', - }, - image: { - objectFit: 'contain', - backgroundSize: 'contain', - minHeight: '250px', - maxHeight: '300px', - }, - expand: { - transform: 'rotate(0deg)', - marginLeft: 'auto', - transition: theme.transitions.create('transform', { - duration: theme.transitions.duration.shortest, - }), - }, - expandOpen: { - transform: 'rotate(180deg)', - }, - wrapper: { - margin: theme.spacing(1), - position: 'relative', - }, - warning: { - padding: theme.spacing(2, 2), - display: "flex", - flexDirection: "column", - alignItems:"center", - justifyContent: "center", - backgroundColor: "#eee" - }, + backdrop: { + position: 'absolute', + zIndex: theme.zIndex.drawer - 1, + opacity: 0.9, + }, + card: {}, + avatar: { + cursor: 'pointer', + }, + media: { + height: 0, + minHeight: '250px', + maxHeight: '300px', + paddingTop: '56.25%', // 16:9 + objectFit: 'contain', + backgroundSize: 'contain', + }, + editor: { + height: '250px', + minHeight: '250px', + maxHeight: '300px', + objectFit: 'contain', + backgroundSize: 'contain', + backgroundColor: '#ccc', + }, + image: { + objectFit: 'contain', + backgroundSize: 'contain', + minHeight: '250px', + maxHeight: '300px', + }, + expand: { + transform: 'rotate(0deg)', + marginLeft: 'auto', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + }, + expandOpen: { + transform: 'rotate(180deg)', + }, + wrapper: { + margin: theme.spacing(1), + position: 'relative', + }, + warning: { + padding: theme.spacing(2, 2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: '#eee', + }, })); function DataURIToBlob(dataURI) { - const splitDataURI = dataURI.split(',') - const byteString = splitDataURI[0].indexOf('base64') >= 0 ? atob(splitDataURI[1]) : decodeURI(splitDataURI[1]) - const mimeString = splitDataURI[0].split(':')[1].split(';')[0] + const splitDataURI = dataURI.split(','); + const byteString = + splitDataURI[0].indexOf('base64') >= 0 ? atob(splitDataURI[1]) : decodeURI(splitDataURI[1]); + const mimeString = splitDataURI[0].split(':')[1].split(';')[0]; - const ia = new Uint8Array(byteString.length) - for (let i = 0; i < byteString.length; i++) - ia[i] = byteString.charCodeAt(i) + const ia = new Uint8Array(byteString.length); + for (let i = 0; i < byteString.length; i++) ia[i] = byteString.charCodeAt(i); - return new Blob([ia], { type: mimeString }) + return new Blob([ia], { type: mimeString }); } -const PostCard = ({post, setAlert, canExpand=true, startExpanded=false, singlePost=false}) => { - - const [loading, setLoading] = useState(true); - const [countersLoading, setCountersLoading] = useState(true); - - const [expanded, setExpanded] = useState(startExpanded); - const [flagged, setFlagged] = useState(post.isFlagged); - - const [actionsOpen, setActionsOpen] = useState(false); - const [shareOpen, setShareOpen] = useState(false); - const [edit, setEdit] = useState(false); - const [prompt, showPrompt] = useState(false); - const [promptFlag, showPromptFlag] = useState(false); - - - const styles = useStyles(); - const inputRef = useRef(); - const mediaRef = createRef(); - - const gutterStyles = usePushingGutterStyles({ space: 1, firstExcluded: false }); - const iconLabelStyles = useLabelIconStyles({ linked: true }); - - const { walletAddress, deployer, profile, identity, goHome, events} = useAppContext(); - - const [name, setName] = useState(); - - const [content, setContent] = useState(); - const [media, setMedia] = useState(); - const [date, setDate] = useState(); - - const [comments, setComments] = useState(); - - const [like, setLike] = useState(false); - const [likes, setLikes] = useState(0); - const [likeLoading, setLikeLoading] = useState(false); - - const [dislike, setDislike] = useState(false); - const [dislikes, setDislikes] = useState(0); - const [dislikeLoading, setDislikeLoading] = useState(false); - - const [isOwner, setIsOwner] = useState(false); - - const actionsAnchor = useRef(); - const shareAnchor = useRef(); - const expandButton = useRef(); - - useEffect(() => { - loadPost(); - }, []); - - useEffect(() => { - if (events.listeners["PointSocial"] && - events.listeners["PointSocial"]["StateChange"]) { - events.listeners["PointSocial"]["StateChange"] - .on("StateChange", handleEvents, { type: "post", id: post.id }); - } - return () => { - if (events.listeners["PointSocial"] && - events.listeners["PointSocial"]["StateChange"]) { - events.listeners["PointSocial"]["StateChange"] - .removeListener("StateChange", handleEvents, { type: "post", id: post.id }); - } - } - }, []); - - const loadPost = async () => { - - if (post.from.toLowerCase() === walletAddress.toLowerCase()) { - setIsOwner(true); - setName((profile && profile.displayName) || identity); - } - else { - try { - const profile = await UserManager.getProfile(post.from); - const { identity } = await point.ownerToIdentity(post.from); - const name = (profile[0] === EMPTY)? identity : await point.getString(profile[0], { encoding: 'utf-8' }); - setName(name); - } - catch(error) { - setAlert(error.message); - } - } +const PostCard = ({ + post, + setAlert, + canExpand = true, + startExpanded = false, + singlePost = false, +}) => { + const [loading, setLoading] = useState(true); + const [countersLoading, setCountersLoading] = useState(true); - try { - const contents = (post.contents === EMPTY)? EMPTY : await point.getString(post.contents, { encoding: 'utf-8' }) - setContent(contents); + const [expanded, setExpanded] = useState(startExpanded); + const [flagged, setFlagged] = useState(post.flagged); - if (post.image !== EMPTY) { - setMedia(`/_storage/${post.image}`); - } + const [actionsOpen, setActionsOpen] = useState(false); + const [shareOpen, setShareOpen] = useState(false); + const [edit, setEdit] = useState(false); + const [prompt, showPrompt] = useState(false); + const [promptFlag, showPromptFlag] = useState(false); - setLike(post.liked); - setDislike(post.disliked); - setCountersLoading(false); - } - catch(error) { - setAlert(error.message); - } - - setDate(post.createdAt); - setLikes(post.likesCount); - setDislikes(post.dislikesCount); - setComments(post.commentsCount); - - setCountersLoading(false); - setLoading(false); - }; + const styles = useStyles(); + const inputRef = useRef(); + const mediaRef = createRef(); + const gutterStyles = usePushingGutterStyles({ space: 1, firstExcluded: false }); + const iconLabelStyles = useLabelIconStyles({ linked: true }); - const handleEvents = async(event) => { - if (event && - (event.component === EventConstants.Component.Post) && - (event.id === post.id)) { - switch(event.action) { - case EventConstants.Action.Like: { - setLikeLoading(true); - const data = await PostManager.getPost(post.id); - console.log(data); - const [,,,,,_likes,, _dislikes, _liked, _disliked] = data; - console.log('like', _likes, _dislikes, _liked, _disliked); - setLikes(parseInt(_likes, 10)); - setDislikes(parseInt(_dislikes, 10)); - setLike(_liked); - setDislike(_disliked); - setLikeLoading(false); - } - break; - case EventConstants.Action.Dislike: { - setLikeLoading(true); - const data = await PostManager.getPost(post.id); - console.log(data); - const [,,,,,_likes,, _dislikes, _liked, _disliked] = data; - console.log('like', _likes, _dislikes, _liked, _disliked); - setLikes(parseInt(_likes, 10)); - setDislikes(parseInt(_dislikes, 10)); - setLike(_liked); - setDislike(_disliked); - setLikeLoading(false); - } - break; - case EventConstants.Action.Edit: - if (event.from.toLowerCase() !== walletAddress.toLowerCase()) { - await loadPost(); - } - break; - case EventConstants.Action.Comment: { - const p = await PostManager.getPost(post.id); - if (p && (parseInt(p[4]) !== 0)) { - setComments(parseInt(p[6])); - } - } - break; - case EventConstants.Action.Flag: { - const isFlagged = await PostManager.isFlaggedPost(post.id); - post.isFlagged = isFlagged; - setFlagged(isFlagged); - } - break; - default: - break; - } - } - } + const { walletAddress, deployer, profile, identity, goHome, events } = useAppContext(); - const handleAction = (action) => { - switch(action) { - case 'edit': - startEdit(); - break; - case 'save': - saveEdit(); - break; - default: - case 'cancel': - cancelEdit(); - break; - case 'delete': - showPrompt(true); - break; - case 'flag': - showPromptFlag(true); - break; - } - setActionsOpen(false); - }; + const [name, setName] = useState(); - const share = async (type) => { - try { - setShareOpen(false); - const url = `https://social.point${((type === 'web2')? '.link' : '')}/post/${post.id}`; + const [content, setContent] = useState(); + const [media, setMedia] = useState(); + const [date, setDate] = useState(); - if (window.navigator.share) { - await window.navigator.share({ url }); - } - else if (window.navigator.clipboard) { - await window.navigator.clipboard.writeText(url); - setAlert("Copied to clipboard!|success"); - } - } - catch(error) { - setAlert(error.message); - } - } + const [comments, setComments] = useState(); - const deletePost = async () => { - try { - setLoading(true); - await PostManager.deletePost(post.id); - if (singlePost) { - await goHome(); - } - } - catch(error) { - setAlert(error.message); - setLoading(false); - } - }; + const [like, setLike] = useState(false); + const [likes, setLikes] = useState(0); + const [likeLoading, setLikeLoading] = useState(false); - const cancelEdit = async () => { - setEdit(false); - }; - - const startEdit = async () => { - setEdit(true); - }; - - const saveEdit = async () => { - - try { - const newContent = (inputRef.current.value.trim())? inputRef.current.value.trim() : ""; - const newMedia = mediaRef.current.media(); + const [dislike, setDislike] = useState(false); + const [dislikes, setDislikes] = useState(0); + const [dislikeLoading, setDislikeLoading] = useState(false); - setEdit(false); - setLoading(true); + const [isOwner, setIsOwner] = useState(false); - let contentId; - if (!newContent) { - contentId = EMPTY; - } - if (newContent === content) { - contentId = post.contents; - } - else { - const storageId = (newContent === EMPTY)? newContent : await point.putString(newContent, { encoding: 'utf-8' }); - contentId = storageId; - } - - let imageId; - if (!newMedia) { - imageId = EMPTY; - } - else if (newMedia === media) { - imageId = post.image; - } - else { - const formData = new FormData() - formData.append("postfile", DataURIToBlob(newMedia)); - const storageId = await point.postFile(formData); - imageId = storageId; - } + const actionsAnchor = useRef(); + const shareAnchor = useRef(); + const expandButton = useRef(); - if (contentId === imageId && contentId === EMPTY) { - throw new Error("Sorry, but you can't create an empty post"); - } - - await PostManager.editPost(post.id, contentId, imageId); - - setContent(newContent); - setMedia(newMedia); - } - catch(error) { - console.log(error); - setAlert(error.message); - } - finally { - setLoading(false); - } - }; + useEffect(() => { + loadPost(); + }, []); - const flagPost = async () => { - try { - showPromptFlag(false); - setLoading(true); - await PostManager.flagPost(post.id); - } - catch(error) { - console.error(error.message); - setAlert(error.message); - } - finally { - setLoading(false); - } + useEffect(() => { + if (events.listeners['PointSocial'] && events.listeners['PointSocial']['StateChange']) { + events.listeners['PointSocial']['StateChange'].on('StateChange', handleEvents, { + type: 'post', + id: post.id, + }); } - - const toggleLike = async () => { - try { - setLikeLoading(true); - await PostManager.addLikeToPost(post.id); - } - catch(error) { - setAlert(error.message); - } - setLikeLoading(false); + return () => { + if (events.listeners['PointSocial'] && events.listeners['PointSocial']['StateChange']) { + events.listeners['PointSocial']['StateChange'].removeListener('StateChange', handleEvents, { + type: 'post', + id: post.id, + }); + } + }; + }, []); + + const loadPost = async () => { + if (post.from.toLowerCase() === walletAddress.toLowerCase()) { + setIsOwner(true); + setName((profile && profile.displayName) || identity); + } else { + try { + const profile = await UserManager.getProfile(post.from); + const { identity } = await point.ownerToIdentity(post.from); + const name = + profile[0] === EMPTY + ? identity + : await point.getString(profile[0], { encoding: 'utf-8' }); + setName(name); + } catch (error) { + setAlert(error.message); + } } - const toggleDislike = async () => { - try { - setDislikeLoading(true); - await PostManager.addDislikeToPost(post.id); - } catch (error) { - setAlert(error.message); - } - setDislikeLoading(false); + try { + const contents = + post.contents === EMPTY + ? EMPTY + : await point.getString(post.contents, { encoding: 'utf-8' }); + setContent(contents); + + if (post.image !== EMPTY) { + setMedia(`/_storage/${post.image}`); + } + + setLike(post.liked); + setDislike(post.disliked); + setCountersLoading(false); + } catch (error) { + setAlert(error.message); } - const handleActionsOpen = () => { - setActionsOpen(true); - }; - - const handleActionsClose = () => { - setActionsOpen(false); - }; - - const handleShareOpen = () => { - setShareOpen(true); - }; - - const handleShareClose = () => { - setShareOpen(false); - }; + setDate(post.createdAt); + setLikes(post.likesCount); + setDislikes(post.dislikesCount); + setComments(post.commentsCount); - const handleExpandClick = () => { - setExpanded(!expanded); - }; + setCountersLoading(false); + setLoading(false); + }; - const dialog = <> - - {"Delete post?"} - - - Are you sure you want to delete this post? This action cannot be undone - - - - - - - + const handleEvents = async (event) => { + if (event && event.component === EventConstants.Component.Post && event.id === post.id) { + switch (event.action) { + case EventConstants.Action.Like: + { + setLikeLoading(true); + const data = await PostManager.getPost(post.id); + console.log(data); + const [, , , , , _likes, , _dislikes, _liked, _disliked] = data; + console.log('like', _likes, _dislikes, _liked, _disliked); + setLikes(parseInt(_likes, 10)); + setDislikes(parseInt(_dislikes, 10)); + setLike(_liked); + setDislike(_disliked); + setLikeLoading(false); + } + break; + case EventConstants.Action.Dislike: + { + setLikeLoading(true); + const data = await PostManager.getPost(post.id); + console.log(data); + const [, , , , , _likes, , _dislikes, _liked, _disliked] = data; + console.log('like', _likes, _dislikes, _liked, _disliked); + setLikes(parseInt(_likes, 10)); + setDislikes(parseInt(_dislikes, 10)); + setLike(_liked); + setDislike(_disliked); + setLikeLoading(false); + } + break; + case EventConstants.Action.Edit: + if (event.from.toLowerCase() !== walletAddress.toLowerCase()) { + await loadPost(); + } + break; + case EventConstants.Action.Comment: + { + const p = await PostManager.getPost(post.id); + if (p && parseInt(p[4]) !== 0) { + setComments(parseInt(p[6])); + } + } + break; + case EventConstants.Action.Flag: + { + const flagged = await PostManager.flaggedPost(post.id); + post.flagged = flagged; + setFlagged(flagged); + } + break; + default: + break; + } + } + }; + + const handleAction = (action) => { + switch (action) { + case 'edit': + startEdit(); + break; + case 'save': + saveEdit(); + break; + default: + case 'cancel': + cancelEdit(); + break; + case 'delete': + showPrompt(true); + break; + case 'flag': + showPromptFlag(true); + break; + } + setActionsOpen(false); + }; + + const share = async (type) => { + try { + setShareOpen(false); + const url = `https://social.point${type === 'web2' ? '.link' : ''}/post/${post.id}`; + + if (window.navigator.share) { + await window.navigator.share({ url }); + } else if (window.navigator.clipboard) { + await window.navigator.clipboard.writeText(url); + setAlert('Copied to clipboard!|success'); + } + } catch (error) { + setAlert(error.message); + } + }; + + const deletePost = async () => { + try { + setLoading(true); + await PostManager.deletePost(post.id); + if (singlePost) { + await goHome(); + } + } catch (error) { + setAlert(error.message); + setLoading(false); + } + }; + + const cancelEdit = async () => { + setEdit(false); + }; + + const startEdit = async () => { + setEdit(true); + }; + + const saveEdit = async () => { + try { + const newContent = inputRef.current.value.trim() ? inputRef.current.value.trim() : ''; + const newMedia = mediaRef.current.media(); + + setEdit(false); + setLoading(true); + + let contentId; + if (!newContent) { + contentId = EMPTY; + } + if (newContent === content) { + contentId = post.contents; + } else { + const storageId = + newContent === EMPTY + ? newContent + : await point.putString(newContent, { encoding: 'utf-8' }); + contentId = storageId; + } + + let imageId; + if (!newMedia) { + imageId = EMPTY; + } else if (newMedia === media) { + imageId = post.image; + } else { + const formData = new FormData(); + formData.append('postfile', DataURIToBlob(newMedia)); + const storageId = await point.postFile(formData); + imageId = storageId; + } + + if (contentId === imageId && contentId === EMPTY) { + throw new Error("Sorry, but you can't create an empty post"); + } + + await PostManager.editPost(post.id, contentId, imageId); + + setContent(newContent); + setMedia(newMedia); + } catch (error) { + console.log(error); + setAlert(error.message); + } finally { + setLoading(false); + } + }; + + const flagPost = async () => { + try { + showPromptFlag(false); + setLoading(true); + await PostManager.flagPost(post.id); + } catch (error) { + console.error(error.message); + setAlert(error.message); + } finally { + setLoading(false); + } + }; + + const toggleLike = async () => { + try { + setLikeLoading(true); + await PostManager.addLikeToPost(post.id); + } catch (error) { + setAlert(error.message); + } + setLikeLoading(false); + }; + + const toggleDislike = async () => { + try { + setDislikeLoading(true); + await PostManager.addDislikeToPost(post.id); + } catch (error) { + setAlert(error.message); + } + setDislikeLoading(false); + }; + + const handleActionsOpen = () => { + setActionsOpen(true); + }; + + const handleActionsClose = () => { + setActionsOpen(false); + }; + + const handleShareOpen = () => { + setShareOpen(true); + }; + + const handleShareClose = () => { + setShareOpen(false); + }; + + const handleExpandClick = () => { + setExpanded(!expanded); + }; + + const dialog = ( + <> + + {'Delete post?'} + + + Are you sure you want to delete this post? This action cannot be undone + + + + + + + - - const flagPrompt = <> - - {`${ post.isFlagged? "Unflag" : "Flag" } post?`} - - - {`Are you sure you want to ${ post.isFlagged? "unflag" : "flag" } this post?`} - - - - - - - - - - const postActions = <> - - - {!edit && deployer && - handleAction('flag')}> - - + ); + + const flagPrompt = ( + <> + + {`${ + post.flagged ? 'Unflag' : 'Flag' + } post?`} + + + {`Are you sure you want to ${post.flagged ? 'unflag' : 'flag'} this post?`} + + + + + + + + + ); + + const postActions = ( + <> + + + + + {!edit && deployer && ( + handleAction('flag')}> + + - { post.isFlagged? "Unflag" : "Flag" } - - } - {!edit && isOwner && (post.commentsCount === 0) && - handleAction('edit')}> - - + + {post.flagged ? 'Unflag' : 'Flag'} + + + )} + {!edit && isOwner && post.commentsCount === 0 && ( + handleAction('edit')}> + + - Edit - - } - {!edit && isOwner && (post.commentsCount === 0) && - handleAction('delete')}> - - + + Edit + + + )} + {!edit && isOwner && post.commentsCount === 0 && ( + handleAction('delete')}> + + - Delete - - } - {edit && isOwner && - handleAction('cancel')}> - - + + Delete + + + )} + {edit && isOwner && ( + handleAction('cancel')}> + + - Cancel - - } - {edit && isOwner && - handleAction('save')}> - - + + Cancel + + + )} + {edit && isOwner && ( + handleAction('save')}> + + - Save - - } - + + Save + + + )} + - - const shareActions = <> - - share('web3')}> - - - - for Web3 - - share('web2')}> - - - - for Web2 - - + ); + + const shareActions = ( + <> + + share('web3')}> + + + + + for Web3 + + + share('web2')}> + + + + + for Web2 + + + - - return ( - <> -
- - {loading && } - {flagged && -
- {setFlagged(false)}}> - - - Warning: Potentially Sensitive Content -
- } -
- - } - action={(isOwner || deployer) && postActions} - title={ - - - { - loading - ? - - : - (post.from === walletAddress) ? ((profile && profile.displayName) || identity) : name - } - - + ); + + return ( + <> +
+ + {loading && } + {flagged && ( +
+ { + setFlagged(false); + }} + > + + + + {' '} + Warning: Potentially Sensitive Content + +
+ )} +
+ + } + action={(isOwner || deployer) && postActions} + title={ + + + {loading ? ( + + ) : post.from === walletAddress ? ( + (profile && profile.displayName) || identity + ) : ( + name + )} + + + } + subheader={ + + {' '} + {loading ? : format(date)} + + } + /> + + {edit ? ( + loading ? ( + + ) : ( + + ) + ) : ( + + {loading ? : content !== EMPTY && content} + + )} + + {loading ? ( + + ) : ( + <> + {edit ? ( + + ) : ( + media && + )} + + )} + + {countersLoading || loading ? ( + + ) : ( + <> +
+ {likeLoading ? ( + - : - - } - - { - dislikeLoading - ? - - : - - } - - - - - -
- { shareActions } - - } - { canExpand && - - - + props={{ + size: 16, + style: { color: '#023600', marginLeft: '8px', marginBottom: '8px' }, + }} + /> + + ) : ( + + )} + + {dislikeLoading ? ( +
- - ) -} + props={{ + size: 16, + style: { color: '#f00', marginLeft: '8px', marginBottom: '8px' }, + }} + /> + + ) : ( + + )} + + + + + +
+ {shareActions} + + )} + {canExpand && ( + + + + )} + + + + + + {dialog} + {flagPrompt} + + + ); +}; -export default PostCard \ No newline at end of file +export default PostCard; diff --git a/src/components/profile/ProfileCard.jsx b/src/components/profile/ProfileCard.jsx index 3649efe..17fa791 100644 --- a/src/components/profile/ProfileCard.jsx +++ b/src/components/profile/ProfileCard.jsx @@ -6,7 +6,6 @@ import { useTheme } from '@material-ui/core/styles'; import { makeStyles } from '@material-ui/core/styles'; import useMediaQuery from '@material-ui/core/useMediaQuery'; -import Avatar from '@material-ui/core/Avatar'; import Backdrop from '@material-ui/core/Backdrop'; import Box from '@material-ui/core/Box'; import Card from '@material-ui/core/Card'; @@ -35,14 +34,23 @@ import SaveOutlinedIcon from '@material-ui/icons/SaveOutlined'; import MoreVertIcon from '@material-ui/icons/MoreVert'; import RoomOutlinedIcon from '@material-ui/icons/RoomOutlined'; import PanoramaOutlinedIcon from '@material-ui/icons/PanoramaOutlined'; +import PersonAddIcon from '@material-ui/icons/PersonAdd'; +import BlockOutlinedIcon from '@material-ui/icons/BlockOutlined'; +import PersonAddDisabledIcon from '@material-ui/icons/PersonAddDisabled'; +import CheckOutlinedIcon from '@material-ui/icons/CheckOutlined'; +import LockOpenOutlinedIcon from '@material-ui/icons/LockOpenOutlined'; +import LockOutlinedIcon from '@material-ui/icons/LockOutlined'; import TabPanel from '../tabs/TabPanel'; import Feed from '../feed/Feed'; import UserAvatar from '../avatar/UserAvatar'; +import UserList from '../user/UserList'; import point from "../../services/PointSDK"; import UserManager from "../../services/UserManager"; +import EventConstants from "../../events"; + const MAX_FILE_SIZE = 100 * 1024 * 1024; const EMPTY = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -199,6 +207,13 @@ const useStyles = makeStyles((theme) => ({ justify: "center", alignItems:"center" }, + followBox: { + display: 'flex', + justifyContent: "end", + justify: "end", + alignItems:"end", + margin: theme.spacing(2) + } })); const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { @@ -211,12 +226,22 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { const [name, setName] = useState(EMPTY); const [location, setLocation] = useState(EMPTY); const [about, setAbout] = useState(EMPTY); + const [followers, setFollowers] = useState(0); const [following, setFollowing] = useState(0); + const [followersList, setFollowersList] = useState([]); + const [followingList, setFollowingList] = useState([]); + const [avatar, setAvatar] = useState(EMPTY); const [banner, setBanner] = useState(EMPTY); const [profile, setProfile] = useState(); + const [isOwner, setIsOwner] = useState(false); + const [isFollowed, setFollowed] = useState(false); + const [isFollower, setFollower] = useState(false); + const [isBlocked, setIsBlocked] = useState(false); + const [imBlocked, setImBlocked] = useState(false); + const [loading, setLoading] = useState(true); const [actionsOpen, setActionsOpen] = useState(false); const [edit, setEdit] = useState(false); @@ -229,7 +254,7 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { const displayAboutRef = useRef(); const actionsAnchor = useRef(); - const { walletAddress, setUserProfile } = useAppContext(); + const { walletAddress, setUserProfile, events } = useAppContext(); useEffect(() => { loadProfile(); @@ -239,6 +264,42 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { renderProfile(profile); }, [profile]); + useEffect(() => { + getEvents(); + return () => { + events.listeners["PointSocial"]["FollowEvent"].removeListener("FollowEvent", handleEvents, { type: 'profile', id: address}); + events.unsubscribe("PointSocial", "FollowEvent"); + }; + }, []); + + const getEvents = async() => { + try { + (await events.subscribe("PointSocial", "FollowEvent")).on("FollowEvent", handleEvents, { type: 'profile', id: address}); + } + catch(error) { + console.log(error.message); + } + } + + const handleEvents = async(event) => { + console.log(event); + if (event && (((event.from.toLowerCase() === walletAddress.toLowerCase()) && + (event.to.toLowerCase() === address.toLowerCase())) || + ((event.to.toLowerCase() === walletAddress.toLowerCase()) && + (event.from.toLowerCase() === address.toLowerCase())))) { + switch(event.action) { + case EventConstants.FollowAction.Follow: + case EventConstants.FollowAction.UnFollow: + case EventConstants.FollowAction.Block: + case EventConstants.FollowAction.UnBlock: + await loadFollowStatus(); + break; + default: + break; + } + } + } + const renderProfile = async (profile) => { if (profile) { try { @@ -248,11 +309,10 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { const location = (profile.displayLocation === EMPTY)? "Point Network" : await point.getString(profile.displayLocation, { encoding: 'utf-8' }); setLocation(location); const about = (profile.displayLocation === EMPTY)? "Hey I'm using Point Social!" : await point.getString(profile.displayAbout, { encoding: 'utf-8' }); - setAbout(about); - setFollowers(profile.followersCount || 0); - setFollowing(profile.followingCount || 0); + setAbout(about); setAvatar(`/_storage/${profile.avatar}`); setBanner((profile.banner=== EMPTY)?defaultBanner:`/_storage/${profile.banner}`); + await loadFollowStatus(); setLoading(false); } catch(error) { @@ -261,6 +321,36 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { } } + const loadFollowStatus = async () => { + setFollowers(await UserManager.followersCount(address) || 0); + setFollowing(await UserManager.followingCount(address) || 0); + + try { + setFollowersList(await UserManager.followersList(address)); + } + catch(error) { + setFollowersList([]); + } + + try { + setFollowingList(await UserManager.followingList(address)); + } + catch(error) { + setFollowingList([]); + } + + const owner = address.toLowerCase() === walletAddress.toLowerCase(); + if (owner) { + setIsOwner(true); + } + else { + setFollowed(await UserManager.isFollowing(walletAddress, address)); + setFollower(await UserManager.isFollowing(address, walletAddress)); + setIsBlocked(await UserManager.isBlocked(walletAddress, address)); + setImBlocked(await UserManager.isBlocked(address, walletAddress)); + } + } + const loadProfile = async () => { try { const profile = await UserManager.getProfile(address); @@ -274,7 +364,7 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { followersCount: 0, followingCount: 0, }); - } + } } catch(error) { setAlert(error.message); @@ -302,6 +392,10 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { case 'cancel': cancelEdit(); break; + case 'block': + case 'unblock': + toggleBlock(); + break; } setActionsOpen(false); @@ -404,7 +498,7 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { followingCount: 0, }; - const result = await UserManager.setProfile( + await UserManager.setProfile( updatedProfile.displayName, updatedProfile.displayLocation, updatedProfile.displayAbout, @@ -432,6 +526,50 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { } }; + const toggleFollow = async () => { + try { + setLoading(true); + if (isFollowed) { + await UserManager.unfollowUser(address); + setFollowed(false); + setAlert(`You're no longer following to ${name}|success`); + } + else { + await UserManager.followUser(address); + setFollowed(true); + setAlert(`Now you're following ${name}|success`); + } + } + catch(error) { + setAlert(error.message); + } + finally { + setLoading(false); + } + } + + const toggleBlock = async () => { + try { + setLoading(true); + if (isBlocked) { + await UserManager.unblockUser(address); + setIsBlocked(false); + setAlert(`You unblocked all activity from ${name}|success`); + } + else { + await UserManager.blockUser(address); + setIsBlocked(true); + setAlert(`You blocked all activity from ${name}|success`); + } + } + catch(error) { + setAlert(error.message); + } + finally { + setLoading(false); + } + } + const bannerContent = @@ -445,7 +583,7 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { className={styles.actionIcon}>@{identity} { !sm && `• ${address}` } }/> - { (walletAddress === address) && + { { transformOrigin={{ vertical: "top", horizontal: "right" }} onClose={handleActionsClose} open={actionsOpen}> - {!edit && + {!isOwner && isBlocked && + handleAction('unblock')}> + + + + Unblock + + } + {!isOwner && !isBlocked && + handleAction('block')}> + + + + Block + + } + {!edit && isOwner && handleAction('edit')}> @@ -471,7 +625,7 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { Edit } - {edit && + {edit && isOwner && handleAction('cancel')}> @@ -479,7 +633,7 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { Cancel } - {edit && + {edit && isOwner && handleAction('save')}> @@ -524,6 +678,21 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { + { !loading && !isOwner && + + { + isBlocked? + : } label={isBlocked? "Unblock" : "Block"} onClick={toggleBlock}/> + : + imBlocked? + } label="Blocked you" color="secondary"/> + : + isFollower && } label="Follows you" color="primary"/> + } +
+ { !(isBlocked || imBlocked) && : } label={isFollowed? "Unfollow" : "Follow"} onClick={toggleFollow}/> } +
+ } { edit && } @@ -579,7 +748,7 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { - {/* Temporarily disabling until functionality is available + {

Followers

@@ -593,14 +762,18 @@ const ProfileCard = ({ address, identity, setUpperLoading, setAlert }) => { {loading ? : following }

-
*/} +
} + + {/* Temporarily disabling until functionality is available */} }/> + }/> + }/> diff --git a/src/components/user/UserItem.jsx b/src/components/user/UserItem.jsx new file mode 100644 index 0000000..c1fcbb4 --- /dev/null +++ b/src/components/user/UserItem.jsx @@ -0,0 +1,69 @@ +import { useState, useEffect } from "react"; +import { useAppContext } from '../../context/AppContext'; +import { makeStyles } from '@material-ui/core/styles'; + +import ListItem from '@material-ui/core/ListItem'; +import Divider from '@material-ui/core/Divider'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; +import UserAvatar from "../avatar/UserAvatar"; + +import point from "../../services/PointSDK"; +import UserManager from "../../services/UserManager"; + +const useStyles = makeStyles((theme) => ({ + root: { + cursor:'pointer', + }, +})); + +const EMPTY = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +const UserItem = ({address}) => { + + const styles = useStyles(); + const [loading, setLoading] = useState(false); + const [name, setName] = useState(''); + const [about, setAbout] = useState(''); + + const { walletAddress, profile, identity } = useAppContext(); + + useEffect(() => { + loadUser(); + }, []); + + const loadUser = async () => { + setLoading(true); + if (address.toString().toLowerCase() === walletAddress.toString().toLowerCase()) { + setName((profile && profile.displayName) || identity); + setAbout(profile.displayAbout || "Hey I'm using Point Social!"); + } + else { + try { + const profile = await UserManager.getProfile(address); + const { identity } = await point.ownerToIdentity(address); + const name = (profile[0] === EMPTY)? identity : await point.getString(profile[0], { encoding: 'utf-8' }); + const about = (profile[2] === EMPTY)? "Hey I'm using Point Social!" : await point.getString(profile[2], {encoding: 'utf-8'}); + setName(name); + setAbout(about); + } + catch(error) {} + } + setLoading(false); + } + + return ( + <> + + + window.open(`/profile/${address}`, "_blank") }}/> + + + + + + ); + +}; + +export default UserItem \ No newline at end of file diff --git a/src/components/user/UserList.jsx b/src/components/user/UserList.jsx new file mode 100644 index 0000000..03f060d --- /dev/null +++ b/src/components/user/UserList.jsx @@ -0,0 +1,87 @@ +import { useEffect, useState, useRef } from "react"; +import { makeStyles } from '@material-ui/core/styles'; +import { useAppContext } from '../../context/AppContext'; + +import CircularProgressWithIcon from "../../components/generic/CircularProgressWithIcon"; + +import {Box, + Divider, + List, + Typography, + } from '@material-ui/core'; + +import Skeleton from '@material-ui/lab/Skeleton'; + +import InboxOutlinedIcon from '@material-ui/icons/InboxOutlined'; +import RichTextField from '../generic/RichTextField'; +import SendOutlinedIcon from '@material-ui/icons/SendOutlined'; +import IconButton from '@material-ui/core/IconButton'; +import SmsOutlinedIcon from '@material-ui/icons/SmsOutlined'; + +import point from "../../services/PointSDK"; +import UserManager from "../../services/UserManager"; +import EventConstants from "../../events"; + +import UserItem from "./UserItem"; + +const useStyles = makeStyles((theme) => ({ + backdrop: { + position: "absolute", + zIndex: theme.zIndex.drawer - 1, + opacity: 0.9 + }, + root: { + width: '100%', + height: '100%', + backgroundColor: theme.palette.background.paper, + }, + inline: { + display: 'inline', + }, + empty: { + padding: theme.spacing(2, 2), + display: "flex", + flexDirection: "column", + alignItems:"center", + justifyContent: "center", + marginBottom: theme.spacing(6) + }, + commentBox: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }, + list: { + minHeight: '50vh' + } +})); + +const UserList = ({users}) => { + + const styles = useStyles(); + return ( +
+ { + (users.length === 0) + ? + +
+ + No users yet. +
+
+ : + + { + users.map((user) => ( + + + )) + } + + } +
+ ) +} + +export default UserList \ No newline at end of file diff --git a/src/events.js b/src/events.js index a27642a..3dc1210 100644 --- a/src/events.js +++ b/src/events.js @@ -14,6 +14,12 @@ const EventConstants = { Feed: "1", Post: "2", Comment: "3", + }, + FollowAction: { + Follow : "0", + UnFollow : "1", + Block : "2", + UnBlock : "3" } } diff --git a/src/mappers/Post.ts b/src/mappers/Post.ts new file mode 100644 index 0000000..302266f --- /dev/null +++ b/src/mappers/Post.ts @@ -0,0 +1,31 @@ +export default function getPostData(post: any[]): Post { + const [ + id, + from, + contents, + image, + createdAt, + likesCount, + commentsCount, + dislikesCount, + liked, + disliked, + weight, + flagged, + ] = post; + + return { + id: parseInt(id, 10), + from, + contents, + image, + createdAt: createdAt * 1000, + likesCount: parseInt(likesCount, 10), + dislikesCount: parseInt(dislikesCount, 10), + commentsCount: parseInt(commentsCount, 10), + liked: !!liked, + disliked: !!disliked, + weight: parseInt(weight, 10), + flagged: !!flagged, + }; +} diff --git a/src/pages/post/Post.jsx b/src/pages/post/Post.jsx index 31cdaad..6ea9155 100644 --- a/src/pages/post/Post.jsx +++ b/src/pages/post/Post.jsx @@ -1,6 +1,6 @@ -import Appbar from "../../components/topbar/Appbar"; -import { useRoute } from "wouter"; -import { useEffect, useState } from "react"; +import Appbar from '../../components/topbar/Appbar'; +import { useRoute } from 'wouter'; +import { useEffect, useState } from 'react'; import { useAppContext } from '../../context/AppContext'; import { makeStyles } from '@material-ui/core/styles'; @@ -11,7 +11,9 @@ import Backdrop from '@material-ui/core/Backdrop'; import Snackbar from '@material-ui/core/Snackbar'; import MuiAlert from '@material-ui/lab/Alert'; -import CircularProgressWithIcon from "../../components/generic/CircularProgressWithIcon"; +import getPostData from '../../mappers/Post'; + +import CircularProgressWithIcon from '../../components/generic/CircularProgressWithIcon'; import Box from '@material-ui/core/Box'; import Container from '@material-ui/core/Container'; @@ -24,141 +26,134 @@ import Typography from '@material-ui/core/Typography'; import PostCard from '../../components/post/PostCard'; function Alert(props) { - return ; + return ; } const useStyles = makeStyles((theme) => ({ - backdrop: { - zIndex: theme.zIndex.drawer + 1, - }, - container: { - padding: theme.spacing(2, 2), - display: "flex", - minHeight: "90vh", - flexDirection: "column", - justifyContent: "center" - } + backdrop: { + zIndex: theme.zIndex.drawer + 1, + }, + container: { + padding: theme.spacing(2, 2), + display: 'flex', + minHeight: '90vh', + flexDirection: 'column', + justifyContent: 'center', + }, })); const Post = () => { - const [match, params] = useRoute("/post/:id"); - const [loading, setLoading] = useState(true); - const [alert, setAlert] = useState(""); - const [post, setPost] = useState(); - - const { walletAddress, events } = useAppContext(); - - const styles = useStyles(); - - const handleAlert = (event, reason) => { - if (reason === 'clickaway') { - return; - } - setAlert(""); - }; - - const getPost = async () => { - console.log("hola"); - try { - const isPost = params.id.match(/^\d+$/); - let isFlagged = false; - - if (isPost) { - const post = await PostManager.getPost(params.id); - - try { - isFlagged = await PostManager.isFlaggedPost(params.id); - } - catch(error) { - console.warn(error); - } - - if (post && (parseInt(post[4]) !== 0)) { - setPost({ - id: post[0], - from: post[1], - contents: post[2], - image: post[3], - createdAt: parseInt(post[4])*1000, - likesCount: parseInt(post[5]), - commentsCount: parseInt(post[6]), - isFlagged - }) - } - } - } - catch(error) { - setAlert(error.message); - } - finally { - setLoading(false); - } - }; - - useEffect(() => { - getPost(); - }, []); - - useEffect(() => { - getEvents(); - return () => { - events.listeners["PointSocial"]["StateChange"].removeListener("StateChange", handleEvents); - events.unsubscribe("PointSocial", "StateChange"); - }; - }, []); - - const getEvents = async() => { - try { - const ev = await events.subscribe("PointSocial", "StateChange"); - ev.on("StateChange", handleEvents); - } - catch(error) { - console.log(error.message); - } - } - - const handleEvents = async(event) => { - if (event) { - //console.log(event.returnValues); + const [match, params] = useRoute('/post/:id'); + const [loading, setLoading] = useState(true); + const [alert, setAlert] = useState(''); + const [post, setPost] = useState(); + + const { walletAddress, events } = useAppContext(); + + const styles = useStyles(); + + const handleAlert = (event, reason) => { + if (reason === 'clickaway') { + return; + } + setAlert(''); + }; + + const getPost = async () => { + try { + const isPost = params.id.match(/^\d+$/); + + if (isPost) { + const post = getPostData(await PostManager.getPost(params.id)); + + if (post && post.createdAt !== 0) { + setPost(post); } } - - - const main = (post)? + } catch (error) { + setAlert(error.message); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + getPost(); + }, []); + + useEffect(() => { + getEvents(); + return () => { + events.listeners['PointSocial']['StateChange'].removeListener('StateChange', handleEvents); + events.unsubscribe('PointSocial', 'StateChange'); + }; + }, []); + + const getEvents = async () => { + try { + const ev = await events.subscribe('PointSocial', 'StateChange'); + ev.on('StateChange', handleEvents); + } catch (error) { + console.log(error.message); + } + }; + + const handleEvents = async (event) => { + if (event) { + //console.log(event.returnValues); + } + }; + + const main = post ? ( - + - : + ) : ( <> - { !loading && - -
- - Post not found -
-
- } + {!loading && ( + +
+ + Post not found +
+
+ )} - - return ( -
- { walletAddress && } - - { - walletAddress? - : - } props={{color : 'inherit'}} /> - } - - { - walletAddress && main - } - - { alert.split("|")[0] } - -
- ); -} - - -export default Post \ No newline at end of file + ); + + return ( +
+ {walletAddress && } + + {walletAddress ? ( + + ) : ( + } + props={{ color: 'inherit' }} + /> + )} + + {walletAddress && main} + + + {alert.split('|')[0]} + + +
+ ); +}; + +export default Post; diff --git a/src/services/UserManager.js b/src/services/UserManager.js index 5ae355c..a910e4e 100644 --- a/src/services/UserManager.js +++ b/src/services/UserManager.js @@ -3,6 +3,18 @@ import point from "./PointSDK" class UserManager { static getProfile = async (userAddress) => point.contractCall("PointSocial", "getProfile", [userAddress]); static setProfile = async (displayName, displayLocation, displayAbout, avatar, banner) => point.contractCall("PointSocial", "setProfile", [displayName, displayLocation, displayAbout, avatar, banner]); + static isFollowing = async (owner, user) => point.contractCall("PointSocial", "isFollowing", [owner, user]); + static followUser = async (user) => point.contractCall("PointSocial", "followUser", [user]); + static unfollowUser = async (user) => point.contractCall("PointSocial", "unfollowUser", [user]); + static blockUser = async (user) => point.contractCall("PointSocial", "blockUser", [user]); + static unblockUser = async (user) => point.contractCall("PointSocial", "unBlockUser", [user]); + static isBlocked = async (owner, user) => point.contractCall("PointSocial", "isBlocked", [owner, user]); + static blockList = async () => point.contractCall("PointSocial", "blockList", []); + static followingList = async (user) => point.contractCall("PointSocial", "followingList", [user]); + static followersList = async (user) => point.contractCall("PointSocial", "followersList", [user]); + static followingCount = async (user) => point.contractCall("PointSocial", "followingCount", [user]); + static followersCount = async (user) => point.contractCall("PointSocial", "followersCount", [user]); + } export default UserManager diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..4b49e92 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,16 @@ +type Address = string; + +type Post = { + id: number; + from: Address; + contents: string; + image: string; + createdAt: number; + likesCount: number; + commentsCount: number; + dislikesCount: number; + weight: number; + liked: boolean; + disliked: boolean; + flagged: boolean; +}; diff --git a/tests/unit/smartcontracts/FeedSortAlgoritm.ts b/tests/unit/smartcontracts/FeedSortAlgoritm.ts new file mode 100644 index 0000000..75ab9f0 --- /dev/null +++ b/tests/unit/smartcontracts/FeedSortAlgoritm.ts @@ -0,0 +1,209 @@ +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber, Contract, ContractReceipt } from 'ethers'; + +const { expect } = require('chai'); +const { ethers, upgrades, network } = require('hardhat'); + +const handle = 'social'; + +function getRandomNumber(max: number) { + return Math.floor(Math.random() * max); +} + +const postContent = '0x9d6b0f937680809a01639ad1ae4770241c7c8a0ded490d2f023669f18c6d744b'; +const postImage = '0x5f04837d78fa7a656419f98d73fc1ddaac1bfdfca9a244a2ee128737a186da6e'; + +type Post = { + id: BigNumber; + createdAt: BigNumber; + weight: number; +}; + +const MINUTE = 60; +const HOUR = MINUTE * 60; +const DAY = HOUR * 24; +const MONTH = DAY * 30; +const YEAR = MONTH * 12; + +async function addTime(seconds: number) { + await network.provider.send('evm_increaseTime', [seconds]); + await network.provider.send('evm_mine'); +} + +describe('PointSocial contract', () => { + let identity: Contract; + let contract: Contract; + let deployer: SignerWithAddress; + let goodActor: SignerWithAddress; + let badActor: SignerWithAddress; + let users: SignerWithAddress[]; + + let badPostId: BigNumber; + let goodPostId: BigNumber; + + async function deployContracts() { + const identityFactory = await ethers.getContractFactory('Identity'); + identity = await upgrades.deployProxy(identityFactory, [], { kind: 'uups' }); + await identity.deployed(); + await identity.setDevMode(true); + await identity.register( + handle, + deployer.address, + '0xed17268897bbcb67127ed550cee2068a15fdb6f69097eebeb6e2ace46305d1ce', + '0xe1e032c91d4c8fe6bab1f198871dbafb8842f073acff8ee9b822f748b180d7eb' + ); + const factory = await ethers.getContractFactory('PointSocial'); + contract = await upgrades.deployProxy(factory, [identity.address, handle], { + kind: 'uups', + }); + await contract.deployed(); + } + + before(async () => { + [deployer, goodActor, badActor, ...users] = await ethers.getSigners(); + await deployContracts(); + await setWeights(); + await populateSocial(); + await voteBadPost(); + await voteGoodPost(); + }); + + beforeEach(async () => { + await setWeights(1, 1, 0, 10, 10); + }); + + async function doTransaction(transaction: Promise): Promise { + const tx = await transaction; + const receipt = tx.wait(); + return receipt; + } + + async function setWeights( + likesWeightMultiplier = 0, + dislikesWeightWultiplier = 0, + oldWeightMultiplierinitialWeight = 0, + weightThreshold = 0, + initialWeight = 0 + ) { + await doTransaction( + contract + .connect(deployer) + .setWeights( + BigNumber.from(likesWeightMultiplier), + BigNumber.from(dislikesWeightWultiplier), + BigNumber.from(oldWeightMultiplierinitialWeight), + BigNumber.from(weightThreshold), + BigNumber.from(initialWeight) + ) + ); + } + + async function voteBadPost() { + await Promise.all( + Array.from(Array(getRandomNumber(10))).map(() => + doTransaction( + contract.connect(users[getRandomNumber(users.length - 1)]).addDislikeToPost(badPostId) + ) + ) + ); + } + + async function addPost( + content: string, + image: string, + user: SignerWithAddress = deployer + ): Promise { + const receipt = await doTransaction(contract.connect(user).addPost(postContent, postImage)); + const id: BigNumber = receipt.events![0].args![0]; + return id; + } + + async function voteGoodPost() { + await Promise.all( + Array.from(Array(getRandomNumber(10))).map(() => + doTransaction( + contract.connect(users[getRandomNumber(users.length - 1)]).addLikeToPost(goodPostId) + ) + ) + ); + } + + async function populateSocial() { + // random posts + await Promise.all( + Array.from(Array(28)).map(() => + addPost(postContent, postImage, users[getRandomNumber(users.length - 1)]) + ) + ); + + badPostId = await addPost(postContent, postImage, badActor); + + // more random posts + await Promise.all( + Array.from(Array(20)).map(() => + addPost(postContent, postImage, users[getRandomNumber(users.length - 1)]) + ) + ); + + goodPostId = await addPost(postContent, postImage, goodActor); + + // more random posts + await Promise.all( + Array.from(Array(50)).map(() => + addPost(postContent, postImage, users[getRandomNumber(users.length - 1)]) + ) + ); + } + + it('all posts should be returned', async () => { + const posts = await contract.getAllPosts([]); + expect(posts.length).to.equal(100); + }); + + describe('with weight threshold', () => { + beforeEach(async () => { + await setWeights(1, 1, 0, 10, 10); + }); + + it(`bad post should be filtered`, async () => { + const posts = await contract.getAllPosts([]); + expect(posts.some(({ id }: { id: BigNumber }) => id === badPostId)).to.equal(false); + }); + }); + + describe('front sort logic', async () => { + beforeEach(async () => { + await setWeights(1, 1, 0, 10, 10); + }); + // get X amount of posts + let posts = await contract.getPaginatedPosts(100, []); + // sort them using weight + posts = posts.sort(({ weight: w1 }: Post, { weight: w2 }: Post) => w2 - w1); + }); + + describe('if a new post is created', () => { + let viewedPosts: Post[]; + before(async () => { + viewedPosts = await contract.getPaginatedPosts(100, []); + await addTime(HOUR); + await addPost(postContent, postImage, users[1]); + }); + + it(`if the users clicks on 'get newest posts', that post should be returned`, async () => { + let newestViewedPostTimestamp = BigNumber.from(0); + const viewedPostIds = viewedPosts.map(({ createdAt, id }: Post) => { + newestViewedPostTimestamp = newestViewedPostTimestamp.lt(createdAt) + ? createdAt + : newestViewedPostTimestamp; + return id; + }); + const newPostsToView = await contract.getNewPosts( + 10, + viewedPostIds, + newestViewedPostTimestamp + ); + expect(newPostsToView.length).to.equal(1); + expect(newPostsToView[0].from).to.equal(users[1].address); + }); + }); +}); diff --git a/tests/unit/smartcontracts/PointSocial.js b/tests/unit/smartcontracts/PointSocial.js index 8c41a07..5b5f76c 100644 --- a/tests/unit/smartcontracts/PointSocial.js +++ b/tests/unit/smartcontracts/PointSocial.js @@ -1,63 +1,64 @@ -const { expect } = require("chai"); -const { ethers, upgrades } = require("hardhat"); - -describe("PointSocial contract", function () { - - let pointSocial; - let identityContract; - let owner; - let addr1; - let addr2; - let addr3; - let addrs; - let handle = "social"; - let postContent = "0x9d6b0f937680809a01639ad1ae4770241c7c8a0ded490d2f023669f18c6d744b"; - let postimage = '0x5f04837d78fa7a656419f98d73fc1ddaac1bfdfca9a244a2ee128737a186da6e'; - - beforeEach(async function () { - [owner, addr1, addr2, addr3, ...addrs] = await ethers.getSigners(); - const identityFactory = await ethers.getContractFactory("Identity"); - identityContract = await upgrades.deployProxy(identityFactory, [], {kind: 'uups'}); - await identityContract.deployed(); - const factory = await ethers.getContractFactory("PointSocial"); - pointSocial = await upgrades.deployProxy(factory, [identityContract.address, handle], {kind: 'uups'}); - await pointSocial.deployed(); +const { expect } = require('chai'); +const { ethers, upgrades } = require('hardhat'); + +describe('PointSocial contract', function () { + let pointSocial; + let identityContract; + let owner; + let addr1; + let addr2; + let addr3; + let addrs; + let handle = 'social'; + let postContent = '0x9d6b0f937680809a01639ad1ae4770241c7c8a0ded490d2f023669f18c6d744b'; + let postimage = '0x5f04837d78fa7a656419f98d73fc1ddaac1bfdfca9a244a2ee128737a186da6e'; + + beforeEach(async function () { + [owner, addr1, addr2, addr3, ...addrs] = await ethers.getSigners(); + const identityFactory = await ethers.getContractFactory('Identity'); + identityContract = await upgrades.deployProxy(identityFactory, [], { kind: 'uups' }); + await identityContract.deployed(); + const factory = await ethers.getContractFactory('PointSocial'); + pointSocial = await upgrades.deployProxy(factory, [identityContract.address, handle], { + kind: 'uups', + }); + await pointSocial.deployed(); + }); + + describe('Testing deployment functions', function () { + it('Should upgrade the proxy by a deployer', async function () { + await identityContract.setDevMode(true); + await identityContract.register( + handle, + owner.address, + '0xed17268897bbcb67127ed550cee2068a15fdb6f69097eebeb6e2ace46305d1ce', + '0xe1e032c91d4c8fe6bab1f198871dbafb8842f073acff8ee9b822f748b180d7eb' + ); + await identityContract.addIdentityDeployer(handle, addr1.address); + const factory = await ethers.getContractFactory('PointSocial'); + let socialFactoryDeployer = factory.connect(addr1); + + await upgrades.upgradeProxy(pointSocial.address, socialFactoryDeployer); }); - describe("Testing deployment functions", function () { - - it("Should upgrade the proxy by a deployer", async function () { - await identityContract.setDevMode(true); - await identityContract.register( - handle, - owner.address, - '0xed17268897bbcb67127ed550cee2068a15fdb6f69097eebeb6e2ace46305d1ce', - '0xe1e032c91d4c8fe6bab1f198871dbafb8842f073acff8ee9b822f748b180d7eb'); - await identityContract.addIdentityDeployer(handle, addr1.address); - const factory = await ethers.getContractFactory("PointSocial"); - let socialFactoryDeployer = factory.connect(addr1); - - await upgrades.upgradeProxy(pointSocial.address, socialFactoryDeployer); - }); - - it("Should not upgrade the proxy by a non-deployer", async function () { - await identityContract.setDevMode(true); - await identityContract.register( - handle, - owner.address, - '0xed17268897bbcb67127ed550cee2068a15fdb6f69097eebeb6e2ace46305d1ce', - '0xe1e032c91d4c8fe6bab1f198871dbafb8842f073acff8ee9b822f748b180d7eb'); - - const factory = await ethers.getContractFactory("PointSocial"); - let socialFactoryDeployer = factory.connect(addr1); - await expect( - upgrades.upgradeProxy(pointSocial.address, socialFactoryDeployer) - ).to.be.revertedWith('You are not a deployer of this identity'); - }); - + it('Should not upgrade the proxy by a non-deployer', async function () { + await identityContract.setDevMode(true); + await identityContract.register( + handle, + owner.address, + '0xed17268897bbcb67127ed550cee2068a15fdb6f69097eebeb6e2ace46305d1ce', + '0xe1e032c91d4c8fe6bab1f198871dbafb8842f073acff8ee9b822f748b180d7eb' + ); + + const factory = await ethers.getContractFactory('PointSocial'); + let socialFactoryDeployer = factory.connect(addr1); + await expect( + upgrades.upgradeProxy(pointSocial.address, socialFactoryDeployer) + ).to.be.revertedWith('ERROR_NOT_DEPLOYER'); }); - - /*describe("Testing migrator functions", function () { + }); + + /*describe("Testing migrator functions", function () { it("User can add migrator", async function () { await pointSocial.addMigrator( addr1.address @@ -162,245 +163,207 @@ describe("PointSocial contract", function () { }); });*/ - describe("Testing user functions", function () { - it("User can create post", async function () { - await pointSocial.addPost( - postContent, - postimage - ) - const post = await pointSocial.getPostById(1); + describe('Testing user functions', function () { + it('User can create post', async function () { + await pointSocial.addPost(postContent, postimage); + const post = await pointSocial.getPostById(1); - expect(post.contents).to.be.equal(postContent); - expect(post.image).to.be.equal(postimage); - }); + expect(post.contents).to.be.equal(postContent); + expect(post.image).to.be.equal(postimage); + }); - it("Get posts by owner", async function () { - await pointSocial.addPost( - postContent, - postimage - ) - const posts = await pointSocial.getAllPostsByOwner(owner.address); + it('Get posts by owner', async function () { + await pointSocial.addPost(postContent, postimage); + const posts = await pointSocial.getAllPostsByOwner(owner.address, []); - expect(posts[0].contents).to.be.equal(postContent); - expect(posts[0].image).to.be.equal(postimage); - }); - - it("Get more then one post by owner", async function () { - await pointSocial.addPost( - postContent, - postimage - ) + expect(posts[0].contents).to.be.equal(postContent); + expect(posts[0].image).to.be.equal(postimage); + }); - await pointSocial.addPost( - "0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + it('Get more then one post by owner', async function () { + await pointSocial.addPost(postContent, postimage); - const postsLength = await pointSocial.getAllPostsLength(); + await pointSocial.addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); - expect(postsLength.toString()).to.be.equal("2"); - }); - - it("Get paginated posts", async function () { - await pointSocial.addPost( - postContent, - postimage - ) + const postsLength = await pointSocial.getAllPostsLength(); - await pointSocial.addPost( - "0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + expect(postsLength.toString()).to.be.equal('2'); + }); - const paginatedPosts = await pointSocial.getPaginatedPostsByOwner(owner.address, "1", "10"); + it('Get paginated posts', async function () { + await pointSocial.addPost(postContent, postimage); - expect(paginatedPosts[0].contents).to.be.equal(postContent); - expect(paginatedPosts[0].image).to.be.equal(postimage); + await pointSocial.addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); - }); - - - it("Get paginated posts more pages", async function () { - await pointSocial.addPost( - postContent, - postimage - ) + const paginatedPosts = await pointSocial.getPaginatedPostsByOwner(owner.address, 10, []); - await pointSocial.addPost( - "0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + expect(paginatedPosts[1].contents).to.be.equal(postContent); + expect(paginatedPosts[1].image).to.be.equal(postimage); + }); - await pointSocial.connect(addr1).addPost( - "0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + it('Get paginated posts more pages', async function () { + await pointSocial.addPost(postContent, postimage); + + await pointSocial.addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + + await pointSocial + .connect(addr1) + .addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + + await pointSocial + .connect(addr1) + .addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + + await pointSocial + .connect(addr2) + .addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + + await pointSocial + .connect(addr2) + .addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + + const paginatedPosts = await pointSocial.getPaginatedPosts('10', ['1']); + + expect(paginatedPosts.length).to.be.equal(5); + }); - await pointSocial.connect(addr1).addPost( - "0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + it('get all posts', async () => { + await pointSocial + .connect(addr2) + .addPost( + '0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + const posts = await pointSocial.getAllPosts([]); + expect(posts.length).to.be.equal(1); + }); - await pointSocial.connect(addr2).addPost( - "0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + it('Add comments to posts', async function () { + await pointSocial.addPost(postContent, postimage); - await pointSocial.connect(addr2).addPost( - "0xdd5a0873f998fff6b00052b51d1662f2993b603d9837da33cbc281a06b9f3b55", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + await pointSocial.addCommentToPost( + '1', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); - const paginatedPosts = await pointSocial.getPaginatedPosts("1", "10"); - - expect(paginatedPosts.length).to.be.equal(5); - }); - - it("Add comments to posts", async function () { - await pointSocial.addPost( - postContent, - postimage - ) + const postCommentId = await pointSocial.commentIdsByPost(1, 0); + const postComments = await pointSocial.commentById(postCommentId); - await pointSocial.addCommentToPost( - "1", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) - - const postCommentId = await pointSocial.commentIdsByPost(1, 0); - const postComments = await pointSocial.commentById(postCommentId); + expect(postComments.contents).to.be.equal( + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + }); - expect(postComments.contents).to.be.equal("0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe"); - }); - - - it("Add dislike to posts", async () => { - await pointSocial.addPost( - postContent, - postimage - ) + it('Add dislike to posts', async () => { + await pointSocial.addPost(postContent, postimage); - await pointSocial.addDislikeToPost( - "1" - ) + await pointSocial.addDislikeToPost('1'); - await pointSocial.connect(addr2).addDislikeToPost( - "1" - ) - - const [post] = await pointSocial.getAllPosts(); + await pointSocial.connect(addr2).addDislikeToPost('1'); - expect(post.dislikesCount).to.be.equal(2); - }); + const [post] = await pointSocial.getAllPosts([]); - it("Add a dislike should remove user's like", async () => { - await pointSocial.addPost( - postContent, - postimage - ) + expect(post.dislikesCount).to.be.equal(2); + }); - await pointSocial.addLikeToPost( - "1" - ) + it("Add a dislike should remove user's like", async () => { + await pointSocial.addPost(postContent, postimage); - await pointSocial.addDislikeToPost( - "1" - ) + await pointSocial.addLikeToPost('1'); - const [post] = await pointSocial.getAllPosts(); + await pointSocial.addDislikeToPost('1'); - expect(post.likesCount).to.be.equal(0); - expect(post.dislikesCount).to.be.equal(1); - }); + const posts = await pointSocial.getAllPosts([]); + const [post] = posts; - it("Add a like should remove user's dislike", async () => { - await pointSocial.addPost( - postContent, - postimage - ) + expect(post.likesCount).to.be.equal(0); + expect(post.dislikesCount).to.be.equal(1); + }); - await pointSocial.addDislikeToPost( - "1" - ) + it("Add a like should remove user's dislike", async () => { + await pointSocial.addPost(postContent, postimage); - await pointSocial.addLikeToPost( - "1" - ) + await pointSocial.addDislikeToPost('1'); - const [post] = await pointSocial.getAllPosts(); + await pointSocial.addLikeToPost('1'); - expect(post.likesCount).to.be.equal(1); - expect(post.dislikesCount).to.be.equal(0); - }); - - it("Add like to posts", async function () { - await pointSocial.addPost( - postContent, - postimage - ) + const [post] = await pointSocial.getAllPosts([]); - await pointSocial.addLikeToPost( - "1" - ) + expect(post.likesCount).to.be.equal(1); + expect(post.dislikesCount).to.be.equal(0); + }); - await pointSocial.connect(addr2).addLikeToPost( - "1" - ) - - const posts = await pointSocial.getAllPosts(); + it('Add like to posts', async function () { + await pointSocial.addPost(postContent, postimage); - expect(posts[0].likesCount).to.be.equal(2); - }); - - it("Add like to posts twice same user", async function () { - await pointSocial.addPost( - postContent, - postimage - ) + await pointSocial.addLikeToPost('1'); - await pointSocial.connect(addr1).addLikeToPost( - 1 - ) + await pointSocial.connect(addr2).addLikeToPost('1'); - await pointSocial.connect(addr1).addLikeToPost( - 1 - ) + const posts = await pointSocial.getAllPosts([]); - await pointSocial.connect(addr2).addLikeToPost( - 1 - ) - //remove index? - - const postsByOwnerLenght = await pointSocial.getAllPostsByOwnerLength(owner.address); - const posts = await pointSocial.getAllPosts(); + expect(posts[0].likesCount).to.be.equal(2); + }); - expect(postsByOwnerLenght).to.be.equal(1); - expect(posts[0].likesCount).to.be.equal(1); - }); - - - - it("Add comments to posts", async function () { - await pointSocial.addPost( - postContent, - postimage - ) + it('Add like to posts twice same user', async function () { + await pointSocial.addPost(postContent, postimage); - await pointSocial.addCommentToPost( - "1", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) + await pointSocial.connect(addr1).addLikeToPost(1); - await pointSocial.addCommentToPost( - "1", - "0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe" - ) - - const postComments = await pointSocial.getAllCommentsForPost(1); + await pointSocial.connect(addr1).addLikeToPost(1); - expect(postComments[0].contents).to.be.equal("0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe"); - expect(postComments[1].contents).to.be.equal("0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe"); - }); + await pointSocial.connect(addr2).addLikeToPost(1); + //remove index? + + const postsByOwnerLenght = await pointSocial.getAllPostsByOwnerLength(owner.address, []); + const posts = await pointSocial.getAllPosts([]); + + expect(postsByOwnerLenght).to.be.equal(1); + expect(posts[0].likesCount).to.be.equal(1); }); -}); \ No newline at end of file + it('Add comments to posts', async function () { + await pointSocial.addPost(postContent, postimage); + + await pointSocial.addCommentToPost( + '1', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + + await pointSocial.addCommentToPost( + '1', + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + + const postComments = await pointSocial.getAllCommentsForPost(1); + + expect(postComments[0].contents).to.be.equal( + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + expect(postComments[1].contents).to.be.equal( + '0x0090916c0e6846d5dc8d22560e90782ded96e4efdeb53db214f612a54d4f5fbe' + ); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3a31b10 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "outDir": "dist" + }, + "exclude": ["dist", "node_modules"], + "include": ["./scripts", "./test"], + "files": ["./hardhat.config.ts"] +} \ No newline at end of file