// Contract: Web3MasonToken
// Target function: function _beforeTokenTransfer(
"),
     *     mailTo,
     *     keccak256(bytes(mailContents))
     * )));
     * address signer = ECDSA.recover(digest, signature);
     * ```
     */
    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
        return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);
    }
}


// Dependency file: @openzeppelin/contracts/utils/Counters.sol

// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)

// pragma solidity ^0.8.0;

/**
 * @title Counters
 * @author Matt Condon (@shrugs)
 * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number
 * of elements in a mapping, issuing ERC721 ids, or counting request ids.
 *
 * Include with `using Counters for Counters.Counter;`
 */
library Counters {
    struct Counter {
        // This variable should never be directly accessed by users of the library: interactions must be restricted to
        // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
        // this feature: see https://github.com/ethereum/solidity/issues/4637
        uint256 _value; // default: 0
    }

    function current(Counter storage counter) internal view returns (uint256) {
        return counter._value;
    }

    function increment(Counter storage counter) internal {
        unchecked {
            counter._value += 1;
        }
    }

    function decrement(Counter storage counter) internal {
        uint256 value = counter._value;
        require(value > 0, "Counter: decrement overflow");
        unchecked {
            counter._value = value - 1;
        }
    }

    function reset(Counter storage counter) internal {
        counter._value = 0;
    }
}


// Dependency file: @openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol

// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/draft-ERC20Permit.sol)

// pragma solidity ^0.8.0;

// import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
// import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
// import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
// import "@openzeppelin/contracts/utils/Counters.sol";

/**
 * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * _Available since v3.4._
 */
abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
    using Counters for Counters.Counter;

    mapping(address => Counters.Counter) private _nonces;

    // solhint-disable-next-line var-name-mixedcase
    bytes32 private constant _PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    /**
     * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.
     * However, to ensure consistency with the upgradeable transpiler, we will continue
     * to reserve a slot.
     * @custom:oz-renamed-from _PERMIT_TYPEHASH
     */
    // solhint-disable-next-line var-name-mixedcase
    bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;

    /**
     * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
     *
     * It's a good idea to use the same `name` that is defined as the ERC20 token name.
     */
    constructor(string memory name) EIP712(name, "1") {}

    /**
     * @dev See {IERC20Permit-permit}.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        require(signer == owner, "ERC20Permit: invalid signature");

        _approve(owner, spender, value);
    }

    /**
     * @dev See {IERC20Permit-nonces}.
     */
    function nonces(address owner) public view virtual override returns (uint256) {
        return _nonces[owner].current();
    }

    /**
     * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    /**
     * @dev "Consume a nonce": return the current value and increment.
     *
     * _Available since v4.1._
     */
    function _useNonce(address owner) internal virtual returns (uint256 current) {
        Counters.Counter storage nonce = _nonces[owner];
        current = nonce.current();
        nonce.increment();
    }
}


// Dependency file: contracts/interfaces/ITokenVesting.sol

// pragma solidity ^0.8.0;

interface ITokenVesting {
    struct VestingEntry {
        uint96 amount;
        uint64 start;
        uint32 lockup;
        uint32 cliff;
        uint32 vesting;
    }

    event AirdropperUpdated(address indexed account, bool status);
    event Airdrop(address indexed account, uint96 amount);
    event VestingEntryUpdated(
        address indexed account,
        uint96 amount,
        uint64 start,
        uint32 lockup,
        uint32 cliff,
        uint32 vesting
    );

    function setAirdropper(address account, bool status) external;

    function vestingOf(address account)
        external
        view
        returns (
            uint64 start,
            uint32 lockup,
            uint32 cliff,
            uint32 vesting,
            uint96 balance,
            uint96 vested,
            uint96 locked,
            uint96 unlocked
        );

    function airdrop(
        uint32 lockup,
        uint32 cliff,
        uint32 vesting,
        address account,
        uint96 amount
    ) external returns (bool);

    function airdropBatch(
        uint32 lockup,
        uint32 cliff,
        uint32 vesting,
        address[] memory accounts,
        uint96[] memory amounts
    ) external returns (bool);
}


// Dependency file: contracts/utils/TokenVesting.sol

// pragma solidity ^0.8.0;

// import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// import "@openzeppelin/contracts/access/Ownable.sol";
// import "contracts/interfaces/ITokenVesting.sol";

abstract contract TokenVesting is ERC20, Ownable, ITokenVesting {
    mapping(address => VestingEntry) private _vestingOf;
    mapping(address => bool) private _isAirdropper;

    function vestingOf(address account)
        external
        view
        override
        returns (
            uint64 start,
            uint32 lockup,
            uint32 cliff,
            uint32 vesting,
            uint96 balance,
            uint96 vested,
            uint96 locked,
            uint96 unlocked
        )
    {
        if (_isVested(account)) {
            start = _vestingOf[account].start;
            lockup = _vestingOf[account].lockup;
            cliff = _vestingOf[account].cliff;
            vesting = _vestingOf[account].vesting;
            vested = _vestingOf[account].amount;
            locked = uint96(_lockedOf(account));
        }
        balance = uint96(balanceOf(account));
        unlocked = balance - locked;
    }

    function _isVested(address account) private view returns (bool) {
        return _vestingOf[account].amount != 0;
    }

    function _lockedOf(address account) private view returns (uint256) {
        return
            _pureLockedOf(
                _vestingOf[account].lockup,
                _vestingOf[account].cliff,
                _vestingOf[account].vesting,
                uint64(block.timestamp) - _vestingOf[account].start,
                uint256(_vestingOf[account].amount)
            );
    }

    function _pureLockedOf(
        uint32 lockupTime,
        uint32 cliffTime,
        uint32 vestingTime,
        uint64 passedTime,
        uint256 amount
    ) private pure returns (uint256) {
        if (passedTime >= (lockupTime + vestingTime)) return 0;
        if (passedTime <= (lockupTime + cliffTime)) return amount;
        return amount - (amount * (passedTime - lockupTime)) / vestingTime;
    }

    // Airdrop with vesting
    function setAirdropper(address account, bool status)
        external
        override
        onlyOwner
    {
        _isAirdropper[account] = status;
        emit AirdropperUpdated(account, status);
    }

    modifier onlyAirdropper() {
        require(_isAirdropper[_msgSender()], "Caller is not allowed");
        _;
    }

    function airdrop(
        uint32 lockup,
        uint32 cliff,
        uint32 vesting,
        address account,
        uint96 amount
    ) external override onlyAirdropper returns (bool) {
        _airdrop(
            _msgSender(),
            uint64(block.timestamp),
            lockup,
            cliff,
            vesting,
            account,
            amount
        );
        return true;
    }

    function airdropBatch(
        uint32 lockup,
        uint32 cliff,
        uint32 vesting,
        address[] memory accounts,
        uint96[] memory amounts
    ) external override onlyAirdropper returns (bool) {
        require(accounts.length == amounts.length, "Mismatch");
        address from = _msgSender();
        uint64 start = uint64(block.timestamp);
        for (uint256 i = 0; i < accounts.length; i++) {
            _airdrop(
                from,
                start,
                lockup,
                cliff,
                vesting,
                accounts[i],
                amounts[i]
            );
        }
        return true;
    }

    function _airdrop(
        address from,
        uint64 start,
        uint32 lockup,
        uint32 cliff,
        uint32 vesting,
        address account,
        uint96 amount
    ) private {
        if (vesting != 0 || cliff != 0 || lockup != 0) {
            if (!_isVested(account)) {
                _vestingOf[account].start = start;
                _vestingOf[account].lockup = lockup;
                _vestingOf[account].cliff = cliff;
                _vestingOf[account].vesting = vesting;
            }
            _vestingOf[account].amount += amount;
            emit VestingEntryUpdated(
                account,
                _vestingOf[account].amount,
                _vestingOf[account].start,
                _vestingOf[account].lockup,
                _vestingOf[account].cliff,
                _vestingOf[account].vesting
            );
        }

        _transfer(from, account, amount);
        emit Airdrop(account, amount);
    }

    // The following functions are overrides required by Solidity.
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        if (_isVested(from)) {
            uint256 lockedAmount = _lockedOf(from);
            if (lockedAmount == 0) {
                delete _vestingOf[from];
            } else {
                require(
                    (balanceOf(from) - amount) >= lockedAmount,
                    "Transfer amount exceeds unlocked amount"
                );
            }
        }
        super._beforeTokenTransfer(from, to, amount);
    }
}


// Dependency file: contracts/interfaces/IRecoverable.sol

// pragma solidity ^0.8.0;

interface IRecoverable {
    event RecoveredFunds(
        address indexed token,
        uint256 amount,
        address indexed recipient
    );

    function recoverFunds(
        address token,
        uint256 amount,
        address recipient
    ) external returns (bool);
}


// Dependency file: contracts/utils/Recoverable.sol

// pragma solidity ^0.8.0;

// import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// import "@openzeppelin/contracts/access/Ownable.sol";
// import "contracts/interfaces/IRecoverable.sol";

/**
 @dev The contract is intendent to help recovering arbitrary ERC20 tokens and ETH accidentally transferred to the contract address
 */
abstract contract Recoverable is Ownable, IRecoverable {
    function _getRecoverableAmount(address token)
        internal
        view
        virtual
        returns (uint256)
    {
        if (token == address(0)) return address(this).balance;
        else return IERC20(token).balanceOf(address(this));
    }

    /**
     @param token ERC20 token's address to recover or address(0) to recover ETH
     @param amount to recover from contract's address
     @param recipient address to receive tokens from the contract
     */
    function recoverFunds(
        address token,
        uint256 amount,
        address recipient
    ) external override onlyOwner returns (bool) {
        uint256 recoverableAmount = _getRecoverableAmount(token);
        require(
            amount <= recoverableAmount,
            "Recoverable: RECOVERABLE_AMOUNT_NOT_ENOUGH"
        );
        if (token == address(0)) _transferEth(amount, recipient);
        else _transferErc20(token, amount, recipient);
        emit RecoveredFunds(token, amount, recipient);
        return true;
    }

    function _transferEth(uint256 amount, address recipient) private {
        address payable toPayable = payable(recipient);
        toPayable.transfer(amount);
    }

    function _transferErc20(
        address token,
        uint256 amount,
        address recipient
    ) private {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(0xa9059cbb, recipient, amount)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "Recoverable: TRANSFER_FAILED"
        );
    }
}


// Dependency file: contracts/interfaces/ITransferBatch.sol

// pragma solidity ^0.8.0;

interface ITransferBatch {
    function transferBatch(address[] memory accounts, uint256[] memory amounts)
        external
        returns (bool);

    function transferBatchFrom(
        address from,
        address[] memory accounts,
        uint256[] memory amounts
    ) external returns (bool);
}


// Dependency file: contracts/utils/TransferBatch.sol

// pragma solidity ^0.8.0;

// import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// import "contracts/interfaces/ITransferBatch.sol";

abstract contract TransferBatch is ERC20, ITransferBatch {
    modifier checkMismatch(
        address[] memory accounts,
        uint256[] memory amounts
    ) {
        require(accounts.length == amounts.length, "Mismatch");
        _;
    }

    function transferBatch(address[] memory accounts, uint256[] memory amounts)
        external
        override
        checkMismatch(accounts, amounts)
        returns (bool)
    {
        address from = _msgSender();
        for (uint256 i = 0; i < accounts.length; i++) {
            _transfer(from, accounts[i], amounts[i]);
        }
        return true;
    }

    function transferBatchFrom(
        address from,
        address[] memory accounts,
        uint256[] memory amounts
    ) external override checkMismatch(accounts, amounts) returns (bool) {
        address spender = _msgSender();
        for (uint256 i = 0; i < accounts.length; i++) {
            _spendAllowance(from, spender, amounts[i]);
            _transfer(from, accounts[i], amounts[i]);
        }
        return true;
    }
}


// Root file: contracts/Web3MasonToken.sol

pragma solidity 0.8.17;

// import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// import "@openzeppelin/contracts/access/Ownable.sol";
// import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
// import "contracts/utils/TokenVesting.sol";
// import "contracts/utils/Recoverable.sol";
// import "contracts/utils/TransferBatch.sol";

/// @custom:security-contact support@web3mason.io
contract Web3MasonToken is
    ERC20,
    Ownable,
    ERC20Permit,
    TokenVesting,
    Recoverable,
    TransferBatch
{
    constructor() ERC20("Web3Mason", "W3M") ERC20Permit("Web3Mason") {
        _mint(_msgSender(), 100_000_000_000 * 10**decimals());
    }