This contract's source code is verified! Compiler
0.8.23+commit.f704f362
File 1 of 10: ERC20.sol
pragma solidity >=0.8.0;
abstract contract ERC20 {
event Transfer (address indexed from , address indexed to, uint256 amount ) ;
event Approval (address indexed owner, address indexed spender, uint256 amount ) ;
string public name;
string public symbol;
uint8 public immutable decimals;
uint256 public totalSupply;
mapping (address = > uint256 ) public balanceOf;
mapping (address = > mapping (address = > uint256 )) public allowance;
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping (address = > uint256 ) public nonces;
constructor (
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block .chainid ;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
function approve (address spender, uint256 amount ) public virtual returns (bool ) {
allowance[msg .sender ][spender] = amount;
emit Approval(msg .sender , spender, amount);
return true ;
}
function transfer (address to, uint256 amount ) public virtual returns (bool ) {
balanceOf[msg .sender ] - = amount;
unchecked {
balanceOf[to] + = amount;
}
emit Transfer(msg .sender , to, amount);
return true ;
}
function transferFrom (
address from ,
address to,
uint256 amount
) public virtual returns (bool ) {
uint256 allowed = allowance[from ][msg .sender ];
if (allowed ! = type (uint256 ).max ) allowance[from ][msg .sender ] = allowed - amount;
balanceOf[from ] - = amount;
unchecked {
balanceOf[to] + = amount;
}
emit Transfer(from , to, amount);
return true ;
}
function permit (
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require (deadline > = block .timestamp , "PERMIT_DEADLINE_EXPIRED" );
unchecked {
address recoveredAddress = ecrecover (
keccak256 (
abi .encodePacked (
"\x19\x01" ,
DOMAIN_SEPARATOR(),
keccak256 (
abi .encode (
keccak256 (
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]+ + ,
deadline
)
)
)
),
v,
r,
s
);
require (recoveredAddress ! = address (0 ) & & recoveredAddress = = owner, "INVALID_SIGNER" );
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR ( ) public view virtual returns (bytes32 ) {
return block .chainid = = INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator ( ) internal view virtual returns (bytes32 ) {
return
keccak256 (
abi .encode (
keccak256 ("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ),
keccak256 (bytes (name)),
keccak256 ("1" ),
block .chainid ,
address (this )
)
);
}
function _mint (address to, uint256 amount ) internal virtual {
totalSupply + = amount;
unchecked {
balanceOf[to] + = amount;
}
emit Transfer(address (0 ), to, amount);
}
function _burn (address from , uint256 amount ) internal virtual {
balanceOf[from ] - = amount;
unchecked {
totalSupply - = amount;
}
emit Transfer(from , address (0 ), amount);
}
}
File 2 of 10: ERC721.sol
pragma solidity >=0.8.0;
abstract contract ERC721 {
event Transfer (address indexed from , address indexed to, uint256 indexed id ) ;
event Approval (address indexed owner, address indexed spender, uint256 indexed id ) ;
event ApprovalForAll (address indexed owner, address indexed operator, bool approved ) ;
string public name;
string public symbol;
function tokenURI (uint256 id ) public view virtual returns (string memory ) ;
mapping (uint256 = > address ) internal _ownerOf;
mapping (address = > uint256 ) internal _balanceOf;
function ownerOf (uint256 id ) public view virtual returns (address owner ) {
require ((owner = _ownerOf[id]) ! = address (0 ), "NOT_MINTED" );
}
function balanceOf (address owner ) public view virtual returns (uint256 ) {
require (owner ! = address (0 ), "ZERO_ADDRESS" );
return _balanceOf[owner];
}
mapping (uint256 = > address ) public getApproved;
mapping (address = > mapping (address = > bool )) public isApprovedForAll;
constructor (string memory _name, string memory _symbol ) {
name = _name;
symbol = _symbol;
}
function approve (address spender, uint256 id ) public virtual {
address owner = _ownerOf[id];
require (msg .sender = = owner | | isApprovedForAll[owner][msg .sender ], "NOT_AUTHORIZED" );
getApproved[id] = spender;
emit Approval(owner, spender, id);
}
function setApprovalForAll (address operator, bool approved ) public virtual {
isApprovedForAll[msg .sender ][operator] = approved;
emit ApprovalForAll(msg .sender , operator, approved);
}
function transferFrom (
address from ,
address to,
uint256 id
) public virtual {
require (from = = _ownerOf[id], "WRONG_FROM" );
require (to ! = address (0 ), "INVALID_RECIPIENT" );
require (
msg .sender = = from | | isApprovedForAll[from ][msg .sender ] | | msg .sender = = getApproved[id],
"NOT_AUTHORIZED"
);
unchecked {
_balanceOf[from ]- - ;
_balanceOf[to]+ + ;
}
_ownerOf[id] = to;
delete getApproved[id];
emit Transfer(from , to, id);
}
function safeTransferFrom (
address from ,
address to,
uint256 id
) public virtual {
transferFrom(from , to, id);
require (
to.code .length = = 0 | |
ERC721TokenReceiver(to).onERC721Received(msg .sender , from , id, "" ) = =
ERC721TokenReceiver.onERC721Received.selector ,
"UNSAFE_RECIPIENT"
);
}
function safeTransferFrom (
address from ,
address to,
uint256 id,
bytes calldata data
) public virtual {
transferFrom(from , to, id);
require (
to.code .length = = 0 | |
ERC721TokenReceiver(to).onERC721Received(msg .sender , from , id, data) = =
ERC721TokenReceiver.onERC721Received.selector ,
"UNSAFE_RECIPIENT"
);
}
function supportsInterface (bytes4 interfaceId ) public view virtual returns (bool ) {
return
interfaceId = = 0x01ffc9a7 | |
interfaceId = = 0x80ac58cd | |
interfaceId = = 0x5b5e139f ;
}
function _mint (address to, uint256 id ) internal virtual {
require (to ! = address (0 ), "INVALID_RECIPIENT" );
require (_ownerOf[id] = = address (0 ), "ALREADY_MINTED" );
unchecked {
_balanceOf[to]+ + ;
}
_ownerOf[id] = to;
emit Transfer(address (0 ), to, id);
}
function _burn (uint256 id ) internal virtual {
address owner = _ownerOf[id];
require (owner ! = address (0 ), "NOT_MINTED" );
unchecked {
_balanceOf[owner]- - ;
}
delete _ownerOf[id];
delete getApproved[id];
emit Transfer(owner, address (0 ), id);
}
function _safeMint (address to, uint256 id ) internal virtual {
_mint(to, id);
require (
to.code .length = = 0 | |
ERC721TokenReceiver(to).onERC721Received(msg .sender , address (0 ), id, "" ) = =
ERC721TokenReceiver.onERC721Received.selector ,
"UNSAFE_RECIPIENT"
);
}
function _safeMint (
address to,
uint256 id,
bytes memory data
) internal virtual {
_mint(to, id);
require (
to.code .length = = 0 | |
ERC721TokenReceiver(to).onERC721Received(msg .sender , address (0 ), id, data) = =
ERC721TokenReceiver.onERC721Received.selector ,
"UNSAFE_RECIPIENT"
);
}
}
abstract contract ERC721TokenReceiver {
function onERC721Received (
address ,
address ,
uint256 ,
bytes calldata
) external virtual returns (bytes4 ) {
return ERC721TokenReceiver.onERC721Received.selector ;
}
}
File 3 of 10: IERC721.sol
pragma solidity ^0.8.23;
interface IERC721 {
function safeTransferFrom (
address from ,
address to,
uint256 tokenId
) external ;
function ownerOf (uint256 tokenId ) external view returns (address owner ) ;
}
File 4 of 10: ILuckyGhouls.sol
pragma solidity ^0.8.23;
interface ILuckyGhouls {
function mintsPerWallet (address wallet ) external view returns (uint256 ) ;
function setGoldenGhoulMinted ( ) external ;
function goldenGhoulMinted ( ) external view returns (bool ) ;
function withdrawRecipient ( ) external view returns (address ) ;
function royaltyRecipient ( ) external view returns (address ) ;
function isMintedOut ( ) external view returns (bool ) ;
function PRICE ( ) external view returns (uint256 ) ;
function isEverythingClosed ( ) external view returns (bool ) ;
function getMintBlockNumber (
uint256 tokenId
) external view returns (uint256 ) ;
function getMintTimestamp (uint256 tokenId ) external view returns (uint256 ) ;
}
File 5 of 10: ILuckyGhoulsBonusSystem.sol
pragma solidity ^0.8.23;
interface ILuckyGhoulsBonusSystem {
struct BonusClaim {
address tokenAddress;
uint256 amount;
uint256 claimedAt;
}
function getBonusClaim (
uint256 tokenId
) external view returns (BonusClaim memory ) ;
function getBonusClaims (
uint256 [] calldata tokenIds
) external view returns (BonusClaim[] memory ) ;
}
File 6 of 10: LibString.sol
pragma solidity >=0.8.0;
library LibString {
function toString (int256 value ) internal pure returns (string memory str ) {
if (value > = 0 ) return toString(uint256 (value));
unchecked {
str = toString(uint256 (- value));
assembly {
let length := mload (str)
mstore (str, 45 )
str := sub (str, 1 )
mstore (str, add (length, 1 ))
}
}
}
function toString (uint256 value ) internal pure returns (string memory str ) {
assembly {
let newFreeMemoryPointer := add (mload (0x40 ), 160 )
mstore (0x40 , newFreeMemoryPointer)
str := sub (newFreeMemoryPointer, 32 )
mstore (str, 0 )
let end := str
for { let temp := value } 1 {} {
str := sub (str, 1 )
mstore8 (str, add (48 , mod (temp, 10 )))
temp := div (temp, 10 )
if iszero (temp) { break }
}
let length := sub (end, str)
str := sub (str, 32 )
mstore (str, length)
}
}
}
File 7 of 10: LuckyGhouls.sol
pragma solidity ^0.8.23;
import {ERC721 } from "solmate/src/tokens/ERC721.sol" ;
import {ERC20 } from "solmate/src/tokens/ERC20.sol" ;
import {LibString } from "solmate/src/utils/LibString.sol" ;
import {Owned } from "solmate/src/auth/Owned.sol" ;
import {SafeTransferLib } from "solmate/src/utils/SafeTransferLib.sol" ;
import {MerkleProofLib } from "solmate/src/utils/MerkleProofLib.sol" ;
import {ILuckyGhouls } from "./interfaces/ILuckyGhouls.sol" ;
import {IERC721 } from "./interfaces/IERC721.sol" ;
import {ILuckyGhoulsBonusSystem } from "./interfaces/ILuckyGhoulsBonusSystem.sol" ;
contract LuckyGhouls is ERC721 , Owned , ILuckyGhouls {
using LibString for uint256 ;
uint256 public immutable MAX_SUPPLY;
uint256 public immutable RESERVED_COUNT;
uint256 public PRICE;
uint256 public DISCOUNT_PRICE;
uint256 public immutable MAX_MINTS_PER_WALLET;
uint256 public publicMintStart;
uint256 public currentTokenId;
uint256 public totalMinted;
address public immutable withdrawRecipient;
address public immutable royaltyRecipient;
uint96 public constant ROYALTY_BPS = 690 ;
bool _isClosedOverride;
bool public goldenGhoulMinted;
bool public mintingActive;
ILuckyGhoulsBonusSystem public bonusSystem;
string public baseUri;
error ZeroQuantity ( ) ;
error ExceedsMaxSupply ( ) ;
error InsufficientPayment ( ) ;
error EmptyBaseURI ( ) ;
error TokenDoesNotExist ( ) ;
error MaxSupplyExceeded ( ) ;
error InvalidMerkleProof ( ) ;
error MaxMintsPerWalletExceeded ( ) ;
error NonExistentTokenURI ( ) ;
error MintingClosed ( ) ;
error Nope ( ) ;
error PublicMintNotStarted ( ) ;
error MintNotActive ( ) ;
bytes32 public discountMerkleRoot;
bytes32 public freeMerkleRoot;
mapping (uint256 = > uint256 ) private _mintBlockNumber;
mapping (uint256 = > uint256 ) public _mintTimestamp;
mapping (address = > bool ) public hasUsedSpecialPrice;
mapping (address = > uint256 ) public mintsPerWallet;
mapping (address = > uint256 []) public lastMintedTokens;
enum SpecialPriceStatus {
None,
Free,
Discount
}
address public authorizedBonusSystem;
struct MintConfig {
uint256 totalPrice;
bool usesSpecialPrice;
SpecialPriceStatus specialPriceStatus;
bool hasUsedSpecialPrice;
uint256 mintsRemaining;
bool isMintedOut;
bool isEverythingClosed;
uint256 remainingSupply;
bool isPublicMintStarted;
}
constructor (
address _withdrawRecipient,
address _royaltyRecipient,
string memory initialBaseUri,
address owner,
uint256 maxSupply,
uint256 reservedCount,
uint256 price,
uint256 discountPrice,
uint256 maxMintsPerWallet
) ERC721 ("Lucky Ghouls" , "LCKY" ) Owned (owner ) {
withdrawRecipient = _withdrawRecipient;
royaltyRecipient = _royaltyRecipient;
if (bytes (initialBaseUri).length = = 0 ) revert EmptyBaseURI();
baseUri = initialBaseUri;
currentTokenId = 1 ;
MAX_SUPPLY = maxSupply;
RESERVED_COUNT = reservedCount;
PRICE = price;
DISCOUNT_PRICE = discountPrice;
MAX_MINTS_PER_WALLET = maxMintsPerWallet;
for (uint256 i = 0 ; i < RESERVED_COUNT; i+ + ) {
uint256 tokenId = MAX_SUPPLY - RESERVED_COUNT + 1 + i;
_mint(owner, tokenId);
totalMinted+ + ;
_mintBlockNumber[tokenId] = block .number ;
_mintTimestamp[tokenId] = block .timestamp ;
}
}
modifier validatePayment (uint256 quantity, bytes32 [] calldata merkleProof ) {
(uint256 price, bool usesSpecialPrice) = getMintPrice(
msg .sender ,
quantity,
merkleProof
);
if (msg .value < price) revert InsufficientPayment();
_ ;
}
modifier validateMint (uint256 quantity, bytes32 [] calldata merkleProof ) {
if (isEverythingClosed()) revert MintingClosed();
if (quantity = = 0 ) revert ZeroQuantity();
if (isMintedOut() | | willBeMintedOut(quantity)) {
revert MaxSupplyExceeded();
}
_ ;
}
modifier lockInEarlyAccessLists (bytes32 [] calldata merkleProof ) {
if (block .timestamp < publicMintStart) {
SpecialPriceStatus status = _checkSpecialPriceStatus(
msg .sender ,
merkleProof
);
if (
status ! = SpecialPriceStatus.Free & &
status ! = SpecialPriceStatus.Discount
) revert PublicMintNotStarted();
}
_ ;
}
function setBonusSystem (address _bonusSystem ) external onlyOwner {
bonusSystem = ILuckyGhoulsBonusSystem(_bonusSystem);
}
function updatePublicMintStart (
uint256 newPublicMintStart
) external onlyOwner {
publicMintStart = newPublicMintStart;
}
function setMintingActive (bool active ) external onlyOwner {
mintingActive = active;
}
function _exists (uint256 tokenId ) internal view returns (bool ) {
return _ownerOf[tokenId] ! = address (0 );
}
function updatePrices (
uint256 price,
uint256 discountPrice
) external onlyOwner {
PRICE = price;
DISCOUNT_PRICE = discountPrice;
}
function mint (
uint256 quantity,
bytes32 [] calldata merkleProof
)
external
payable
lockInEarlyAccessLists (merkleProof )
validateMint (quantity, merkleProof )
validatePayment (quantity, merkleProof )
{
if (! mintingActive) revert MintNotActive();
if (mintsPerWallet[msg .sender ] + quantity > MAX_MINTS_PER_WALLET) {
revert MaxMintsPerWalletExceeded();
}
(, bool usesSpecialPrice) = getMintPrice(
msg .sender ,
quantity,
merkleProof
);
if (usesSpecialPrice) {
hasUsedSpecialPrice[msg .sender ] = true ;
}
delete lastMintedTokens[msg .sender ];
unchecked {
mintsPerWallet[msg .sender ] + = quantity;
for (uint256 i; i < quantity; + + i) {
_mint(msg .sender , currentTokenId);
_mintBlockNumber[currentTokenId] = block .number ;
_mintTimestamp[currentTokenId] = block .timestamp ;
lastMintedTokens[msg .sender ].push (currentTokenId);
currentTokenId+ + ;
totalMinted+ + ;
}
}
}
function setBaseURI (string calldata newBaseUri ) external onlyOwner {
if (bytes (newBaseUri).length = = 0 ) revert EmptyBaseURI();
baseUri = newBaseUri;
}
function withdraw ( ) external {
SafeTransferLib.safeTransferETH(
withdrawRecipient,
address (this ).balance
);
}
function withdrawERC20 (address token ) external onlyOwner {
uint256 balance = ERC20(token).balanceOf(address (this ));
SafeTransferLib.safeTransfer(ERC20(token), withdrawRecipient, balance);
}
function withdrawERC721 (
address token,
address to,
uint256 tokenId
) external onlyOwner {
IERC721(token).safeTransferFrom(address (this ), to, tokenId);
}
function supportsInterface (
bytes4 interfaceId
) public pure virtual override (ERC721 ) returns (bool ) {
return
interfaceId = = 0x01ffc9a7 | |
interfaceId = = 0x80ac58cd | |
interfaceId = = 0x5b5e139f | |
interfaceId = = 0x2a55205a ;
}
function royaltyInfo (
uint256 ,
uint256 salePrice
) external view returns (address receiver, uint256 royaltyAmount ) {
return (royaltyRecipient, (salePrice * ROYALTY_BPS) / 10000 );
}
function tokenURI (
uint256 id
) public view virtual override returns (string memory ) {
if (! _exists(id)) revert TokenDoesNotExist();
return string .concat (baseUri, id.toString(), ".json" );
}
function mintsRemaining (address wallet ) external view returns (uint256 ) {
return MAX_MINTS_PER_WALLET - mintsPerWallet[wallet];
}
function getMintBlockNumber (
uint256 tokenId
) external view returns (uint256 ) {
if (! _exists(tokenId)) revert TokenDoesNotExist();
return _mintBlockNumber[tokenId];
}
function getMintTimestamp (uint256 tokenId ) external view returns (uint256 ) {
if (! _exists(tokenId)) revert TokenDoesNotExist();
return _mintTimestamp[tokenId];
}
function isMintedOut ( ) public view returns (bool ) {
return totalMinted > = MAX_SUPPLY;
}
function willBeMintedOut (uint256 additionalQty ) public view returns (bool ) {
return totalMinted + additionalQty > MAX_SUPPLY;
}
function remainingSupply ( ) public view returns (uint256 ) {
return MAX_SUPPLY - totalMinted;
}
function setDiscountMerkleRoot (bytes32 _merkleRoot ) external onlyOwner {
discountMerkleRoot = _merkleRoot;
}
function setFreeMerkleRoot (bytes32 _merkleRoot ) external onlyOwner {
freeMerkleRoot = _merkleRoot;
}
function _checkSpecialPriceStatus (
address user,
bytes32 [] calldata merkleProof
) internal view returns (SpecialPriceStatus ) {
if (
freeMerkleRoot ! = bytes32 (0 ) & &
MerkleProofLib.verify(
merkleProof,
freeMerkleRoot,
keccak256 (abi .encodePacked (user))
)
) {
return SpecialPriceStatus.Free;
}
if (
discountMerkleRoot ! = bytes32 (0 ) & &
MerkleProofLib.verify(
merkleProof,
discountMerkleRoot,
keccak256 (abi .encodePacked (user))
)
) {
return SpecialPriceStatus.Discount;
}
return SpecialPriceStatus.None;
}
function getMintPrice (
address user,
uint256 quantity,
bytes32 [] calldata merkleProof
) public view returns (uint256 totalPrice, bool usesSpecialPrice ) {
if (hasUsedSpecialPrice[user]) {
return (PRICE * quantity, false );
}
SpecialPriceStatus status = _checkSpecialPriceStatus(user, merkleProof);
if (status = = SpecialPriceStatus.Free) {
return (PRICE * (quantity - 1 ), true );
}
if (status = = SpecialPriceStatus.Discount) {
return (DISCOUNT_PRICE + (PRICE * (quantity - 1 )), true );
}
return (PRICE * quantity, false );
}
function getUserSpecialPriceStatus (
address user,
bytes32 [] calldata merkleProof
) public view returns (SpecialPriceStatus status, bool hasUsedPrice ) {
hasUsedPrice = hasUsedSpecialPrice[user];
status = _checkSpecialPriceStatus(user, merkleProof);
return (status, hasUsedPrice);
}
function getLastMintedTokens (
address wallet
) external view returns (uint256 [] memory ) {
return lastMintedTokens[wallet];
}
function getClaimedBonusesForLastMintedTokens (
address wallet
)
external
view
returns (
ILuckyGhoulsBonusSystem.BonusClaim[] memory claims,
uint256 [] memory tokenIds
)
{
tokenIds = lastMintedTokens[wallet];
claims = bonusSystem.getBonusClaims(tokenIds);
return (claims, tokenIds);
}
function setMintingClosed (bool closed ) external onlyOwner {
_isClosedOverride = closed;
}
function isEverythingClosed ( ) public view returns (bool ) {
return _isClosedOverride | | goldenGhoulMinted;
}
function setAuthorizedBonusSystem (
address _authorizedBonusSystem
) external onlyOwner {
authorizedBonusSystem = _authorizedBonusSystem;
}
function setGoldenGhoulMinted ( ) external {
if (msg .sender ! = owner & & msg .sender ! = authorizedBonusSystem)
revert Nope();
goldenGhoulMinted = true ;
}
function getMintConfig (
address user,
uint256 quantity,
bytes32 [] calldata merkleProof
) external view returns (MintConfig memory ) {
(uint256 totalPrice, bool usesSpecialPrice) = getMintPrice(
user,
quantity,
merkleProof
);
(
SpecialPriceStatus specialPriceStatus,
bool _hasUsedSpecialPrice
) = getUserSpecialPriceStatus(user, merkleProof);
return
MintConfig({
totalPrice: totalPrice,
usesSpecialPrice: usesSpecialPrice,
specialPriceStatus: specialPriceStatus,
hasUsedSpecialPrice: _hasUsedSpecialPrice,
mintsRemaining: MAX_MINTS_PER_WALLET - mintsPerWallet[user],
isMintedOut: isMintedOut(),
isEverythingClosed: isEverythingClosed(),
remainingSupply: remainingSupply(),
isPublicMintStarted: block .timestamp > = publicMintStart
});
}
receive ( ) external payable {}
}
File 8 of 10: MerkleProofLib.sol
pragma solidity >=0.8.0;
library MerkleProofLib {
function verify (
bytes32 [] calldata proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool isValid ) {
assembly {
if proof.length {
let end := add (proof.offset , shl (5 , proof.length ))
let offset := proof.offset
for {} 1 {} {
let leafSlot := shl (5 , gt (leaf, calldataload (offset)))
mstore (leafSlot, leaf)
mstore (xor (leafSlot, 32 ), calldataload (offset))
leaf := keccak256 (0 , 64 )
offset := add (offset, 32 )
if iszero (lt (offset, end)) { break }
}
}
isValid := eq (leaf, root)
}
}
}
File 9 of 10: Owned.sol
pragma solidity >=0.8.0;
abstract contract Owned {
event OwnershipTransferred (address indexed user, address indexed newOwner ) ;
address public owner;
modifier onlyOwner ( ) virtual {
require (msg .sender = = owner, "UNAUTHORIZED" );
_ ;
}
constructor (address _owner ) {
owner = _owner;
emit OwnershipTransferred(address (0 ), _owner);
}
function transferOwnership (address newOwner ) public virtual onlyOwner {
owner = newOwner;
emit OwnershipTransferred(msg .sender , newOwner);
}
}
File 10 of 10: SafeTransferLib.sol
pragma solidity >=0.8.0;
import {ERC20 } from "../tokens/ERC20.sol" ;
library SafeTransferLib {
function safeTransferETH (address to, uint256 amount ) internal {
bool success;
assembly {
success := call (gas (), to, amount, 0 , 0 , 0 , 0 )
}
require (success, "ETH_TRANSFER_FAILED" );
}
function safeTransferFrom (
ERC20 token,
address from ,
address to,
uint256 amount
) internal {
bool success;
assembly {
let freeMemoryPointer := mload (0x40 )
mstore (freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000 )
mstore (add (freeMemoryPointer, 4 ), and (from, 0xffffffffffffffffffffffffffffffffffffffff ))
mstore (add (freeMemoryPointer, 36 ), and (to, 0xffffffffffffffffffffffffffffffffffffffff ))
mstore (add (freeMemoryPointer, 68 ), amount)
success := call (gas (), token, 0 , freeMemoryPointer, 100 , 0 , 32 )
if and (iszero (and (eq (mload (0 ), 1 ), gt (returndatasize (), 31 ))), success) {
success := iszero (or (iszero (extcodesize (token)), returndatasize ()))
}
}
require (success, "TRANSFER_FROM_FAILED" );
}
function safeTransfer (
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
let freeMemoryPointer := mload (0x40 )
mstore (freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000 )
mstore (add (freeMemoryPointer, 4 ), and (to, 0xffffffffffffffffffffffffffffffffffffffff ))
mstore (add (freeMemoryPointer, 36 ), amount)
success := call (gas (), token, 0 , freeMemoryPointer, 68 , 0 , 32 )
if and (iszero (and (eq (mload (0 ), 1 ), gt (returndatasize (), 31 ))), success) {
success := iszero (or (iszero (extcodesize (token)), returndatasize ()))
}
}
require (success, "TRANSFER_FAILED" );
}
function safeApprove (
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
let freeMemoryPointer := mload (0x40 )
mstore (freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000 )
mstore (add (freeMemoryPointer, 4 ), and (to, 0xffffffffffffffffffffffffffffffffffffffff ))
mstore (add (freeMemoryPointer, 36 ), amount)
success := call (gas (), token, 0 , freeMemoryPointer, 68 , 0 , 32 )
if and (iszero (and (eq (mload (0 ), 1 ), gt (returndatasize (), 31 ))), success) {
success := iszero (or (iszero (extcodesize (token)), returndatasize ()))
}
}
require (success, "APPROVE_FAILED" );
}
}
{
"compilationTarget" : {
"src/LuckyGhouls.sol" : "LuckyGhouls"
} ,
"evmVersion" : "shanghai" ,
"libraries" : { } ,
"metadata" : {
"bytecodeHash" : "ipfs"
} ,
"optimizer" : {
"enabled" : true ,
"runs" : 200
} ,
"remappings" : [
":ds-test/=lib/solmate/lib/ds-test/src/" ,
":forge-std/=lib/forge-std/src/" ,
":solmate/=lib/solmate/"
]
} [{"inputs":[{"internalType":"address","name":"_withdrawRecipient","type":"address"},{"internalType":"address","name":"_royaltyRecipient","type":"address"},{"internalType":"string","name":"initialBaseUri","type":"string"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"maxSupply","type":"uint256"},{"internalType":"uint256","name":"reservedCount","type":"uint256"},{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"discountPrice","type":"uint256"},{"internalType":"uint256","name":"maxMintsPerWallet","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"EmptyBaseURI","type":"error"},{"inputs":[],"name":"ExceedsMaxSupply","type":"error"},{"inputs":[],"name":"InsufficientPayment","type":"error"},{"inputs":[],"name":"InvalidMerkleProof","type":"error"},{"inputs":[],"name":"MaxMintsPerWalletExceeded","type":"error"},{"inputs":[],"name":"MaxSupplyExceeded","type":"error"},{"inputs":[],"name":"MintNotActive","type":"error"},{"inputs":[],"name":"MintingClosed","type":"error"},{"inputs":[],"name":"NonExistentTokenURI","type":"error"},{"inputs":[],"name":"Nope","type":"error"},{"inputs":[],"name":"PublicMintNotStarted","type":"error"},{"inputs":[],"name":"TokenDoesNotExist","type":"error"},{"inputs":[],"name":"ZeroQuantity","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DISCOUNT_PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_MINTS_PER_WALLET","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESERVED_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROYALTY_BPS","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"_mintTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"authorizedBonusSystem","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseUri","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bonusSystem","outputs":[{"internalType":"contract ILuckyGhoulsBonusSystem","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"discountMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"freeMerkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"getClaimedBonusesForLastMintedTokens","outputs":[{"components":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"claimedAt","type":"uint256"}],"internalType":"struct ILuckyGhoulsBonusSystem.BonusClaim[]","name":"claims","type":"tuple[]"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"getLastMintedTokens","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getMintBlockNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"getMintConfig","outputs":[{"components":[{"internalType":"uint256","name":"totalPrice","type":"uint256"},{"internalType":"bool","name":"usesSpecialPrice","type":"bool"},{"internalType":"enum LuckyGhouls.SpecialPriceStatus","name":"specialPriceStatus","type":"uint8"},{"internalType":"bool","name":"hasUsedSpecialPrice","type":"bool"},{"internalType":"uint256","name":"mintsRemaining","type":"uint256"},{"internalType":"bool","name":"isMintedOut","type":"bool"},{"internalType":"bool","name":"isEverythingClosed","type":"bool"},{"internalType":"uint256","name":"remainingSupply","type":"uint256"},{"internalType":"bool","name":"isPublicMintStarted","type":"bool"}],"internalType":"struct LuckyGhouls.MintConfig","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"getMintPrice","outputs":[{"internalType":"uint256","name":"totalPrice","type":"uint256"},{"internalType":"bool","name":"usesSpecialPrice","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getMintTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"getUserSpecialPriceStatus","outputs":[{"internalType":"enum LuckyGhouls.SpecialPriceStatus","name":"status","type":"uint8"},{"internalType":"bool","name":"hasUsedPrice","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"goldenGhoulMinted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"hasUsedSpecialPrice","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isEverythingClosed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isMintedOut","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"lastMintedTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bytes32[]","name":"merkleProof","type":"bytes32[]"}],"name":"mint","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"mintingActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"mintsPerWallet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"mintsRemaining","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"owner","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicMintStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"remainingSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"salePrice","type":"uint256"}],"name":"royaltyInfo","outputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"royaltyAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"royaltyRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_authorizedBonusSystem","type":"address"}],"name":"setAuthorizedBonusSystem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newBaseUri","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_bonusSystem","type":"address"}],"name":"setBonusSystem","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_merkleRoot","type":"bytes32"}],"name":"setDiscountMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_merkleRoot","type":"bytes32"}],"name":"setFreeMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setGoldenGhoulMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"active","type":"bool"}],"name":"setMintingActive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"closed","type":"bool"}],"name":"setMintingClosed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"discountPrice","type":"uint256"}],"name":"updatePrices","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newPublicMintStart","type":"uint256"}],"name":"updatePublicMintStart","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"additionalQty","type":"uint256"}],"name":"willBeMintedOut","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"withdrawERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawRecipient","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]