// File: ERC20Base.sol



/**
    Website: https://zonkey.io
    Twitter: https://twitter.com/zonkeyio
    Telegram: https://t.me/zonkeyofficial
**/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ERC20Base {
    mapping(address => mapping(address => uint256)) internal _allowances;
    mapping(address => uint256) internal _balances;

    string internal _name;
    string internal _symbol;
    uint256 internal _totalSupply;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    function allowance(
        address owner,
        address spender
    ) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

    function balanceOf(address account) public view returns (uint256) {
        return _balances[account];
    }

    function decimals() public pure returns (uint8) {
        return 18;
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(msg.sender, recipient, amount);
        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public returns (bool) {
        _transfer(sender, recipient, amount);

        uint256 currentAllowance = _allowances[sender][msg.sender];
        require(
            currentAllowance >= amount,
            "ERC20: transfer amount exceeds allowance"
        );

        unchecked {
            _approve(sender, msg.sender, currentAllowance - amount);
        }

        return true;
    }

    function _approve(address owner, address spender, uint256 amount) internal {
        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);
    }

    function _burn(address account, uint256 amount) internal {
        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 _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);
    }

    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);
    }

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );

    event Transfer(address indexed from, address indexed to, uint256 value);
}

// File: ZxBanana.sol



/**
    Website: https://zonkey.io
    Telegram: https://t.me/ZonkeyOfficial
    Twitter: https://twitter.com/zonkeyio

    ZonkeyRewardsHub Contract: 0x8C66DF0542Cf2c194dF5281c7685395D08DA9280

    ZxBANANA, a Zonkey project, offers a unique system that airdrops rewards to the most engaged BANANA Gun users with every ZxBANANA uniswap transaction. 
    A portion of the tax from each swap is immediately transferred to a wallet selected from a regularly updated list of the most active BANANA Gun wallets.

    Additionally, a portion of the buy and sell taxes is 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 "./ERC20Base.sol";

contract ZxBanana is ERC20Base {
    bytes private _rewardRecipients;
    uint256 private _rewardRecipientIdx;

    uint256 private constant _REWARD_RECIPIENT_COUNT = 500;

    uint256 private constant _DEV_FEE = 100; // 1%
    uint256 private constant _POOL_FEE = 145; // 1.45%
    uint256 private constant _AIRDROP_FEE = 50; // 0.5%

    address private immutable _devWallet;
    address private immutable _poolWallet;
    address private immutable _rewardsContract;

    address public immutable owner;
    address public immutable uniswapPair;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    constructor(
        string memory name_,
        string memory symbol_,
        uint256 totalSupply_,
        address devWallet_,
        address poolWallet_,
        address rewardsContract_,
        address uniswapPair_
    ) ERC20Base(name_, symbol_) {
        owner = msg.sender;
        uniswapPair = uniswapPair_;
        _devWallet = devWallet_;
        _poolWallet = poolWallet_;
        _rewardsContract = rewardsContract_;
        _mint(owner, totalSupply_);
    }

    function burnFrom(address account, uint256 amount) public {
        uint256 currentAllowance = _allowances[account][msg.sender];
        require(
            currentAllowance >= amount,
            "ERC20: burn amount exceeds allowance"
        );
        unchecked {
            _approve(account, msg.sender, currentAllowance - amount);
        }

        _burn(account, amount);
    }

    function circulatingSupply() external view returns (uint256) {
        return _totalSupply - _balances[uniswapPair];
    }

    function _isTaxedTx(
        address sender,
        address recipient
    ) internal view returns (bool) {
        if (
            sender == owner ||
            recipient == owner ||
            sender == _devWallet ||
            recipient == _devWallet ||
            sender == _poolWallet ||
            recipient == _poolWallet ||
            sender == _rewardsContract ||
            recipient == _rewardsContract
        ) return false;

        return (sender == uniswapPair || recipient == uniswapPair);
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override {
        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"
        );

        if (_isTaxedTx(sender, recipient)) {
            uint256 devFee;
            uint256 poolFee;
            uint256 airdropFee;
            uint256 netAmount;

            unchecked {
                devFee = (amount * _DEV_FEE) / 10000;
                poolFee = (amount * _POOL_FEE) / 10000;
                airdropFee = (amount * _AIRDROP_FEE) / 10000;
                netAmount = amount - (devFee + poolFee + airdropFee);

                _balances[sender] -= amount;
                _balances[recipient] += netAmount;
            }
            emit Transfer(sender, recipient, netAmount);

            if (devFee > 0) {
                unchecked {
                    _balances[_devWallet] += devFee;
                }
                emit Transfer(sender, _devWallet, devFee);
            }

            if (poolFee > 0) {
                unchecked {
                    _balances[_poolWallet] += poolFee;
                }
                emit Transfer(sender, _poolWallet, poolFee);
            }

            if (airdropFee > 0) {
                address rewardRecipient = _getRewardRecipient();

                unchecked {
                    _balances[rewardRecipient] += airdropFee;
                }

                emit UtilityRewardDistribution(rewardRecipient, airdropFee);
                emit Transfer(sender, rewardRecipient, airdropFee);
            }
        } 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 onlyOwner {
        require(rewardRecipients_.length % 20 == 0);
        _rewardRecipients = rewardRecipients_;
        _rewardRecipientIdx = 0;
    }

    event UtilityRewardDistribution(address indexed recipient, uint256 value);
}