MintManager

Description:

Smart contract deployed on Ethereum.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

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


/// @title Shleepy MintManager — EIP-712 voucher minter (single-file, no imports) + metadataURI
/// @notice Ownable + Pausable + EIP-712 + SKU/per-wallet limits + anti-replay + setTokenURI after mint
/// @dev Constructor: (address _signer, address _nft)


interface IMintAuto { function mint(address to) external returns (uint256 tokenId); }
interface IMintFixed { function mint(address to, uint256 tokenId) external; }
interface ISetTokenURI { function setTokenURI(uint256 tokenId, string calldata uri) external; }


contract MintManager {
address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
modifier onlyOwner() { require(msg.sender == owner, "NOT_OWNER"); _; }


bool public paused; event Paused(address indexed by); event Unpaused(address indexed by);
modifier whenNotPaused() { require(!paused, "PAUSED"); _; }


string public constant NAME = "ShleepyMint"; string public constant VERSION = "1";
bytes32 private constant EIP712DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
bytes32 private constant MINTVOUCHER_TYPEHASH = keccak256("MintVoucher(address recipient,uint256 tokenId,string sku,uint256 deadline,bytes32 orderId,uint256 nonce,string metadataURI)");
bytes32 public DOMAIN_SEPARATOR;


struct MintVoucher { address recipient; uint256 tokenId; string sku; uint256 deadline; bytes32 orderId; uint256 nonce; string metadataURI; }


address public signer; address public nft;
mapping(bytes32 => uint256) public skuSupplyLimit; mapping(bytes32 => uint256) public skuMinted; mapping(bytes32 => uint256) public skuPerWalletLimit; mapping(bytes32 => mapping(address => uint256)) public skuWalletMinted;
mapping(uint256 => bool) public usedNonce; mapping(bytes32 => bool) public usedOrder;


event SignerChanged(address indexed oldSigner, address indexed newSigner);
event NftChanged(address indexed oldNft, address indexed newNft);
event Minted(bytes32 indexed orderId, address indexed recipient, uint256 tokenId, bytes32 indexed skuHash, string sku, string metadataURI);
event SkuLimitSet(bytes32 indexed skuHash, string sku, uint256 supplyLimit, uint256 perWalletLimit);


constructor(address _signer, address _nft) { owner = msg.sender; _updateDomainSeparator(); signer = _signer; nft = _nft; }


function pause() external onlyOwner { paused = true; emit Paused(msg.sender); }
function unpause() external onlyOwner { paused = false; emit Unpaused(msg.sender); }
function setSigner(address _signer) external onlyOwner { emit SignerChanged(signer, _signer); signer = _signer; }
function setNft(address _nft) external onlyOwner { emit NftChanged(nft, _nft); nft = _nft; }
function setSkuLimits(string calldata sku, uint256 supplyLimit, uint256 perWalletLimit) external onlyOwner { bytes32 h = keccak256(bytes(sku)); skuSupplyLimit[h] = supplyLimit; skuPerWalletLimit[h] = perWalletLimit; emit SkuLimitSet(h, sku, supplyLimit, perWalletLimit); }
function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0), "ZERO_ADDR"); emit OwnershipTransferred(owner, newOwner); owner = newOwner; }


function mintWithVoucher(MintVoucher calldata v, bytes calldata sig) external whenNotPaused returns (uint256 tokenId) {
require(block.timestamp <= v.deadline, "DEADLINE"); require(!usedNonce[v.nonce], "NONCE_USED"); require(!usedOrder[v.orderId], "ORDER_USED"); require(v.recipient != address(0), "BAD_RECIPIENT"); require(nft != address(0) && signer != address(0), "NOT_CONFIGURED"); require(bytes(v.metadataURI).length > 0, "NO_METADATA");
bytes32 digest = _hashTypedDataV4(_hashVoucher(v)); address recovered = _recover(digest, sig); require(recovered == signer, "BAD_SIG");
bytes32 skuHash = keccak256(bytes(v.sku)); uint256 limit = skuSupplyLimit[skuHash]; if (limit != 0) { require(skuMinted[skuHash] < limit, "SKU_SUPPLY_EXCEEDED"); }
uint256 perWallet = skuPerWalletLimit[skuHash]; if (perWallet != 0) { require(skuWalletMinted[skuHash][v.recipient] < perWallet, "SKU_WALLET_LIMIT"); }
usedNonce[v.nonce] = true; usedOrder[v.orderId] = true; skuMinted[skuHash] += 1; skuWalletMinted[skuHash][v.recipient] += 1;
if (v.tokenId == 0) { try IMintAuto(nft).mint(v.recipient) returns (uint256 newId) { tokenId = newId; } catch { uint256 derived = uint256(keccak256(abi.encodePacked(v.orderId, v.nonce))) & ((1 << 96) - 1); IMintFixed(nft).mint(v.recipient, derived); tokenId = derived; } }
else { IMintFixed(nft).mint(v.recipient, v.tokenId); tokenId = v.tokenId; }
ISetTokenURI(nft).setTokenURI(tokenId, v.metadataURI); emit Minted(v.orderId, v.recipient, tokenId, skuHash, v.sku, v.metadataURI); return tokenId;
}


function domainSeparator() external view returns (bytes32) { return DOMAIN_SEPARATOR; }


function _updateDomainSeparator() internal { uint256 chainId; assembly { chainId := chainid() } DOMAIN_SEPARATOR = keccak256(abi.encode(EIP712DOMAIN_TYPEHASH, keccak256(bytes(NAME)), keccak256(bytes(VERSION)), chainId, address(this))); }
function _hashTypedDataV4(bytes32 structHash) internal view returns (bytes32) { return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash)); }
function _hashVoucher(MintVoucher calldata v) internal pure returns (bytes32) { return keccak256(abi.encode(MINTVOUCHER_TYPEHASH, v.recipient, v.tokenId, keccak256(bytes(v.sku)), v.deadline, v.orderId, v.nonce, keccak256(bytes(v.metadataURI)))); }
function _recover(bytes32 digest, bytes memory signature) internal pure returns (address) {
require(signature.length == 65, "BAD_SIG_LEN"); bytes32 r; bytes32 s; uint8 v; assembly { r := mload(add(signature, 32)) s := mload(add(signature, 64)) v := byte(0, mload(add(signature, 96))) } if (v < 27) v += 27; require(v == 27 || v == 28, "BAD_SIG_V"); address recovered = ecrecover(digest, v, r, s); require(recovered != address(0), "ECDSA_FAIL"); return recovered; }
}

Tags:
addr:0x7b3182132b20623f4c9129d5eb488463422b3832|verified:true|block:23469869|tx:0x630e93e944fdf0454a1681ec4bbce3fe305c25d18dcd7dadff75dcc51e4cd84f|first_check:1759167242

Submitted on: 2025-09-29 19:34:02

Comments

Log in to comment.

No comments yet.