Abstract Testnet

Contract Diff Checker

Contract Name:
PRESALE

Contract Source Code:

//SPDX-License-Identifier: Prima Nocta
pragma solidity ^0.8.20;

import "./interfaces/IThresholdERC1155.sol";
import "./ThresholdERC1155.sol";
import "./interfaces/INftVault.sol";
import "./interfaces/IAggregatorV3.sol";
import "./interfaces/IUniswapV3PoolState.sol";
import "./interfaces/IMagicSwapV2Router.sol";
import "./interfaces/INftVaultFactory.sol";
import "./interfaces/IMagicSwapUniswapV2Factory.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/proxy/Clones.sol";

contract PRESALE {

    struct AltPairInfo {
        address lpaddress;
        address vaultaddress;   
        bool approved;
        uint256 tokenid;
        string symbol;
    }

    struct PresaleInfo {
        bool readyToGraduate;           
        bool graduated;  
        uint256 targetBaseTokenToRaise;    
        uint256 presalePrice;          
        uint256 returnForOne;
        uint256 baseTokenRaised;           
        uint256 totalsupply;               
        address paircoin;
        uint256 amounttolp;
        uint256 amounttosell;
        address memecoin;
    }

    IERC20 public baseToken;  
    address public owner;
    address public stakingRewards;

    uint256 public constant MAX_TOTAL_SUPPLY = 1_000_000_000_000; // 1 trillion
    uint256 public constant MIN_TOTAL_SUPPLY = 1_000_000_000;     // 1 billion
    uint256 public TARGETMCAP = 42000;
    uint256[] IDALWAYSZERO;

    mapping(address => PresaleInfo) public presaleInfo;
    mapping(address => AltPairInfo) public approvedpaircoins;
    
    INftVaultFactory factory; 
    IMagicSwapV2Router router; 
    IMagicSwapUniswapV2Factory msUniFactory;

    IThresholdERC1155 memecoinImplementation;

    address public immutable BASE_USD_AGGREGATOR;

    event MemeMade(string name, string symbol, string uri, uint256 amount, PresaleInfo presaleinfo);
    event Buy(address indexed memeCoinAddress, address indexed buyer, uint256 amountNFT, uint256 amountBaseToken);
    event Sell(address indexed memeCoinAddress, address indexed seller, uint256 amountNFT, uint256 amountBaseToken);
    event GraduationReady(address indexed memeCoinAddress, PresaleInfo presaleinfo);
    event Graduation(address indexed lpaddress, PresaleInfo presaleinfo);
    event PairCoinApproved(address indexed _collectionAddress, AltPairInfo alt);
    event PairCoinRemoved(address indexed _collectionAddress);

    constructor(IERC20 _baseToken, 
                address _stakingRewards, 
                INftVaultFactory _factory, 
                IMagicSwapV2Router _router, 
                IMagicSwapUniswapV2Factory _msufactory, 
                IThresholdERC1155 _memecoinImplementation, 
                address _baseUsdAggregator) {
        owner = msg.sender;
        baseToken = _baseToken;
        IDALWAYSZERO.push(0);
        stakingRewards = _stakingRewards;
        factory = _factory;
        router = _router;
        msUniFactory = _msufactory;
        memecoinImplementation = _memecoinImplementation;
        BASE_USD_AGGREGATOR = _baseUsdAggregator;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, 'not owner');
        _;
    }

    function startPresale(string memory _name, string memory _symbol, uint256 _totalsupply, string memory _uri, uint256[] memory _thresholds) external returns (address) {
        require(_totalsupply >= MIN_TOTAL_SUPPLY, "Total supply too low - min 10 million");
        require(_totalsupply <= MAX_TOTAL_SUPPLY, "Total supply too high - max 1 trillion");

        ThresholdERC1155 memecoin = new ThresholdERC1155();
        memecoin.initialize(msg.sender, stakingRewards, _totalsupply, _name, _symbol, _uri, _thresholds);

        (uint amountmemecointolp, uint amountmemecointosell, uint baseTokenNeededToFill) = _calculateAmounts(_totalsupply, address(0));
        uint adjustment = baseTokenNeededToFill % amountmemecointosell;

        PresaleInfo memory info;
        info.amounttolp = amountmemecointolp;
        info.amounttosell = amountmemecointosell;
        info.totalsupply = _totalsupply;
        info.memecoin = address(memecoin);
        info.targetBaseTokenToRaise = baseTokenNeededToFill - adjustment;
        info.presalePrice = info.targetBaseTokenToRaise / amountmemecointosell; 
        info.returnForOne = 0;
        info.paircoin = address(baseToken);
        presaleInfo[address(memecoin)] = info;

        emit MemeMade(_name, _symbol, _uri, _totalsupply, info);
        return address(memecoin);
    }  

    function startPresale1155(string memory _name, string memory _symbol, string memory _uri, address _paircoin1155, uint256[] memory _thresholds) external returns (address) {
        uint256 _totalsupply = 1000000000;
        AltPairInfo memory alt = approvedpaircoins[_paircoin1155];
        require(alt.approved, "not approved");
        ThresholdERC1155 memecoin = new ThresholdERC1155();
        memecoin.initialize(msg.sender, stakingRewards, _totalsupply, _name, _symbol, _uri, _thresholds);
        
        (uint amountmemecointolp, uint amountmemecointosell, uint paircoinneededtofill) = _calculateAmounts(_totalsupply, _paircoin1155);
        paircoinneededtofill = paircoinneededtofill / 1e18; 

        PresaleInfo memory info;
        info.amounttolp = amountmemecointolp;
        info.amounttosell = amountmemecointosell;
        info.graduated = false;
        info.totalsupply = _totalsupply;
        info.memecoin = address(memecoin); 
        info.paircoin = _paircoin1155;
        
        if (paircoinneededtofill > _totalsupply) {            
            // this tweaks the targetBaseTokenToRaise so it is evenly divisable by the presalePrice
            info.presalePrice = paircoinneededtofill / amountmemecointosell;
            info.returnForOne = 0;
            uint adjustment = paircoinneededtofill % amountmemecointosell;
            info.targetBaseTokenToRaise = paircoinneededtofill - adjustment;
        } else {
            info.returnForOne = amountmemecointosell / paircoinneededtofill;  
            info.presalePrice = 0; 
            uint adjustment = amountmemecointosell % paircoinneededtofill;
            info.targetBaseTokenToRaise = paircoinneededtofill - adjustment;
        }

        presaleInfo[address(memecoin)] = info;

        emit MemeMade(_name, _symbol, _uri, _totalsupply, info);
        return address(memecoin);
    }


    function buyPresale(address _memeCoinAddress, uint256 _amountNftToBuy) external {
        PresaleInfo memory info = presaleInfo[_memeCoinAddress];     
        require(!info.readyToGraduate, "already ready to graduate");
        
        (uint totalamountbaseToken, uint tax) = getQuote(_memeCoinAddress, _amountNftToBuy);

        // Check if purchase would exceed target and adjust if needed
        if (info.baseTokenRaised + totalamountbaseToken > info.targetBaseTokenToRaise) {
            totalamountbaseToken = info.targetBaseTokenToRaise - info.baseTokenRaised;
            _amountNftToBuy = totalamountbaseToken / info.presalePrice;
        }
        
        if (info.paircoin == address(baseToken)) {
            bool chaching = IERC20(info.paircoin).transferFrom(msg.sender, address(this), totalamountbaseToken + tax);
            bool taxed = IERC20(info.paircoin).transfer(stakingRewards, tax);
            require(chaching && taxed, "buy failed");
            
            presaleInfo[_memeCoinAddress].baseTokenRaised = info.baseTokenRaised + totalamountbaseToken;        
            IThresholdERC1155(info.memecoin).safeTransferFrom(address(this), msg.sender, 0, _amountNftToBuy, "");
        } else {
            IERC1155(info.paircoin).safeTransferFrom(msg.sender, address(this), 0, totalamountbaseToken, "");
            presaleInfo[_memeCoinAddress].baseTokenRaised = info.baseTokenRaised + totalamountbaseToken;        
            IThresholdERC1155(info.memecoin).safeTransferFrom(address(this), msg.sender, 0, _amountNftToBuy, "");
        }

        // Mark as ready to graduate if target is met
        if (info.baseTokenRaised + totalamountbaseToken >= info.targetBaseTokenToRaise) {
            _graduate(_memeCoinAddress);
        }

        emit Buy(_memeCoinAddress, msg.sender, _amountNftToBuy, totalamountbaseToken);
    }

    function sellPresale(address _memeCoinAddress, uint256 _sellAmountNFT) external {
        PresaleInfo memory info = presaleInfo[_memeCoinAddress];        
        require(!info.readyToGraduate, "already graduated");
        (uint baseTokenToReturn, uint tax) = getQuote(_memeCoinAddress, _sellAmountNFT);
        require(baseTokenToReturn <= info.baseTokenRaised, "plunge protection");

        if (info.paircoin == address(baseToken)) {
            bool sendtouser = IERC20(info.paircoin).transfer(msg.sender, baseTokenToReturn - tax);
            bool taxed = IERC20(info.paircoin).transfer(stakingRewards, tax);
            require(sendtouser && taxed, "sell failed");
            presaleInfo[_memeCoinAddress].baseTokenRaised = info.baseTokenRaised - baseTokenToReturn;       

            IThresholdERC1155(info.memecoin).safeTransferFrom(msg.sender, address(this), 0, _sellAmountNFT, "");
        } else {
            IThresholdERC1155(info.memecoin).safeTransferFrom(msg.sender, address(this), 0, _sellAmountNFT, "");
            presaleInfo[_memeCoinAddress].baseTokenRaised = info.baseTokenRaised - baseTokenToReturn;       

            IERC1155(info.paircoin).safeTransferFrom(address(this), msg.sender, 0, baseTokenToReturn, "");   
        } 
        emit Sell(_memeCoinAddress, msg.sender, _sellAmountNFT, baseTokenToReturn);
    }

    function _graduateBaseTokenPool(PresaleInfo memory info, INftVault vaultMemeCoin, IMagicSwapV2Router.NftVaultLiquidityData memory vaultdataMemeCoin) internal returns (address lpaddy) {
            IERC20(info.paircoin).approve(address(router), info.baseTokenRaised); 
            (uint256 amountA, uint256 amountB, uint256 lpAmount) = router.addLiquidityNFT(
                vaultdataMemeCoin, 
                address(info.paircoin), 
                info.baseTokenRaised, 
                info.baseTokenRaised, 
                address(0),
                block.timestamp
            );
            require(lpAmount > 0, "something bad happened");
            lpaddy = msUniFactory.getPair(info.paircoin, address(vaultMemeCoin));
    }

    function _graduate1155Pool(PresaleInfo memory info, INftVault vaultMemeCoin, IMagicSwapV2Router.NftVaultLiquidityData memory vaultdataMemeCoin) internal returns (address lpaddy) {
            IERC1155(info.paircoin).setApprovalForAll(address(router), true);
            AltPairInfo memory alt = approvedpaircoins[info.paircoin];
            INftVault vaultPaircoin = INftVault(alt.vaultaddress);

            address[] memory collection = new address[](1);
            uint256[] memory amount = new uint256[](1);

            collection[0] = info.paircoin; // cd.addr;
            amount[0] = info.baseTokenRaised;
            IMagicSwapV2Router.NftVaultLiquidityData memory vaultdataPairCoin = IMagicSwapV2Router.NftVaultLiquidityData(
                vaultPaircoin,
                collection,
                IDALWAYSZERO,
                amount
            );
            
            (uint256 amountA, uint256 amountB,) = router.addLiquidityNFTNFT(
                vaultdataMemeCoin,      // shitcoin
                vaultdataPairCoin,      // pair coin
                IThresholdERC1155(info.memecoin).balanceOf(address(this), 0),
                info.baseTokenRaised,
                address(0),
                block.timestamp
            );

            lpaddy = msUniFactory.getPair(address(vaultPaircoin), address(vaultMemeCoin));
    }

    function _graduate(address _memeCoinAddress) internal {
        PresaleInfo storage info = presaleInfo[_memeCoinAddress];
        require(!info.readyToGraduate, "already ready to graduate");
        require(info.baseTokenRaised >= info.targetBaseTokenToRaise, "target not met");
        
        // Mark the presale as ready to graduate
        info.readyToGraduate = true;

        emit GraduationReady(_memeCoinAddress, info);
    }

    function graduatePresale(address _memeCoinAddress) external returns (address lpaddy) {
        require(
            presaleInfo[_memeCoinAddress].readyToGraduate && 
            !presaleInfo[_memeCoinAddress].graduated, 
            "not ready to graduate or already graduated"
        );
        
        PresaleInfo memory info = presaleInfo[_memeCoinAddress];
        presaleInfo[_memeCoinAddress].graduated = true;

        IThresholdERC1155(info.memecoin).setTradingOpen(true);

        (INftVault vaultMemeCoin, IMagicSwapV2Router.NftVaultLiquidityData memory vaultdataMemeCoin) = 
            _createMemeCoinVault(_memeCoinAddress);
      
        IThresholdERC1155(info.memecoin).setApprovalForAll(address(router), true);

        if (info.paircoin == address(baseToken)) {
            lpaddy = _graduateBaseTokenPool(info, vaultMemeCoin, vaultdataMemeCoin);
        } else {
            lpaddy = _graduate1155Pool(info, vaultMemeCoin, vaultdataMemeCoin);            
        }
        
        require(lpaddy != address(0), "lp failed");
        
        emit Graduation(lpaddy, presaleInfo[_memeCoinAddress]); 
    }


    function _createMemeCoinVault(address _memeCoinAddress) internal returns (INftVault, IMagicSwapV2Router.NftVaultLiquidityData memory) {
        // creates a magicswap vault for the presaled 1155
        address[] memory collection = new address[](1);
        uint256[] memory amount = new uint256[](1);
        INftVault.CollectionData[] memory vaultCD = new INftVault.CollectionData[](1);
        INftVault.CollectionData memory vaultCDData = INftVault.CollectionData(address(_memeCoinAddress), INftVault.NftType.ERC1155, false, IDALWAYSZERO);

        vaultCD[0] = vaultCDData;
        collection[0] = _memeCoinAddress;                     
        amount[0] = IThresholdERC1155(presaleInfo[_memeCoinAddress].memecoin).balanceOf(address(this), 0);
        
        bool exists = factory.exists(vaultCD);
        INftVault vault;
        if (exists) {
            vault = factory.getVault(vaultCD);
        } else {
            vault = factory.createVault(vaultCD);        
        }

        IMagicSwapV2Router.NftVaultLiquidityData memory vaultdata = IMagicSwapV2Router.NftVaultLiquidityData(
            vault, 
            collection, 
            IDALWAYSZERO, 
            amount
        );


        return (vault, vaultdata); 
    }  

    function getQuote(address _memeCoinAddress, uint256 _amountOfNftToBuy) public view returns(uint totalbaseToken, uint tax ){
        PresaleInfo memory info = presaleInfo[_memeCoinAddress];
        if (info.presalePrice > 0) {    
            totalbaseToken = info.presalePrice * _amountOfNftToBuy; 
            tax = totalbaseToken / 200; // 0.5%
        } else {
            require(_amountOfNftToBuy % info.returnForOne == 0, "wrong multiple");            
            totalbaseToken = _amountOfNftToBuy / info.returnForOne; 
            require(totalbaseToken > 0, 'xxxxx');
            tax = 0;
        }
    }

    function _calculateAmounts(uint256 _totalsupply, address _paircoin1155) internal view returns (uint256, uint256, uint256) {
        require(_totalsupply >= MIN_TOTAL_SUPPLY, "Min 10M");
        require(_totalsupply <= MAX_TOTAL_SUPPLY, "Max 1T");
        require(_totalsupply % 1000 == 0, "Must be divisible by 1000");

        // Calculate initial amount after staking rewards (10%)
        uint256 amount = (_totalsupply * 90) / 100;
        
        // Calculate amounts for LP and presale (50/50 split)
        uint256 amountmemecointolp = amount / 2;
        uint256 amountmemecointosell = amount / 2;
        
        uint256 baseTokenNeededToFill;
        if (_paircoin1155 == address(0)) {
            baseTokenNeededToFill = calculateBaseTokenNeededForTargetMarketCap(
                TARGETMCAP * 1e6, // $42k total target
                amountmemecointolp,
                getBaseTokenPriceUSD(),
                18
            );
        } else {
            baseTokenNeededToFill = calculateBaseTokenNeededForTargetMarketCap(
                TARGETMCAP * 1e6,
                amountmemecointolp,
                getAltPairCoinPriceUSD(_paircoin1155),
                18 // Assuming the ERC1155 are using 18 decimals
            );
        }
        
        require(baseTokenNeededToFill > 0, "bad amounts");
        return (amountmemecointolp, amountmemecointosell, baseTokenNeededToFill);
    }

    function getAltPairCoinPriceUSD(address _altcoin) public view returns(uint256) {
        AltPairInfo memory alt = approvedpaircoins[_altcoin];
        require(alt.approved, "Pair not approved");
        
        uint256 baseTokenBal = baseToken.balanceOf(alt.lpaddress);
        uint256 altPairBal = IERC20(alt.vaultaddress).balanceOf(alt.lpaddress);
        require(altPairBal > 0, "No liquidity");
        
        uint256 altPairValInBaseToken = (baseTokenBal * 1e18) / altPairBal;
        uint256 baseTokenPriceUSD = getBaseTokenPriceUSD();
        
        return (altPairValInBaseToken * baseTokenPriceUSD) / 1e18;
    }

    function addAltPair(address _ca, AltPairInfo memory _alp) external onlyOwner {
        require(_ca != address(0), "Zero address not allowed");
        require(_alp.lpaddress != address(0), "Invalid LP address");
        require(_alp.vaultaddress != address(0), "Invalid vault address");
        require(_alp.approved, "Pair must be approved");
        approvedpaircoins[_ca] = _alp;
        emit PairCoinApproved(_ca, _alp);
    }

    function removePairCoin(address _ca) external onlyOwner {
        require(approvedpaircoins[_ca].approved, "Pair not found");
        delete approvedpaircoins[_ca];
        emit PairCoinRemoved(_ca);
    }

    function changeTargetMcap(uint256 _mcapindollars) external onlyOwner {
        require(_mcapindollars > 0, "mcap must be greater than 0");
        TARGETMCAP = _mcapindollars;
    }

    
    function changeOwner(address _newowner) external onlyOwner {
        require(_newowner != address(0), "new owner is the zero address");
        owner = _newowner;
    }   

    function getBaseTokenPriceUSD() public view returns (uint baseTokenPrice) {
        // Get the latest BASE_TOKEN/USD price from the Chainlink oracle
        IAggregatorV3 baseUsdOracle = IAggregatorV3(BASE_USD_AGGREGATOR);
        (
                ,
                int256 basePrice,
                ,
                ,

            ) = baseUsdOracle.latestRoundData();

        // Convert from Chainlink's 8 decimals to 6 decimals
        baseTokenPrice = uint256(basePrice) / 100;
    }


    function calculateBaseTokenNeededForTargetMarketCap(
        uint256 targetMcapUSD,  // $42k in 6 decimals
        uint256 lpTokenAmount,
        uint256 pairPriceUSD,   // Price in 6 decimals
        uint256 pairDecimals
    ) internal pure returns (uint256) {
        require(pairPriceUSD > 0, "Invalid price");
        
        // We want half the target for each side (presale/LP)
        uint256 valuePerSideUSD = targetMcapUSD / 2;  // $21k in 6 decimals
        
        // Calculate pair tokens needed for one side
        uint256 pairTokensForOneSide = (valuePerSideUSD * (10 ** pairDecimals)) / pairPriceUSD;
        
        // Double it (same amount needed for both presale and LP)
        return pairTokensForOneSide * 2;
    }

    function ceil(uint a, uint m) internal pure returns (uint r) {
        return (a + m - 1) / m * m;
    }

    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] calldata,
        uint256[] calldata,
        bytes calldata
    ) external virtual returns (bytes4) {
        return this.onERC1155BatchReceived.selector;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

contract ThresholdERC1155 is ERC165, IERC1155 {
    using Address for address;

    // Core state - just track total balances
    mapping(address => uint256) private _totalBalances;
    mapping(address => mapping(address => bool)) private _operatorApprovals;
    uint256[] public thresholds;
    uint256 public totalSupply;
    
    // Token metadata
    string private _baseURI;
    string public name;
    string public symbol;
    
    // Admin state
    bool public initialized;
    address public creator;
    address public presalefactory;
    bool public tradingEnabled;

    event ThresholdAdded(uint256 threshold, uint256 index);

    constructor() {}

    modifier onlyPresaleFactory() {
        require(msg.sender == presalefactory, "not factory");
        _;
    }

    modifier onlyCreator() {
        require(msg.sender == creator, "not creator");
        _;
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IERC1155).interfaceId || super.supportsInterface(interfaceId);
    }

    function setTradingOpen(bool _x) public onlyPresaleFactory() {
        tradingEnabled = _x;
    }

    function uri(uint256 id) public view virtual returns (string memory) {
        require(id <= thresholds.length, "Invalid token ID");
        return _baseURI;
    }
    
    function setBaseURI(string memory newuri) public onlyCreator {
        _baseURI = newuri;
    }

    function addThreshold(uint256 newThreshold) external onlyCreator {
        // Must be higher than the last threshold
        require(thresholds.length > 0, "Thresholds array is empty");
        require(newThreshold > thresholds[thresholds.length - 1], "New threshold must be higher than existing ones");
        
        require(newThreshold < totalSupply, "Threshold must be less than total supply");
        
        thresholds.push(newThreshold);
        
        emit ThresholdAdded(newThreshold, thresholds.length);
    }

    function initialize(
        address _creator,
        address _stakingRewards,
        uint256 _totalSupply,
        string memory _name,
        string memory _symbol,
        string memory baseURI,
        uint256[] memory _thresholds  // New parameter
    ) external {
        require(!initialized, "Already initialized");
        require(_creator != address(0), "Zero creator address");
        require(_thresholds.length > 0, "Empty thresholds");
        
        // Verify thresholds are in ascending order
        for(uint256 i = 1; i < _thresholds.length; i++) {
            require(_thresholds[i] > _thresholds[i-1], "Thresholds must be ascending");
        }
        require(_thresholds[_thresholds.length - 1] < _totalSupply, "Highest threshold must be less than total supply");
        
        thresholds = _thresholds;
        creator = _creator;
        presalefactory = msg.sender;
        initialized = true;
        name = _name;
        symbol = _symbol;
        _baseURI = baseURI;

        // Initial distribution (90/10 split)
        uint256 presaleAmount = _totalSupply * 90 / 100;
        uint256 stakingAmount = _totalSupply - presaleAmount;
        
        _totalBalances[msg.sender] = presaleAmount;
        _totalBalances[_stakingRewards] = stakingAmount;

        totalSupply = _totalSupply;

        // Emit transfer events with appropriate IDs
        emit TransferSingle(msg.sender, address(0), msg.sender, 0, presaleAmount);
        emit TransferSingle(msg.sender, address(0), _stakingRewards, 0, stakingAmount);
    }

    // Core balance view functions
    function balanceOf(address account, uint256 id) public view override returns (uint256) {
        require(account != address(0), "ERC1155: address zero is not a valid owner");
        
        // For contracts, only show balance at id 0
        if (!isExternallyOwned(account)) {
            return id == 0 ? _totalBalances[account] : 0;
        }
        
        // For EOAs, show balance only at appropriate id
        uint256 appropriateId = _getAppropriateId(_totalBalances[account]);
        return id == appropriateId ? _totalBalances[account] : 0;
    }

    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) public view override returns (uint256[] memory) {
        require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

        uint256[] memory batchBalances = new uint256[](accounts.length);
        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }
        return batchBalances;
    }

    // Transfer functions
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) public override {
        require(
            from == msg.sender || isApprovedForAll(from, msg.sender),
            "ERC1155: caller is not owner nor approved"
        );
        require(to != address(0), "ERC1155: transfer to the zero address");
        require(tradingEnabled || to == presalefactory || from == presalefactory, "Trading not enabled");
        
        require(id <= thresholds.length, "Invalid token ID");
        // Verify balance
        require(_totalBalances[from] >= amount, "ERC1155: insufficient balance for transfer");

        // Update total balances
        _totalBalances[from] -= amount;
        _totalBalances[to] += amount;

        // Determine recipient's appropriate ID
        uint256 toId = !isExternallyOwned(to) ? 0 : _getAppropriateId(_totalBalances[to]);
        
        emit TransferSingle(msg.sender, from, to, toId, amount);

        _doSafeTransferAcceptanceCheck(msg.sender, from, to, toId, amount, data);
    }

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) public override {
        require(
            from == msg.sender || isApprovedForAll(from, msg.sender),
            "ERC1155: transfer caller is not owner nor approved"
        );
        require(to != address(0), "ERC1155: transfer to the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        require(tradingEnabled || to == presalefactory || from == presalefactory, "Trading not enabled");

        // Calculate total amount and verify balances
        uint256 totalAmount = 0;
        for (uint256 i = 0; i < ids.length; i++) {
            totalAmount += amounts[i];
        }

        require(_totalBalances[from] >= totalAmount, "ERC1155: insufficient balance for transfer");


        // Update total balances
        _totalBalances[from] -= totalAmount;
        _totalBalances[to] += totalAmount;

        // For the batch event, use the appropriate ID for the recipient
        uint256 toId = !isExternallyOwned(to) ? 0 : _getAppropriateId(_totalBalances[to]);
        uint256[] memory newIds = new uint256[](ids.length);
        for (uint256 i = 0; i < ids.length; i++) {
            newIds[i] = toId;
        }

        emit TransferBatch(msg.sender, from, to, newIds, amounts);

        _doSafeBatchTransferAcceptanceCheck(msg.sender, from, to, newIds, amounts, data);
    }

    // Approval functions
    function setApprovalForAll(address operator, bool approved) public override {
        require(msg.sender != operator, "ERC1155: setting approval status for self");
        _operatorApprovals[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function isApprovedForAll(address account, address operator) public view override returns (bool) {
        return _operatorApprovals[account][operator];
    }

    function totalBalanceOf(address account) public view returns (uint256) {
        return _totalBalances[account];
    }

    // Internal helpers
    function isExternallyOwned(address account) private view returns (bool) {
        return account.code.length == 0;
    }

    function _getAppropriateId(uint256 balance) internal view returns (uint256) {
        for (uint256 i = thresholds.length; i > 0; i--) {
            if (balance >= thresholds[i - 1]) {
                return i;
            }
        }
        return 0;
    }

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (!isExternallyOwned(to)) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (!isExternallyOwned(to)) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non ERC1155Receiver implementer");
            }
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.12;

interface IThresholdERC1155 {
    // Events
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 amount);
    event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] amounts);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    // ERC1155 core functions
    function setApprovalForAll(address operator, bool approved) external;
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
    function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external;
    function balanceOf(address account, uint256 id) external view returns (uint256);
    function balanceOfBatch(address[] calldata owners, uint256[] calldata ids) external view returns (uint256[] memory);
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    function supportsInterface(bytes4 interfaceId) external view returns (bool);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function uri(uint256 id) external view returns (string memory);

    // Additional view functions
    function totalBalanceOf(address account) external view returns (uint256);
    function thresholds(uint256 index) external view returns (uint256);

    // Admin functions
    function initialize(address _creator, address _teamwallet, uint256 _totalSupply, string memory _name, string memory _symbol, string memory baseURI, uint256[] memory _thresholds) external;
    function setTradingOpen(bool _x) external;

    // State view functions
    function initialized() external view returns (bool);
    function creator() external view returns (address);
    function presalefactory() external view returns (address);
    function tradingEnabled() external view returns (bool);
}

//SPDX-License-Identifier: Prima Nocta
pragma solidity ^0.8.20;

interface IAggregatorV3 {
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}

//SPDX-License-Identifier: Prima Nocta
pragma solidity ^0.8.20;

interface IUniswapV3PoolState {
    function slot0() external view returns (
        uint160 sqrtPriceX96,
        int24 tick,
        uint16 observationIndex,
        uint16 observationCardinality,
        uint16 observationCardinalityNext,
        uint8 feeProtocol,
        bool unlocked
    );
}

//SPDX-License-Identifier: Prima Nocta
pragma solidity ^0.8.20;

import "./INftVault.sol";

interface INftVaultFactory {
    function createVault(INftVault.CollectionData[] memory collections) external returns (INftVault vault);
    function getVault(INftVault.CollectionData[] memory collections) external view returns (INftVault vault);
    function getVaultLength() external view returns (uint256);
    function getVaultAt(uint256 index) external view returns (address);
    function exists(INftVault.CollectionData[] memory collections) external view returns (bool);
}

//SPDX-License-Identifier: Prima Nocta
pragma solidity ^0.8.20;

import "./INftVault.sol";

interface IMagicSwapV2Router { 
    struct NftVaultLiquidityData {
        INftVault token;
        address[] collection;
        uint256[] tokenId;
        uint256[] amount;
    }
    
    function addLiquidityNFT(
        NftVaultLiquidityData calldata _vault,
        address _tokenB,
        uint256 _amountBDesired,
        uint256 _amountBMin,
        address _to,
        uint256 _deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 lpAmount);
    
    function addLiquidityNFTNFT(
        NftVaultLiquidityData calldata _vaultA,
        NftVaultLiquidityData calldata _vaultB,
        uint256 _amountAMin,
        uint256 _amountBMin,
        address _to,
        uint256 _deadline
    ) external returns (uint256 amountA, uint256 amountB, uint256 lpAmount);
    
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    
    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

//SPDX-License-Identifier: Prima Nocta
pragma solidity ^0.8.20;

interface IMagicSwapUniswapV2Factory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);   
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title Vault contract for wrapping NFTs (ERC721/ERC1155) to ERC20
interface INftVault {
    enum NftType {
        ERC721,
        ERC1155
    }

    /// @notice Vault configuration struct that specifies which NFTs are accepted in vault.
    /// @param addr address of nft contract
    /// @param nftType standard that NFT supports { ERC721, ERC1155 }
    /// @param allowAllIds if true, all tokens are allowed in the vault. If false, tokenIds must be
    ///        listed one by one.
    /// @param tokenIds list of tokens supported by vault. If allowAllIds is true, list must be empty.
    struct CollectionData {
        address addr;
        NftType nftType;
        bool allowAllIds;
        uint256[] tokenIds;
    }

    /// @notice Struct for allowed tokens. Stores data in an optimized way to read it in vault.
    /// @param tokenIds mapping from tokenid to is-allowed
    /// @param tokenIdList list of all tokens that are allowed
    /// @param allowAllIds if true, all tokens are allowed
    struct AllowedTokenIds {
        mapping(uint256 => bool) tokenIds;
        uint256[] tokenIdList;
        bool allowAllIds;
    }

    /// @notice Emitted during initiation when collection added to allowed list
    /// @param collection collection details
    event CollectionAllowed(CollectionData collection);

    /// @notice Emitted on depositing NFT to vault
    /// @param to address that gets vault ERC20 tokens
    /// @param collection NFT address that is deposited
    /// @param tokenId token id that is deposited
    /// @param amount amount of token that is deposited, for ERC721 always 1
    event Deposit(address indexed to, address indexed collection, uint256 tokenId, uint256 amount);

    /// @notice Emitted on withdrawing NFT from vault
    /// @param to address that gets withdrawn NFTs
    /// @param collection NFT address that is withdrawn
    /// @param tokenId token id that is withdrawn
    /// @param amount amount of token that is withdrawn, for ERC721 always 1
    event Withdraw(address indexed to, address indexed collection, uint256 tokenId, uint256 amount);

    /// @dev Contract is already initialized
    error Initialized();
    /// @dev Collection data is empty
    error InvalidCollections();
    /// @dev Collection already added
    error DuplicateCollection();
    /// @dev Token id is listed twice in CollectionData.tokenIds array
    error TokenIdAlreadySet();
    /// @dev Token ids in CollectionData.tokenIds array are not sorted
    error TokenIdsMustBeSorted();
    /// @dev ERC165 suggests that NFT is supporting ERC721 but ERC1155 is claimed
    error ExpectedERC721();
    /// @dev ERC165 suggests that NFT is supporting ERC1155 but ERC721 is claimed
    error ExpectedERC1155();
    /// @dev Collection does not support all token IDs however list of IDs is empty.
    ///      CollectionData.tokenIds is empty and CollectionData.allowAllIds is false.
    error MissingTokenIds();
    /// @dev CollectionData.tokenIds is not empty however Collection supports all token IDs.
    error TokenIdsMustBeEmpty();
    /// @dev Token is not allowed in vault
    error DisallowedToken();
    /// @dev Token amount is invalid eg. amount == 0
    error WrongAmount();
    /// @dev Token amount is invalid for ERC721, amount != 1
    error WrongERC721Amount();
    /// @dev Trying to interact with token that does not support ERC721 nor ERC1155
    error UnsupportedNft();
    /// @dev Token is allowed in vault but must not be
    error MustBeDisallowedToken();

    /// @notice value of 1 token, including decimals
    function ONE() external view returns (uint256);

    /// @notice amount of token required for last NFT to be redeemed
    function LAST_NFT_AMOUNT() external view returns (uint256);

    /// @notice unique id of the vault generated using its configuration
    function VAULT_HASH() external view returns (bytes32);

    /// @notice Initialize Vault with collection config
    /// @dev Called by factory during deployment
    /// @param collections struct array of allowed collections and token IDs
    function init(CollectionData[] memory collections) external;

    /// @notice Returns hash of vault configuration
    /// @param collections struct array of allowed collections and token IDs
    /// @return configuration hash
    function hashVault(CollectionData[] memory collections) external pure returns (bytes32);

    /// @notice Returns balances of NFT deposited to the vault
    /// @param collectionAddr NFT address
    /// @param tokenId NFT's token ID
    /// @return amount amount of NFT deposited to the vault
    function balances(address collectionAddr, uint256 tokenId) external view returns (uint256 amount);

    /// @notice Get array of NFT addresses that are allowed to be deposited to the vault
    /// @dev Keep in mind that returned address(es) can be further restricted on token ID level
    /// @return collections array of NFT addresses that are allowed to be deposited to the vault
    function getAllowedCollections() external view returns (address[] memory collections);

    /// @return number of NFT addresses that are allowed to be deposited to the vault
    function getAllowedCollectionsLength() external view returns (uint256);

    /// @notice Get details of allowed collection
    /// @return struct with details of allowed collection
    function getAllowedCollectionData(address collectionAddr) external view returns (CollectionData memory);

    /// @notice Validates type of collection (ERC721 or ERC1155)
    /// @dev It uses ERC165 to check interface support. If support can not be detected without doubt, user input is trusted.
    /// @param collectionAddr NFT address
    /// @param nftType NFT type, ERC721 or ERC1155
    /// @return validatedNftType returns validated enum NftType as uint256
    function validateNftType(address collectionAddr, NftType nftType)
        external
        view
        returns (uint256 validatedNftType);

    /// @notice Returns if true token can be deposited
    /// @param collection NFT address
    /// @param tokenId NFT token ID
    /// @return true if allowed
    function isTokenAllowed(address collection, uint256 tokenId) external view returns (bool);

    /// @notice Returns balance of token sent to the vault
    /// @dev Reads balance of tokens freshy sent to the vault
    /// @param collection NFT address
    /// @param tokenId NFT token ID
    /// @return balance of sent token, for ERC721 it's always 1
    function getSentTokenBalance(address collection, uint256 tokenId) external view returns (uint256);

    /// @notice Deposit NFT to vault
    /// @dev This low-level function should be called from a contract which performs important safety checks
    /// @param to address that gets minted ERC20 token
    /// @param collection address of deposited NFT
    /// @param tokenId token ID of deposited NFT
    /// @param amount amount of deposited NFT, for ERC721 it's always 1
    /// @return amountMinted amount of minted ERC20 token
    function deposit(address to, address collection, uint256 tokenId, uint256 amount)
        external
        returns (uint256 amountMinted);

    /// @notice Deposit NFTs to vault
    /// @dev This low-level function should be called from a contract which performs important safety checks
    /// @param to address that gets minted ERC20 token
    /// @param collection array of addresses of deposited NFTs
    /// @param tokenId array of token IDs of deposited NFTs
    /// @param amount array if amounts of deposited NFTs, for ERC721 it's always 1
    /// @return amountMinted amount of minted ERC20 token
    function depositBatch(address to, address[] memory collection, uint256[] memory tokenId, uint256[] memory amount)
        external
        returns (uint256 amountMinted);

    /// @notice Withdraw NFT from vault
    /// @dev This low-level function should be called from a contract which performs important safety checks
    /// @param to address that gets NFT
    /// @param collection address of NFT to withdraw
    /// @param tokenId token ID of NFT to withdraw
    /// @param amount amount of NFT to withdraw, for ERC721 it's always 1
    /// @return amountBurned amount of burned ERC20
    function withdraw(address to, address collection, uint256 tokenId, uint256 amount)
        external
        returns (uint256 amountBurned);

    /// @notice Withdraw NFTs from vault
    /// @dev This low-level function should be called from a contract which performs important safety checks
    /// @param to address that gets NFT
    /// @param collection array of addresses of NFTs to withdraw
    /// @param tokenId array of token IDs of NFTs to withdraw
    /// @param amount array of amounts of NFTs to withdraw, for ERC721 it's always 1
    /// @return amountBurned amount of burned ERC20
    function withdrawBatch(address to, address[] memory collection, uint256[] memory tokenId, uint256[] memory amount)
        external
        returns (uint256 amountBurned);

    /// @notice Allow anyone to withdraw tokens sent to this vault by accident
    ///         Only unsupported NFTs can be skimmed.
    /// @param to address that gets NFT
    /// @param nftType NftType of skimmed NFT
    /// @param collection address of NFT to skim
    /// @param tokenId token ID of NFT to skim
    /// @param amount amount of NFT to skim, for ERC721 it's always 1
    function skim(address to, NftType nftType, address collection, uint256 tokenId, uint256 amount) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[ERC].
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

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

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the zero address.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155Received} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `value` amount.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits either a {TransferSingle} or a {TransferBatch} event, depending on the length of the array arguments.
     *
     * Requirements:
     *
     * - `ids` and `values` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (proxy/Clones.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[ERC-1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        return clone(implementation, 0);
    }

    /**
     * @dev Same as {xref-Clones-clone-address-}[clone], but with a `value` parameter to send native currency
     * to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function clone(address implementation, uint256 value) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(value, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        return cloneDeterministic(implementation, salt, 0);
    }

    /**
     * @dev Same as {xref-Clones-cloneDeterministic-address-bytes32-}[cloneDeterministic], but with
     * a `value` parameter to send native currency to the new contract.
     *
     * NOTE: Using a non-zero value at creation will require the contract using this function (e.g. a factory)
     * to always have enough balance for new deployments. Consider exposing this function under a payable method.
     */
    function cloneDeterministic(
        address implementation,
        bytes32 salt,
        uint256 value
    ) internal returns (address instance) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        assembly ("memory-safe") {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(value, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert Errors.FailedDeployment();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := and(keccak256(add(ptr, 0x43), 0x55), 0xffffffffffffffffffffffffffffffffffffffff)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}

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

pragma solidity ^0.8.20;

/**
 * @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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Interface that must be implemented by smart contracts in order to receive
 * ERC-1155 token transfers.
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC-1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC-1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert Errors.FailedCall();
        }
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {Errors.FailedCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
     * of an unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {Errors.FailedCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            assembly ("memory-safe") {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert Errors.FailedCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC-165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):