creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "projects/ppp3/PPP3.sol": {
        "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.25;\nimport \"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/access/Ownable.sol\";\nimport \"./libs/ShufflerV3.sol\";\n\n\ncontract PPP3 is Ownable {\n    IDelegateRegistry internal constant registry = IDelegateRegistry(0x00000000000000447e69651d841bD8D104Bed493);\n    INFT internal immutable CONTRACT_AD; // mainnet: 0x9CF0aB1cc434dB83097B7E9c831a764481DEc747\n    INFT internal immutable CONTRACT_FPP; // mainnet: 0xA8A425864dB32fCBB459Bf527BdBb8128e6abF21\n\n    struct UserStats {\n        uint256 passCount;\n        uint256 aDCount;\n        uint256 availableExclusiveMints;\n        uint256[10] mintsPerArtist;\n        uint256[10] mintsAvailable;\n    }\n\n    uint256 public startTime;\n    uint256 public exclusivePrice = 0.05 ether;\n    uint256 public publicPrice = 0.1 ether;\n    uint256 public maxPerArtist = 10;\n    uint256 public mintsPerPass = 2;\n    uint256 public stage1Duration = 30 minutes;\n    uint256 public stage2Duration = 30 minutes; \n    address public mintable;\n    bool public paused;\n    uint256 public totalSold;\n\n    mapping(address => uint256[10]) public mintCount;\n    mapping(address => uint256) public exclusiveMintCount;\n    mapping(uint256 => ShufflerV3) public collectionToShuffler;\n\n    constructor(\n        uint256 _startTime,\n        address collectionAddress,\n        address FPPAddress,\n        address ADAddress\n    ) Ownable(msg.sender) {\n        startTime = _startTime;\n        mintable = collectionAddress;\n        for (uint256 i; i < 10; i++) {\n            collectionToShuffler[i] = new ShufflerV3(100);\n        }\n        CONTRACT_FPP = INFT(FPPAddress);\n        CONTRACT_AD = INFT(ADAddress);\n    }\n\n    function mint(\n        uint256[] calldata wantedCollections,\n        uint256[] calldata wantedQuantity\n    ) external payable {\n        require(totalSold < 1000, \"Sold out.\");\n        require(!paused, \"Sale is paused.\");\n        require(wantedCollections.length == wantedQuantity.length, \"Array length mismatch.\");\n        require(\n            block.timestamp >= startTime,\n            \"Sale not started.\"\n        );\n        uint256 totalQuantity;\n        uint256 globalMax = fPPCount(msg.sender) * mintsPerPass;\n        if (block.timestamp >= startTime + stage1Duration) {\n            globalMax += aDCount(msg.sender) * mintsPerPass;\n        }\n        if(isPublic()) {\n            globalMax = 1000;\n        }\n\n        for (uint256 i; i < wantedCollections.length; i++) {\n            uint256 collection = wantedCollections[i];\n            ShufflerV3 shuffler = collectionToShuffler[collection];\n            uint256 amount = wantedQuantity[i];\n            uint256 remaining = shuffler.remainingNumbers();\n            if (amount > remaining) {\n                amount = remaining;\n            }\n            uint256 alreadyMinted = mintCount[msg.sender][collection];\n            uint256 allowance = maxPerArtist - alreadyMinted;\n            if (amount > allowance) {\n                amount = allowance;\n            }\n            uint256 alreadyMintedExclusive = exclusiveMintCount[msg.sender];\n            uint256 globalAllowance = globalMax - alreadyMintedExclusive;\n            if (amount > globalAllowance) {\n              amount = globalAllowance;\n            }\n            mintCount[msg.sender][collection] += amount;\n            // public mints don't count against pass limit\n            if (!isPublic()) {\n                exclusiveMintCount[msg.sender] += amount;\n            }\n            totalQuantity += amount;\n            for (uint256 j; j < amount; j++) {\n                INFT(mintable).mint(msg.sender, collection * 100 + shuffler.drawNext());\n            }\n        }\n        totalSold += totalQuantity;\n        uint256 price = isPublic() ? publicPrice : exclusivePrice;\n        uint256 totalPrice = price * totalQuantity;\n        require(totalPrice <= msg.value, \"Insufficient ETH sent.\");\n        uint256 refund = msg.value - totalPrice;\n        if (refund > 0) {\n            (bool success,) = msg.sender.call{value: refund}(\"\");\n            require(success, \"Refund failed\");\n        }\n    }\n\n    function aDCount(address user) internal view returns (uint256 total) {\n        total = CONTRACT_AD.balanceOf(user);\n        IDelegateRegistry.Delegation[] memory delegations = registry.getIncomingDelegations(user);\n        for(uint256 i; i < delegations.length; i++) {\n            IDelegateRegistry.Delegation memory dele = delegations[i];\n            if (dele.type_ == IDelegateRegistry.DelegationType.ALL) {\n                total += CONTRACT_AD.balanceOf(dele.from);\n            }\n            if (dele.type_ == IDelegateRegistry.DelegationType.CONTRACT && dele.contract_ == address(CONTRACT_AD)) {\n                total += CONTRACT_AD.balanceOf(dele.from);\n            }\n        }\n    }\n\n    function fPPCount(address user) internal view returns (uint256 total) {\n        total = CONTRACT_FPP.balanceOf(user);\n        IDelegateRegistry.Delegation[] memory delegations = registry.getIncomingDelegations(user);\n        for(uint256 i; i < delegations.length; i++) {\n            IDelegateRegistry.Delegation memory dele = delegations[i];\n            if (dele.type_ == IDelegateRegistry.DelegationType.ALL) {\n                total += CONTRACT_FPP.balanceOf(dele.from);\n            }\n            if (dele.type_ == IDelegateRegistry.DelegationType.CONTRACT && dele.contract_ == address(CONTRACT_FPP)) {\n                total += CONTRACT_FPP.balanceOf(dele.from);\n            }\n        }\n    }\n\n    function isPublic() public view returns (bool) {\n        return block.timestamp >= startTime + stage1Duration + stage2Duration;\n    }\n\n    function userStats(address user) external view returns (UserStats memory stats) {\n        uint256 _passCount = fPPCount(user);\n        uint256 _aDCount = aDCount(user);\n        uint256[10] memory mintsAvailable;\n        for (uint256 i; i < 10; i++) {\n            mintsAvailable[i] = collectionToShuffler[i].remainingNumbers();\n        }\n        stats = UserStats(\n            _passCount,\n            _aDCount,\n            isPublic() ? 0 : \n                (block.timestamp >= startTime + stage1Duration ? \n                    (_passCount + _aDCount) * mintsPerPass : _passCount * mintsPerPass\n                ) - exclusiveMintCount[user],\n            mintCount[user],\n            mintsAvailable\n        );\n    }\n\n    // OWNER FUNCTIONS\n    function editConfig(\n        uint256 _startTime,\n        uint256 _exclusivePrice,\n        uint256 _publicPrice,\n        uint256 _maxPerArtist,\n        uint256 _mintsPerPass,\n        uint256 _stage1Duration,\n        uint256 _stage2Duration\n    ) external onlyOwner {\n        startTime = _startTime;\n        exclusivePrice = _exclusivePrice;\n        publicPrice = _publicPrice;\n        maxPerArtist = _maxPerArtist;\n        mintsPerPass = _mintsPerPass;\n        stage1Duration = _stage1Duration;\n        stage2Duration = _stage2Duration;\n    }\n\n    function pause() external onlyOwner {\n        paused = true;\n    }\n\n    function unpause() external onlyOwner {\n        paused = false;\n    }\n\n    function withdraw(address recipient) external onlyOwner {\n        (bool success,) = recipient.call{value: address(this).balance}(\"\");\n        require(success, \"Withdraw failed.\");\n    }\n}\n\ninterface INFT {\n  function balanceOf(address account) external view returns (uint256);\n  function mint(address to, uint256 tokenId) external;\n}\n\ninterface IDelegateRegistry {\n    /// @notice Delegation type, NONE is used when a delegation does not exist or is revoked\n    enum DelegationType {\n        NONE,\n        ALL,\n        CONTRACT,\n        ERC721,\n        ERC20,\n        ERC1155\n    }\n\n    /// @notice Struct for returning delegations\n    struct Delegation {\n        DelegationType type_;\n        address to;\n        address from;\n        bytes32 rights;\n        address contract_;\n        uint256 tokenId;\n        uint256 amount;\n    }\n\n    function getIncomingDelegations(address to) external view returns (Delegation[] memory delegations);\n}"
      },
      "projects/ppp3/libs/ShufflerV3.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.25;\nimport \"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/access/Ownable.sol\";\n\ncontract ShufflerV3 is Ownable {\n    uint256 internal immutable totalNumbers;\n    uint256 public remainingNumbers;\n    \n    mapping(uint256 => uint256) public numberAtIndex;\n\n    constructor(uint256 n) Ownable(msg.sender) {\n        totalNumbers = n;\n        remainingNumbers = n;\n    }\n\n    function drawNext() public onlyOwner returns (uint256) {\n        require(remainingNumbers > 0);\n\n        uint256 randomIndex = uint256(keccak256(abi.encodePacked(remainingNumbers, block.prevrandao))) %\n            remainingNumbers;\n        uint256 numberToDraw = numberAtIndex[randomIndex];\n        if (numberToDraw == 0) {\n            numberToDraw = randomIndex + 1;\n        }\n\n        remainingNumbers -= 1;\n\n        uint256 swapNumber = numberAtIndex[remainingNumbers];\n        if (swapNumber == 0) {\n            swapNumber = remainingNumbers + 1;\n        }\n\n        numberAtIndex[randomIndex] = swapNumber;\n        numberAtIndex[remainingNumbers] = numberToDraw;\n\n        return numberToDraw;\n    }\n}"
      },
      "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/access/Ownable.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)\n\npragma solidity ^0.8.20;\n\nimport {Context} from \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * The initial owner is set to the address provided by the deployer. This can\n * later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n    address private _owner;\n\n    /**\n     * @dev The caller account is not authorized to perform an operation.\n     */\n    error OwnableUnauthorizedAccount(address account);\n\n    /**\n     * @dev The owner is not a valid owner account. (eg. `address(0)`)\n     */\n    error OwnableInvalidOwner(address owner);\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.\n     */\n    constructor(address initialOwner) {\n        if (initialOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(initialOwner);\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        _checkOwner();\n        _;\n    }\n\n    /**\n     * @dev Returns the address of the current owner.\n     */\n    function owner() public view virtual returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if the sender is not the owner.\n     */\n    function _checkOwner() internal view virtual {\n        if (owner() != _msgSender()) {\n            revert OwnableUnauthorizedAccount(_msgSender());\n        }\n    }\n\n    /**\n     * @dev Leaves the contract without owner. It will not be possible to call\n     * `onlyOwner` functions. Can only be called by the current owner.\n     *\n     * NOTE: Renouncing ownership will leave the contract without an owner,\n     * thereby disabling any functionality that is only available to the owner.\n     */\n    function renounceOwnership() public virtual onlyOwner {\n        _transferOwnership(address(0));\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Can only be called by the current owner.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        if (newOwner == address(0)) {\n            revert OwnableInvalidOwner(address(0));\n        }\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers ownership of the contract to a new account (`newOwner`).\n     * Internal function without access restriction.\n     */\n    function _transferOwnership(address newOwner) internal virtual {\n        address oldOwner = _owner;\n        _owner = newOwner;\n        emit OwnershipTransferred(oldOwner, newOwner);\n    }\n}\n"
      },
      "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/utils/Context.sol": {
        "content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n    function _msgSender() internal view virtual returns (address) {\n        return msg.sender;\n    }\n\n    function _msgData() internal view virtual returns (bytes calldata) {\n        return msg.data;\n    }\n\n    function _contextSuffixLength() internal view virtual returns (uint256) {\n        return 0;\n    }\n}\n"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": true,
        "runs": 200
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "abi"
          ]
        }
      },
      "remappings": []
    }
  }}