contract AgoraS01 is AccessControl, EIP712, ERC721, ERC721Enumerable {  

    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
    bytes32 private constant _INTERFACE = keccak256("AgoraS01(address claimer,bool allowed,uint256 amount)");

    using Counters for Counters.Counter;
    Counters.Counter public _tokenIds;
    string public _tokenURI;

    uint256 public _deadline;
    address public _treasury;
    IERC20 public _token;

    constructor(IERC20 token) EIP712("AgoraS01", "1") ERC721("Agora Citizenship S01", "AS01") {

        _grantRole(DEFAULT_ADMIN_ROLE, 0x5581853b68b9EeE4f91dbA5Ad1202D9375AA5997);
        _grantRole(MINTER_ROLE, 0x6f22E55508Bb7cAB9E0E080D997D682F024844A1);
        _grantRole(BURNER_ROLE, 0x5581853b68b9EeE4f91dbA5Ad1202D9375AA5997);

        _deadline = 1640995200;
        _treasury = 0x41886617412d65AAB0Fe58e71946892e56D00CDa;
        _token = token;

    }

    function claim(uint256 amount, bytes calldata signature) external {

        // Check signature.
        bytes32 structHash = keccak256(abi.encode(_INTERFACE, msg.sender, true, amount));
        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, signature);
        require(hasRole(MINTER_ROLE, signer), "Invalid signature.");
        require(balanceOf(msg.sender) == 0, "Caller has already claimed.");

        // Mint the token.
        _tokenIds.increment();
        _safeMint(msg.sender, _tokenIds.current());

        // Transfer the tokens. (If it's before the deadline).
        if (block.timestamp < _deadline) {
            _token.transfer(msg.sender, amount);
        }

    }

    function claimRemainingTokens() external onlyTreasury {

        require(block.timestamp > _deadline);

        uint256 balance = _token.balanceOf(address(this));
        _token.transfer(_treasury, balance);

    }

    function burn(uint256 tokenId) external onlyRole(BURNER_ROLE) {

        require(block.timestamp > _deadline);
        _burn(tokenId);

    }

    function setDeadline(uint256 deadline) external onlyTreasury {

        require(block.timestamp < _deadline);
        _deadline = deadline;

    }

    function setTreasury(address treasury) external onlyTreasury {

        _treasury = treasury;

    }

    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {

        require(_exists(tokenId), "Token hasn't been minted yet.");
        return _tokenURI;

    }

    function setTokenURI(string memory newTokenURI) external onlyTreasury {

        _tokenURI = newTokenURI;

    }

    modifier onlyTreasury {

        require(msg.sender == _treasury, "Caller must be the treasury.");
        _;

    }

    // Overrides for transfer related functions.

    function approve(address, uint256) public virtual override {

        revert("Token is not transferrable.");

    }

    function setApprovalForAll(address, bool) public virtual override {

        revert("Token is not transferrable.");

    }

    function transferFrom(address, address, uint256) public virtual override {

        revert("Token is not transferrable.");

    }

    function safeTransferFrom(address, address, uint256) public virtual override {

        revert("Token is not transferrable.");

    }

    function safeTransferFrom(address, address, uint256, bytes memory) public virtual override {

        revert("Token is not transferrable.");

    }

    // Overrides for Solidity.

    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {

        return super._beforeTokenTransfer(from, to, tokenId);

    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(AccessControl, ERC721, ERC721Enumerable) returns (bool) {

        return super.supportsInterface(interfaceId);

    }

}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {

    return super._beforeTokenTransfer(from, to, tokenId);

}

// Overrides for Solidity.