Skip to content

Instantly share code, notes, and snippets.

@AllenAJ
Created September 3, 2023 18:58
Show Gist options
  • Save AllenAJ/6e1c9d327bd7bed5e64862367fbc271a to your computer and use it in GitHub Desktop.
Save AllenAJ/6e1c9d327bd7bed5e64862367fbc271a to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
contract DigitalMarketplace is Ownable {
using Counters for Counters.Counter;
using EnumerableSet for EnumerableSet.UintSet;
enum ListingStatus { Active, Inactive, Sold, Reported, Resolved, Auction }
struct Listing {
uint256 id;
address seller;
uint256 tokenId;
uint256 price;
uint256 timestamp;
string title;
string category;
string description;
EnumerableSet.AddressSet likes;
uint256 highestBid;
address highestBidder;
uint256 auctionEndTime;
uint256 minBidIncrement;
bool active;
}
struct User {
uint256 reputation;
EnumerableSet.UintSet purchasedTokens;
EnumerableSet.UintSet likedListings;
EnumerableSet.UintSet reportedListings;
bool isBlocked;
mapping(address => uint256) ratings;
}
struct Comment {
address author;
string text;
}
struct Report {
address reporter;
uint256 listingId;
string reason;
bool resolved;
}
IERC721Enumerable public nftContract;
Counters.Counter private listingIdCounter;
Counters.Counter private reportIdCounter;
mapping(uint256 => Listing) private listings;
mapping(address => User) private users;
mapping(uint256 => ListingStatus) public listingStatus;
mapping(uint256 => Comment[]) public listingComments;
mapping(uint256 => Report) public listingReports;
mapping(address => uint256[]) public userPurchaseHistories;
event ListingCreated(uint256 indexed id, address indexed seller, uint256 tokenId, uint256 price, uint256 timestamp, string title, string category, string description, ListingStatus status);
event ListingSold(uint256 indexed id, address indexed seller, address indexed buyer, uint256 tokenId, uint256 price, ListingStatus status);
event UserReputationUpdated(address indexed user, uint256 newReputation);
event FundsWithdrawn(address indexed user, uint256 amount);
event ListingLiked(uint256 indexed id, address indexed user);
event ListingCommented(uint256 indexed id, address indexed user, string comment);
event ListingStatusUpdated(uint256 indexed id, ListingStatus status);
event CommentAdded(uint256 indexed id, address indexed author, string text);
event ListingReported(uint256 indexed reportId, address indexed reporter, uint256 indexed listingId, string reason);
event ReportResolved(uint256 indexed reportId);
event PurchaseHistoryUpdated(address indexed user, uint256[] purchasedListings);
event UserBlocked(address indexed user, bool blocked);
event ListingBidPlaced(uint256 indexed id, address indexed bidder, uint256 bidAmount);
event ListingAuctionEnded(uint256 indexed id, address indexed winner, uint256 winningBid);
mapping(address => bool) public admins;
uint256 public feePercentage = 2;
modifier onlyAdmin() {
require(msg.sender == owner() || isAdmin(msg.sender), "Only admin can perform this action");
_;
}
modifier onlyValidToken(uint256 _tokenId) {
require(nftContract.ownerOf(_tokenId) == msg.sender, "You don't own this token");
_;
}
modifier onlySeller(uint256 _listingId) {
require(listings[_listingId].seller == msg.sender, "Only the seller can perform this action");
_;
}
modifier onlyValidListing(uint256 _listingId) {
require(listingStatus[_listingId] == ListingStatus.Active, "Listing is inactive");
_;
}
modifier notBlocked() {
require(!users[msg.sender].isBlocked, "Your account is blocked");
_;
}
constructor(address _nftContractAddress) {
nftContract = IERC721Enumerable(_nftContractAddress);
admins[msg.sender] = true;
}
function addAdmin(address _adminAddress) external onlyOwner {
admins[_adminAddress] = true;
}
function removeAdmin(address _adminAddress) external onlyOwner {
admins[_adminAddress] = false;
}
function isAdmin(address _address) public view returns (bool) {
return admins[_address];
}
function createUserReputation(address _user, uint256 _reputation) external onlyAdmin {
users[_user].reputation = _reputation;
emit UserReputationUpdated(_user, _reputation);
}
function blockUser(address _user, bool _blocked) external onlyAdmin {
users[_user].isBlocked = _blocked;
emit UserBlocked(_user, _blocked);
}
function setFeePercentage(uint256 _percentage) external onlyOwner {
require(_percentage <= 5, "Fee percentage cannot exceed 5%");
feePercentage = _percentage;
}
function createListing(uint256 _tokenId, uint256 _price, string memory _title, string memory _category, string memory _description) external onlyValidToken(_tokenId) notBlocked {
require(_price > 0, "Price must be greater than 0");
require(!listings[_tokenId].active, "Token is already listed");
listingIdCounter.increment();
uint256 listingId = listingIdCounter.current();
Listing storage newListing = listings[listingId];
newListing.id = listingId;
newListing.seller = msg.sender;
newListing.tokenId = _tokenId;
newListing.price = _price;
newListing.timestamp = block.timestamp;
newListing.title = _title;
newListing.description = _description;
newListing.category = _category;
newListing.active = true; // Mark the listing as active
listingStatus[listingId] = ListingStatus.Active;
emit ListingCreated(listingId, msg.sender, _tokenId, _price, block.timestamp, _title, _category, _description, ListingStatus.Active);
}
function buyListing(uint256 _listingId) external payable onlyValidListing(_listingId) notBlocked {
Listing storage listing = listings[_listingId];
require(msg.value == listing.price, "Incorrect amount sent");
address seller = listing.seller;
address buyer = msg.sender;
uint256 tokenId = listing.tokenId;
uint256 price = listing.price;
listing.active = false;
listingStatus[_listingId] = ListingStatus.Sold;
users[seller].reputation += 1;
users[buyer].reputation += 1;
users[buyer].purchasedTokens.add(tokenId);
uint256 feeAmount = (price * feePercentage) / 100;
uint256 sellerAmount = price - feeAmount;
payable(owner()).transfer(feeAmount);
payable(seller).transfer(sellerAmount);
nftContract.safeTransferFrom(seller, buyer, tokenId);
emit ListingSold(_listingId, seller, buyer, tokenId, price, ListingStatus.Sold);
emit UserReputationUpdated(seller, users[seller].reputation);
emit UserReputationUpdated(buyer, users[buyer].reputation);
updatePurchaseHistory(buyer, _listingId);
}
function likeListing(uint256 _listingId) external onlyValidListing(_listingId) notBlocked {
Listing storage listing = listings[_listingId];
require(!EnumerableSet.contains(listing.likes, msg.sender), "User has already liked this listing");
EnumerableSet.add(listing.likes, msg.sender);
EnumerableSet.add(users[msg.sender].likedListings, _listingId);
emit ListingLiked(_listingId, msg.sender);
}
function commentOnListing(uint256 _listingId, string memory _comment) external onlyValidListing(_listingId) notBlocked {
Listing storage listing = listings[_listingId];
listingComments[_listingId].push(Comment(msg.sender, _comment));
emit ListingCommented(_listingId, msg.sender, _comment);
}
function withdrawFunds(uint256 _amount) external {
require(_amount > 0, "Withdrawal amount must be greater than 0");
require(address(this).balance >= _amount, "Insufficient contract balance");
payable(msg.sender).transfer(_amount);
emit FundsWithdrawn(msg.sender, _amount);
}
function updateListingStatus(uint256 _listingId, ListingStatus _status) external onlySeller(_listingId) {
require(_status == ListingStatus.Inactive || _status == ListingStatus.Active || _status == ListingStatus.Auction, "Invalid listing status");
require(listingStatus[_listingId] != ListingStatus.Sold, "Cannot update status of a sold listing");
listingStatus[_listingId] = _status;
emit ListingStatusUpdated(_listingId, _status);
}
function addComment(uint256 _listingId, string memory _comment) external onlyValidListing(_listingId) notBlocked {
listingComments[_listingId].push(Comment(msg.sender, _comment));
emit CommentAdded(_listingId, msg.sender, _comment);
}
function hasUserLikedListing(uint256 _listingId, address _user) internal view returns (bool) {
return users[_user].likedListings.contains(_listingId);
}
function reportListing(uint256 _listingId, string memory _reason) external onlyValidListing(_listingId) notBlocked {
reportIdCounter.increment();
uint256 reportId = reportIdCounter.current();
listingReports[reportId] = Report(msg.sender, _listingId, _reason, false);
users[msg.sender].reportedListings.add(_listingId);
listingStatus[_listingId] = ListingStatus.Reported;
emit ListingReported(reportId, msg.sender, _listingId, _reason);
}
function resolveReport(uint256 _reportId) external onlyAdmin {
require(!listingReports[_reportId].resolved, "Report is already resolved");
listingReports[_reportId].resolved = true;
uint256 listingId = listingReports[_reportId].listingId;
listingStatus[listingId] = ListingStatus.Resolved;
emit ReportResolved(_reportId);
}
function updatePurchaseHistory(address _user, uint256 _listingId) internal {
userPurchaseHistories[_user].push(_listingId);
emit PurchaseHistoryUpdated(_user, userPurchaseHistories[_user]);
}
function getUserPurchaseHistory(address _user) external view returns (uint256[] memory) {
return userPurchaseHistories[_user];
}
function placeBid(uint256 _listingId, uint256 _bidAmount) external onlyValidListing(_listingId) notBlocked {
Listing storage listing = listings[_listingId];
require(listingStatus[_listingId] == ListingStatus.Auction, "Listing is not in auction mode");
require(_bidAmount > listing.highestBid, "Bid must be higher than the current highest bid");
require(msg.sender != listing.seller, "Seller cannot place bids");
if (listing.highestBidder != address(0)) {
payable(listing.highestBidder).transfer(listing.highestBid);
}
listing.highestBid = _bidAmount;
listing.highestBidder = msg.sender;
emit ListingBidPlaced(_listingId, msg.sender, _bidAmount);
}
function endAuction(uint256 _listingId) external onlySeller(_listingId) notBlocked {
Listing storage listing = listings[_listingId];
require(listingStatus[_listingId] == ListingStatus.Auction, "Listing is not in auction mode");
require(block.timestamp >= listing.auctionEndTime, "Auction has not ended yet");
address winner = listing.highestBidder;
uint256 winningBid = listing.highestBid;
listing.active = false;
listingStatus[_listingId] = ListingStatus.Sold;
users[listing.seller].reputation += 1;
users[winner].reputation += 1;
users[winner].purchasedTokens.add(listing.tokenId);
uint256 feeAmount = (winningBid * feePercentage) / 100;
uint256 sellerAmount = winningBid - feeAmount;
payable(owner()).transfer(feeAmount);
payable(listing.seller).transfer(sellerAmount);
nftContract.safeTransferFrom(listing.seller, winner, listing.tokenId);
emit ListingAuctionEnded(_listingId, winner, winningBid);
emit UserReputationUpdated(listing.seller, users[listing.seller].reputation);
emit UserReputationUpdated(winner, users[winner].reputation);
updatePurchaseHistory(winner, _listingId);
}
function setAuctionDetails(uint256 _listingId, uint256 _auctionEndTime, uint256 _minBidIncrement) external onlySeller(_listingId) notBlocked {
Listing storage listing = listings[_listingId];
require(listingStatus[_listingId] == ListingStatus.Active, "Listing is not active");
require(_auctionEndTime > block.timestamp, "Auction end time must be in the future");
require(_minBidIncrement > 0, "Minimum bid increment must be greater than 0");
listing.auctionEndTime = _auctionEndTime;
listing.minBidIncrement = _minBidIncrement;
listingStatus[_listingId] = ListingStatus.Auction;
}
// Additional helper functions to access listing information
function getListing(uint256 _listingId) external view returns (Listing memory) {
return listings[_listingId];
}
function getListingLikes(uint256 _listingId) external view returns (address[] memory) {
EnumerableSet.AddressSet storage likes = listings[_listingId].likes;
address[] memory likesArray = new address[](likes.length());
for (uint256 i = 0; i < likes.length(); i++) {
likesArray[i] = likes.at(i);
}
return likesArray;
}
function getListingCommentCount(uint256 _listingId) external view returns (uint256) {
return listingComments[_listingId].length;
}
function getListingComment(uint256 _listingId, uint256 _index) external view returns (Comment memory) {
require(_index < listingComments[_listingId].length, "Comment index out of range");
return listingComments[_listingId][_index];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment