ETH Price: $2,092.29 (-2.75%)

Contract Diff Checker

Contract Name:
OnChainMonstersFaucet

Contract Source Code:

<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;

interface IOCM {
    function mintMonster() external;
    function transferFrom(address from, address to, uint256 tokenId) external;
    function balanceOf(address owner) external view returns (uint256);
    function tokenOfOwnerByIndex(
        address owner,
        uint256 index
    ) external view returns (uint256);
    function burnForMint(uint256 tokenId) external;
    function totalSupply() external view returns (uint256);
}

interface IOCMD {
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
    function balanceOf(address) external view returns (uint256);
    function allowance(
        address owner,
        address spender
    ) external view returns (uint256);
}

contract TempMinter {
    constructor(
        address _ocm,
        address _ocmd,
        address mintToAddr,
        address remainingBalanceToAddr,
        uint256 _amount,
        uint256 _doughPerToken
    ) {
        // All operations must happen in constructor because only the constructor is allowed to call mintMonster
        // Once the constructor finalizes, the contract cannot call mintMonster anymore

        IOCM ocm = IOCM(_ocm);
        IOCMD ocmd = IOCMD(_ocmd);

        // Calculate total tokens needed for all monsters
        uint256 totalTokens = _amount * _doughPerToken * 1 ether;

        // Approve OCM to spend all OCMD tokens at once
        ocmd.approve(address(ocm), totalTokens);

        // Mint and transfer
        for (uint256 i = 0; i < _amount; ) {
            ocm.mintMonster();

            uint256 tokenId = ocm.tokenOfOwnerByIndex(address(this), 0);
            ocm.transferFrom(address(this), mintToAddr, tokenId);

            unchecked {
                ++i;
            }
        }

        // Return any unused OCMD tokens back to the specified address
        uint256 remainingBalance = ocmd.balanceOf(address(this));
        if (remainingBalance > 0) {
            ocmd.transfer(remainingBalanceToAddr, remainingBalance);
        }
    }
}

contract TempImmolator {
    constructor(
        address _ocm,
        address _tokenOwner,
        uint256 _tokenId,
        uint256 _rounds
    ) {
        // All operations must happen in constructor because only the constructor is allowed to call burnForMint
        // Once the constructor finalizes, the contract cannot call burnForMint anymore

        IOCM ocm = IOCM(_ocm);

        uint256 currentTokenId = _tokenId;

        // Perform the immolation rounds
        for (uint256 i = 0; i < _rounds; ) {
            // Burn the current token (this will mint a new one)
            ocm.burnForMint(currentTokenId);

            // Get the newly minted token ID (it will be the latest one)
            currentTokenId = ocm.totalSupply() - 1;

            unchecked {
                ++i;
            }
        }

        // Transfer the final token back to the owner
        ocm.transferFrom(address(this), _tokenOwner, currentTokenId);
    }
}

contract OnChainMonstersFaucet {
    address public owner;
    bool public isClosed;
    IOCM public OCM = IOCM(0xaA5D0f2E6d008117B16674B0f00B6FCa46e3EFC4);
    IOCMD public OCMD = IOCMD(0x10971797FcB9925d01bA067e51A6F8333Ca000B1);

    // Track how many tokens each address has minted through publicMint
    mapping(address => uint256) public publicMintCount;
    uint256 public constant MAX_PUBLIC_MINT_PER_ADDRESS = 20;
    uint256 public constant MAX_PUBLIC_MINT_PER_CALL = 10;
    uint256 public constant PUBLIC_MINT_DOUGH_PER_TOKEN = 4;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }

    modifier notClosed() {
        require(!isClosed, "Faucet is closed");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    /*
    function mintWithMyOwnTokens(uint256 amount, uint256 doughPerToken) external notClosed {
        // Transfer tokens from the caller to this contract
        uint256 requiredTokens = amount * doughPerToken * 1 ether;
        OCMD.transferFrom(msg.sender, address(this), requiredTokens);
        
        // Call internal minting function
        _mintInternal(msg.sender, msg.sender, amount, doughPerToken);
    }

    function publicMint(uint256 amount) external notClosed {
        require(amount > 0, "Amount must be greater than 0");
        require(amount <= MAX_PUBLIC_MINT_PER_CALL, "Max mints per call exceeded");
        require(publicMintCount[msg.sender] + amount <= MAX_PUBLIC_MINT_PER_ADDRESS, "Exceeds maximum mint limit per address");
        
        publicMintCount[msg.sender] += amount;

        _mintInternal(msg.sender, address(this), amount, PUBLIC_MINT_DOUGH_PER_TOKEN);
    }

    function _mintInternal(address mintToAddr, address remainingBalanceToAddr, uint256 amount, uint256 doughPerToken) internal {
        // Use CREATE2 to predict the single TempMinter address
        bytes32 salt = keccak256(abi.encodePacked(msg.sender, block.timestamp, amount, doughPerToken));
        address predictedAddress = address(uint160(uint256(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(
                type(TempMinter).creationCode,
                abi.encode(address(OCM), address(OCMD), mintToAddr, remainingBalanceToAddr, amount, doughPerToken)
            ))
        )))));
        
        // Transfer all required tokens to the predicted address in one go
        uint256 requiredTokens = amount * doughPerToken * 1 ether;
        OCMD.transfer(predictedAddress, requiredTokens);
        
        // Create single TempMinter contract that will mint all monsters
        TempMinter tempMinter = new TempMinter{salt: salt}(address(OCM), address(OCMD), mintToAddr, remainingBalanceToAddr, amount, doughPerToken);

        // Validate that the actual deployed address matches our prediction
        require(address(tempMinter) == predictedAddress, "Address prediction mismatch");
    }

    function approveDough() external onlyOwner {
        OCMD.approve(address(OCM), 100000 ether);
    }
    */

    function selfImmolate(uint256 tokenId, uint256 rounds) external notClosed {
        // Use CREATE2 to predict the single TempImmolator address
        bytes32 salt = keccak256(
            abi.encodePacked(msg.sender, block.timestamp, tokenId, rounds)
        );
        address predictedAddress = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            bytes1(0xff),
                            address(this),
                            salt,
                            keccak256(
                                abi.encodePacked(
                                    type(TempImmolator).creationCode,
                                    abi.encode(
                                        address(OCM),
                                        msg.sender,
                                        tokenId,
                                        rounds
                                    )
                                )
                            )
                        )
                    )
                )
            )
        );

        // Transfer the token to the predicted address
        OCM.transferFrom(msg.sender, predictedAddress, tokenId);

        // Create single TempImmolator contract that will handle the immolation
        TempImmolator tempImmolator = new TempImmolator{salt: salt}(
            address(OCM),
            msg.sender,
            tokenId,
            rounds
        );

        // Validate that the actual deployed address matches our prediction
        require(
            address(tempImmolator) == predictedAddress,
            "Address prediction mismatch"
        );
    }

    function close() external onlyOwner {
        isClosed = true;
    }

    function withdraw() external onlyOwner {
        payable(owner).transfer(address(this).balance);
    }

    function destroy() public onlyOwner {
        selfdestruct(payable(owner));
    }
}

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

Context size (optional):