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));
}
}