# Base Learn Roles - 5

*Base Learn Supreme Role Contract Codes*

By [timurlenk](https://paragraph.com/@timurlenk) · 2025-09-21

base

---

### SCD ERC721 Pin NFT Contract Code:

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.23;
    
    import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
    
    contract Submission is ERC721 {
        struct Haiku {
            address author;
            string line1;
            string line2;
            string line3;
        }
    
        uint public counter;
        mapping(uint256 => Haiku) private haikus;
        mapping(bytes32 => bool) private line1Hashes;
        mapping(address => uint256[]) private sharedHaikus;
    
        error HaikuNotUnique();
        error NotYourHaiku(uint256 tokenId);
        error NoHaikusShared();
    
        constructor() ERC721("HaikuNFT", "HNK") {}
    
        function mintHaiku(
            string memory _line1,
            string memory _line2,
            string memory _line3
        ) external {
            bytes32 line1Hash = keccak256(abi.encodePacked(_line1));
            if (line1Hashes[line1Hash]) {
                revert HaikuNotUnique();
            }
            line1Hashes[line1Hash] = true;
    
            uint256 id = counter;
            haikus[id] = Haiku(msg.sender, _line1, _line2, _line3);
            _mint(msg.sender, id);
            counter++;
        }
    
        function shareHaiku(uint256 _id, address _to) external {
            if (ownerOf(_id) != msg.sender) {
                revert NotYourHaiku(_id);
            }
            sharedHaikus[_to].push(_id);
        }
    
        function getMySharedHaikus() external view returns (Haiku[] memory) {
            uint256[] memory ids = sharedHaikus[msg.sender];
            if (ids.length == 0) {
                revert NoHaikusShared();
            }
    
            Haiku[] memory result = new Haiku[](ids.length);
            for (uint256 i = 0; i < ids.length; i++) {
                result[i] = haikus[ids[i]];
            }
            return result;
        }
    }
    

### Minimal Token Pin NFT Contract Code:

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.23;
    
    contract Submission {
        mapping(address => uint256) public balances;
        mapping(address => bool) private hasClaimed;
        uint256 public totalClaimed;
    
        error TokensClaimed();
        error UnsafeTransfer(address to);
        error InsufficientBalance(uint256 available, uint256 required);
    
        function claim() external {
            if (hasClaimed[msg.sender]) {
                revert TokensClaimed();
            }
            hasClaimed[msg.sender] = true;
            balances[msg.sender] += 1000;
            totalClaimed += 1000;
        }
    
        function safeTransfer(address _to, uint256 _amount) external {
            if (_to == address(0)) {
                revert UnsafeTransfer(_to);
            }
    
            if (_to.code.length > 0) {
                if (_to.balance == 0) {
                    revert UnsafeTransfer(_to);
                }
            }
    
            if (balances[msg.sender] < _amount) {
                revert InsufficientBalance(balances[msg.sender], _amount);
            }
    
            balances[msg.sender] -= _amount;
            balances[_to] += _amount;
        }
    
        function totalSupply() external view returns (uint256) {
            return totalClaimed;
        }
    }
    
    

### ERC20 Pin NFT Contract Code:

    // SPDX-License-Identifier: UNLICENSED
    pragma solidity ^0.8.23;
    
    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
    
    contract Submission is ERC20 {
        using EnumerableSet for EnumerableSet.AddressSet;
    
        struct Issue {
            EnumerableSet.AddressSet voters;
            string issueDesc;
            uint256 votesFor;
            uint256 votesAgainst;
            uint256 votesAbstain;
            uint256 totalVotes;
            uint256 quorum;
            bool passed;
            bool closed;
        }
    
        struct ReturnableIssue {
            address[] voters;
            string issueDesc;
            uint256 votesFor;
            uint256 votesAgainst;
            uint256 votesAbstain;
            uint256 totalVotes;
            uint256 quorum;
            bool passed;
            bool closed;
        }
    
        enum Vote {
            AGAINST,
            FOR,
            ABSTAIN
        }
    
        mapping(address => bool) private hasClaimed;
        Issue[] private issues;
    
        error TokensClaimed();
        error NoTokensHeld();
        error AlreadyVoted();
    
        constructor() ERC20("VoteToken", "VTK") {}
    
        function claim() external {
            if (hasClaimed[msg.sender]) {
                revert TokensClaimed();
            }
            hasClaimed[msg.sender] = true;
            _mint(msg.sender, 100);
        }
    
        function createIssue(
            string memory _issueDesc,
            uint256 _quorum
        ) external returns (uint256) {
            if (balanceOf(msg.sender) == 0) {
                revert NoTokensHeld();
            }
            Issue storage newIssue = issues.push();
            newIssue.issueDesc = _issueDesc;
            newIssue.quorum = _quorum;
            return issues.length - 1;
        }
    
        function getIssue(
            uint256 _id
        ) external view returns (ReturnableIssue memory) {
            Issue storage issue = issues[_id];
            return
                ReturnableIssue(
                    issue.voters.values(),
                    issue.issueDesc,
                    issue.votesFor,
                    issue.votesAgainst,
                    issue.votesAbstain,
                    issue.totalVotes,
                    issue.quorum,
                    issue.passed,
                    issue.closed
                );
        }
    
        function vote(uint256 _issueId, Vote _vote) external {
            Issue storage issueToVote = issues[_issueId];
            if (issueToVote.voters.contains(msg.sender)) {
                revert AlreadyVoted();
            }
    
            uint256 voteWeight = balanceOf(msg.sender);
            issueToVote.voters.add(msg.sender);
    
            if (_vote == Vote.FOR) {
                issueToVote.votesFor += voteWeight;
            } else if (_vote == Vote.AGAINST) {
                issueToVote.votesAgainst += voteWeight;
            } else {
                issueToVote.votesAbstain += voteWeight;
            }
    
            issueToVote.totalVotes += voteWeight;
    
            if (issueToVote.totalVotes >= issueToVote.quorum) {
                issueToVote.closed = true;
                if (issueToVote.votesFor > issueToVote.votesAgainst) {
                    issueToVote.passed = true;
                }
            }
        }
    }

---

*Originally published on [timurlenk](https://paragraph.com/@timurlenk/base-learn-roles-5)*
