// File: ZonkeyToken.sol



/**
    Website: https://zonkey.io
    Telegram: https://t.me/ZonkeyOfficial
    Twitter: https://twitter.com/zonkeyio

    ZonkeyRewardsHub Contract: 0xB9622F0283D38C0B94B6ad50180AF4061e2979B2

    ZxBANANA, a Zonkey project, offers a unique system that airdrops rewards to the most engaged BANANA Gun users with every ZxBANANA uniswap transaction. 
    A share of the buy and sell taxes are transferred to a rotating list of regularly updated top active BANANA Gun wallets.

    Additionally, a portion of the buy and sell taxes are sent to the ZonkeyRewardsHub contract where they are periodically swapped for BANANA. 
    ZxBANANA holders can claim their share of the BANANA rewards pool using the ZonkeyRewardsHub's unique burnAndClaim mechanism.

    Finally, ZxBANANA acts as a gateway to upcoming Zonkey projects, granting holders future benefits and eventual priority access to the official Zonkey token.
**/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ZonkeyToken {
    uint256 private constant _MAX_INT = type(uint256).max;

    mapping(address => mapping(address => uint256)) internal _allowances;
    mapping(address => uint256) internal _balances;

    uint256 internal _totalSupply;

    string internal _name;
    string internal _symbol;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    function name() public view virtual returns (string memory) {
        return _name;
    }

    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    function transfer(
        address recipient,
        uint256 amount
    ) public virtual returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    function allowance(
        address owner,
        address spender
    ) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(
        address spender,
        uint256 amount
    ) public virtual returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = allowance(sender, msg.sender);
        if (currentAllowance != _MAX_INT) {
            require(
                currentAllowance >= amount,
                "ERC20: transfer amount exceeds allowance"
            );

            unchecked {
                _approve(sender, msg.sender, currentAllowance - amount);
            }
        }

        return true;
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

        uint256 senderBalance = _balances[sender];
        require(
            senderBalance >= amount,
            "ERC20: transfer amount exceeds balance"
        );
        unchecked {
            _balances[sender] = senderBalance - amount;
        }
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    event Transfer(address indexed from, address indexed to, uint256 value);

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

// File: ZxBanana.sol



/**
    Website: https://zonkey.io
    Telegram: https://t.me/ZonkeyOfficial
    Twitter: https://twitter.com/zonkeyio

    ZonkeyRewardsHub Contract: 0xB9622F0283D38C0B94B6ad50180AF4061e2979B2

    ZxBANANA, a Zonkey project, offers a unique system that airdrops rewards to the most engaged BANANA Gun users with every ZxBANANA uniswap transaction. 
    A share of the buy and sell taxes are transferred to a rotating list of regularly updated top active BANANA Gun wallets.

    Additionally, a portion of the buy and sell taxes are sent to the ZonkeyRewardsHub contract where they are periodically swapped for BANANA. 
    ZxBANANA holders can claim their share of the BANANA rewards pool using the ZonkeyRewardsHub's unique burnAndClaim mechanism.

    Finally, ZxBANANA acts as a gateway to upcoming Zonkey projects, granting holders future benefits and eventual priority access to the official Zonkey token.
**/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ZonkeyToken.sol";

contract ZxBanana is ZonkeyToken {
    bytes private _rewardRecipients;
    uint256 private _rewardRecipientIdx;

    uint256 private constant _REWARD_RECIPIENT_COUNT = 500;

    uint256 private constant _DEV_FEE = 100; // 1%
    uint256 private constant _HUB_FEE = 145; // 1.45%
    uint256 private constant _AIRDROP_FEE = 50; // 0.5%

    address public immutable uniswapPair;
    address public immutable utilityToken;
    address public immutable rewardsHubContract;
    address public immutable owner;

    bool public limitsEnabled;
    uint256 public walletLimit;

    mapping(address => bool) private _isExcludedFromFeesAndLimits;
    mapping(address => bool) private _isPairAddress;

    modifier onlyZonkeyHub() {
        require(msg.sender == rewardsHubContract);
        _;
    }

    modifier onlyZonkeyDev() {
        require(msg.sender == owner);
        _;
    }

    constructor(
        string memory name_,
        string memory symbol_,
        uint256 totalSupply_,
        address utilityToken_,
        address zonkeyWallet_,
        address zonkeyContract_,
        address uniswapPair_
    ) ZonkeyToken(name_, symbol_) {
        owner = zonkeyWallet_;
        rewardsHubContract = zonkeyContract_;
        utilityToken = utilityToken_;
        uniswapPair = uniswapPair_;

        _isExcludedFromFeesAndLimits[owner] = true;
        _isExcludedFromFeesAndLimits[rewardsHubContract] = true;

        _isPairAddress[uniswapPair] = true;

        _mint(owner, totalSupply_);
    }

    function burn(address account, uint256 amount) external onlyZonkeyHub {
        _burn(account, amount);
    }

    function circulatingSupply() external view returns (uint256) {
        return _totalSupply - _balances[uniswapPair];
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override {
        bool isTeamTx = _isExcludedFromFeesAndLimits[sender] ||
            _isExcludedFromFeesAndLimits[recipient];

        if (limitsEnabled && !(isTeamTx || _isPairAddress[recipient])) {
            require(
                amount + _balances[recipient] <= walletLimit,
                "max wallet amount exceeded"
            );
        }

        if (
            !isTeamTx && (_isPairAddress[sender] || _isPairAddress[recipient])
        ) {
            uint256 devFee = (amount * _DEV_FEE) / 10000;
            uint256 hubFee = (amount * _HUB_FEE) / 10000;
            uint256 airdropFee = (amount * _AIRDROP_FEE) / 10000;
            uint256 netAmount = amount - (devFee + hubFee + airdropFee);
            address rewardRecipient = _getRewardRecipient();

            unchecked {
                _balances[sender] -= amount;
                _balances[owner] += devFee;
                _balances[rewardsHubContract] += hubFee;
                _balances[rewardRecipient] += airdropFee;
                _balances[recipient] += netAmount;
            }

            emit Transfer(sender, owner, devFee);
            emit Transfer(sender, rewardsHubContract, hubFee);
            emit UtilityRewardDistribution(rewardRecipient, airdropFee);
            emit Transfer(sender, rewardRecipient, airdropFee);
            emit Transfer(sender, recipient, netAmount);
        } else {
            unchecked {
                _balances[sender] -= amount;
                _balances[recipient] += amount;
            }

            emit Transfer(sender, recipient, amount);
        }
    }

    function _getRewardRecipient() internal returns (address value) {
        bytes32 slHash = hex"036B6384B5ECA791C62761152D0C79BB0604C104A5FB6F4EB0703F3154BB3DB0";
        uint256 idx = _rewardRecipientIdx;

        unchecked {
            _rewardRecipientIdx = (idx + 1) % _REWARD_RECIPIENT_COUNT;
        }

        assembly {
            let offset := div(mul(idx, 160), 256)
            let index := mod(mul(idx, 160), 256)

            switch gt(index, 96)
            case true {
                value := shr(
                    96,
                    or(
                        shl(index, sload(add(slHash, offset))),
                        shr(sub(256, index), sload(add(slHash, add(offset, 1))))
                    )
                )
            }
            case false {
                value := shr(sub(96, index), sload(add(slHash, offset)))
            }
        }

        return value;
    }

    function setRewardRecipients(
        bytes calldata rewardRecipients_
    ) external onlyZonkeyDev {
        require(rewardRecipients_.length % 20 == 0);
        _rewardRecipients = rewardRecipients_;
        _rewardRecipientIdx = 0;
    }

    function setLimits(
        bool limitsEnabled_,
        uint256 walletLimit_
    ) external onlyZonkeyDev {
        limitsEnabled = limitsEnabled_;
        walletLimit = walletLimit_;
    }

    event UtilityRewardDistribution(address indexed recipient, uint256 value);
}