diff --git a/web3backend/contracts/writersDAO.sol b/web3backend/contracts/writersDAO.sol new file mode 100644 index 0000000..1bf6cd9 --- /dev/null +++ b/web3backend/contracts/writersDAO.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface ISoulNft { + function balanceOf(address owner) external view returns (uint256); + + function burn(uint256 tokenId) external; + + function showIds(address _member) external view returns (uint256); + + function showMembers() external view returns (address[] memory); +} + +contract WritersDAO { + + struct Writer{ + uint256 id_; + address owner; + string title; + string content; + uint yayvotes; + uint nayvotes; + Status status; + } + + +enum Status { + Pending, + Approved +} + uint256 public id; + uint256 votingTime; + uint256 _time; + address admin; + ISoulNft soulnft; + + struct DAOTime { + uint256 daovotetime; + } + + enum Votes { + YAY, + NAY + } + + constructor(address _address, address _writersDAOToken) { + admin = _address; + soulnft = ISoulNft(_writersDAOToken); + votingTime = 1 days; + } + + mapping(uint256 => Writer) _writer; + mapping(address => uint256[]) usersPost; + mapping(uint256 => DAOTime) public daotime; + mapping(address => uint256) memberVotes; + mapping(address => mapping(uint256 => bool)) public hasVoted; + + error AlreadyVoted(); + error VotingTimeElapsed(); + error NotDAOMember(); + error OnlyAdmin(); + error NotYetTime(); + error VotingInProgress(); + + event CreatePost(uint256 _id, string _title, string _content); + event Vote(address member, uint256 _id); + event ApproveImpact(uint256 _id); + + function createPost(string memory _title, string memory _content) public returns (uint256 _id) { + id++; + _id = id; + if (_id == 1) { + _time = block.timestamp + 30 days; + } + Writer storage writer = _writer[_id]; + DAOTime storage time = daotime[_id]; + write.id_ = _id; + write.owner = msg.sender; + write.title = _title; + write.content = _content; + write.status = Status.Pending; + + usersPost[msg.sender].push(_id); + emit CreatePost(_id, _title, _content); + } + + function vote(uint256 _id, Votes votes) external { + if (soulnft.balanceOf(msg.sender) != 1) revert NotDAOMember(); + Writer storage writer = _writer[_id]; + if (hasVoted[msg.sender][_id] != false) revert AlreadyVoted(); + if (block.timestamp > daotime[_id].daovotetime) { + revert VotingTimeElapsed(); + } + hasVoted[msg.sender][_id] = true; + + uint8 numVotes = 1; + if (votes == Votes.YAY) { + writer.yayvotes += numVotes; + } else { + writer.nayvotes += numVotes; + } + memberVotes[msg.sender]++; + + emit Vote(msg.sender, _id); + } + + function approveImpact(uint256 _id) external { + if (soulnft.balanceOf(msg.sender) != 1) revert NotDAOMember(); + if (daotime[_id].daovotetime > block.timestamp) { + revert VotingInProgress(); + } + Writer storage writer = _writer[_id]; + if (writer.yayvotes > writer.nayvotes) { + writer.status = Status.Approved; + } + + emit ApproveImpact(_id); + } + + +} \ No newline at end of file diff --git a/web3backend/contracts/writersDAOToken.sol b/web3backend/contracts/writersDAOToken.sol new file mode 100644 index 0000000..bc54fd7 --- /dev/null +++ b/web3backend/contracts/writersDAOToken.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; + +/// @title WildLifeGuardianToken - A unique NFT contract for Wildlife Guardians. + +contract writersDAOToken is ERC721, ERC721URIStorage, Ownable { + uint256 private _tokenIdCounter; + bytes32 public rootHash; + string tokenUri; + + address[] public members; + + error InvalidAddress(address); + error AlreadyClaimed(); + error NotWhitelisted(); + + /// @dev Mapping to keep track of claimed tokens. + + mapping(address => bool) claimed; + mapping(address => uint256) addressToIds; + + /// @notice Constructor to initialize the contract. + /// @param _owner The address of the contract owner. + /// @param _tokenUri The token URI for the new tokens. + constructor(address _owner, string memory _tokenUri) ERC721("Impact Token", "ITK") Ownable(_owner) { + tokenUri = _tokenUri; + } + + /// @notice Safely mints new tokens and assigns them to specified addresses. + /// @param to An array of addresses to receive the newly minted tokens. + + function safeMint(address[] calldata to) public onlyOwner { + string memory URI = tokenUri; + for (uint256 i = 0; i < to.length; ++i) { + if (to[i] == address(0)) { + revert InvalidAddress(to[i]); + } + if (balanceOf(to[i]) == 0) { + _setTokenURI(_tokenIdCounter, URI); + _safeMint(to[i], _tokenIdCounter); + members.push(to[i]); + addressToIds[to[i]] = _tokenIdCounter; + _tokenIdCounter++; + } else { + continue; + } + } + } + + /// @notice Claims a token for an address using a Merkle proof. + /// @param _merkleProof The Merkle proof to verify the claim. + /// @param _account The address claiming the token. + /// @return true if the claim is successful, false otherwise. + + function claimToken(bytes32[] calldata _merkleProof, address _account) external returns (bool) { + require(_account == msg.sender, "Only owner of account can claim"); + require(balanceOf(_account) == 0, "You already own an nft"); + if (claimed[_account]) { + revert AlreadyClaimed(); + } + bytes32 leaf = keccak256(abi.encodePacked(_account, uint256(1))); + if (!MerkleProof.verify(_merkleProof, rootHash, leaf)) { + revert NotWhitelisted(); + } + + claimed[_account] = true; + _setTokenURI(_tokenIdCounter, tokenUri); + _safeMint(_account, _tokenIdCounter); + _tokenIdCounter++; + + return true; + } + + /// @notice Adds a new Merkle root hash for token whitelisting. + /// @param _rootHash The new Merkle root hash. + + function addRootHash(bytes32 _rootHash) external onlyOwner { + rootHash = _rootHash; + } + + /// @notice Burns a token by its ID. + /// @param tokenId The ID of the token to burn. + + function burn(uint256 tokenId) external { + _burn(tokenId); + } + + function showIds(address _member) public view returns (uint256) { + return addressToIds[_member]; + } + + function showMembers() external view returns (address[] memory) { + return members; + } + + // function overrides + + /// @dev Overrides the transferFrom function to disable transfers. + + function transferFrom(address from, address to, uint256 tokenId) public pure override(ERC721, IERC721) { + (from, to, tokenId); + revert("SoulBoundToken: transfer is disabled"); + } + + /// @inheritdoc ERC721 + + function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { + return super.tokenURI(tokenId); + } + + /// @inheritdoc ERC721 + + function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) { + return super.supportsInterface(interfaceId); + } +}