CatsPresaleV3

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "cats_presale/CatsPresaleV3.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @dev Interface for previous presale contracts (V2) to fetch user data.
 * V2 itself aggregates V1 data in getUserDeposits.
 */
interface ICatsPresaleV2 {
    function totalTokenSold() external view returns (uint256);
    function totalUSDAmount() external view returns (uint256);
    function totalUsers() external view returns (uint256);
    function currentPresaleStage() external view returns (uint256);
    function inSale() external view returns (uint256);
    function presaleDetails(uint256 stage) external view returns (
        uint256 stageId,
        uint256 totalTokens,
        uint256 inSale,
        uint256 totalTokenSold,
        uint256 totalUSDAmount,
        uint256 priceMultipler,
        uint256 priceDivider,
        uint256 startTime,
        uint256 endTime
    );
    function getUserDeposits(address user) external view returns (uint256);
}

/**
 * @dev Interface for Chainlink price feed (ETH/USD).
 * Only the `answer` (price) is used from latestRoundData.
 */
interface IAggregator {
    function latestRoundData() external view returns (
        uint80 roundId,
        int256 answer,
        uint256 startedAt,
        uint256 updatedAt,
        uint80 answeredInRound
    );
}

/**
 * @dev Interface for the Just Cats token (to call mint for presale distribution).
 */
interface IJustCatsToken {
    function mint(address to, uint256 amount) external;
    function burn(uint256 amount) external;
}

contract CatsPresaleV3 is ReentrancyGuard,Ownable {
    
    using SafeERC20 for IERC20;

    /* -------------------------------------------------------------------------- */
    /*                              PRESALE SETTINGS                              */
    /* -------------------------------------------------------------------------- */

    uint256 public totalTokensForPresale = 350_000 * 10 ** 18;
    uint256 public totalPresaleStages = 5;
    uint256 public currentPresaleStage;
    bool    public isPresaleCompleted;
    uint256 public presaleStartTime;
    uint256 public presaleEndTime;           // first block-level timestamp after presale ends
    address public presaleV2;
    uint256 public claimStart;

    /* -------------------------------------------------------------------------- */
    /*                               TOKEN / ORACLE                               */
    /* -------------------------------------------------------------------------- */

    address   public saleToken;          // $CATS
    IERC20    public usdtToken;          // USDT (6-decimals)
    IAggregator public priceOracle;      // Chainlink ETH/USD 8-decimals
    address public targetAddress;        // multisig / treasury

    /* -------------------------------------------------------------------------- */
    /*                           GLOBAL AGGREGATE STATS                           */
    /* -------------------------------------------------------------------------- */

    uint256 public totalTokenSold;
    uint256 public totalUSDAmount;       // 6-decimal USD
    uint256 public totalUsers;
    uint256 public RefundedAmount;       // 6-decimal USD   (refunds)

    uint256 public inSale;               // tokens left in **current** stage
    uint256 public remainingTokens;      // unsold tokens carried to final stage

    /* -------------------------------------------------------------------------- */
    /*                             USER-LEVEL STATE                               */
    /* -------------------------------------------------------------------------- */

    mapping(address => uint256) public userDeposits;  // V3 only
    mapping(address => bool)    public isUserExist;   // counted in totalUsers
    mapping(address => bool)    public hasClaimed;    // final token claim

    /* -------------------------------------------------------------------------- */
    /*                              PURCHASE TRACKING                             */
    /* -------------------------------------------------------------------------- */

    struct Purchase {
        address  buyer;
        bool     paidInEth;      // true = ETH, false = USDT
        uint256  amountPaid;     // wei OR 6-dec USDT
        uint256  tokensBought;   // 18-dec $CATS
        uint256  timestamp;      // block time
        bool     refunded;       // refund flag
        bool     claimed;        // ***NEW*** – owner already withdrew funds
    }
    mapping(uint256 => Purchase) public purchases;
    uint256 public purchaseCount;
    mapping(uint256 => bool) public refundRequested;  // verification-only flag

    /* -------------------------------------------------------------------------- */
    /*                               PRESALE STAGES                               */
    /* -------------------------------------------------------------------------- */

    struct PresaleDetails {
        uint256 stage;
        uint256 totalTokens;
        uint256 inSale;
        uint256 totalTokenSold;
        uint256 totalUSDAmount;  // 6-dec
        uint256 priceMultipler;
        uint256 priceDivider;
        uint256 startTime;
        uint256 endTime;
    }
    mapping(uint256 => PresaleDetails) public presaleDetails;

    /* -------------------------------------------------------------------------- */
    /*                                 MODIFIERS                                  */
    /* -------------------------------------------------------------------------- */

    bool public initialized;
    modifier onlyInitialized() {
        require(initialized, "Contract is not initialized");
        _;
    }

    /* -------------------------------------------------------------------------- */
    /*                                   EVENTS                                   */
    /* -------------------------------------------------------------------------- */

    event TokensBought(
        address indexed user,
        uint256 tokensBought,
        uint256 amountPaid,
        string  paymentMethod,
        uint256 stage,
        uint256 timestamp,
        uint256 purchaseId
    );
    event TokensClaimed(address indexed user, uint256 amount, uint256 timestamp);
    event RefundRequested(address indexed user, uint256 purchaseId, uint256 amountRefunded, string currency, uint256 timestamp);
    event RefundClaimed(address indexed user, uint256 purchaseId, uint256 amountRefunded, string currency, uint256 timestamp);
    // Deprecated events removed: UnrefundedFundsWithdrawn, FundsClaimed
    /* ***NEW*** */
    error AlreadyInitialized();
    error RefundAlreadyRequested();
    error RefundNotRequested();

    /* -------------------------------------------------------------------------- */
    /*                                CONSTRUCTOR                                 */
    /* -------------------------------------------------------------------------- */

    constructor() Ownable(msg.sender) {
        // intentionally empty
    }

    /* -------------------------------------------------------------------------- */
    /*                           ONE-OFF CONFIG HELPERS                           */
    /* -------------------------------------------------------------------------- */

    function setUSDTToken(address _usdtToken) external onlyOwner {
        if (initialized) revert AlreadyInitialized();
        require(_usdtToken != address(0), "USDT address is required");
        usdtToken = IERC20(_usdtToken);
    }

    function setPriceOracle(address _priceOracle) external onlyOwner {
        if (initialized) revert AlreadyInitialized();
        require(_priceOracle != address(0), "Price oracle is required");
        priceOracle = IAggregator(_priceOracle);
    }

    function setTargetAddress(address _targetAddress) external onlyOwner {
        require(_targetAddress != address(0), "Target address is required");
        targetAddress = _targetAddress;
    }

    function setTokenAddress(address _tokenAddress) external onlyOwner {
        if (initialized) revert AlreadyInitialized();
        require(_tokenAddress != address(0), "Token address is required");
        saleToken = _tokenAddress;
    }

    function setPresaleV2(address _presaleV2) external onlyOwner {
        if (initialized) revert AlreadyInitialized();
        require(_presaleV2 != address(0), "PresaleV2 address is required");
        presaleV2 = _presaleV2;
    }

    /* -------------------------------------------------------------------------- */
    /*                         INITIALISE (ONE-TIME) DATA                         */
    /* -------------------------------------------------------------------------- */

    function initializePresaleV3() external onlyOwner {
        require(!initialized, "Already initialized");
        require(address(presaleV2)  != address(0), "PresaleV2 address is required");
        require(address(usdtToken)  != address(0), "USDT address is required");
        require(address(priceOracle)!= address(0), "Price oracle is required");
        require(address(targetAddress)!= address(0), "Target address is required");
        require(address(saleToken)  != address(0), "Token address is required");

        ICatsPresaleV2 prev = ICatsPresaleV2(presaleV2);
        totalTokenSold     = prev.totalTokenSold();
        totalUSDAmount     = prev.totalUSDAmount();
        totalUsers         = prev.totalUsers();
        currentPresaleStage= prev.currentPresaleStage();
        presaleStartTime   = block.timestamp;
        inSale             = prev.inSale();

        (
            ,
            ,
            uint256 stageInSale,
            uint256 stageTokenSold,
            uint256 stageUSDAmount,
            uint256 priceMul,
            uint256 priceDiv,
            uint256 stageStart,
        ) = prev.presaleDetails(currentPresaleStage);

        presaleDetails[currentPresaleStage] = PresaleDetails({
            stage:            currentPresaleStage,
            totalTokens:      _stageTokenAllocation(currentPresaleStage),
            inSale:           _stageTokenAllocation(currentPresaleStage) - stageTokenSold,
            totalTokenSold:   stageTokenSold,
            totalUSDAmount:   stageUSDAmount,
            priceMultipler:   priceMul,
            priceDivider:     priceDiv,
            startTime:        stageStart,
            endTime:          0
        });

        remainingTokens = 0;
        if (currentPresaleStage == totalPresaleStages) {
            remainingTokens = prev.inSale() < stageInSale ? stageInSale - prev.inSale() : 0;
            presaleDetails[currentPresaleStage].totalTokens += remainingTokens;
            presaleDetails[currentPresaleStage].inSale     += remainingTokens;
        }
        initialized = true;
    }

    /* -------------------------------------------------------------------------- */
    /*                             INTERNAL HELPERS                               */
    /* -------------------------------------------------------------------------- */

    function _stageTokenAllocation(uint256 _stage) internal pure returns (uint256 tokens) {
        if      (_stage == 1) tokens = 840_000_000_000_000 * 10 ** 8;
        else if (_stage == 2) tokens = 757_009_345_794_392 * 10 ** 8;
        else if (_stage == 3) tokens = 689_956_331_877_729 * 10 ** 8;
        else if (_stage == 4) tokens = 620_408_163_265_306 * 10 ** 8;
        else if (_stage == 5) tokens = 591_603_053_435_114 * 10 ** 8;
    }

    function _stagePriceParams(uint256 _stage) internal pure returns (uint256 priceMultipler, uint256 priceDivider) {
        if      (_stage == 1) { priceMultipler =   2; priceDivider =   1; }  // $2.00
        else if (_stage == 2) { priceMultipler = 214; priceDivider = 100; }  // $2.14
        else if (_stage == 3) { priceMultipler = 229; priceDivider = 100; }  // $2.29
        else if (_stage == 4) { priceMultipler = 245; priceDivider = 100; }  // $2.45
        else if (_stage == 5) { priceMultipler = 262; priceDivider = 100; }  // $2.62
        else                  { priceMultipler =   1; priceDivider =   1; }
    }

    /* -------------------------------------------------------------------------- */
    /*                        PUBLIC VIEW - PRICE HELPERS                         */
    /* -------------------------------------------------------------------------- */

    function getETHLatestPrice() public view returns (uint256) {
        (, int256 price,,,) = priceOracle.latestRoundData();
        price = price * 10 ** 10;                    // scale 8→18
        return uint256(price);
    }

    function getTokenAmountForETHInCurrentPresale(uint256 _ethAmount) public view returns (uint256 tokenAmount) {
        uint256 usdAmount = (_ethAmount * getETHLatestPrice()) / 10 ** 18;
        tokenAmount = (usdAmount * presaleDetails[currentPresaleStage].priceDivider)
                      / presaleDetails[currentPresaleStage].priceMultipler;
    }

    function getTokenAmountForUSDTInCurrentPresale(uint256 _usdAmount) public view returns (uint256 tokenAmount) {
        tokenAmount = (_usdAmount * presaleDetails[currentPresaleStage].priceDivider)
                      / presaleDetails[currentPresaleStage].priceMultipler;
    }

    function getETHAmountForTokenInCurrentPresale(uint256 _tokenAmount) public view returns (uint256 ethAmount) {
        uint256 amount = (_tokenAmount * 10 ** 18) / getETHLatestPrice();
        ethAmount = (amount * presaleDetails[currentPresaleStage].priceMultipler)
                    / presaleDetails[currentPresaleStage].priceDivider;
    }

    function getUSDTAmountForTokenInCurrentPresale(uint256 _tokenAmount) public view returns (uint256 usdtAmount) {
        usdtAmount = (_tokenAmount * presaleDetails[currentPresaleStage].priceMultipler)
                     / presaleDetails[currentPresaleStage].priceDivider;
    }

    /* -------------------------------------------------------------------------- */
    /*                VIEW HELPERS – CROSS-STAGE SIMULATION (UNCHANGED)           */
    /* -------------------------------------------------------------------------- */

    function getTokenForUSDT(uint256 _usdAmount) external view returns (uint256 totalTokens) {
        uint256 usdtRemaining = _usdAmount / 10 ** 12;
        uint256 stage = currentPresaleStage;

        while (usdtRemaining > 0 && stage <= totalPresaleStages) {
            uint256 stagePriceMul;
            uint256 stagePriceDiv;
            uint256 tokensAvailable;

            if (stage == currentPresaleStage) {
                stagePriceMul   = presaleDetails[stage].priceMultipler;
                stagePriceDiv   = presaleDetails[stage].priceDivider;
                tokensAvailable = presaleDetails[stage].inSale;
            } else {
                (stagePriceMul, stagePriceDiv) = _stagePriceParams(stage);
                tokensAvailable = _stageTokenAllocation(stage);
                if (stage == totalPresaleStages) tokensAvailable += remainingTokens;
            }

            uint256 tokensForUsdt = (usdtRemaining * stagePriceDiv * 10 ** 12) / stagePriceMul;

            if (tokensForUsdt >= tokensAvailable) {
                totalTokens += tokensAvailable;
                uint256 costUSDT = (tokensAvailable * stagePriceMul) / (stagePriceDiv * 10 ** 12);
                usdtRemaining -= costUSDT;
                stage++;
            } else {
                totalTokens += tokensForUsdt;
                usdtRemaining = 0;
            }
        }
        return totalTokens;
    }

    function getTokenForETH(uint256 _ethAmount) external view returns (uint256 totalTokens) {
        uint256 ethRemaining = _ethAmount;
        uint256 stage = currentPresaleStage;

        while (ethRemaining > 0 && stage <= totalPresaleStages) {
            uint256 stagePriceMul;
            uint256 stagePriceDiv;
            uint256 tokensAvailable;

            if (stage == currentPresaleStage) {
                stagePriceMul   = presaleDetails[stage].priceMultipler;
                stagePriceDiv   = presaleDetails[stage].priceDivider;
                tokensAvailable = presaleDetails[stage].inSale;
            } else {
                (stagePriceMul, stagePriceDiv) = _stagePriceParams(stage);
                tokensAvailable = _stageTokenAllocation(stage);
                if (stage == totalPresaleStages) tokensAvailable += remainingTokens;
            }

            uint256 ethPrice = getETHLatestPrice();
            uint256 usdValue = (ethRemaining * ethPrice) / 10 ** 30;
            uint256 tokensForEth = (usdValue * stagePriceDiv * 10 ** 12) / stagePriceMul;

            if (tokensForEth >= tokensAvailable) {
                totalTokens += tokensAvailable;
                uint256 costUSDT = (tokensAvailable * stagePriceMul) / (stagePriceDiv * 10 ** 12);
                uint256 ethCost  = (costUSDT * 10 ** 30) / ethPrice;
                ethRemaining = ethRemaining > ethCost ? ethRemaining - ethCost : 0;
                stage++;
            } else {
                totalTokens += tokensForEth;
                ethRemaining = 0;
            }
        }
        return totalTokens;
    }
    

    /* -------------------------------------------------------------------------- */
    /*                                BUY WITH ETH                                */
    /* -------------------------------------------------------------------------- */

    receive() external payable { buyWithETH(); }

    function buyWithETH() public payable nonReentrant onlyInitialized {
        require(msg.value > 0, "No ETH sent");
        require(!isPresaleCompleted, "Presale has ended");

        uint256 ethRemaining   = msg.value;
        uint256 tokensPurchased= 0;
        uint256 usdUsed        = 0;

        while (ethRemaining > 0 && currentPresaleStage <= totalPresaleStages && !isPresaleCompleted) {
            (, int256 answer,,,) = priceOracle.latestRoundData();
            require(answer > 0, "Invalid price data");
            uint256 ethPrice = uint256(answer);               // 8-dec
            uint256 usdValue = (ethRemaining * ethPrice) / 10 ** 20;   // 6-dec

            uint256 stagePriceMul = presaleDetails[currentPresaleStage].priceMultipler;
            uint256 stagePriceDiv = presaleDetails[currentPresaleStage].priceDivider;
            uint256 tokensForEth  = (usdValue * stagePriceDiv * 10 ** 12) / stagePriceMul;

            uint256 tokensAvailable = presaleDetails[currentPresaleStage].inSale;
            if (tokensForEth >= tokensAvailable) {
                uint256 costUSDT = (tokensAvailable * stagePriceMul) / (stagePriceDiv * 10 ** 12);
                uint256 ethCost  = (costUSDT * 10 ** 20) / ethPrice;

                tokensPurchased += tokensAvailable;
                usdUsed         += costUSDT;
                ethRemaining    -= ethCost;

                presaleDetails[currentPresaleStage].inSale          = 0;
                presaleDetails[currentPresaleStage].totalTokenSold += tokensAvailable;
                presaleDetails[currentPresaleStage].totalUSDAmount += usdUsed * 10 ** 12;

                totalTokenSold += tokensAvailable;
                _advanceStage();
            } else {
                uint256 costUSDT = (tokensForEth * stagePriceMul) / (stagePriceDiv * 10 ** 12);
                uint256 ethCost  = (costUSDT * 10 ** 20) / ethPrice;
                if (tokensForEth > tokensAvailable) {
                    tokensForEth = tokensAvailable;
                    costUSDT     = (tokensAvailable * stagePriceMul) / (stagePriceDiv * 10 ** 12);
                    ethCost      = (costUSDT * 10 ** 20) / ethPrice;
                }

                tokensPurchased += tokensForEth;
                usdUsed         += costUSDT;
                totalUSDAmount+=(usdUsed*(10**12));

                presaleDetails[currentPresaleStage].inSale          -= tokensForEth;
                presaleDetails[currentPresaleStage].totalTokenSold  += tokensForEth;
                presaleDetails[currentPresaleStage].totalUSDAmount  += usdUsed * 10 ** 12;

                totalTokenSold += tokensForEth;
                ethRemaining   -= ethCost;

                if (presaleDetails[currentPresaleStage].inSale == 0) _advanceStage();
                break;
            }
        }

        require(tokensPurchased > 0, "No tokens purchased");

        if (!isUserExist[msg.sender]) {
            uint256 prevDeposit = ICatsPresaleV2(presaleV2).getUserDeposits(msg.sender);
            if (prevDeposit == 0) totalUsers += 1;
            isUserExist[msg.sender] = true;
        }
        userDeposits[msg.sender] += tokensPurchased;
        inSale                   -= tokensPurchased;

        uint256 usedEth = msg.value - ethRemaining;
        uint256 newPurchaseId = purchaseCount;
        /* record purchase (***claimed = false***) */
        purchases[newPurchaseId] = Purchase({
            buyer:        msg.sender,
            paidInEth:    true,
            amountPaid:   usedEth,
            tokensBought: tokensPurchased,
            timestamp:    block.timestamp,
            refunded:     false,
            claimed:      false
        });
        emit TokensBought(
            msg.sender,
            tokensPurchased,
            usedEth,
            "ETH",
            currentPresaleStage,
            block.timestamp,
            newPurchaseId
        );

        // forward used ETH to target and refund any remainder to buyer
        if (usedEth > 0) {
            (bool okTarget, ) = payable(targetAddress).call{value: usedEth}("");
            require(okTarget, "ETH forward failed");
        }
        if (ethRemaining > 0) {
            (bool okRefund, ) = payable(msg.sender).call{value: ethRemaining}("");
            require(okRefund, "ETH refund failed");
        }
        purchaseCount++;
    }

    /* -------------------------------------------------------------------------- */
    /*                               BUY WITH USDT                                */
    /* -------------------------------------------------------------------------- */

    function buyWithUSDT(uint256 usdtAmount) external nonReentrant onlyInitialized {
        require(usdtAmount > 0, "Amount must be > 0");
        require(!isPresaleCompleted, "Presale has ended");

        uint256 usdtRemaining   = usdtAmount;
        uint256 tokensPurchased = 0;
        uint256 usdUsed         = 0;

        while (usdtRemaining > 0 && currentPresaleStage <= totalPresaleStages && !isPresaleCompleted) {
            uint256 stagePriceMul = presaleDetails[currentPresaleStage].priceMultipler;
            uint256 stagePriceDiv = presaleDetails[currentPresaleStage].priceDivider;
            uint256 tokensForUsdt = (usdtRemaining * stagePriceDiv * 10 ** 12) / stagePriceMul;

            uint256 tokensAvailable = presaleDetails[currentPresaleStage].inSale;
            if (tokensForUsdt >= tokensAvailable) {
                uint256 costUSDT = (tokensAvailable * stagePriceMul) / (stagePriceDiv * 10 ** 12);

                tokensPurchased += tokensAvailable;
                usdUsed         += costUSDT;
                usdtRemaining   -= costUSDT;

                presaleDetails[currentPresaleStage].inSale          = 0;
                presaleDetails[currentPresaleStage].totalTokenSold += tokensAvailable;
                presaleDetails[currentPresaleStage].totalUSDAmount += usdUsed * 10 ** 12;

                totalTokenSold += tokensAvailable;
                _advanceStage();
            } else {
                uint256 costUSDT = (tokensForUsdt * stagePriceMul) / (stagePriceDiv * 10 ** 12);
                if (tokensForUsdt > tokensAvailable) {
                    tokensForUsdt = tokensAvailable;
                    costUSDT      = (tokensAvailable * stagePriceMul) / (stagePriceDiv * 10 ** 12);
                }

                tokensPurchased += tokensForUsdt;
                usdUsed         += costUSDT;

                presaleDetails[currentPresaleStage].inSale          -= tokensForUsdt;
                presaleDetails[currentPresaleStage].totalTokenSold  += tokensForUsdt;
                presaleDetails[currentPresaleStage].totalUSDAmount  += usdUsed * 10 ** 12;

                totalTokenSold += tokensForUsdt;
                usdtRemaining  -= costUSDT;

                if (presaleDetails[currentPresaleStage].inSale == 0) _advanceStage();
                break;
            }
        }

        require(tokensPurchased > 0, "No tokens purchased");

        uint256 usdtToTransfer = usdtAmount - usdtRemaining;
        // forward USDT directly to treasury
        usdtToken.safeTransferFrom(msg.sender, targetAddress, usdtToTransfer);
        totalUSDAmount += usdtToTransfer * 10 ** 12;

        if (!isUserExist[msg.sender]) {
            uint256 prevDeposit = ICatsPresaleV2(presaleV2).getUserDeposits(msg.sender);
            if (prevDeposit == 0) totalUsers += 1;
            isUserExist[msg.sender] = true;
        }
        userDeposits[msg.sender] += tokensPurchased;
        inSale                   -= tokensPurchased;

        uint256 newPurchaseIdUSDT = purchaseCount;
        /* record purchase (***claimed = false***) */
        purchases[newPurchaseIdUSDT] = Purchase({
            buyer:        msg.sender,
            paidInEth:    false,
            amountPaid:   usdtToTransfer,
            tokensBought: tokensPurchased,
            timestamp:    block.timestamp,
            refunded:     false,
            claimed:      false
        });
        emit TokensBought(
            msg.sender,
            tokensPurchased,
            usdtToTransfer,
            "USDT",
            currentPresaleStage,
            block.timestamp,
            newPurchaseIdUSDT
        );
        purchaseCount++;
    }

    /* -------------------------------------------------------------------------- */
    /*                               REFUND LOGIC                                 */
    /* -------------------------------------------------------------------------- */

    function requestRefund(uint256 purchaseId) external nonReentrant {
        require(purchaseId < purchaseCount, "Invalid purchaseId");
        Purchase storage p = purchases[purchaseId];

        require(p.buyer == msg.sender, "Not the buyer");
        require(!p.refunded, "Already refunded");
        require(!p.claimed, "Funds already claimed by owner");
        require(block.timestamp <= p.timestamp + 14 days, "Refund window expired");
        if (refundRequested[purchaseId]) revert RefundAlreadyRequested();

        refundRequested[purchaseId] = true; // verification-only
        emit RefundRequested(msg.sender, purchaseId, p.amountPaid, p.paidInEth ? "ETH" : "USDT", block.timestamp);
    }

    /* owner-claim functions removed (direct forwarding implemented) */

    /**
     * @notice Admin-only refund processor. For ETH purchases, send exact ETH with the call.
     *         For USDT purchases, the admin must have approved this contract for amountPaid.
     */
    function processRefund(uint256 purchaseId) external payable onlyOwner nonReentrant {
        require(purchaseId < purchaseCount, "Invalid purchaseId");
        Purchase storage p = purchases[purchaseId];

        require(!p.refunded, "Already refunded");
        require(!p.claimed,  "Funds already claimed by owner");
        if (!refundRequested[purchaseId]) revert RefundNotRequested();

        uint256 refundedUSD;
        if (p.tokensBought > 0) {
            if (p.paidInEth) {
                (, int256 answer,,,) = priceOracle.latestRoundData();
                require(answer > 0, "Invalid price data");
                uint256 ethPrice = uint256(answer); // 8-dec
                refundedUSD = (p.amountPaid * ethPrice) / 10 ** 20; // 6-dec
            } else {
                refundedUSD = p.amountPaid; // 6-dec
            }

            userDeposits[p.buyer] -= p.tokensBought;
            totalTokenSold        -= p.tokensBought;

            uint256 currentPriceMul = presaleDetails[currentPresaleStage].priceMultipler;
            uint256 currentPriceDiv = presaleDetails[currentPresaleStage].priceDivider;
            uint256 tokensRelisted  = (refundedUSD * currentPriceDiv * 10 ** 12) / currentPriceMul;
            if (tokensRelisted > p.tokensBought) tokensRelisted = p.tokensBought;
            presaleDetails[currentPresaleStage].inSale += tokensRelisted;
            inSale                                      += tokensRelisted;
        }

        if (p.paidInEth) {
            require(msg.value == p.amountPaid, "Incorrect ETH amount");
            RefundedAmount += refundedUSD * 10 ** 12;
            (bool ok, ) = payable(p.buyer).call{value: p.amountPaid}("");
            require(ok, "ETH refund failed");
            emit RefundClaimed(p.buyer, purchaseId, p.amountPaid, "ETH", block.timestamp);
        } else {
            RefundedAmount += p.amountPaid * 10 ** 12;
            usdtToken.safeTransferFrom(msg.sender, p.buyer, p.amountPaid);
            emit RefundClaimed(p.buyer, purchaseId, p.amountPaid, "USDT", block.timestamp);
        }

        p.refunded = true;
        refundRequested[purchaseId] = false;
    }

    /* -------------------------------------------------------------------------- */
    /*                     ADVANCE STAGE & FINAL FUND SWEEP                       */
    /* -------------------------------------------------------------------------- */

    function _advanceStage() internal {
        presaleDetails[currentPresaleStage].endTime = block.timestamp;
        if (presaleDetails[currentPresaleStage].inSale > 0) {
            remainingTokens += presaleDetails[currentPresaleStage].inSale;
        }

        if (currentPresaleStage >= totalPresaleStages) {
            isPresaleCompleted = true;
            presaleEndTime = block.timestamp;
            return;
        }

        currentPresaleStage += 1;
        (uint256 newMul, uint256 newDiv) = _stagePriceParams(currentPresaleStage);
        uint256 alloc = _stageTokenAllocation(currentPresaleStage);

        presaleDetails[currentPresaleStage] = PresaleDetails({
            stage:            currentPresaleStage,
            totalTokens:      alloc,
            inSale:           alloc,
            totalTokenSold:   0,
            totalUSDAmount:   0,
            priceMultipler:   newMul,
            priceDivider:     newDiv,
            startTime:        block.timestamp,
            endTime:          0
        });

        if (currentPresaleStage == totalPresaleStages && remainingTokens > 0) {
            presaleDetails[currentPresaleStage].totalTokens += remainingTokens;
            presaleDetails[currentPresaleStage].inSale     += remainingTokens;
            remainingTokens = 0;
        }
    }

    /* -------------------------------------------------------------------------- */
    /*                               USER CLAIMING                                */
    /* -------------------------------------------------------------------------- */

    function claimTokens() external nonReentrant {
        require(isPresaleCompleted, "Presale not completed");
        require(!hasClaimed[msg.sender], "Already claimed");
        require(block.timestamp >= claimStart, "Claim not started");

        uint256 totalTokensOwed = userDeposits[msg.sender];
        uint256 prevTokens = ICatsPresaleV2(presaleV2).getUserDeposits(msg.sender);
        totalTokensOwed += prevTokens;

        require(totalTokensOwed > 0, "Nothing to claim");

        hasClaimed[msg.sender] = true;
        userDeposits[msg.sender] = 0;

        IJustCatsToken(saleToken).mint(msg.sender, totalTokensOwed);
        emit TokensClaimed(msg.sender, totalTokensOwed, block.timestamp);
    }

    /* final owner withdraw removed (direct forwarding implemented) */

    /* -------------------------------------------------------------------------- */
    /*                              MISC OWNER TOOLS                              */
    /* -------------------------------------------------------------------------- */

    function isPresaleStarted() public view returns (bool) {
        return presaleStartTime < block.timestamp;
    }

    function setPresaleStartTime(uint256 _startTime) external onlyOwner {
        require(_startTime > block.timestamp, "Must be in future");
        presaleStartTime = _startTime;
        presaleDetails[currentPresaleStage].startTime = _startTime;
    }

    function startClaim(address _saleToken) external onlyOwner {
        require(_saleToken != address(0), "Zero address");
        claimStart = block.timestamp;
        saleToken  = _saleToken;
    }

    /* -------------------------------------------------------------------------- */
    /*                                VIEW HELPERS                                */
    /* -------------------------------------------------------------------------- */

    // getUSDTBalance() removed: funds no longer held in contract

    function totalUSDAfterRefund() public view returns (uint256) {
        return totalUSDAmount - RefundedAmount;
    }

    function totalRefundClaimed() external view onlyOwner returns (uint256) {
        return RefundedAmount;
    }
}"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}
"
    },
    "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "@openzeppelin/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
"
    },
    "@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}
"
    },
    "@openzeppelin/contracts/interfaces/IERC1363.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)

pragma solidity >=0.6.2;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
"
    },
    "@openzeppelin/contracts/utils/Context.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
"
    },
    "@openzeppelin/contracts/interfaces/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;

import {IERC165} from "../utils/introspection/IERC165.sol";
"
    },
    "@openzeppelin/contracts/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)

pragma solidity >=0.4.16;

import {IERC20} from "../token/ERC20/IERC20.sol";
"
    },
    "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": []
  }
}}

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xe5875f63dae46a1e3d2276f6cd4ead2bd5f245bb|verified:true|block:23724955|tx:0x60df959a3ce3f67cd78425c49b26937538bad486dd28f0a3c25bf879d6686b24|first_check:1762256778

Submitted on: 2025-11-04 12:46:21

Comments

Log in to comment.

No comments yet.