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 "./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;
        IThresholdERC1155 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");

        IThresholdERC1155 memecoin = IThresholdERC1155(Clones.clone(address(memecoinImplementation)));
        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 = 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");
        IThresholdERC1155 memecoin = IThresholdERC1155(Clones.clone(address(memecoinImplementation)));
        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 = 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;        
            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;        
            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;       

            info.memecoin.safeTransferFrom(msg.sender, address(this), 0, _sellAmountNFT, "");
        } else {
            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
                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;

        info.memecoin.setTradingOpen(true);

        (INftVault vaultMemeCoin, IMagicSwapV2Router.NftVaultLiquidityData memory vaultdataMemeCoin) = 
            _createMemeCoinVault(_memeCoinAddress);
      
        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] = 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.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: 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: 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;

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;

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

//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: 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) (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) (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/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) (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):