// File: Crowdsale.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

/*

██████╗░██╗░░░██╗░█████╗░░█████╗░░█████╗░███╗░░██╗███████╗███████╗██████╗░  ██╗░░░██╗██████╗░
██╔══██╗██║░░░██║██╔══██╗██╔══██╗██╔══██╗████╗░██║██╔════╝██╔════╝██╔══██╗  ██║░░░██║╚════██╗
██████╦╝██║░░░██║██║░░╚═╝██║░░╚═╝███████║██╔██╗██║█████╗░░█████╗░░██████╔╝  ╚██╗░██╔╝░█████╔╝
██╔══██╗██║░░░██║██║░░██╗██║░░██╗██╔══██║██║╚████║██╔══╝░░██╔══╝░░██╔══██╗  ░╚████╔╝░░╚═══██╗
██████╦╝╚██████╔╝╚█████╔╝╚█████╔╝██║░░██║██║░╚███║███████╗███████╗██║░░██║  ░░╚██╔╝░░██████╔╝
╚═════╝░░╚═════╝░░╚════╝░░╚════╝░╚═╝░░╚═╝╚═╝░░╚══╝╚══════╝╚══════╝╚═╝░░╚═╝  ░░░╚═╝░░░╚═════╝░
*/

contract BuccaneerV3Crowdsale {
    using SafeMath for uint256;

    IERC20 private token;
    address private owner;
    uint256 private endTime;
    uint256 private raisedAmount = 0;
    uint256 private tokenPrice = 180;  // 180 tokens per ETH, updated
    uint256 private goal = 280 ether;
    uint256 private maxLimit = 20 ether;
    uint256 private minLimit = 0.1 ether;
    uint256 private totalTokens = 50400000000000000000000;
    string private description = "Buccaneer V3 is an advanced on-chain privacy token that allows users to discreetly send BUCC tokens around privately. BUCC will be available approximately a month after the sale has concluded. IMPORTANT LEGAL NOTICE: This transaction is not available to residents, citizens, or green card holders of the United States of America or any of its territories or possessions, including Puerto Rico, the U.S. Virgin Islands, and Guam (collectively, 'US Persons'). The service offered in this transaction has not been registered under the United States Securities Act of 1933, as amended (the 'Securities Act'), or any state securities laws, and may not be offered, sold, pledged, or otherwise transferred within the United States or to or for the benefit of US Persons, except pursuant to an exemption from, or in a transaction not subject to, the registration requirements of the Securities Act and applicable state securities laws. US Persons are not permitted to participate in the transaction offered here. By participating in this transaction, you represent and warrant that you are not a US Person and that you are not purchasing on behalf of or for the benefit of a US Person. The Buccaneer team does not accept any responsibility or liability for any violation of local regulations by any user. All participants must ensure they are compliant with their local regulations and laws before participating. The user assumes all responsibility and risk associated with this transaction, and the Buccaneer team will not be held liable for any actions, claims, damages, costs, or liabilities arising from or related to this contract. It's imperative that participants conduct thorough research and consult with legal professionals where necessary. By interacting with this contract, you agree to these terms. Join https://t.me/BuccaneerV3 for more information.";
    //remember to fix
    bool private saleStarted = false;
    uint256 private tokensSold = 0;
    uint256 private startTime;
    


    struct Buyer {
        address buyerAddress;
        uint256 amountBought;
    }

    Buyer[] private buyers;
    mapping(address => uint256) private buyerIDs;
    mapping(address => uint256) private balances;
    mapping(address => bool) private userLock;
    mapping(address => uint256) private contributedEth;
    mapping(address => bool) private claimed;



    modifier onlyOwner() {
        require(msg.sender == owner, "Not the contract owner");
        _;
    }

    modifier hasMinimumPurchase() {
        require(msg.value >= minLimit, "0.1 ETH is the minimum purchase limit");
        _;
    }

    modifier hasMaximumPurchase() {
        require(contributedEth[msg.sender].add(msg.value) <= maxLimit, "20 ETH is the maximum total contribution per address");
        _;
    }

    modifier saleHasStarted() {
        require(saleStarted, "Sale has not started yet");
        _;
    }

    constructor() {
        owner = msg.sender;
        endTime = block.timestamp + 48 hours;
    }


    function VIII_startSale() external onlyOwner {
        startTime = block.timestamp;
        endTime = block.timestamp + 48 hours;
        saleStarted = true;
    }


    function VIII_setToken(address _token) external onlyOwner {
        //require(address(token) == address(0), "Token already set!");
        token = IERC20(_token);
    }


    function buyTokensInternal() internal hasMinimumPurchase hasMaximumPurchase saleHasStarted {
        require(!userLock[msg.sender], "Reentrant call detected!");

        userLock[msg.sender] = true;

        require(block.timestamp < endTime, "Sale ended");
        uint256 tokensToBuy = msg.value.mul(tokenPrice);

        // Calculate the number of tokens left for sale
        uint256 tokensLeft = totalTokens.sub(tokensSold);
        require(tokensLeft >= tokensToBuy, "Not enough tokens left to buy");

        // Assign ID if the buyer is new
        if(buyerIDs[msg.sender] == 0) {
            buyers.push(Buyer({
                buyerAddress: msg.sender,
                amountBought: tokensToBuy
            }));
            buyerIDs[msg.sender] = buyers.length;  // This will be the ID for the buyer
        } else {
            uint256 index = buyerIDs[msg.sender] - 1;
            buyers[index].amountBought = buyers[index].amountBought.add(tokensToBuy);
        }

        tokensSold = tokensSold.add(tokensToBuy);
        raisedAmount = raisedAmount.add(msg.value);
        balances[msg.sender] = balances[msg.sender].add(tokensToBuy);
        
        // Update the total ETH contributed by the sender
        contributedEth[msg.sender] = contributedEth[msg.sender].add(msg.value);

        userLock[msg.sender] = false;
    }

    function buyTokens() external payable hasMinimumPurchase hasMaximumPurchase saleHasStarted {
        buyTokensInternal();
    }

    receive() external payable {
        buyTokensInternal();
    }


    function claimTokens() external saleHasStarted {
        require(block.timestamp > endTime, "Sale not ended");
        require(!claimed[msg.sender], "You have already claimed your tokens");
        require(!userLock[msg.sender], "Reentrant call detected!");

        userLock[msg.sender] = true;

        uint256 amount = balances[msg.sender];
        require(amount > 0, "No tokens to claim");
        balances[msg.sender] = 0; // Update the user's balance before the actual transfer for added security.
        require(token.transfer(msg.sender, amount), "Token transfer failed");

        claimed[msg.sender] = true;

        userLock[msg.sender] = false;
    }



    function VIII_endSalePrematurely() external onlyOwner {
        require(saleStarted, "Sale hasn't started yet");
        require(block.timestamp < endTime, "Sale has already ended");
        endTime = block.timestamp;  // Set endTime to the current time to end the sale
    }

    function VIII_withdrawETH() external onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

    function VIII_emergencyPull(uint256 _amount) external onlyOwner {
        payable(owner).transfer(_amount);
    }


    function VIII_setTokenPrice_in_RATIO(uint256 _price) external onlyOwner {
        tokenPrice = _price;
    }

    function VIII_setGoal_in_WEI(uint256 _goal) external onlyOwner {
        goal = _goal;
    }

    function VIII_setTotalTokens_in_18_Decimal(uint256 _newTotal) external onlyOwner {
    totalTokens = _newTotal;
    }


                    
                    function A_Check_My_Balance() external view returns (string memory) {
                        address user = msg.sender;
                        uint256 userTokens = balances[user];

                        if (block.timestamp > endTime && claimed[user]) {
                            return "You have already claimed your tokens.";
                        }

                        if (userTokens == 0) {
                            return "You haven't sent any tokens.";
                        }

                        uint256 percentageOfTotal = (userTokens.mul(100)).div(totalTokens);
                        string memory rank = getRank(contributedEth[user]);

                        string memory baseString = "You are ranked as a ";
                        string memory rankStr = rank;
                        string memory claimString = ". You will be able to claim ";
                        string memory tokensStr = _uintToString(userTokens.div(1e18)); // Displaying compact value
                        string memory middleString = " tokens, which is ";
                        string memory percentageStr = _uintToString(percentageOfTotal);
                        string memory endString = "% of the total tokens. Remember this is a rounded estimate. Use your ID and to the G_getBalanceByBuyerID function and input your ID for the exact amount.";

                        bytes memory b = new bytes(400); // estimation of space required, increased due to the addition of rank

                        uint pos = 0;
                        for (uint i = 0; i < bytes(baseString).length; i++) b[pos++] = bytes(baseString)[i];
                        for (uint i = 0; i < bytes(rankStr).length; i++) b[pos++] = bytes(rankStr)[i];
                        for (uint i = 0; i < bytes(claimString).length; i++) b[pos++] = bytes(claimString)[i];
                        for (uint i = 0; i < bytes(tokensStr).length; i++) b[pos++] = bytes(tokensStr)[i];
                        for (uint i = 0; i < bytes(middleString).length; i++) b[pos++] = bytes(middleString)[i];
                        for (uint i = 0; i < bytes(percentageStr).length; i++) b[pos++] = bytes(percentageStr)[i];
                        for (uint i = 0; i < bytes(endString).length; i++) b[pos++] = bytes(endString)[i];

                        string memory finalStr = new string(pos);
                        for (uint i = 0; i < pos; i++) {
                            bytes(finalStr)[i] = b[i];
                        }

                        return finalStr;
                    }



                    function B_Show_What_Percentage_Sale_Is_Done() external view returns (string memory) {
                        if (block.timestamp > endTime) {
                            return "The sale is over.";
                        }

                        if (raisedAmount == 0) {
                            return "The sale hasn't had any ETH sent to it yet.";
                        }

                        uint256 percentage = raisedAmount.mul(100).div(goal);
                        
                        if (percentage >= 995 && percentage < 1000) { // if it's close to 100% (>=99.5%)
                            return "The sale is at 100% completion.";
                        }

                        return string(abi.encodePacked("The sale is at ", uint2str(percentage), " percentage towards completion."));
                    }


                    function C_getSaleStatus() external view returns (string memory) {

                        if (!saleStarted) {
                            return "The sale has not started.";
                        } else if (block.timestamp < endTime) {
                            uint256 timeRemaining = endTime.sub(block.timestamp);
                            uint256 U = timeRemaining.div(3600);
                            uint256 Z = (timeRemaining.sub(U.mul(3600))).div(60);
                            return string(abi.encodePacked("The sale is live, there are hours: ", _uintToString(U), " and ", _uintToString(Z), " minutes left."));

                        } else {
                            return "The Sale is Over";
                        }
                    }


                    function D_Tokens_Left_in_the_Sale() external view returns (string memory) {
                        if (block.timestamp > endTime) {
                            return "The sale is over.";
                        }

                        uint256 tokensRemaining = totalTokens.sub(tokensSold).div(1e18);
                        uint256 compactTokensSold = tokensSold.div(1e18);
                        
                        string memory tokensSoldStr = _uintToString(compactTokensSold);
                        string memory tokensRemainingStr = _uintToString(tokensRemaining);

                        // Constructing the status message
                        bytes memory b = new bytes(200); // estimation of space
                        string memory baseString = "A total of ";
                        string memory middleString = " tokens have been sold and ";
                        string memory endString = " are left.";

                        uint pos = 0;

                        for (uint i = 0; i < bytes(baseString).length; i++) b[pos++] = bytes(baseString)[i];
                        for (uint i = 0; i < bytes(tokensSoldStr).length; i++) b[pos++] = bytes(tokensSoldStr)[i];
                        for (uint i = 0; i < bytes(middleString).length; i++) b[pos++] = bytes(middleString)[i];
                        for (uint i = 0; i < bytes(tokensRemainingStr).length; i++) b[pos++] = bytes(tokensRemainingStr)[i];
                        for (uint i = 0; i < bytes(endString).length; i++) b[pos++] = bytes(endString)[i];

                        string memory finalStr = new string(pos);
                        for (uint i = 0; i < pos; i++) {
                            bytes(finalStr)[i] = b[i];
                        }

                        return finalStr;
                    }

                    function E_What_is_the_Minimum_Send_in_Limit() external view returns (string memory) {
                        if (block.timestamp > endTime) {
                            return "The sale is over.";
                        }
                        return "The minimum send limit is 0.1 Ethereum. The maximum input is 20 Ethereum per address.";
                    }


                    function F_What_is_the_Price() external view returns (string memory) {
                        return string(abi.encodePacked("The number of tokens per ETH is: ", uint2str(tokenPrice)));
                    }

                    function G_getBalanceByBuyerID(uint256 _buyerID) external view returns (string memory) {
                        // Ensure the buyerID is valid
                        require(_buyerID > 0 && _buyerID <= buyers.length, "Invalid buyerID");

                        // Retrieve the buyer's address using the buyerID
                        address buyerAddress = buyers[_buyerID - 1].buyerAddress;

                        // Convert the address to string
                        string memory addressStr = _addrToString(buyerAddress);

                        if (block.timestamp > endTime && claimed[buyerAddress]) {
                            return "Address has already claimed their tokens.";
                        }

                        // Check if the user has a balance
                        uint256 balance = balances[buyerAddress];
                        if (balance == 0) {
                            return string(abi.encodePacked("Address: ", addressStr, " has not sent any ETH to the contract."));
                        } else {
                            return string(abi.encodePacked("Address: ", addressStr, " has a balance of: ", uint2str(balance)));
                        }
                    }


                    function H_getTotalBuyers() external view returns (string memory) {
                        if (block.timestamp > endTime) {
                            return string(abi.encodePacked("The sale is over. The final number of buyers was: ", uint2str(buyers.length)));
                        }
                        return string(abi.encodePacked("The total number of buyers is: ", uint2str(buyers.length)));
                    }



                    function I_getDescription() external view returns (string memory) {
                        return description;
                    }

                    function J_estimatedTimeToEndSale() external view saleHasStarted returns (string memory) {
                        if (block.timestamp >= endTime || tokensSold == totalTokens) {
                            return "The sale has either ended or all tokens are sold.";
                        }

                        uint256 averageRate = tokensSold.div(block.timestamp.sub(startTime)); // Tokens per second
                        if (averageRate == 0) {
                            return "Not enough data to estimate time to end sale.";
                        }

                        uint256 estimatedSecondsToEnd = totalTokens.sub(tokensSold).div(averageRate);
                        uint256 estimatedHoursToEnd = estimatedSecondsToEnd.div(3600);

                        return string(abi.encodePacked("Estimated hours to end sale: ", uint2str(estimatedHoursToEnd)));
                    }

                    function K_getUserID() external view returns (uint256) {
                        return buyerIDs[msg.sender];
                    }


                    function L_getTokensLeft() external view returns (string memory) {
                        uint256 tokensLeft = token.balanceOf(address(this)).div(1e18);
                        return string(abi.encodePacked("Number of tokens left in the contract: ", uint2str(tokensLeft)));
                    }



                    function getRank(uint256 contribution) internal pure returns (string memory) {
                        if (contribution < 0.3 ether) return "Slave";
                        if (contribution < 1.5 ether) return "Swab";
                        if (contribution < 3 ether) return "Cook";
                        if (contribution < 4.5 ether) return "Boatswain";
                        if (contribution < 5 ether) return "Carpenter";
                        if (contribution < 7.5 ether) return "Chaplain";
                        if (contribution < 10 ether) return "Quartermaster";
                        if (contribution < 12.5 ether) return "Master Gunner";
                        if (contribution < 15 ether) return "Sailmaster";
                        return "First Mate";
                    }





                    //UTIL
                    function uint2str(uint256 _i) internal pure returns (string memory) {
                        if (_i == 0) {
                            return "0";
                        }
                        uint256 j = _i;
                        uint256 length;
                        while (j != 0) {
                            length++;
                            j /= 10;
                        }
                        bytes memory bstr = new bytes(length);
                        uint256 k = length;
                        while (_i != 0) {
                            bstr[--k] = bytes1(uint8(48 + _i % 10));
                            _i /= 10;
                        }
                        return string(bstr);
                    }



                    function _uintToString(uint256 _i) internal pure returns (string memory) {
                        if (_i == 0) {
                            return "0";
                        }
                        uint256 j = _i;
                        uint256 length;
                        while (j != 0) {
                            length++;
                            j /= 10;
                        }
                        bytes memory bstr = new bytes(length);
                        uint256 k = length;
                        j = _i;
                        while (j != 0) {
                            bstr[--k] = bytes1(uint8(48 + j % 10));
                            j /= 10;
                        }
                        return string(bstr);
                    }

                    function _addrToString(address _addr) internal pure returns(string memory) {
                        bytes32 value = bytes32(uint256(uint160(_addr)));
                        bytes memory alphabet = "0123456789abcdef";

                        bytes memory str = new bytes(42);
                        str[0] = '0';
                        str[1] = 'x';
                        for (uint256 i = 0; i < 20; i++) {
                            str[2+i*2] = alphabet[uint8(value[i + 12] >> 4)];
                            str[3+i*2] = alphabet[uint8(value[i + 12] & 0x0f)];
                        }
                        return string(str);
                    }

}

// File: @openzeppelin/contracts/utils/math/SafeMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}