Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.0;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.2) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates merkle trees that are safe
* against this attack out of the box.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*
* _Available since v4.7._
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*
* _Available since v4.7._
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*
* _Available since v4.7._
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
require(proofPos == proofLen, "MerkleProof: invalid multiproof");
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all merkle trees admit multiproofs. See {processMultiProof} for details.
*
* _Available since v4.7._
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
require(leavesLen + proofLen - 1 == totalHashes, "MerkleProof: invalid multiproof");
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
require(proofPos == proofLen, "MerkleProof: invalid multiproof");
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "./IAccessControl.sol";
abstract contract AccessControl is IAccessControl{
bytes32 public constant COLLECTION_ADMIN_ROLE =
keccak256("COLLECTION_ADMIN_ROLE");
function _getAccessControlState()
internal
pure
returns (RoleState storage state)
{
bytes32 position = keccak256("liveart.AccessControl");
assembly {
state.slot := position
}
}
/**
* @notice Checks that msg.sender has a specific role.
* Reverts with a AccessControlNotAllowed.
*
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @notice Checks that msg.sender has COLLECTION_ADMIN_ROLE
* Reverts with a AccessControlNotAllowed.
*
*/
modifier onlyAdmin() {
_checkRole(COLLECTION_ADMIN_ROLE);
_;
}
function isAdmin(address theAddress) public view returns (bool) {
return hasRole(COLLECTION_ADMIN_ROLE, theAddress);
}
/**
* @notice Checks if role is assigned to account
*
*/
function hasRole(bytes32 role, address account) public view returns (bool) {
RoleState storage state = _getAccessControlState();
return state._roles[role][account];
}
/**
* @notice Revert with a AccessControlNotAllowed message if `msg.sender` is missing `role`.
*
*/
function _checkRole(bytes32 role) internal view virtual {
if (!hasRole(role, msg.sender)) {
revert AccessControlNotAllowed();
}
}
/**
* @notice Grants `role` to `account`.
*
* @dev If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account)
public
onlyAdmin
{
_grantRole(role, account);
}
/**
* @notice Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have COLLECTION_ADMIN_ROLE role.
*/
function revokeRole(bytes32 role, address account)
public
onlyAdmin
{
_revokeRole(role, account);
}
/**
* @notice Revokes `role` from the calling account.
*
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) public virtual {
if (account != msg.sender) {
revert AccessControlNotAllowed();
}
_revokeRole(role, account);
}
/**
* @dev Grants `role` to `account`.
*
* Internal function without access restriction.
*/
function _grantRole(bytes32 role, address account) internal {
RoleState storage state = _getAccessControlState();
if (!hasRole(role, account)) {
state._roles[role][account] = true;
emit RoleGranted(role, account, msg.sender);
}
}
/**
* @dev Revokes `role` from `account`.
*
* Internal function without access restriction.
*/
function _revokeRole(bytes32 role, address account) internal {
RoleState storage state = _getAccessControlState();
if (hasRole(role, account)) {
state._roles[role][account] = false;
emit RoleRevoked(role, account, msg.sender);
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../libraries/CustomErrors.sol";
import "../libraries/BPS.sol";
import "../libraries/CustomErrors.sol";
import "../libraries/LANFTUtils.sol";
import "../tokens/ERC721State.sol";
import "../tokens/ERC721LACore.sol";
import "./IAirDropable.sol";
import "../platform/royalties/RoyaltiesState.sol";
abstract contract AirDropable is IAirDropable, ERC721LACore {
uint256 public constant AIRDROP_MAX_BATCH_SIZE = 100;
function airdrop(
uint256 editionId,
address[] calldata recipients,
uint24 quantityPerAddress
) external onlyAdmin {
if (recipients.length > AIRDROP_MAX_BATCH_SIZE) {
revert TooManyAddresses();
}
for (uint256 i = 0; i < recipients.length; i++) {
_safeMint(editionId, quantityPerAddress, recipients[i]);
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../libraries/CustomErrors.sol";
import "../libraries/BPS.sol";
import "../tokens/ERC721LACore.sol";
import "../libraries/LANFTUtils.sol";
import "../tokens/ERC721State.sol";
abstract contract Burnable is ERC721LACore {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* BURNABLE
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function burn(uint256 tokenId) public {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
address owner = ownerOf(tokenId);
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
revert CustomErrors.TransferError();
}
_transferCore(owner, ERC721LACore.burnAddress, tokenId);
// Looksrare and other marketplace require the owner to be null address
emit Transfer(owner, address(0), tokenId);
(uint256 editionId, ) = parseEditionFromTokenId(tokenId);
// Update the number of tokens burned for this edition
state._editions[editionId].burnedSupply += 1;
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
* @param tokenIds array of tokenIds to burn
* Requirements:
* - `tokenId` must exist.
* Emits a {Transfer} event.
*/
function burnMultiple(uint256[] calldata tokenIds) public {
for (uint i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
burn(tokenId);
}
}
function burnRedeemEditionTokens(
uint256 _editionId,
uint24 _quantity,
uint256[] calldata tokenIdsToBurn
) public whenPublicMintOpened(_editionId) whenNotPaused {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
ERC721State.Edition memory edition = getEdition(_editionId);
if (edition.burnableEditionId == 0 || tokenIdsToBurn.length == 0) {
revert CustomErrors.BurnRedeemNotAvailable();
}
uint256 mintableAmount = tokenIdsToBurn.length / edition.amountToBurn;
if (mintableAmount < _quantity) {
revert CustomErrors.BurnRedeemNotAvailable();
}
// Check max mint per wallet restrictions (if maxMintPerWallet is 0, no restriction apply)
uint256 mintedCountKey = uint256(
keccak256(abi.encodePacked(_editionId, msg.sender))
);
if (edition.maxMintPerWallet != 0) {
if (
state._mintedPerWallet[mintedCountKey] + _quantity >
edition.maxMintPerWallet
) {
revert CustomErrors.MaximumMintAmountReached();
}
}
state._mintedPerWallet[mintedCountKey] += _quantity;
// We iterate and burn only the required amount of tokens (preventing burning more than necessary)
// burn will revert if the sender is not the owner of a given token
for (uint256 i; i < edition.amountToBurn * _quantity; i++) {
burn(tokenIdsToBurn[i]);
}
_safeMint(_editionId, _quantity, msg.sender);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
interface IAccessControl {
error AccessControlNotAllowed();
struct RoleState {
mapping(bytes32 => mapping(address => bool)) _roles;
}
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
*/
event RoleGranted(
bytes32 indexed role,
address indexed account,
address indexed sender
);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/
event RoleRevoked(
bytes32 indexed role,
address indexed account,
address indexed sender
);
/**
* @notice Checks if role is assigned to account
*
*/
function hasRole(bytes32 role, address account) external returns (bool);
/**
* @notice Grants `role` to `account`.
*
* @dev If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function grantRole(bytes32 role, address account)
external;
/**
* @notice Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have COLLECTION_ADMIN_ROLE role.
*/
function revokeRole(bytes32 role, address account)
external;
/**
* @notice Revokes `role` from the calling account.
*
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
function isAdmin(address theAddress) external view returns (bool);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
interface IAirDropable {
error TooManyAddresses();
function airdrop(uint256 editionId, address[] calldata recipients, uint24 quantityPerAddres) external;
}
/// @title EIP-721 Metadata Update Extension
interface IERC4906 {
/// @dev This event emits when the metadata of a token is changed.
/// So that the third-party platforms such as NFT market could
/// timely update the images and related attributes of the NFT.
event MetadataUpdate(uint256 _tokenId);
/// @dev This event emits when the metadata of a range of tokens is changed.
/// So that the third-party platforms such as NFT market could
/// timely update the images and related attributes of the NFTs.
event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
interface IWhitelistable {
/**
* Raised when trying create a WhiteList config that already exisit (mint amounts are the same)
*/
error WhiteListAlreadyExists();
error NotWhitelisted();
error InvalidMintDuration();
function whitelistMint(
uint256 editionId,
uint8 maxAmount,
uint24 mintPriceInFinney,
bytes32[] calldata merkleProof,
uint24 quantity,
address receiver,
uint24 tokenId,
uint24 mintPriceWithPrintInFinney
) external payable;
function setWLConfig(
uint256 editionId,
uint8 amount,
uint24 mintPriceInFinney,
uint32 mintStartTS,
uint32 mintEndTS,
bytes32 merkleRoot,
uint24 mintPriceWithPrintInFinney
) external;
function updateWLConfig(
uint256 editionId,
uint8 amount,
uint24 mintPriceInFinney,
uint8 newAmount,
uint24 newMintPriceInFinney,
uint32 newMintStartTS,
uint32 newMintEndTS,
bytes32 newMerkleRoot,
uint24 mintPriceWithPrintInFinney,
uint24 newMintPriceWithPrintInFinney
) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
abstract contract LAInitializable {
error AlreadyInitialized();
struct InitializableState {
bool _initialized;
}
function _getInitializableState() internal pure returns (InitializableState storage state) {
bytes32 position = keccak256("liveart.Initializable");
assembly {
state.slot := position
}
}
modifier notInitialized() {
InitializableState storage state = _getInitializableState();
if (state._initialized) {
revert AlreadyInitialized();
}
_;
state._initialized = true;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
abstract contract Ownable {
error CallerIsNotOwner();
error NewOwnerIsZeroAddress();
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
struct OwnableState {
address owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view returns(address){
OwnableState storage state = _getOwnableState();
return state.owner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if(newOwner == address(0)){
revert NewOwnerIsZeroAddress();
}
_transferOwnership(newOwner);
}
function _getOwnableState()
internal
pure
returns (OwnableState storage state)
{
bytes32 position = keccak256("liveart.Ownable");
assembly {
state.slot := position
}
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if(owner() != msg.sender) {
revert CallerIsNotOwner();
}
}
function _setOwner(address newOwner) internal {
OwnableState storage state = _getOwnableState();
state.owner = newOwner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal {
address previousOwner = owner();
_setOwner(newOwner);
emit OwnershipTransferred(previousOwner, newOwner);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
contract Pausable {
event Paused(address account);
event Unpaused(address account);
struct PausableState {
bool _paused;
}
function _getPausableState()
internal
pure
returns (PausableState storage state)
{
bytes32 position = keccak256("liveart.Pausable");
assembly {
state.slot := position
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* MODIFIERS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view returns (bool) {
PausableState storage state = _getPausableState();
return state._paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view {
require(!paused(), "Pausable: paused");
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal whenNotPaused {
PausableState storage state = _getPausableState();
state._paused = true;
emit Paused(msg.sender);
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal whenPaused {
PausableState storage state = _getPausableState();
state._paused = false;
emit Unpaused(msg.sender);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../libraries/BPS.sol";
import "../libraries/CustomErrors.sol";
import "../libraries/LANFTUtils.sol";
import "../tokens/ERC721State.sol";
import "../tokens/ERC721LACore.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./IWhitelistable.sol";
import "./WhitelistableState.sol";
interface ILiveArtXcard {
function balanceOf(address owner) external view returns (uint256);
}
abstract contract Whitelistable is IWhitelistable, ERC721LACore {
/**
* Create a Whitelist configuration
* @param _editionId the edition ID
* @param amount How many mint allowed per Whitelist spot
* @param mintPriceInFinney Price of the whitelist mint in Finney
* @param mintStartTS Starting time of the Whitelist mint
* @param mintEndTS Starting time of the Whitelist mint
* @param merkleRoot The whitelist merkle root
* @param mintPriceWithPrintInFinney The mint price with print in Finney
*
*/
function setWLConfig(
uint256 _editionId,
uint8 amount,
uint24 mintPriceInFinney,
uint32 mintStartTS,
uint32 mintEndTS,
bytes32 merkleRoot,
uint24 mintPriceWithPrintInFinney
) public onlyAdmin {
WhitelistableState.WLState storage state = WhitelistableState
._getWhitelistableState();
// This reverts if edition does not exist
getEdition(_editionId);
uint256 wlId = uint256(
keccak256(
abi.encodePacked(
_editionId,
amount,
mintPriceInFinney,
mintPriceWithPrintInFinney
)
)
);
if (state._whitelistConfig[wlId].amount != 0) {
revert WhiteListAlreadyExists();
}
if (mintEndTS != 0 && mintEndTS < mintStartTS) {
revert InvalidMintDuration();
}
WhitelistableState.WhitelistConfig
memory whitelistConfig = WhitelistableState.WhitelistConfig({
merkleRoot: merkleRoot,
amount: amount,
mintPriceInFinney: mintPriceInFinney,
mintStartTS: mintStartTS,
mintEndTS: mintEndTS,
mintPriceWithPrintInFinney: mintPriceWithPrintInFinney
});
state._whitelistConfig[wlId] = whitelistConfig;
}
/**
* Update a Whitelist configuration
* @param _editionId Edition ID of the WL to be updated
* @param _amount Amount of the WL to be updated
* @param mintPriceInFinney Price of the WL to be updated
* @param newAmount New Amount
* @param newMintPriceInFinney New mint price in Finney
* @param newMintStartTS New Mint time
* @param newMerkleRoot New Merkle root
*
* Note: When changing a single property of the WL config,
* make sure to also pass the value of the property that did not change.
*
*/
function updateWLConfig(
uint256 _editionId,
uint8 _amount,
uint24 mintPriceInFinney,
uint8 newAmount,
uint24 newMintPriceInFinney,
uint32 newMintStartTS,
uint32 newMintEndTS,
bytes32 newMerkleRoot,
uint24 mintPriceWithPrintInFinney,
uint24 newMintPriceWithPrintInFinney
) public onlyAdmin {
WhitelistableState.WLState storage state = WhitelistableState
._getWhitelistableState();
// This reverts if edition does not exist
getEdition(_editionId);
uint256 wlId = uint256(
keccak256(
abi.encodePacked(
_editionId,
_amount,
mintPriceInFinney,
mintPriceWithPrintInFinney
)
)
);
WhitelistableState.WhitelistConfig memory whitelistConfig;
// If amount or price differ, then set previous WL config key to amount 0, which effectively disable the WL
if (
_amount != newAmount ||
mintPriceInFinney != newMintPriceInFinney ||
mintPriceWithPrintInFinney != newMintPriceWithPrintInFinney
) {
state._whitelistConfig[wlId] = WhitelistableState.WhitelistConfig({
merkleRoot: newMerkleRoot,
amount: 0,
mintPriceInFinney: newMintPriceInFinney,
mintStartTS: newMintStartTS,
mintEndTS: newMintEndTS,
mintPriceWithPrintInFinney: newMintPriceWithPrintInFinney
});
wlId = uint256(
keccak256(
abi.encodePacked(
_editionId,
newAmount,
newMintPriceInFinney,
newMintPriceWithPrintInFinney
)
)
);
state._whitelistConfig[wlId] = whitelistConfig;
}
if (newMintEndTS != 0 && newMintEndTS < newMintStartTS) {
revert InvalidMintDuration();
}
whitelistConfig = WhitelistableState.WhitelistConfig({
merkleRoot: newMerkleRoot,
amount: newAmount,
mintPriceInFinney: newMintPriceInFinney,
mintStartTS: newMintStartTS,
mintEndTS: newMintEndTS,
mintPriceWithPrintInFinney: newMintPriceWithPrintInFinney
});
state._whitelistConfig[wlId] = whitelistConfig;
}
/**
* Whitelist mint function
* @param _editionId the edition ID
* @param _maxAmount How many mint allowed per Whitelist spot
* @param _merkleProof the merkle proof of the minter
* @param _quantity How many NFTs to mint
* @param _recipient The recipient of the NFTs
* @param _xCardTokenId The XCard token ID
* @param _mintPriceWithPrintInFinney The mint price with print in Finney
*/
function whitelistMint(
uint256 _editionId,
uint8 _maxAmount,
uint24 _mintPriceInFinney,
bytes32[] calldata _merkleProof,
uint24 _quantity,
address _recipient,
uint24 _xCardTokenId,
uint24 _mintPriceWithPrintInFinney
) public payable {
_validateWhitelistMintingParameters(
_editionId,
_maxAmount,
_mintPriceInFinney,
_merkleProof,
_quantity,
_mintPriceWithPrintInFinney
);
_validateMintPrice(_mintPriceInFinney, _quantity);
uint256 firstTokenId = _safeMint(_editionId, _quantity, _recipient);
_sendRoyaltiesAfterMint(firstTokenId);
}
/**
* Whitelist mint with print function
* @param _editionId the edition ID
* @param _maxAmount How many mint allowed per Whitelist spot
* @param _merkleProof the merkle proof of the minter
* @param _quantity How many NFTs to mint
* @param _recipient The recipient of the NFTs
* @param _xCardTokenId The XCard token ID
* @param _mintPriceWithPrintInFinney The mint price with print in Finney
*/
function whitelistMintWithPrint(
uint256 _editionId,
uint8 _maxAmount,
uint24 _mintPriceInFinney,
bytes32[] calldata _merkleProof,
uint24 _quantity,
address _recipient,
uint24 _xCardTokenId,
uint24 _mintPriceWithPrintInFinney
) public payable {
ERC721State.EditionWithPrintData memory printData = getEditionPrintData(
_editionId
);
_validateWhitelistMintingParameters(
_editionId,
_maxAmount,
_mintPriceInFinney,
_merkleProof,
_quantity,
_mintPriceWithPrintInFinney
);
_validateEditionPrintData(printData);
_validateMintPrice(_mintPriceWithPrintInFinney, _quantity);
uint256 firstTokenId = _safeMint(_editionId, _quantity, _recipient);
_sendRoyaltiesAfterMint(firstTokenId);
_mintPrintVouchers(_editionId, _quantity, firstTokenId);
}
/**
* Get WL config for given editionId, amout, and mintPrice.
* Should not be used internally when trying to modify the state as it returns a memory copy of the structs
*/
function getWLConfig(
uint256 editionId,
uint8 amount,
uint24 mintPriceInFinney,
uint24 mintPriceWithPrintInFinney
) public view returns (WhitelistableState.WhitelistConfig memory) {
WhitelistableState.WLState storage state = WhitelistableState
._getWhitelistableState();
// This reverts if edition does not exist
getEdition(editionId);
uint256 wlId = uint256(
keccak256(
abi.encodePacked(
editionId,
amount,
mintPriceInFinney,
mintPriceWithPrintInFinney
)
)
);
WhitelistableState.WhitelistConfig storage whitelistConfig = state
._whitelistConfig[wlId];
if (whitelistConfig.amount == 0) {
revert CustomErrors.NotFound();
}
return whitelistConfig;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* INTERNAL FUNCTIONS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function _validateWhitelistMintingParameters(
uint256 _editionId,
uint8 maxAmount,
uint24 mintPriceInFinney,
bytes32[] calldata merkleProof,
uint24 _quantity,
uint24 mintPriceWithPrintInFinney
) internal {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
// This reverts if WL does not exist (or is disabled)
WhitelistableState.WhitelistConfig memory whitelistConfig = getWLConfig(
_editionId,
maxAmount,
mintPriceInFinney,
mintPriceWithPrintInFinney
);
// Check for allowed mint count
uint256 mintCountKey = uint256(
keccak256(abi.encodePacked(_editionId, msg.sender))
);
if (
state._mintedPerWallet[mintCountKey] + _quantity >
whitelistConfig.amount
) {
revert CustomErrors.MaximumMintAmountReached();
}
if (
whitelistConfig.mintStartTS == 0 ||
block.timestamp < whitelistConfig.mintStartTS
) {
revert CustomErrors.MintClosed();
}
if (
whitelistConfig.mintEndTS != 0 &&
block.timestamp > whitelistConfig.mintEndTS
) {
revert CustomErrors.MintClosed();
}
// We use msg.sender for the WL merkle root
// Ran only if the user is not an XCard holder
bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
if (
!MerkleProof.verify(
merkleProof,
whitelistConfig.merkleRoot,
leaf
) &&
ILiveArtXcard(state._xCardContractAddress).balanceOf(msg.sender) ==
0
) {
revert NotWhitelisted();
}
state._mintedPerWallet[mintCountKey] += _quantity;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
library WhitelistableState {
struct WhitelistConfig {
bytes32 merkleRoot;
uint8 amount;
uint24 mintPriceInFinney;
uint32 mintStartTS;
uint32 mintEndTS;
uint24 mintPriceWithPrintInFinney;
}
struct WLState {
// hash(EditionId + mintable amount + price)
mapping(uint256 => WhitelistConfig) _whitelistConfig;
}
/**
* @dev Get storage data from dedicated slot.
* This pattern avoids storage conflict during proxy upgrades
* and give more flexibility when creating extensions
*/
function _getWhitelistableState()
internal
pure
returns (WLState storage state)
{
bytes32 storageSlot = keccak256("liveart.Whitelistable");
assembly {
state.slot := storageSlot
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../extensions/AccessControl.sol";
/**
* Used to set Winter whitelisted minting addresses
*/
contract Winter is AccessControl {
struct WinterState {
address[] winterAddresses;
}
function _getWinterState()
internal
pure
returns (WinterState storage state)
{
bytes32 position = keccak256("liveart.Winter");
assembly {
state.slot := position
}
}
// function addWinterWallets(address[] calldata newAddresses) public onlyAdmin {
// for(uint256 i; i < newAddresses.length; i += 1) {
// _setWinterWallet(newAddresses[i]);
// }
// }
function addWinterWallet(address newAddress) public onlyAdmin {
_setWinterWallet(newAddress);
}
function _setWinterWallet(address newAddress) internal {
WinterState storage state = _getWinterState();
state.winterAddresses.push(newAddress);
}
function deleteWinterWallet(address newAddress) public onlyAdmin {
WinterState storage state = _getWinterState();
for (uint256 i; i < state.winterAddresses.length; i += 1) {
if (newAddress == state.winterAddresses[i]) {
delete state.winterAddresses[i];
}
}
state.winterAddresses.push(newAddress);
}
function _isWinterWallet() internal view returns (bool) {
WinterState storage state = _getWinterState();
for (uint256 i; i < state.winterAddresses.length; i += 1) {
if (msg.sender == state.winterAddresses[i]) {
return true;
}
}
return false;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../libraries/CustomErrors.sol";
import "./WithOperatorRegistryState.sol";
import "../libraries/LANFTUtils.sol";
import "../extensions/AccessControl.sol";
import "operator-filter-registry/src/IOperatorFilterRegistry.sol";
import {CANONICAL_CORI_SUBSCRIPTION} from "operator-filter-registry/src/lib/Constants.sol";
contract WithOperatorRegistry is AccessControl {
address constant DEFAULT_OPERATOR_REGISTRY_ADDRESS =
0x000000000000AAeB6D7670E522A718067333cd4E;
/// @dev The upgradeable initialize function that should be called when the contract is being upgraded.
function _initOperatorRegsitry() internal {
WithOperatorRegistryState.OperatorRegistryState
storage registryState = WithOperatorRegistryState
._getOperatorRegistryState();
IOperatorFilterRegistry registry = IOperatorFilterRegistry(
DEFAULT_OPERATOR_REGISTRY_ADDRESS
);
registryState.operatorFilterRegistry = registry;
if (address(registry).code.length > 0) {
registry.registerAndSubscribe(
address(this),
CANONICAL_CORI_SUBSCRIPTION
);
}
}
function initOperatorRegsitry() public onlyAdmin {
_initOperatorRegsitry();
}
/**
* @dev A helper modifier to check if the operator is allowed.
*/
modifier onlyAllowedOperator(address from) virtual {
// Allow spending tokens from addresses with balance
// Note that this still allows listings and marketplaces with escrow to transfer tokens if transferred
// from an EOA.
if (from != msg.sender) {
_checkFilterOperator(msg.sender);
}
_;
}
/**
* @dev A helper modifier to check if the operator approval is allowed.
*/
modifier onlyAllowedOperatorApproval(address operator) virtual {
_checkFilterOperator(operator);
_;
}
/**
* @dev A helper function to check if the operator is allowed.
*/
function _checkFilterOperator(address operator) internal view virtual {
WithOperatorRegistryState.OperatorRegistryState
storage registryState = WithOperatorRegistryState
._getOperatorRegistryState();
// Check registry code length to facilitate testing in environments without a deployed registry.
if (address(registryState.operatorFilterRegistry).code.length > 0) {
// under normal circumstances, this function will revert rather than return false, but inheriting or
// upgraded contracts may specify their own OperatorFilterRegistry implementations, which may behave
// differently
if (
!registryState.operatorFilterRegistry.isOperatorAllowed(
address(this),
operator
)
) {
revert CustomErrors.NotAllowed();
}
}
}
/**
* @notice Update the address that the contract will make OperatorFilter checks against. When set to the zero
* address, checks will be bypassed. OnlyOwner.
*/
function updateOperatorFilterRegistryAddress(address newRegistry) public onlyAdmin {
WithOperatorRegistryState.OperatorRegistryState
storage registryState = WithOperatorRegistryState
._getOperatorRegistryState();
IOperatorFilterRegistry registry = IOperatorFilterRegistry(
newRegistry
);
registryState.operatorFilterRegistry = registry;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "operator-filter-registry/src/IOperatorFilterRegistry.sol";
library WithOperatorRegistryState {
struct OperatorRegistryState {
IOperatorFilterRegistry operatorFilterRegistry;
}
/**
* @dev Get storage data from dedicated slot.
* This pattern avoids storage conflict during proxy upgrades
* and give more flexibility when creating extensions
*/
function _getOperatorRegistryState()
internal
pure
returns (OperatorRegistryState storage state)
{
bytes32 storageSlot = keccak256("liveart.OperatorRegistryState");
assembly {
state.slot := storageSlot
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "./BitScan.sol";
/**
* Derived from: https://github.com/estarriolvetch/solidity-bits
*/
/**
* @dev This Library is a modified version of Openzeppelin's BitMaps library.
* Functions of finding the index of the closest set bit from a given index are added.
* The indexing of each bucket is modifed to count from the MSB to the LSB instead of from the LSB to the MSB.
* The modification of indexing makes finding the closest previous set bit more efficient in gas usage.
*/
/**
* @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential.
* Largelly inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor].
*/
error BitMapHeadNotFound();
library BitMaps {
using BitScan for uint256;
uint256 private constant MASK_INDEX_ZERO = (1 << 255);
struct BitMap {
mapping(uint256 => uint256) _data;
}
/**
* @dev Returns whether the bit at `index` is set.
*/
function get(BitMap storage bitmap, uint256 index)
internal
view
returns (bool)
{
uint256 bucket = index >> 8;
uint256 mask = MASK_INDEX_ZERO >> (index & 0xff);
return bitmap._data[bucket] & mask != 0;
}
/**
* @dev Sets the bit at `index` to the boolean `value`.
*/
function setTo(
BitMap storage bitmap,
uint256 index,
bool value
) internal {
if (value) {
set(bitmap, index);
} else {
unset(bitmap, index);
}
}
/**
* @dev Sets the bit at `index`.
*/
function set(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = MASK_INDEX_ZERO >> (index & 0xff);
bitmap._data[bucket] |= mask;
}
/**
* @dev Unsets the bit at `index`.
*/
function unset(BitMap storage bitmap, uint256 index) internal {
uint256 bucket = index >> 8;
uint256 mask = MASK_INDEX_ZERO >> (index & 0xff);
bitmap._data[bucket] &= ~mask;
}
/**
* @dev Find the closest index of the set bit before `index`.
*/
function scanForward(
BitMap storage bitmap,
uint256 index,
uint256 lowerBound
) internal view returns (uint256 matchedIndex) {
uint256 bucket = index >> 8;
uint256 lowerBoundBucket = lowerBound >> 8;
// index within the bucket
uint256 bucketIndex = (index & 0xff);
// load a bitboard from the bitmap.
uint256 bb = bitmap._data[bucket];
// offset the bitboard to scan from `bucketIndex`.
bb = bb >> (0xff ^ bucketIndex); // bb >> (255 - bucketIndex)
if (bb > 0) {
unchecked {
return (bucket << 8) | (bucketIndex - bb.bitScanForward256());
}
} else {
while (true) {
// require(bucket > lowerBound, "BitMaps: The set bit before the index doesn't exist.");
if (bucket < lowerBoundBucket) {
revert BitMapHeadNotFound();
}
unchecked {
bucket--;
}
// No offset. Always scan from the least significiant bit now.
bb = bitmap._data[bucket];
if (bb > 0) {
unchecked {
return (bucket << 8) | (255 - bb.bitScanForward256());
}
}
}
}
}
}
// SPDX-License-Identifier: UNLICENSED
/**
_____ ___ ___ __ ____ _ __
/ ___/____ / (_)___/ (_) /___ __ / __ )(_) /______
\__ \/ __ \/ / / __ / / __/ / / / / __ / / __/ ___/
___/ / /_/ / / / /_/ / / /_/ /_/ / / /_/ / / /_(__ )
/____/\____/_/_/\__,_/_/\__/\__, / /_____/_/\__/____/
/____/
- npm: https://www.npmjs.com/package/solidity-bits
- github: https://github.com/estarriolvetch/solidity-bits
*/
pragma solidity ^0.8.4;
library BitScan {
uint256 constant private DEBRUIJN_256 = 0x818283848586878898a8b8c8d8e8f929395969799a9b9d9e9faaeb6bedeeff;
bytes constant private LOOKUP_TABLE_256 = hex"0001020903110a19042112290b311a3905412245134d2a550c5d32651b6d3a7506264262237d468514804e8d2b95569d0d495ea533a966b11c886eb93bc176c9071727374353637324837e9b47af86c7155181ad4fd18ed32c9096db57d59ee30e2e4a6a5f92a6be3498aae067ddb2eb1d5989b56fd7baf33ca0c2ee77e5caf7ff0810182028303840444c545c646c7425617c847f8c949c48a4a8b087b8c0c816365272829aaec650acd0d28fdad4e22d6991bd97dfdcea58b4d6f29fede4f6fe0f1f2f3f4b5b6b607b8b93a3a7b7bf357199c5abcfd9e168bcdee9b3f1ecf5fd1e3e5a7a8aa2b670c4ced8bbe8f0f4fc3d79a1c3cde7effb78cce6facbf9f8";
/**
@dev Isolate the least significant set bit.
*/
function isolateLS1B256(uint256 bb) pure internal returns (uint256) {
require(bb > 0);
unchecked {
return bb & (0 - bb);
}
}
/**
@dev Isolate the most significant set bit.
*/
function isolateMS1B256(uint256 bb) pure internal returns (uint256) {
require(bb > 0);
unchecked {
bb |= bb >> 256;
bb |= bb >> 128;
bb |= bb >> 64;
bb |= bb >> 32;
bb |= bb >> 16;
bb |= bb >> 8;
bb |= bb >> 4;
bb |= bb >> 2;
bb |= bb >> 1;
return (bb >> 1) + 1;
}
}
/**
@dev Find the index of the lest significant set bit. (trailing zero count)
*/
function bitScanForward256(uint256 bb) pure internal returns (uint8) {
unchecked {
return uint8(LOOKUP_TABLE_256[(isolateLS1B256(bb) * DEBRUIJN_256) >> 248]);
}
}
/**
@dev Find the index of the most significant set bit.
*/
function bitScanReverse256(uint256 bb) pure internal returns (uint8) {
unchecked {
return 255 - uint8(LOOKUP_TABLE_256[((isolateMS1B256(bb) * DEBRUIJN_256) >> 248)]);
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
library BPS {
function _calculatePercentage(uint256 number, uint256 percentage)
internal
pure
returns (uint256)
{
// https://ethereum.stackexchange.com/a/55702
// https://www.investopedia.com/terms/b/basispoint.asp
return (number * percentage) / 10000;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
library CustomErrors {
/**
* Raised when trying to manipulate editions (CRUD) with invalid data
*/
error InvalidEditionData();
error MaxSupplyError();
error InvalidEditionId();
/**
* Raised when trying to mint with invalid data
*/
error InvalidMintData();
/**
* Raised when trying to transfer an NFT to a non ERC721Receiver
*/
error NotERC721Receiver();
/**
* Raised when trying to query a non minted token
*/
error TokenNotFound();
/**
* Raised when transfer fail
*/
error TransferError();
/**
* Generic Not Allowed action
*/
error NotAllowed();
/**
* Generic Not Found error
*/
error NotFound();
/**
* Raised when direct minting with insufficient funds
*/
error InsufficientFunds();
/**
* Raised when fund transfer fails
*/
error FundTransferError();
/**
* Raised when trying to mint a print with invalid data
*/
error InvalidPrintData();
error MintClosed();
error MaximumMintAmountReached();
error BurnRedeemNotAvailable();
error NotXCardOwner();
error TokenStaked();
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "./CustomErrors.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
library LANFTUtils {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
// Inspired by OraclizeAPI's implementation - MIT licence
// https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
* The call is not executed if the target address is an EOA
*
*/
function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory _data
) internal returns (bool) {
if (LANFTUtils.isContract(to)) {
try
IERC721Receiver(to).onERC721Received(
msg.sender,
from,
tokenId,
_data
)
returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert CustomErrors.NotERC721Receiver();
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "./specs/IRarible.sol";
import "./RoyaltiesState.sol";
/// @dev Royalty registry interface
interface IRoyaltiesRegistry is IERC165 {
/// @dev Raised when trying to set a royalty override for a token
error NotApproved();
error NotOwner();
/// @dev Raised when providing multiple royalty overrides when only one is expected
error MultipleRoyaltyRecievers();
/// @dev Raised when sales percentage is not between 0 and 100
error PrimarySalePercentageOutOfRange();
error SecondarySalePercentageOutOfRange();
/// @dev Raised accumulated primary royalty percentage is not 100
error PrimarySalePercentageNotEqualToMax();
/**
* Raised trying to set edition or token royalties
*/
error NotEditionCreator();
// ==============================
// EVENTS
// ==============================
event RoyaltyOverride(
address owner,
address tokenAddress,
address royaltyAddress
);
event RoyaltyTokenOverride(
address owner,
address tokenAddress,
uint256 tokenId,
address royaltyAddress
);
// ==============================
// IERC165
// ==============================
/// @dev See {IERC165-supportsInterface}.
function supportsInterface(
bytes4 interfaceId
) external view override returns (bool);
// ==============================
// SECONDARY ROYALTY
// ==============================
/*
@notice Called with the sale price to determine how much royalty is owed and to whom.
@param _contractAddress - The collection address
@param _tokenId - the NFT asset queried for royalty information
@param _value - the sale price of the NFT asset specified by _tokenId
@return _receiver - address of who should be sent the royalty payment
@return _royaltyAmount - the royalty payment amount for value sale price
*/
function royaltyInfo(
address _contractAddress,
uint256 _tokenId,
uint256 _value
) external view returns (address _receiver, uint256 _royaltyAmount);
/**
* Return RoyaltyReceivers for primary sales
*
*/
function primaryRoyaltyInfo(
address collectionAddress,
uint256 tokenId
) external view returns (address payable[] memory, uint256[] memory);
/**
* @dev CreatorCore - Supports Manifold, ArtBlocks
*
* getRoyalties
*/
function getRoyalties(
address collectionAddress,
uint256 tokenId
) external view returns (address payable[] memory, uint256[] memory);
/**
* @dev Foundation
*
* getFees
*/
function getFees(
address collectionAddress,
uint256 editionId
) external view returns (address payable[] memory, uint256[] memory);
/**
* @dev Rarible: RoyaltiesV1
*
* getFeeBps
*/
function getFeeBps(
address collectionAddress,
uint256 tokenId
) external view returns (uint256[] memory);
/**
* @dev Rarible: RoyaltiesV1
*
* getFeeRecipients
*/
function getFeeRecipients(
address collectionAddress,
uint256 editionId
) external view returns (address payable[] memory);
/**
* @dev Rarible: RoyaltiesV2
*
* getRaribleV2Royalties
*/
function getRaribleV2Royalties(
address collectionAddress,
uint256 tokenId
) external view returns (IRaribleV2.Part[] memory);
/**
* @dev CreatorCore - Support for KODA
*
* getKODAV2RoyaltyInfo
*/
function getKODAV2RoyaltyInfo(
address collectionAddress,
uint256 tokenId
)
external
view
returns (address payable[] memory recipients_, uint256[] memory bps);
/**
* @dev CreatorCore - Support for Zora
*
* convertBidShares
*/
function convertBidShares(
address collectionAddress,
uint256 tokenId
)
external
view
returns (address payable[] memory recipients_, uint256[] memory bps);
/*
@notice Called from a collection contract to set a primary royalty override
@param collectionAddress - The collection address
@param sender - The address of the caller
@param RoyaltyReceiver[] - The royalty receivers details
*/
function registerCollectionRoyaltyReceivers(
address collectionAddress,
address sender,
RoyaltiesState.RoyaltyReceiver[] memory royaltyReceivers
) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
library RoyaltiesState {
struct RoyaltyReceiver {
address payable wallet;
uint48 primarySalePercentage;
uint48 secondarySalePercentage;
}
/**
* @dev Storage layout
* This pattern allow us to extend current contract using DELETGATE_CALL
* without worrying about storage slot conflicts
*/
struct RoyaltiesRegistryState {
// contractAddress => RoyaltyReceiver
mapping(address => RoyaltyReceiver[]) _collectionRoyaltyReceivers;
// contractAddress => editionId => RoyaltyReceiver
mapping(address => mapping(uint256 => RoyaltyReceiver[])) _editionRoyaltyReceivers;
// contractAddress => editionId => tokenNumber => RoyaltyReceiver
mapping(address => mapping(uint256 => mapping(uint256 => RoyaltyReceiver[]))) _tokenRoyaltyReceivers;
}
/**
* @dev Get storage data from dedicated slot.
* This pattern avoids storage conflict during proxy upgrades
* and give more flexibility when creating extensions
*/
function _getRoyaltiesState()
internal
pure
returns (RoyaltiesRegistryState storage state)
{
bytes32 storageSlot = keccak256("liveart.RoyalitiesState");
assembly {
state.slot := storageSlot
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
interface IRaribleV1 {
/*
* bytes4(keccak256('getFeeBps(uint256)')) == 0x0ebd4c7f
* bytes4(keccak256('getFeeRecipients(uint256)')) == 0xb9c4d9fb
*
* => 0x0ebd4c7f ^ 0xb9c4d9fb == 0xb7799584
*/
function getFeeBps(uint256 id) external view returns (uint256[] memory);
function getFeeRecipients(uint256 id)
external
view
returns (address payable[] memory);
}
interface IRaribleV2 {
/*
* bytes4(keccak256('getRaribleV2Royalties(uint256)')) == 0xcad96cca
*/
struct Part {
address payable account;
uint96 value;
}
function getRaribleV2Royalties(uint256 id)
external
view
returns (Part[] memory);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../extensions/Burnable.sol";
import "../extensions/WithOperatorRegistry.sol";
import "../extensions/AirDropable.sol";
import "./IERC721LA.sol";
import "../extensions/Pausable.sol";
import "../extensions/Whitelistable.sol";
// import "../extensions/PermissionedTransfers.sol";
import "../extensions/LAInitializable.sol";
import "../libraries/LANFTUtils.sol";
import "../libraries/BPS.sol";
import "../libraries/CustomErrors.sol";
import "../platform/royalties/RoyaltiesState.sol";
import "./ERC721State.sol";
import "./ERC721LACore.sol";
/**
* @notice LiveArt ERC721 implementation contract
* Supports multiple edtioned NFTs and gas optimized batch minting
*/
contract ERC721LA is ERC721LACore, Burnable, AirDropable, Whitelistable {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Royalties
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function setRoyaltyRegistryAddress(
address _royaltyRegistry
) public onlyAdmin {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._royaltyRegistry = IRoyaltiesRegistry(_royaltyRegistry);
}
function royaltyRegistryAddress() public view returns (IRoyaltiesRegistry) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._royaltyRegistry;
}
/// @dev see: EIP-2981
function royaltyInfo(
uint256 _tokenId,
uint256 _value
) external view returns (address _receiver, uint256 _royaltyAmount) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return
state._royaltyRegistry.royaltyInfo(address(this), _tokenId, _value);
}
function registerCollectionRoyaltyReceivers(
RoyaltiesState.RoyaltyReceiver[] memory royaltyReceivers
) public onlyAdmin {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
IRoyaltiesRegistry(state._royaltyRegistry)
.registerCollectionRoyaltyReceivers(
address(this),
msg.sender,
royaltyReceivers
);
}
function primaryRoyaltyInfo(
uint256 tokenId
) public view returns (address payable[] memory, uint256[] memory) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return
IRoyaltiesRegistry(state._royaltyRegistry).primaryRoyaltyInfo(
address(this),
tokenId
);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../extensions/AccessControl.sol";
import "../extensions/Winter.sol";
import "./IERC721LA.sol";
import "../extensions/Pausable.sol";
import "../extensions/IERC4906.sol";
import "../extensions/Ownable.sol";
import "../extensions/LAInitializable.sol";
import "../libraries/LANFTUtils.sol";
import "../libraries/BPS.sol";
import "../libraries/CustomErrors.sol";
import "./IERC721Events.sol";
import "./ERC721State.sol";
import "../extensions/WithOperatorRegistry.sol";
interface IStakingContract {
function isTokenStaked(uint256 tokenId) external view returns (bool);
}
interface IPrintVoucherContract {
function mintPrintVoucher(
address _recipient,
uint256[] calldata _tokenIds
) external;
}
/**
* @notice LiveArt ERC721 implementation contract
* Supports multiple edtioned NFTs and gas optimized batch minting
*/
abstract contract ERC721LACore is
LAInitializable,
AccessControl,
WithOperatorRegistry,
Winter,
Pausable,
Ownable,
IERC721LA,
IERC4906
{
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* LIBRARIES
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
using BitMaps for BitMaps.BitMap;
using ERC721State for ERC721State.ERC721LAState;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* LIBRARIES
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
using BitMaps for BitMaps.BitMap;
using ERC721State for ERC721State.ERC721LAState;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* CONSTANTS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bytes32 public constant IERC721METADATA_INTERFACE = hex"5b5e139f";
bytes32 public constant IERC721_INTERFACE = hex"80ac58cd";
bytes32 public constant IERC2981_INTERFACE = hex"2a55205a";
bytes32 public constant IERC165_INTERFACE = hex"01ffc9a7";
bytes32 public constant IERC4906_INTERFACE = hex"49064906";
// Used for separating editionId and tokenNumber from the tokenId (cf. createEdition)
uint24 public constant DEFAULT_EDITION_TOKEN_MULTIPLIER = 10e5;
// Used to differenciate burnt tokens in the bitmap logic (Null address being used for unminted tokens)
address public constant burnAddress = address(0xDEAD);
// Logic contracts addresses
address public constant GOERLI_LOGIC_CONTRACT =
address(0xBb7093937225983269A7003e15a7b8200f8b523d);
address public constant MAINNET_LOGIC_CONTRACT =
address(0xE291354a3aeB7EB49da0f1333d667F17fa33F1c5);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* INITIALIZERS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @dev Initialize function. Should be called by the factory when deploying new instances.
* @param _collectionAdmin is the address of the default admin for this contract
*/
function initialize(
string calldata _name,
string calldata _symbol,
address _collectionAdmin,
address _royaltyRegistry
) external notInitialized {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._name = _name;
state._symbol = _symbol;
state._royaltyRegistry = IRoyaltiesRegistry(_royaltyRegistry);
state._editionCounter = 1;
state._edition_max_tokens = DEFAULT_EDITION_TOKEN_MULTIPLIER;
_grantRole(COLLECTION_ADMIN_ROLE, _collectionAdmin);
_setOwner(_collectionAdmin);
_setWinterWallet(0xdAb1a1854214684acE522439684a145E62505233);
_initOperatorRegsitry();
}
/**
* @dev Overload `initialize` function with `_edition_max_tokens` argument
*/
// function initialize(
// string calldata _name,
// string calldata _symbol,
// address _collectionAdmin,
// address _royaltyRegistry,
// uint24 _edition_max_tokens
// ) external notInitialized {
// ERC721State.ERC721LAState storage state = ERC721State
// ._getERC721LAState();
// state._name = _name;
// state._symbol = _symbol;
// state._royaltyRegistry = IRoyaltiesRegistry(_royaltyRegistry);
// state._editionCounter = 1;
// state._edition_max_tokens = _edition_max_tokens;
// _grantRole(COLLECTION_ADMIN_ROLE, _collectionAdmin);
// _setOwner(_collectionAdmin);
// _setWinterWallet(0xdAb1a1854214684acE522439684a145E62505233);
// _initOperatorRegsitry();
// }
/// @dev See {IERC165-supportsInterface}.
// function supportsInterface(
// bytes4 interfaceId
// ) external pure override returns (bool) {
// return
// interfaceId == IERC4906_INTERFACE ||
// interfaceId == IERC2981_INTERFACE ||
// interfaceId == IERC721_INTERFACE ||
// interfaceId == IERC721METADATA_INTERFACE ||
// interfaceId == IERC165_INTERFACE;
// }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* LogicContract
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function _delegateLogic() internal virtual {
address implementation = getLogicContract();
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(
gas(),
implementation,
0,
calldatasize(),
0,
0
)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
function getLogicContract() internal view returns (address) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
if (state._logicContractAddress != address(0)) {
return state._logicContractAddress;
}
if (block.chainid == 5) {
return GOERLI_LOGIC_CONTRACT;
}
return MAINNET_LOGIC_CONTRACT;
}
function setLogicContractAddress(address newAddress) external onlyAdmin {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._logicContractAddress = newAddress;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* IERC721Metadata
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function name() external view override returns (string memory) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._name;
}
function symbol() external view returns (string memory) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._symbol;
}
// function setName(string calldata _name) public onlyAdmin {
// ERC721State.ERC721LAState storage state = ERC721State
// ._getERC721LAState();
// state._name = _name;
// }
// function setSymbol(string calldata _symbol) public onlyAdmin {
// ERC721State.ERC721LAState storage state = ERC721State
// ._getERC721LAState();
// state._symbol = _symbol;
// }
function tokenURI(
uint256 tokenId
) external view override returns (string memory) {
if (!_exists(tokenId)) {
revert CustomErrors.TokenNotFound();
}
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
(uint256 editionId, ) = parseEditionFromTokenId(tokenId);
ERC721State.Edition memory edition = getEdition(editionId);
if (edition.perTokenMetadata) {
return
string(
abi.encodePacked(
state._baseURIByEdition[editionId],
LANFTUtils.toString(tokenId)
)
);
}
return state._baseURIByEdition[editionId];
}
function totalSupply() external view override returns (uint256) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
uint256 _count;
for (uint256 i = 1; i < state._editionCounter; i += 1) {
_count += editionMintedTokens(i);
}
return _count;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* EDITIONS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @notice Backward compatibility with the frontend
*/
function EDITION_TOKEN_MULTIPLIER() public view returns (uint24) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._edition_max_tokens;
}
function EDITION_MAX_SIZE() public view returns (uint24) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._edition_max_tokens - 1;
}
/**
* @notice Creates a new Edition
* Editions can be seen as collections within a collection.
* The token Ids for the a given edition have the following format:
* `[editionId][tokenNumber]`
* eg.: The Id of the 2nd token of the 5th edition is: `5000002`
*
*/
function createEdition(
string calldata _baseURI,
uint24 _maxSupply,
uint24 _publicMintPriceInFinney,
uint32 _publicMintStartTS,
uint32 _publicMintEndTS,
uint8 _maxMintPerWallet,
bool _perTokenMetadata,
uint8 _burnableEditionId,
uint8 _amountToBurn
) public onlyAdmin returns (uint256) {
_delegateLogic();
}
/**
* @notice updates an edition base URI
*/
function updateEditionBaseURI(
uint256 editionId,
string calldata _baseURI
) public onlyAdmin {
_delegateLogic();
}
/**
* @notice updates an edition base URI
*/
function updateEditionPrintData(
uint256 _editionId,
address _newPrintVoucherAddress,
uint24 _newPublicMintPriceWithPrintInFinney
) public onlyAdmin {
_delegateLogic();
}
/**
* @notice updates edition parameter. Careful: This will overwrite all previously set values on that edition.
*/
function updateEdition(
uint256 editionId,
uint24 _publicMintPriceInFinney,
uint32 _publicMintStartTS,
uint32 _publicMintEndTS,
uint8 _maxMintPerWallet,
uint24 _maxSupply,
bool _perTokenMetadata
) public onlyAdmin {
_delegateLogic();
}
/**
* @notice fetch edition struct data by editionId
*/
function getEdition(
uint256 _editionId
) public view override returns (ERC721State.Edition memory) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
if (_editionId > state._editionCounter) {
revert CustomErrors.InvalidEditionId();
}
return state._editions[_editionId];
}
/**
* @notice fetch edition struct data by editionId
*/
function getEditionWithURI(
uint256 _editionId
)
public
view
override
returns (ERC721State.EditionWithURI memory editionWithURI)
{
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
if (_editionId > state._editionCounter) {
revert CustomErrors.InvalidEditionId();
}
editionWithURI = ERC721State.EditionWithURI({
data: state._editions[_editionId],
baseURI: state._baseURIByEdition[_editionId]
});
}
function getEditionPrintData(
uint256 _editionId
)
public
view
override
returns (ERC721State.EditionWithPrintData memory editionPrintData)
{
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
if (_editionId > state._editionCounter) {
revert CustomErrors.InvalidEditionId();
}
editionPrintData = ERC721State.EditionWithPrintData({
printVoucherContractAddress: state
._printDataByEdition[_editionId]
.printVoucherContractAddress,
publicMintPriceWithPrintInFinney: state
._printDataByEdition[_editionId]
.publicMintPriceWithPrintInFinney
});
}
/**
* @notice Returns the total number of editions
*/
function totalEditions() external view returns (uint256 total) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
total = state._editionCounter - 1;
}
/**
* @notice Returns the current supply of a given edition
*/
function editionMintedTokens(
uint256 editionId
) public view returns (uint256 supply) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
ERC721State.Edition memory edition = state._editions[editionId];
return edition.currentSupply - edition.burnedSupply;
}
/**
* @dev Given an editionId and tokenNumber, returns tokenId in the following format:
* `[editionId][tokenNumber]` where `tokenNumber` is between 1 and state._edition_max_tokens - 1
* eg.: The second token from the 5th edition would be `500002`
*
*/
function editionedTokenId(
uint256 editionId,
uint256 tokenNumber
) public view returns (uint256 tokenId) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
uint256 paddedEditionID = editionId * state._edition_max_tokens;
tokenId = paddedEditionID + tokenNumber;
}
/**
* @dev Given a tokenId return editionId and tokenNumber.
* eg.: 3000005 => editionId 3 and tokenNumber 5
*/
function parseEditionFromTokenId(
uint256 tokenId
) public view returns (uint256 editionId, uint256 tokenNumber) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
// Divide first to lose the decimal. ie. 1000001 / 1000000 = 1
editionId = tokenId / state._edition_max_tokens;
tokenNumber = tokenId - (editionId * state._edition_max_tokens);
}
/// @dev Is public mint open for given edition
function isPublicMintStarted(
uint256 editionId
) public view override returns (bool) {
ERC721State.Edition memory edition = getEdition(editionId);
bool started = (edition.publicMintStartTS != 0 &&
edition.publicMintStartTS <= block.timestamp) &&
(edition.publicMintEndTS == 0 ||
edition.publicMintEndTS > block.timestamp);
return started;
}
/**
* @notice sets the staking contract address
**/
function setStakingContractAddress(
address stakingContractAddress
) external onlyAdmin {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._stakingContractAddress = stakingContractAddress;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* STAKING
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @notice toggle staking on/off for an edition
**/
function toggleStakingByEdition(uint256 editionId) external onlyAdmin {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._editions[editionId].stakingEnabled = !state
._editions[editionId]
.stakingEnabled;
}
function getStakingContractAddress() external view returns (address) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._stakingContractAddress;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* STAKING
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
modifier whenPublicMintOpened(uint256 editionId) {
if (!isPublicMintStarted(editionId)) {
revert CustomErrors.MintClosed();
}
_;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* MINTABLE
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @dev Internal batch minting function
*/
function _safeMint(
uint256 _editionId,
uint24 _quantity,
address _recipient
) internal virtual returns (uint256 firstTokenId) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
ERC721State.Edition storage edition = state._editions[_editionId];
uint256 tokenNumber = edition.currentSupply + 1;
if (_editionId > state._editionCounter) {
revert CustomErrors.InvalidEditionId();
}
if (_quantity == 0 || _recipient == address(0)) {
revert CustomErrors.InvalidMintData();
}
if (tokenNumber > edition.maxSupply) {
revert CustomErrors.MaxSupplyError();
}
firstTokenId = editionedTokenId(_editionId, tokenNumber);
if (edition.currentSupply + _quantity > edition.maxSupply) {
revert CustomErrors.MaxSupplyError();
}
edition.currentSupply += _quantity;
state._owners[firstTokenId] = _recipient;
state._batchHead.set(firstTokenId);
state._balances[_recipient] += _quantity;
// Emit events
for (
uint256 tokenId = firstTokenId;
tokenId < firstTokenId + _quantity;
tokenId++
) {
emit Transfer(address(0), _recipient, tokenId);
LANFTUtils._checkOnERC721Received(
address(0),
_recipient,
tokenId,
""
);
}
}
function mintEditionTokens(
uint256 _editionId,
uint24 _quantity,
address _recipient
) public payable whenPublicMintOpened(_editionId) whenNotPaused {
ERC721State.Edition memory edition = getEdition(_editionId);
_validatePublicMintParams(
_editionId,
_quantity,
edition.maxMintPerWallet
);
_validateMintPrice(edition.publicMintPriceInFinney, _quantity);
uint256 firstTokenId = _safeMint(_editionId, _quantity, _recipient);
_sendRoyaltiesAfterMint(firstTokenId);
}
function mintEditionTokensWithPrints(
uint256 _editionId,
uint24 _quantity,
address _recipient
) public payable whenPublicMintOpened(_editionId) whenNotPaused {
ERC721State.Edition memory edition = getEdition(_editionId);
ERC721State.EditionWithPrintData memory printData = getEditionPrintData(
_editionId
);
_validatePublicMintParams(
_editionId,
_quantity,
edition.maxMintPerWallet
);
_validateEditionPrintData(printData);
_validateMintPrice(
printData.publicMintPriceWithPrintInFinney,
_quantity
);
uint256 firstTokenId = _safeMint(_editionId, _quantity, _recipient);
_sendRoyaltiesAfterMint(firstTokenId);
_mintPrintVouchers(_editionId, _quantity, firstTokenId);
}
function adminMint(
uint256 _editionId,
uint24 _quantity,
address _recipient
) public onlyAdmin {
_safeMint(_editionId, _quantity, _recipient);
}
function getMintedCount(
uint256 _editionId,
address _recipient
) public view returns (uint256) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
uint256 mintedCountKey = uint256(
keccak256(abi.encodePacked(_editionId, _recipient))
);
return state._mintedPerWallet[mintedCountKey];
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* PAUSABLE
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function pauseContract() public onlyAdmin {
_pause();
}
function unpauseContract() public onlyAdmin {
_unpause();
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* ERC721
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/// @dev See {IERC721-approve}.
function approve(
address to,
uint256 tokenId
) external override onlyAllowedOperatorApproval(to) {
address owner = ownerOf(tokenId);
if (
msg.sender == to ||
(msg.sender != owner && !isApprovedForAll(owner, msg.sender))
) {
revert CustomErrors.NotAllowed();
}
_approve(to, tokenId);
}
/// @dev See {IERC721-transferFrom}.
function transferFrom(
address from,
address to,
uint256 tokenId
) external override onlyAllowedOperator(from) {
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
revert CustomErrors.TransferError();
}
_transfer(from, to, tokenId);
}
/// @dev See {IERC721-ownerOf}.
function ownerOf(uint256 tokenId) public view override returns (address) {
(address owner, ) = _ownerAndBatchHeadOf(tokenId);
return owner;
}
/// @dev Returns the number of tokens in ``owner``'s account.
function balanceOf(
address owner
) external view returns (uint256 tokenBalance) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
tokenBalance = state._balances[owner];
}
/// @dev See {IERC721-getApproved}.
function getApproved(
uint256 tokenId
) public view override returns (address) {
if (!_exists(tokenId)) {
revert CustomErrors.TokenNotFound();
}
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
if (!_isTransferAllowed(tokenId)) {
revert CustomErrors.TokenStaked();
}
return state._tokenApprovals[tokenId];
}
/// @dev See {IERC721-isApprovedForAll}.
function isApprovedForAll(
address owner,
address operator
) public view override returns (bool) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._operatorApprovals[owner][operator];
}
/// @dev See {IERC721-setApprovalForAll}.
function setApprovalForAll(
address operator,
bool approved
) external override onlyAllowedOperatorApproval(operator) {
if (operator == msg.sender) {
revert CustomErrors.NotAllowed();
}
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._operatorApprovals[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
/// @dev See {IERC721-safeTransferFrom}.
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external override onlyAllowedOperator(from) {
safeTransferFrom(from, to, tokenId, "");
}
/// @dev See {IERC721-safeTransferFrom}.
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory _data
) public override onlyAllowedOperator(from) {
if (!_isApprovedOrOwner(msg.sender, tokenId)) {
revert CustomErrors.NotAllowed();
}
_safeTransfer(from, to, tokenId, _data);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* INTERNAL / PUBLIC HELPERS
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/// @dev Returns whether `tokenId` exists.
function _exists(uint256 tokenId) internal view virtual returns (bool) {
(uint256 editionId, uint256 tokenNumber) = parseEditionFromTokenId(
tokenId
);
if (isBurned(tokenId)) {
return false;
}
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
ERC721State.Edition memory edition = state._editions[editionId];
return tokenNumber <= edition.currentSupply;
}
/**
* @dev Returns the index of the batch for a given token.
* If the token was not bought in a batch tokenId == tokenIdBatchHead
*/
function _getBatchHead(
uint256 tokenId
) internal view returns (uint256 tokenIdBatchHead) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
(uint256 editionId, ) = parseEditionFromTokenId(tokenId);
tokenIdBatchHead = state._batchHead.scanForward(
tokenId,
editionId * state._edition_max_tokens
);
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits a {Approval} event.
*/
function _approve(address to, uint256 tokenId) internal {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._tokenApprovals[tokenId] = to;
emit Approval(ownerOf(tokenId), to, tokenId);
}
/**
* @dev Returns the index of the batch for a given token.
* and the batch owner address
*/
function _ownerAndBatchHeadOf(
uint256 tokenId
) internal view returns (address owner, uint256 tokenIdBatchHead) {
if (!_exists(tokenId)) {
revert CustomErrors.TokenNotFound();
}
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
tokenIdBatchHead = _getBatchHead(tokenId);
owner = state._owners[tokenIdBatchHead];
}
/**
* @dev Returns whether `spender` is allowed to manage `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _isApprovedOrOwner(
address spender,
uint256 tokenId
) internal view returns (bool) {
if (!_exists(tokenId)) {
revert CustomErrors.TokenNotFound();
}
address owner = ownerOf(tokenId);
return (spender == owner ||
getApproved(tokenId) == spender ||
isApprovedForAll(owner, spender));
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* Internal function intened to split the logic for different transfer use cases
* Emits a {Transfer} event.
*/
function _transferCore(address from, address to, uint256 tokenId) internal {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
(, uint256 tokenIdBatchHead) = _ownerAndBatchHeadOf(tokenId);
address owner = ownerOf(tokenId);
if (owner != from) {
revert CustomErrors.TransferError();
}
// We check if the token after the one being transfer
// belong to the batch, if it does, we have to update it's owner
// while being careful to not overflow the edition maxSupply
uint256 nextTokenId = tokenId + 1;
(, uint256 nextTokenNumber) = parseEditionFromTokenId(nextTokenId);
(uint256 currentEditionId, ) = parseEditionFromTokenId(tokenId);
ERC721State.Edition memory edition = state._editions[currentEditionId];
if (
nextTokenNumber <= edition.maxSupply &&
!state._batchHead.get(nextTokenId)
) {
state._owners[nextTokenId] = from;
state._batchHead.set(nextTokenId);
}
// Finaly we update the owners and balances
state._owners[tokenId] = to;
if (tokenId != tokenIdBatchHead) {
state._batchHead.set(tokenId);
}
state._balances[to] += 1;
state._balances[from] -= 1;
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal {
_beforeTokenTransfer(from, to, tokenId);
// Remove approval
_approve(address(0), tokenId);
emit Transfer(from, to, tokenId);
_transferCore(from, to, tokenId);
}
function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory _data
) internal virtual {
_transfer(from, to, tokenId);
LANFTUtils._checkOnERC721Received(from, to, tokenId, _data);
}
/**
* @dev Hook that is called before any token transfer. This includes minting
* and burning.
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
* transferred to `to`.
* - When `from` is zero, `tokenId` will be minted for `to`.
* - When `to` is zero, ``from``'s `tokenId` will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {
if (!_isTransferAllowed(tokenId)) {
revert CustomErrors.TokenStaked();
}
}
/**
* Check if the token is staked
*/
function _isTransferAllowed(uint256 tokenId) internal view returns (bool) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
(uint256 currentEditionId, ) = parseEditionFromTokenId(tokenId);
ERC721State.Edition memory edition = state._editions[currentEditionId];
// If the token is staked, we don't allow transfer
if (edition.stakingEnabled) {
bool tokenIsStaked = IStakingContract(state._stakingContractAddress)
.isTokenStaked(tokenId);
if (tokenIsStaked) {
return false;
}
}
return true;
}
function _mintPrintVouchers(
uint256 _editionId,
uint24 _quantity,
uint256 _firstTokenId
) internal {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
address printVoucherContractAddress = state
._printDataByEdition[_editionId]
.printVoucherContractAddress;
// Mint print vouchers
uint256[] memory tokenArray = new uint256[](_quantity);
for (uint256 i = 0; i < _quantity; i++) {
tokenArray[i] = _firstTokenId + i;
}
IPrintVoucherContract(printVoucherContractAddress).mintPrintVoucher(
tx.origin,
tokenArray
);
}
function _validatePublicMintParams(
uint256 _editionId,
uint24 _quantity,
uint8 _maxMintPerWallet
) internal {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
// Check max mint per wallet restrictions (if maxMintPerWallet is 0, no restriction apply)
if (_maxMintPerWallet != 0 && !_isWinterWallet()) {
uint256 mintedCountKey = uint256(
keccak256(abi.encodePacked(_editionId, msg.sender))
);
if (
state._mintedPerWallet[mintedCountKey] + _quantity >
_maxMintPerWallet
) {
revert CustomErrors.MaximumMintAmountReached();
}
state._mintedPerWallet[mintedCountKey] += _quantity;
}
}
function _validateEditionPrintData(
ERC721State.EditionWithPrintData memory printData
) internal pure {
if (
printData.printVoucherContractAddress == address(0) ||
printData.publicMintPriceWithPrintInFinney == 0
) {
revert CustomErrors.InvalidPrintData();
}
}
function _validateMintPrice(
uint256 _mintPriceInFinney,
uint24 _quantity
) internal view {
uint256 mintPriceInWei = uint256(_mintPriceInFinney) * 10e14;
if (mintPriceInWei * _quantity > msg.value) {
revert CustomErrors.InsufficientFunds();
}
}
function _sendRoyaltiesAfterMint(uint256 _firstTokenId) internal {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
// Send primary royalties
(
address payable[] memory wallets,
uint256[] memory primarySalePercentages
) = state._royaltyRegistry.primaryRoyaltyInfo(
address(this),
_firstTokenId
);
uint256 nReceivers = wallets.length;
for (uint256 i = 0; i < nReceivers; i++) {
uint256 royalties = BPS._calculatePercentage(
msg.value,
primarySalePercentages[i]
);
(bool sent, ) = wallets[i].call{value: royalties}("");
if (!sent) {
revert CustomErrors.FundTransferError();
}
}
}
/**
* Check if the token is staked
*/
// function _areAllNFTsStaked(address from) internal view returns(bool) {
// ERC721State.ERC721LAState storage state = ERC721State
// ._getERC721LAState();
// (uint256 currentEditionId, ) = parseEditionFromTokenId(tokenId);
// ERC721State.Edition memory edition = state._editions[currentEditionId];
// // If the token is staked, we don't allow transfer
// if (edition.stakingEnabled) {
// bool tokenIsStaked = IStakingContract(state._stakingContractAddress)
// .isTokenStaked(tokenId);
// if (tokenIsStaked) {
// return false;
// }
// }
// return true;
// }
function isBurned(uint256 tokenId) public view returns (bool) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
address owner = state._owners[tokenId];
return owner == burnAddress;
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* ETHER
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function balance() public view returns (uint256) {
return address(this).balance;
}
function withdrawAmount(
address payable recipient,
uint256 amount
) external onlyAdmin {
(bool succeed, ) = recipient.call{value: amount}("");
if (!succeed) {
revert CustomErrors.FundTransferError();
}
}
function withdrawAll(address payable recipient) external onlyAdmin {
(bool succeed, ) = recipient.call{value: balance()}("");
if (!succeed) {
revert CustomErrors.FundTransferError();
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* X-CARD
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
function setXCardContractAddress(
address xCardContractAddress
) public override onlyAdmin {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
state._xCardContractAddress = xCardContractAddress;
}
function getXCardContractAddress() public view override returns (address) {
ERC721State.ERC721LAState storage state = ERC721State
._getERC721LAState();
return state._xCardContractAddress;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../libraries/BitMaps/BitMaps.sol";
import "../platform/royalties/IRoyaltiesRegistry.sol";
library ERC721State {
using BitMaps for BitMaps.BitMap;
struct Edition {
// Max. number of token mintable per edition
uint24 maxSupply;
// Currently minted token coutner
uint24 currentSupply;
// Burned token counter
uint24 burnedSupply;
// Public mint price
uint24 publicMintPriceInFinney;
// Public mint start time in seconds
uint32 publicMintStartTS;
// Public mint ending time in seconds
uint32 publicMintEndTS;
// Max mint per wallet. If 0, no limit
uint8 maxMintPerWallet;
// If perTokenMetadata == false, all tokens in this edition will have the same metadata
bool perTokenMetadata;
// An edition Id associated with this collection that is approved to be burned in order to mint on the current edition.
uint24 burnableEditionId;
// Amount to burn
uint24 amountToBurn;
// determines if this edition supports staking
bool stakingEnabled;
}
struct EditionWithURI {
Edition data;
string baseURI;
}
/**
* @dev Storage layout
* This pattern allow us to extend current contract using DELETGATE_CALL
* without worrying about storage slot conflicts
*/
struct ERC721LAState {
// The number of edition created, indexed from 1
uint64 _editionCounter;
// Max token by edition. Defines the number of 0 in token Id (see editions)
uint24 _edition_max_tokens;
// Contract Name
string _name;
// Ticker
string _symbol;
// Edtion by editionId
mapping(uint256 => Edition) _editions;
// Owner by tokenId
mapping(uint256 => address) _owners;
// Token Id to operator address
mapping(uint256 => address) _tokenApprovals;
// Owned token count by address
mapping(address => uint256) _balances;
// Allower to allowee
mapping(address => mapping(address => bool)) _operatorApprovals;
// Tracking of batch heads
BitMaps.BitMap _batchHead;
// LiveArt global royalty registry address
IRoyaltiesRegistry _royaltyRegistry;
// Amount of ETH withdrawn by edition
mapping(uint256 => uint256) _withdrawnBalancesByEdition;
// EditionID => Base URI
mapping(uint256 => string) _baseURIByEdition;
// Minted counter per wallet/edition. hash(address, editionId) => counter
mapping(uint256 => uint256) _mintedPerWallet;
// xCardContract Address
address _xCardContractAddress;
// Staking contract address
address _stakingContractAddress;
// Logic contract address
address _logicContractAddress;
// EditionPrintData by editionId
mapping(uint256 => EditionWithPrintData) _printDataByEdition;
}
struct EditionWithPrintData {
address printVoucherContractAddress;
uint24 publicMintPriceWithPrintInFinney;
}
/**
* @dev Get storage data from dedicated slot.
* This pattern avoids storage conflict during proxy upgrades
* and give more flexibility when creating extensions
*/
function _getERC721LAState()
internal
pure
returns (ERC721LAState storage state)
{
bytes32 storageSlot = keccak256("liveart.ERC721LA");
assembly {
state.slot := storageSlot
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;
import "../libraries/BitMaps/BitMaps.sol";
import "../platform/royalties/IRoyaltiesRegistry.sol";
interface IERC721Events {
event EditionCreated(
address indexed contractAddress,
uint256 editionId,
uint24 maxSupply,
string baseURI,
uint24 contractMintPrice,
bool perTokenMetadata
);
event EditionUpdated(
address indexed contractAddress,
uint256 editionId,
uint256 maxSupply,
string baseURI
);
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/
event Transfer(
address indexed from,
address indexed to,
uint256 indexed tokenId
);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/
event Approval(
address indexed owner,
address indexed approved,
uint256 indexed tokenId
);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
}
// SPDX-License-Identifier: UNLICENSED
import "../libraries/BitMaps/BitMaps.sol";
import "../platform/royalties/IRoyaltiesRegistry.sol";
import "./IERC721Events.sol";
import "./ERC721State.sol";
pragma solidity ^0.8.4;
/**
* @dev Interface of an ERC721LA compliant contract.
*/
abstract contract IERC721LA is IERC721Events {
using BitMaps for BitMaps.BitMap;
// ==============================
// IERC165
// ==============================
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
// function supportsInterface(
// bytes4 interfaceId
// ) external view virtual returns (bool);
// ==============================
// IERC721
// ==============================
/**
* @dev Returns the total amount of tokens stored by the contract.
*
* Burned tokens are calculated here, use `_totalMinted()` if you want to count just minted tokens.
*/
function totalSupply() external view virtual returns (uint256);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
// function balanceOf(address owner) external view returns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(
uint256 tokenId
) external view virtual returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external virtual;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external virtual;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) external virtual;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address to, uint256 tokenId) external virtual;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the caller.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(
address operator,
bool _approved
) external virtual;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(
uint256 tokenId
) external view virtual returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(
address owner,
address operator
) external view virtual returns (bool);
// ==============================
// IERC721Metadata
// ==============================
/**
* @dev Returns the token collection name.
*/
function name() external view virtual returns (string memory);
/**
* @dev Returns the token collection symbol.
*/
// function symbol() external view virtual returns (string memory);
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(
uint256 tokenId
) external view virtual returns (string memory);
// ==============================
// Editions
// ==============================
/**
* @dev fetch edition struct data by editionId
*/
function getEdition(
uint256 _editionId
) external view virtual returns (ERC721State.Edition memory);
/**
* @dev fetch edition struct data by editionId
*/
function getEditionWithURI(
uint256 _editionId
) external view virtual returns (ERC721State.EditionWithURI memory);
/**
* @dev fetch editionPrintData struct data by editionId
*/
function getEditionPrintData(
uint256 _editionId
)
external
view
virtual
returns (ERC721State.EditionWithPrintData memory editionPrintData);
// ==============================
// Helpers
// ==============================
function setXCardContractAddress(
address xCardContractAddress
) external virtual;
function getXCardContractAddress() external view virtual returns (address);
function isPublicMintStarted(
uint256 editionId
) external view virtual returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
interface IOperatorFilterRegistry {
/**
* @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns
* true if supplied registrant address is not registered.
*/
function isOperatorAllowed(address registrant, address operator) external view returns (bool);
/**
* @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner.
*/
function register(address registrant) external;
/**
* @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes.
*/
function registerAndSubscribe(address registrant, address subscription) external;
/**
* @notice Registers an address with the registry and copies the filtered operators and codeHashes from another
* address without subscribing.
*/
function registerAndCopyEntries(address registrant, address registrantToCopy) external;
/**
* @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner.
* Note that this does not remove any filtered addresses or codeHashes.
* Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes.
*/
function unregister(address addr) external;
/**
* @notice Update an operator address for a registered address - when filtered is true, the operator is filtered.
*/
function updateOperator(address registrant, address operator, bool filtered) external;
/**
* @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates.
*/
function updateOperators(address registrant, address[] calldata operators, bool filtered) external;
/**
* @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered.
*/
function updateCodeHash(address registrant, bytes32 codehash, bool filtered) external;
/**
* @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates.
*/
function updateCodeHashes(address registrant, bytes32[] calldata codeHashes, bool filtered) external;
/**
* @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous
* subscription if present.
* Note that accounts with subscriptions may go on to subscribe to other accounts - in this case,
* subscriptions will not be forwarded. Instead the former subscription's existing entries will still be
* used.
*/
function subscribe(address registrant, address registrantToSubscribe) external;
/**
* @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes.
*/
function unsubscribe(address registrant, bool copyExistingEntries) external;
/**
* @notice Get the subscription address of a given registrant, if any.
*/
function subscriptionOf(address addr) external returns (address registrant);
/**
* @notice Get the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscribers(address registrant) external returns (address[] memory);
/**
* @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant.
* Note that order is not guaranteed as updates are made.
*/
function subscriberAt(address registrant, uint256 index) external returns (address);
/**
* @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr.
*/
function copyEntriesOf(address registrant, address registrantToCopy) external;
/**
* @notice Returns true if operator is filtered by a given address or its subscription.
*/
function isOperatorFiltered(address registrant, address operator) external returns (bool);
/**
* @notice Returns true if the hash of an address's code is filtered by a given address or its subscription.
*/
function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool);
/**
* @notice Returns true if a codeHash is filtered by a given address or its subscription.
*/
function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool);
/**
* @notice Returns a list of filtered operators for a given address or its subscription.
*/
function filteredOperators(address addr) external returns (address[] memory);
/**
* @notice Returns the set of filtered codeHashes for a given address or its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashes(address addr) external returns (bytes32[] memory);
/**
* @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredOperatorAt(address registrant, uint256 index) external returns (address);
/**
* @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or
* its subscription.
* Note that order is not guaranteed as updates are made.
*/
function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32);
/**
* @notice Returns true if an address has registered
*/
function isRegistered(address addr) external returns (bool);
/**
* @dev Convenience method to compute the code hash of an arbitrary contract
*/
function codeHashOf(address addr) external returns (bytes32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
address constant CANONICAL_OPERATOR_FILTER_REGISTRY_ADDRESS = 0x000000000000AAeB6D7670E522A718067333cd4E;
address constant CANONICAL_CORI_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6;