Contract Name:
SykyArtistToken
Contract Source Code:
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
pragma solidity ^0.8.0;
import "./interface/IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/
abstract contract ERC165 is IERC165 {
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
// 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
* [EIP](https://eips.ethereum.org/EIPS/eip-165).
*
* 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
* [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
* 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: Apache 2.0
pragma solidity ^0.8.0;
import "./IERC165.sol";
/**
* @dev Interface for the NFT Royalty Standard.
*
* A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
* support for royalty payments across all NFT marketplaces and ecosystem participants.
*
* _Available since v4.5._
*/
interface IERC2981 is IERC165 {
/**
* @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
* exchange. The royalty amount is denominated and should be payed in that same unit of exchange.
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 {
/**
* @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);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/
function balanceOf(address owner) external view returns (uint256);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address);
/**
* @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;
/**
* @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;
/**
* @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;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address);
/**
* @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;
/**
* @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 returns (bool);
/**
* @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;
}
// SPDX-License-Identifier: MIT
// ERC721A Contracts v3.3.0
// Creator: Chiru Labs
pragma solidity ^0.8.4;
import "./IERC721.sol";
import "./IERC721Metadata.sol";
/**
* @dev Interface of an ERC721A compliant contract.
*/
interface IERC721A is IERC721, IERC721Metadata {
/**
* The caller must own the token or be an approved operator.
*/
error ApprovalCallerNotOwnerNorApproved();
/**
* The token does not exist.
*/
error ApprovalQueryForNonexistentToken();
/**
* The caller cannot approve to their own address.
*/
error ApproveToCaller();
/**
* The caller cannot approve to the current owner.
*/
error ApprovalToCurrentOwner();
/**
* Cannot query the balance for the zero address.
*/
error BalanceQueryForZeroAddress();
/**
* Cannot mint to the zero address.
*/
error MintToZeroAddress();
/**
* The quantity of tokens minted must be more than zero.
*/
error MintZeroQuantity();
/**
* The token does not exist.
*/
error OwnerQueryForNonexistentToken();
/**
* The caller must own the token or be an approved operator.
*/
error TransferCallerNotOwnerNorApproved();
/**
* The token must be owned by `from`.
*/
error TransferFromIncorrectOwner();
/**
* Cannot safely transfer to a contract that does not implement the ERC721Receiver interface.
*/
error TransferToNonERC721ReceiverImplementer();
/**
* Cannot transfer to the zero address.
*/
error TransferToZeroAddress();
/**
* The token does not exist.
*/
error URIQueryForNonexistentToken();
// Compiler will pack this into a single 256bit word.
struct TokenOwnership {
// The address of the owner.
address addr;
// Keeps track of the start time of ownership with minimal overhead for tokenomics.
uint64 startTimestamp;
// Whether the token has been burned.
bool burned;
}
// Compiler will pack this into a single 256bit word.
struct AddressData {
// Realistically, 2**64-1 is more than enough.
uint64 balance;
// Keeps track of mint count with minimal overhead for tokenomics.
uint64 numberMinted;
// Keeps track of burn count with minimal overhead for tokenomics.
uint64 numberBurned;
// For miscellaneous variable(s) pertaining to the address
// (e.g. number of whitelist mint slots used).
// If there are multiple variables, please pack them into a uint64.
uint64 aux;
}
/**
* @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 returns (uint256);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// Note: the ERC-165 identifier for this interface is 0x5b5e139f.
/* is ERC721 */
interface IERC721Metadata {
/// @notice A descriptive name for a collection of NFTs in this contract
function name() external view returns (string memory);
/// @notice An abbreviated name for NFTs in this contract
function symbol() external view returns (string memory);
/// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
/// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
/// 3986. The URI may point to a JSON file that conforms to the "ERC721
/// Metadata JSON Schema".
function tokenURI(uint256 _tokenId) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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 `IERC721.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "./interface/IContractMetadata.sol";
/**
* @title Contract Metadata
* @notice Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
* for you contract.
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
*/
abstract contract ContractMetadata is IContractMetadata {
/// @notice Returns the contract metadata URI.
string public override contractURI;
/**
* @notice Lets a contract admin set the URI for contract-level metadata.
* @dev Caller should be authorized to setup contractURI, e.g. contract admin.
* See {_canSetContractURI}.
* Emits {ContractURIUpdated Event}.
*
* @param _uri keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
*/
function setContractURI(string memory _uri) external override {
if (!_canSetContractURI()) {
revert("Not authorized");
}
_setupContractURI(_uri);
}
/// @dev Lets a contract admin set the URI for contract-level metadata.
function _setupContractURI(string memory _uri) internal {
string memory prevURI = contractURI;
contractURI = _uri;
emit ContractURIUpdated(prevURI, _uri);
}
/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view virtual returns (bool);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
* for you contract.
*
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
*/
interface IContractMetadata {
/// @dev Returns the metadata URI of the contract.
function contractURI() external view returns (string memory);
/**
* @dev Sets contract URI for the storefront-level metadata of the contract.
* Only module admin can call this function.
*/
function setContractURI(string calldata _uri) external;
/// @dev Emitted when the contract URI is updated.
event ContractURIUpdated(string prevURI, string newURI);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* _Available since v4.1._
*/
interface IMulticall {
/**
* @dev Receives and executes a batch of function calls on this contract.
*/
function multicall(bytes[] calldata data) external returns (bytes[] memory results);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* Thirdweb's `Ownable` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* who the 'owner' of the inheriting smart contract is, and lets the inheriting contract perform conditional logic that uses
* information about who the contract's owner is.
*/
interface IOwnable {
/// @dev Returns the owner of the contract.
function owner() external view returns (address);
/// @dev Lets a module admin set a new owner for the contract. The new owner must be a module admin.
function setOwner(address _newOwner) external;
/// @dev Emitted when a new Owner is set.
event OwnerUpdated(address indexed prevOwner, address indexed newOwner);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/
interface IPermissions {
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*
* _Available since v3.1._
*/
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/
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);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) external view returns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) external view returns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* 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;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/
function revokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `account`.
*/
function renounceRole(bytes32 role, address account) external;
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "../../eip/interface/IERC2981.sol";
/**
* Thirdweb's `Royalty` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* the recipient of royalty fee and the royalty fee basis points, and lets the inheriting contract perform conditional logic
* that uses information about royalty fees, if desired.
*
* The `Royalty` contract is ERC2981 compliant.
*/
interface IRoyalty is IERC2981 {
struct RoyaltyInfo {
address recipient;
uint256 bps;
}
/// @dev Returns the royalty recipient and fee bps.
function getDefaultRoyaltyInfo() external view returns (address, uint16);
/// @dev Lets a module admin update the royalty bps and recipient.
function setDefaultRoyaltyInfo(address _royaltyRecipient, uint256 _royaltyBps) external;
/// @dev Lets a module admin set the royalty recipient for a particular token Id.
function setRoyaltyInfoForToken(
uint256 tokenId,
address recipient,
uint256 bps
) external;
/// @dev Returns the royalty recipient for a particular token Id.
function getRoyaltyInfoForToken(uint256 tokenId) external view returns (address, uint16);
/// @dev Emitted when royalty info is updated.
event DefaultRoyalty(address indexed newRoyaltyRecipient, uint256 newRoyaltyBps);
/// @dev Emitted when royalty recipient for tokenId is set
event RoyaltyForToken(uint256 indexed tokenId, address indexed royaltyRecipient, uint256 royaltyBps);
}
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "../lib/TWAddress.sol";
import "./interface/IMulticall.sol";
/**
* @dev Provides a function to batch together multiple calls in a single external call.
*
* _Available since v4.1._
*/
contract Multicall is IMulticall {
/**
* @notice Receives and executes a batch of function calls on this contract.
* @dev Receives and executes a batch of function calls on this contract.
*
* @param data The bytes data that makes up the batch of function calls to execute.
* @return results The bytes data that makes up the result of the batch of function calls executed.
*/
function multicall(bytes[] calldata data) external virtual override returns (bytes[] memory results) {
results = new bytes[](data.length);
for (uint256 i = 0; i < data.length; i++) {
results[i] = TWAddress.functionDelegateCall(address(this), data[i]);
}
return results;
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "./interface/IOwnable.sol";
/**
* @title Ownable
* @notice Thirdweb's `Ownable` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* who the 'owner' of the inheriting smart contract is, and lets the inheriting contract perform conditional logic that uses
* information about who the contract's owner is.
*/
abstract contract Ownable is IOwnable {
/// @dev Owner of the contract (purpose: OpenSea compatibility)
address private _owner;
/// @dev Reverts if caller is not the owner.
modifier onlyOwner() {
if (msg.sender != _owner) {
revert("Not authorized");
}
_;
}
/**
* @notice Returns the owner of the contract.
*/
function owner() public view override returns (address) {
return _owner;
}
/**
* @notice Lets an authorized wallet set a new owner for the contract.
* @param _newOwner The address to set as the new owner of the contract.
*/
function setOwner(address _newOwner) external override {
if (!_canSetOwner()) {
revert("Not authorized");
}
_setupOwner(_newOwner);
}
/// @dev Lets a contract admin set a new owner for the contract. The new owner must be a contract admin.
function _setupOwner(address _newOwner) internal {
address _prevOwner = _owner;
_owner = _newOwner;
emit OwnerUpdated(_prevOwner, _newOwner);
}
/// @dev Returns whether owner can be set in the given execution context.
function _canSetOwner() internal view virtual returns (bool);
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "./interface/IPermissions.sol";
import "../lib/TWStrings.sol";
/**
* @title Permissions
* @dev This contracts provides extending-contracts with role-based access control mechanisms
*/
contract Permissions is IPermissions {
/// @dev Map from keccak256 hash of a role => a map from address => whether address has role.
mapping(bytes32 => mapping(address => bool)) private _hasRole;
/// @dev Map from keccak256 hash of a role to role admin. See {getRoleAdmin}.
mapping(bytes32 => bytes32) private _getRoleAdmin;
/// @dev Default admin role for all roles. Only accounts with this role can grant/revoke other roles.
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/// @dev Modifier that checks if an account has the specified role; reverts otherwise.
modifier onlyRole(bytes32 role) {
_checkRole(role, msg.sender);
_;
}
/**
* @notice Checks whether an account has a particular role.
* @dev Returns `true` if `account` has been granted `role`.
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
* @param account Address of the account for which the role is being checked.
*/
function hasRole(bytes32 role, address account) public view override returns (bool) {
return _hasRole[role][account];
}
/**
* @notice Checks whether an account has a particular role;
* role restrictions can be swtiched on and off.
*
* @dev Returns `true` if `account` has been granted `role`.
* Role restrictions can be swtiched on and off:
* - If address(0) has ROLE, then the ROLE restrictions
* don't apply.
* - If address(0) does not have ROLE, then the ROLE
* restrictions will apply.
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
* @param account Address of the account for which the role is being checked.
*/
function hasRoleWithSwitch(bytes32 role, address account) public view returns (bool) {
if (!_hasRole[role][address(0)]) {
return _hasRole[role][account];
}
return true;
}
/**
* @notice Returns the admin role that controls the specified role.
* @dev See {grantRole} and {revokeRole}.
* To change a role's admin, use {_setRoleAdmin}.
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
*/
function getRoleAdmin(bytes32 role) external view override returns (bytes32) {
return _getRoleAdmin[role];
}
/**
* @notice Grants a role to an account, if not previously granted.
* @dev Caller must have admin role for the `role`.
* Emits {RoleGranted Event}.
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
* @param account Address of the account to which the role is being granted.
*/
function grantRole(bytes32 role, address account) public virtual override {
_checkRole(_getRoleAdmin[role], msg.sender);
if (_hasRole[role][account]) {
revert("Can only grant to non holders");
}
_setupRole(role, account);
}
/**
* @notice Revokes role from an account.
* @dev Caller must have admin role for the `role`.
* Emits {RoleRevoked Event}.
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
* @param account Address of the account from which the role is being revoked.
*/
function revokeRole(bytes32 role, address account) public virtual override {
_checkRole(_getRoleAdmin[role], msg.sender);
_revokeRole(role, account);
}
/**
* @notice Revokes role from the account.
* @dev Caller must have the `role`, with caller being the same as `account`.
* Emits {RoleRevoked Event}.
*
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
* @param account Address of the account from which the role is being revoked.
*/
function renounceRole(bytes32 role, address account) public virtual override {
if (msg.sender != account) {
revert("Can only renounce for self");
}
_revokeRole(role, account);
}
/// @dev Sets `adminRole` as `role`'s admin role.
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = _getRoleAdmin[role];
_getRoleAdmin[role] = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/// @dev Sets up `role` for `account`
function _setupRole(bytes32 role, address account) internal virtual {
_hasRole[role][account] = true;
emit RoleGranted(role, account, msg.sender);
}
/// @dev Revokes `role` from `account`
function _revokeRole(bytes32 role, address account) internal virtual {
_checkRole(role, account);
delete _hasRole[role][account];
emit RoleRevoked(role, account, msg.sender);
}
/// @dev Checks `role` for `account`. Reverts with a message including the required role.
function _checkRole(bytes32 role, address account) internal view virtual {
if (!_hasRole[role][account]) {
revert(
string(
abi.encodePacked(
"Permissions: account ",
TWStrings.toHexString(uint160(account), 20),
" is missing role ",
TWStrings.toHexString(uint256(role), 32)
)
)
);
}
}
/// @dev Checks `role` for `account`. Reverts with a message including the required role.
function _checkRoleWithSwitch(bytes32 role, address account) internal view virtual {
if (!hasRoleWithSwitch(role, account)) {
revert(
string(
abi.encodePacked(
"Permissions: account ",
TWStrings.toHexString(uint160(account), 20),
" is missing role ",
TWStrings.toHexString(uint256(role), 32)
)
)
);
}
}
}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
/// @author thirdweb
import "./interface/IRoyalty.sol";
/**
* @title Royalty
* @notice Thirdweb's `Royalty` is a contract extension to be used with any base contract. It exposes functions for setting and reading
* the recipient of royalty fee and the royalty fee basis points, and lets the inheriting contract perform conditional logic
* that uses information about royalty fees, if desired.
*
* @dev The `Royalty` contract is ERC2981 compliant.
*/
abstract contract Royalty is IRoyalty {
/// @dev The (default) address that receives all royalty value.
address private royaltyRecipient;
/// @dev The (default) % of a sale to take as royalty (in basis points).
uint16 private royaltyBps;
/// @dev Token ID => royalty recipient and bps for token
mapping(uint256 => RoyaltyInfo) private royaltyInfoForToken;
/**
* @notice View royalty info for a given token and sale price.
* @dev Returns royalty amount and recipient for `tokenId` and `salePrice`.
* @param tokenId The tokenID of the NFT for which to query royalty info.
* @param salePrice Sale price of the token.
*
* @return receiver Address of royalty recipient account.
* @return royaltyAmount Royalty amount calculated at current royaltyBps value.
*/
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
virtual
override
returns (address receiver, uint256 royaltyAmount)
{
(address recipient, uint256 bps) = getRoyaltyInfoForToken(tokenId);
receiver = recipient;
royaltyAmount = (salePrice * bps) / 10_000;
}
/**
* @notice View royalty info for a given token.
* @dev Returns royalty recipient and bps for `_tokenId`.
* @param _tokenId The tokenID of the NFT for which to query royalty info.
*/
function getRoyaltyInfoForToken(uint256 _tokenId) public view override returns (address, uint16) {
RoyaltyInfo memory royaltyForToken = royaltyInfoForToken[_tokenId];
return
royaltyForToken.recipient == address(0)
? (royaltyRecipient, uint16(royaltyBps))
: (royaltyForToken.recipient, uint16(royaltyForToken.bps));
}
/**
* @notice Returns the defualt royalty recipient and BPS for this contract's NFTs.
*/
function getDefaultRoyaltyInfo() external view override returns (address, uint16) {
return (royaltyRecipient, uint16(royaltyBps));
}
/**
* @notice Updates default royalty recipient and bps.
* @dev Caller should be authorized to set royalty info.
* See {_canSetRoyaltyInfo}.
* Emits {DefaultRoyalty Event}; See {_setupDefaultRoyaltyInfo}.
*
* @param _royaltyRecipient Address to be set as default royalty recipient.
* @param _royaltyBps Updated royalty bps.
*/
function setDefaultRoyaltyInfo(address _royaltyRecipient, uint256 _royaltyBps) external override {
if (!_canSetRoyaltyInfo()) {
revert("Not authorized");
}
_setupDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps);
}
/// @dev Lets a contract admin update the default royalty recipient and bps.
function _setupDefaultRoyaltyInfo(address _royaltyRecipient, uint256 _royaltyBps) internal {
if (_royaltyBps > 10_000) {
revert("Exceeds max bps");
}
royaltyRecipient = _royaltyRecipient;
royaltyBps = uint16(_royaltyBps);
emit DefaultRoyalty(_royaltyRecipient, _royaltyBps);
}
/**
* @notice Updates default royalty recipient and bps for a particular token.
* @dev Sets royalty info for `_tokenId`. Caller should be authorized to set royalty info.
* See {_canSetRoyaltyInfo}.
* Emits {RoyaltyForToken Event}; See {_setupRoyaltyInfoForToken}.
*
* @param _recipient Address to be set as royalty recipient for given token Id.
* @param _bps Updated royalty bps for the token Id.
*/
function setRoyaltyInfoForToken(
uint256 _tokenId,
address _recipient,
uint256 _bps
) external override {
if (!_canSetRoyaltyInfo()) {
revert("Not authorized");
}
_setupRoyaltyInfoForToken(_tokenId, _recipient, _bps);
}
/// @dev Lets a contract admin set the royalty recipient and bps for a particular token Id.
function _setupRoyaltyInfoForToken(
uint256 _tokenId,
address _recipient,
uint256 _bps
) internal {
if (_bps > 10_000) {
revert("Exceeds max bps");
}
royaltyInfoForToken[_tokenId] = RoyaltyInfo({ recipient: _recipient, bps: _bps });
emit RoyaltyForToken(_tokenId, _recipient, _bps);
}
/// @dev Returns whether royalty info can be set in the given execution context.
function _canSetRoyaltyInfo() internal view virtual returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* @dev Collection of functions related to the address type
*/
library TWAddress {
/**
* @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 Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* [EIP1884](https://eips.ethereum.org/EIPS/eip-1884) increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.0;
/// @author thirdweb
/**
* @dev String operations.
*/
library TWStrings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
/**
* @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 Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @author Syky - Nathan Rempel
interface IProductArtistToken {
/*//////////////////////////////////////////////////////////////
Methods
//////////////////////////////////////////////////////////////*/
/// @notice Lets an authorized address mint single NFTs to a recipient.
function mintTo(address _to, uint256 _productId) external;
function mintTo(address _to, uint256 _productId, bytes memory _data) external;
function mintTo(
address _to,
uint256 _productId,
string calldata _uri,
bytes memory _data
) external;
/// @notice Lets an authorized address mint multiple NFTs at once to a recipient.
function batchMintTo(address _to, uint256 _productId, uint256 _quantity) external;
function batchMintTo(
address _to,
uint256 _productId,
uint256 _quantity,
bytes memory _data
) external;
function batchMintTo(
address _to,
uint256 _productId,
uint256 _quantity,
string[] calldata _uris,
bytes memory _data
) external;
/*//////////////////////////////////////////////////////////////
Events
//////////////////////////////////////////////////////////////*/
/// @dev Emitted when all minting is enabled or disabled
event GlobalMintingRestricted(bool restricted);
/// @dev Emitted when all transfers are enabled or disabled
event GlobalTransfersRestricted(bool restricted);
/// @dev Emitted when all burning is enabled or disabled
event GlobalBurningRestricted(bool restricted);
/*//////////////////////////////////////////////////////////////
Errors
//////////////////////////////////////////////////////////////*/
/// @dev URIs array length for batch mint must match _quantity
error BatchMintURICountMismatch();
/// @dev Action requires the manager or admin role
error ManagerRoleRequired();
/// @dev Action requires the minting or admin role
error MintingRoleRequired();
/// @dev Minting has been disabled via address(0) role
error GlobalMintingDisabled();
/// @dev Transfers have been disabled via address(0) role
error GlobalTransfersDisabled();
/// @dev Burning has been disabled via address(0) role
error GlobalBurningDisabled();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @author Syky - Nathan Rempel
import "../../extension/interface/IProductCreators.sol";
interface IProductDataContract {
function tieredURI(
uint256 _tokenId,
uint256 _productId,
bool _isUnique
) external view returns (string memory);
function getProductCreators(
uint256 _productId
) external view returns (IProductCreators.CreatorData[] memory);
function baseURI() external view returns (string memory);
function setBaseURI(string memory _uri) external returns (string memory);
function productURI(uint256 _productId) external view returns (string memory);
function setProductURI(
uint256 _productId,
string calldata _uri
) external returns (string memory);
function tokenURI(uint256 _tokenId) external view returns (string memory);
function setTokenURI(
uint256 _tokenId,
string calldata _uri
) external returns (string memory);
function setTokenURIs(
uint256[] calldata _tokenIds,
string[] calldata _uris
) external returns (string[] memory);
function setTokenURIs(
uint256 _startTokenId,
uint256 _quantity,
string[] calldata _uri
) external returns (string[] memory);
function runBeforeExtensions(
uint256 _productId,
address _from,
address _to,
uint256 _startTokenId,
uint256 _quantity
) external;
function runAfterExtensions(
uint256 _productId,
address _from,
address _to,
uint256 _startTokenId,
uint256 _quantity
) external;
error ManagerRoleRequired();
error CalledFromTokenOnly();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @author Syky - Nathan Rempel
import "../eip/ERC721ArtistProduct.sol";
import "./interface/IProductArtistToken.sol";
import "../extension/ProductDataConnector.sol";
import "@thirdweb-dev/contracts/extension/ContractMetadata.sol";
import "@thirdweb-dev/contracts/extension/Multicall.sol";
import "@thirdweb-dev/contracts/extension/Ownable.sol";
import "@thirdweb-dev/contracts/extension/Royalty.sol";
import "@thirdweb-dev/contracts/extension/Permissions.sol";
import "@thirdweb-dev/contracts/lib/TWStrings.sol";
// ERC-5192 (lock/unlock a token);
// ERC-5192 (recommended for gas efficiency)
// event Locked(uint256 tokenId);
// event Unlocked(uint256 tokenId);
// ERC-4906 metadata update
// event MetadataUpdate(uint256 _tokenId);
// event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);
//means the metadata is permanently frozen
//event PermanentURI(string _value, uint256 indexed _id);
// To refresh a whole collection, emit _toTokenId with type(uint256).max
//event PermanentURI(string _value, uint256 indexed _id);
//ERC721Tradable - allow opensea proxy
contract ProductArtistToken is
IProductArtistToken,
ERC721ArtistProduct,
ContractMetadata,
ProductDataConnector,
Multicall,
Ownable,
Royalty,
Permissions
{
using TWStrings for uint256;
/*//////////////////////////////////////////////////////////////
Roles
//////////////////////////////////////////////////////////////*/
bytes32 public constant BURNING_ROLE = keccak256("BURNING_ROLE");
bytes32 public constant MINTING_ROLE = keccak256("MINTING_ROLE");
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
bytes32 public constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE");
/*//////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/
constructor(
address defaultAdmin_,
string memory name_,
string memory symbol_,
uint256 productOffset_,
address royaltyRecipient_,
uint128 royaltyBps_
) ERC721ArtistProduct(name_, symbol_, productOffset_) {
//sets the owner of the contract (important for OpenSea)
_setupOwner(defaultAdmin_);
//sets the admin of the contract to the owner
_setupRole(DEFAULT_ADMIN_ROLE, defaultAdmin_);
//enables transfers for all tokens
_setupRole(TRANSFER_ROLE, address(0));
_setupRole(MINTING_ROLE, address(0));
//sets the admin for various roles
_setRoleAdmin(BURNING_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(MINTING_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(TRANSFER_ROLE, DEFAULT_ADMIN_ROLE);
//sets the default royalty setup
_setupDefaultRoyaltyInfo(royaltyRecipient_, royaltyBps_);
}
/*//////////////////////////////////////////////////////////////
ERC165 Logic
//////////////////////////////////////////////////////////////*/
/// @dev See ERC165: https://eips.ethereum.org/EIPS/eip-165
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC721ArtistProduct, IERC165) returns (bool) {
return
interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x5b5e139f || // ERC165 Interface ID for ERC721Metadata
interfaceId == 0x49064906 || // ERC165 Interface ID for ERC4906 - MetadataUpdated
interfaceId == type(IERC2981).interfaceId; // ERC165 ID for ERC2981 - Royalties
}
/*//////////////////////////////////////////////////////////////
Overriden ERC721 logic
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns the metadata URI for an NFT.
*
* @param _tokenId The tokenId of an NFT.
*/
function tokenURI(
uint256 _tokenId
) public view override(ERC721ArtistProduct) returns (string memory) {
if (!_exists(_tokenId)) revert URIQueryForNonexistentToken();
uint256 productId = _getProductId(_tokenId);
bool isUnique = _getProductUniqueMetadata(productId);
return _tieredURI(_tokenId, productId, isUnique);
}
/*//////////////////////////////////////////////////////////////
Minting logic
//////////////////////////////////////////////////////////////*/
/**
* @notice Lets an authorized address mint an NFT to a recipient.
*
* @param _to The recipient of the NFT to mint.
* @param _productId The productId of the NFT to mint.
*/
function mintTo(address _to, uint256 _productId) external onlyMinter {
_safeMint(_to, _productId, 1, "");
}
/**
* @notice Lets an authorized address mint an NFT to a recipient.
*
* @param _to The recipient of the NFT to mint.
* @param _productId The productId of the NFT to mint.
* @param _data Data to be passed along during minting.
*/
function mintTo(
address _to,
uint256 _productId,
bytes memory _data
) external onlyMinter {
_safeMint(_to, _productId, 1, _data);
}
/**
* @notice Lets an authorized address mint an NFT to a recipient.
*
* @param _to The recipient of the NFT to mint.
* @param _productId The productId of the NFT to mint.
* @param _uri The full metadata uri for the token being minted.
* @param _data Data to be passed along during minting.
*/
function mintTo(
address _to,
uint256 _productId,
string calldata _uri,
bytes memory _data
) external onlyMinter {
(uint256 startTokenId, ) = _safeMint(_to, _productId, 1, _data);
_setTokenUri(startTokenId, _uri);
}
/**
* @notice Lets an authorized address mint multiple NFTs at once to a recipient.
*
* @param _to The recipient of the NFT to mint.
* @param _productId The productId of the NFT to mint.
* @param _quantity The number of NFTs to mint.
*/
function batchMintTo(
address _to,
uint256 _productId,
uint256 _quantity
) external onlyMinter {
_safeMint(_to, _productId, _quantity);
}
/**
* @notice Lets an authorized address mint multiple NFTs at once to a recipient.
*
* @param _to The recipient of the NFT to mint.
* @param _productId The productId of the NFT to mint.
* @param _quantity The number of NFTs to mint.
* @param _data Data to be passed along during minting.
*/
function batchMintTo(
address _to,
uint256 _productId,
uint256 _quantity,
bytes memory _data
) external onlyMinter {
_safeMint(_to, _productId, _quantity, _data);
}
/**
* @notice Lets an authorized address mint multiple NFTs at once to a recipient.
*
* @param _to The recipient of the NFT to mint.
* @param _productId The productId of the NFT to mint.
* @param _quantity The number of NFTs to mint.
* @param _uris The custom token URIs for each NFT in the batch
* @param _data Data to be passed along during minting.
*/
function batchMintTo(
address _to,
uint256 _productId,
uint256 _quantity,
string[] calldata _uris,
bytes memory _data
) external onlyMinter {
(uint256 startTokenId, ) = _safeMint(_to, _productId, _quantity, _data);
_setTokenUris(startTokenId, _quantity, _uris);
}
/**
* @notice Lets an owner or approved operator burn the NFT of the given tokenId.
* @dev ERC721A"s `_burn(uint256,bool)` internally checks for token approvals.
*
* @param _tokenId The tokenId of the NFT to burn.
*/
function burn(uint256 _tokenId) external {
_burn(_tokenId, true);
}
/*//////////////////////////////////////////////////////////////
Public getters
//////////////////////////////////////////////////////////////*/
/// @notice The total minted tokens among all products
function totalMinted() external view returns (uint256) {
return _mintCounter;
}
/// @notice The total burned tokens among all products
function totalBurned() external view returns (uint256) {
return _burnCounter;
}
/// @notice The owner data for a given address
function getOwnerData(address _owner) external view returns (AddressData memory) {
return _getAddressData(_owner);
}
/// @notice The token data for a given token id
function getTokenData(
uint256 _tokenId
) external view returns (TokenOwnership memory) {
return _ownershipOf(_tokenId);
}
/// @notice The product offset for token id formation
function getProductOffset() external view returns (uint256) {
return _getProductOffset();
}
/// @notice The product details for a given product id
function getProduct(uint256 _productId) external view returns (ProductData memory) {
return _getProduct(_productId);
}
/// @notice The product details for a given product id
function getProductArtist(uint256 _productId) external view returns (address) {
return _getArtist(_productId);
}
/// @notice The product details for a given token id
function getProductForToken(
uint256 _tokenId
) external view returns (ProductData memory) {
return _getProduct(_getProductId(_tokenId));
}
/// @notice The product details for a given token id
function getArtistForToken(uint256 _tokenId) external view returns (address) {
return _getArtist(_getProductId(_tokenId));
}
/// @notice Returns whether a given address is the owner, or approved to transfer an NFT.
function isApprovedOrOwner(
address _operator,
uint256 _tokenId
) external view returns (bool isApprovedOrOwnerOf) {
address owner = ownerOf(_tokenId);
isApprovedOrOwnerOf = (_operator == owner ||
isApprovedForAll(owner, _operator) ||
getApproved(_tokenId) == _operator);
}
/*//////////////////////////////////////////////////////////////
Admin setters
//////////////////////////////////////////////////////////////*/
function setProductURI(
uint256 _productId,
string calldata _uri
) external override onlyManager {
_setProductUri(_productId, _uri);
if (_getProductQuantity(_productId) != 0) {
emit BatchMetadataUpdate(
_getMinTokenId(_productId),
_getMaxTokenId(_productId)
);
}
}
function setOwnerAux(address owner, uint64 aux) external onlyManager {
_setAux(owner, aux);
}
function setProductIdOffset(uint256 _offset) external onlyManager {
_setProductOffset(_offset);
}
function setProduct(uint256 _productId, uint64 _quantity) external onlyManager {
_setProductQuantity(_productId, _quantity);
}
function setProductArtist(uint256 _productId, address _artist) external onlyManager {
_setArtist(_productId, _artist);
}
function setProductUniqueMetadata(
uint256 _productId,
bool _isUnique
) external onlyManager {
_setProductUniqueMetadata(_productId, _isUnique);
}
function restrictProductMinting(
uint256 _productId,
bool _toRestrict
) external onlyManager {
_setProductLocked(_productId, _toRestrict);
}
function restrictProductTransfers(
uint256 _productId,
bool _toRestrict
) external onlyManager {
_setProductFrozen(_productId, _toRestrict);
}
function restrictProductBurning(
uint256 _productId,
bool _toRestrict
) external onlyManager {
_setProductBurnable(_productId, !_toRestrict);
}
function restrictMinting(bool _toRestrict) external onlyManager {
if (_toRestrict) {
_revokeRole(MINTING_ROLE, address(0));
} else {
_setupRole(MINTING_ROLE, address(0));
}
emit GlobalMintingRestricted(_toRestrict);
}
function restrictTransfers(bool _toRestrict) external onlyManager {
if (_toRestrict) {
_revokeRole(TRANSFER_ROLE, address(0));
} else {
_setupRole(TRANSFER_ROLE, address(0));
}
emit GlobalTransfersRestricted(_toRestrict);
}
function restrictBurning(bool _toRestrict) external onlyManager {
if (_toRestrict) {
_revokeRole(BURNING_ROLE, address(0));
} else {
_setupRole(BURNING_ROLE, address(0));
}
emit GlobalBurningRestricted(_toRestrict);
}
/*//////////////////////////////////////////////////////////////
ERC-721 overrides
//////////////////////////////////////////////////////////////*/
/**
* @dev See {ERC721A-_beforeTokenTransfers}.
* @notice Product levels are restricted prior to hook
* This means even if specific users has a transfer or burn role
* The product level setting will override that functionality
*/
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal override(ERC721ArtistProduct) {
// Check transfer restrictions
if (from != address(0) && to != address(0)) {
//transfers restricted globally, unless from and to both have transfer role
if (
!hasRole(TRANSFER_ROLE, address(0)) &&
!(hasRole(TRANSFER_ROLE, from) && hasRole(TRANSFER_ROLE, to))
) revert GlobalTransfersDisabled();
}
// Check burn restrictions
if (from != address(0) && to == address(0)) {
//burning restricted globally, unless from has burn permission
if (!hasRoleWithSwitch(BURNING_ROLE, from)) revert GlobalBurningDisabled();
}
// Check mint restrictions
if (from == address(0) && to != address(0)) {
//minting restricted globally, regardless of operator roles
if (!hasRole(MINTING_ROLE, address(0))) revert GlobalMintingDisabled();
}
_runBeforeExtensions(
_getProductId(startTokenId),
from,
to,
startTokenId,
quantity
);
}
/// @dev See {ERC721A-_afterTokenTransfers}.
function _afterTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal override(ERC721ArtistProduct) {
_runAfterExtensions(
_getProductId(startTokenId),
from,
to,
startTokenId,
quantity
);
}
/*//////////////////////////////////////////////////////////////
Modifiers
//////////////////////////////////////////////////////////////*/
/// @dev Modifier that checks if an account has admin or product role; reverts otherwise.
modifier onlyManager() {
_checkManagerAdmin();
_;
}
/// @dev Modifier that checks if an account has admin or minter role; reverts otherwise.
modifier onlyMinter() {
_checkMintingAdmin();
_;
}
/*//////////////////////////////////////////////////////////////
Module admin checks
//////////////////////////////////////////////////////////////*/
/// @dev Function that checks if an account has admin or product role; reverts otherwise.
function _checkManagerAdmin() internal view {
if (
!hasRole(DEFAULT_ADMIN_ROLE, msg.sender) && !hasRole(MANAGER_ROLE, msg.sender)
) {
revert ManagerRoleRequired();
}
}
/// @dev Function that checks if an account has admin or minter role; reverts otherwise.
function _checkMintingAdmin() internal view {
if (
!hasRole(DEFAULT_ADMIN_ROLE, msg.sender) && !hasRole(MINTING_ROLE, msg.sender)
) {
revert MintingRoleRequired();
}
}
/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view override returns (bool) {
return
hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(MANAGER_ROLE, msg.sender);
}
/// @dev Returns whether contract metadata can be set in the given execution context.
function _canSetData() internal view override returns (bool) {
return
hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(MANAGER_ROLE, msg.sender);
}
/// @dev Returns whether owner can be set in the given execution context.
function _canSetOwner() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
/// @dev Returns whether royalty info can be set in the given execution context.
function _canSetRoyaltyInfo() internal view override returns (bool) {
return hasRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @author Syky - Nathan Rempel
import "./interface/IERC721ArtistProduct.sol";
import "@thirdweb-dev/contracts/eip/ERC165.sol";
import "@thirdweb-dev/contracts/eip/interface/IERC721Receiver.sol";
import "@thirdweb-dev/contracts/external-deps/openzeppelin/utils/Context.sol";
import "@thirdweb-dev/contracts/lib/TWStrings.sol";
import "@thirdweb-dev/contracts/lib/TWAddress.sol";
/**
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
* the Metadata extension, along with ERC-1155 like functionality of "Product" with mint numbers.
* Built to optimize for lower gas during batch mints.
*
* Assumes that an owner cannot have more than 2**64 - 1 (max value of uint64) of supply.
*
* Assumes that the maximum token id cannot exceed 2**256 - 1 (max value of uint256).
*/
contract ERC721ArtistProduct is Context, ERC165, IERC721ArtistProduct {
using TWAddress for address;
using TWStrings for uint256;
// The product offset factor N to represent product
// Example: offset of 6 means that token ids will be:
// product 1 will have the token ids 1000001, 1000002, etc
// product 69 will have the token ids 69000001, 69000002, etc
// The offset provides 10 ** N - 1 possible supply
//enables 9 assets to be minted per product
uint256 constant PRODUCT_OFFSET_MIN = 1;
//enables 1e19-1 assets to be minted, fitting uint64 max
uint256 constant PRODUCT_OFFSET_MAX = 19;
//stores the 10**N value to save math operations
uint256 private _productOffset;
// The number of tokens minted.
uint256 internal _mintCounter;
// The number of tokens burned.
uint256 internal _burnCounter;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to ownership details
// An empty struct value does not necessarily mean the token is unowned. See _ownershipOf implementation for details.
mapping(uint256 => TokenOwnership) internal _ownerships;
//Mapping from product ID to product details
mapping(uint256 => ProductData) private _productData;
//Mapping from product ID to artist address
mapping(uint256 => address) private _productArtist;
//Mapping owner address to product balance
mapping(address => mapping(uint256 => uint256)) private _productBalance;
// Mapping owner address to address data
mapping(address => AddressData) private _addressData;
// Mapping from token ID to approved address
mapping(uint256 => address) private _tokenApprovals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) private _operatorApprovals;
constructor(string memory name_, string memory symbol_, uint256 offset_) {
_name = name_;
_symbol = symbol_;
_setProductOffset(offset_);
}
/**
* @dev Burned tokens are calculated here, use _totalMinted() if you want to count just minted tokens.
*/
function totalSupply() public view override returns (uint256) {
// Counter underflow is impossible as _burnCounter cannot be incremented
// more than _mintCounter times
unchecked {
return _mintCounter - _burnCounter;
}
}
/**
* @dev Burned tokens are calculated here, use _getProductBurned() if you want to count just minted tokens.
*/
function productSupply(uint256 productId) public view returns (uint256) {
// Counter underflow is impossible as numberBurned cannot be incremented
// more than numberMinted times
ProductData memory product = _productData[productId];
unchecked {
return product.numberMinted - product.numberBurned;
}
}
/**
* Returns the total amount of tokens minted in the contract.
*/
function _totalMinted() internal view returns (uint256) {
return _mintCounter;
}
/**
* Returns the total amount of tokens burned in the contract.
*/
function _totalBurned() internal view returns (uint256) {
return _burnCounter;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
/**
* @dev See {IERC721-balanceOf}.
*/
function productBalanceOf(
address owner,
uint256 productId
) public view returns (uint256) {
if (owner == address(0)) revert BalanceQueryForZeroAddress();
return _productBalance[owner][productId];
}
/**
* @dev See {IERC721-balanceOf}.
*/
function balanceOf(address owner) public view override returns (uint256) {
if (owner == address(0)) revert BalanceQueryForZeroAddress();
return uint256(_addressData[owner].balance);
}
/**
* Returns the number of tokens minted by `owner`.
*/
function _getAddressData(address owner) internal view returns (AddressData memory) {
return _addressData[owner];
}
/**
* Returns the number of tokens minted by `owner`.
*/
function _numberMinted(address owner) internal view returns (uint256) {
return uint256(_addressData[owner].numberMinted);
}
/**
* Returns the number of tokens burned by or on behalf of `owner`.
*/
function _numberBurned(address owner) internal view returns (uint256) {
return uint256(_addressData[owner].numberBurned);
}
/**
* Returns the auxillary data for `owner`. (e.g. number of whitelist mint slots used).
*/
function _getAux(address owner) internal view returns (uint64) {
return _addressData[owner].aux;
}
/**
* Sets the auxillary data for `owner`. (e.g. number of whitelist mint slots used).
* If there are multiple variables, please pack them into a uint64.
*/
function _setAux(address owner, uint64 aux) internal {
_addressData[owner].aux = aux;
}
/**
* Returns the auxillary data for `owner`. (e.g. number of whitelist mint slots used).
*/
function _getArtist(uint256 _productId) internal view returns (address) {
return _productArtist[_productId];
}
/**
* Sets the auxillary data for `owner`. (e.g. number of whitelist mint slots used).
* If there are multiple variables, please pack them into a uint64.
*/
function _setArtist(uint256 _productId, address _artist) internal {
_productArtist[_productId] = _artist;
}
/**
* Gets the product configuration
*/
function _getProduct(uint256 _productId) internal view returns (ProductData memory) {
return _productData[_productId];
}
/**
* Gets the product offset
*/
function _getProductOffset() internal view returns (uint256 _offset) {
uint256 offset = _productOffset;
while (offset % 10 == 0) {
offset /= 10;
++_offset;
}
}
/**
* Sets the product offset if prior to minting
*/
function _setProductOffset(uint256 _offset) internal {
if (_mintCounter > 0) revert ProductOffsetAfterMinting();
if (_offset < PRODUCT_OFFSET_MIN || _offset > PRODUCT_OFFSET_MAX) {
revert ProductOffsetOutsideRange(
_offset,
PRODUCT_OFFSET_MIN,
PRODUCT_OFFSET_MAX
);
}
_productOffset = 10 ** _offset;
}
/**
* Gets the maximum productId based on the product offset
*/
function _getProductMaxId() internal view returns (uint256) {
return type(uint256).max / _productOffset - _productOffset;
}
/**
* Gets the maximum product supply based on the product offset
*/
function _getProductMaxQuantity() internal view returns (uint64) {
return uint64(_productOffset - 1);
}
/**
* Gets the product token supply
*/
function _getProductQuantity(uint256 _productId) internal view returns (uint64) {
return _productData[_productId].quantity;
}
/**
* Creates a product or modifies the supply
*/
function _setProductQuantity(uint256 _productId, uint64 _quantity) internal {
if (_getProductMaxId() < _productId)
revert ProductIdExceedsOffsetMaximum(_productId, _getProductMaxId());
if (_getProductMaxQuantity() < _quantity)
revert ProductQuantityExceedsOffsetMaximum(
_quantity,
_getProductMaxQuantity()
);
ProductData storage product = _productData[_productId];
uint64 oldQuantity = product.quantity;
if (_quantity < product.numberMinted) revert ProductMintsExceedsQuantity();
product.quantity = _quantity;
emit ProductQuantityUpdated(_productId, oldQuantity, _quantity);
}
/**
* Gets the product tokens minted
*/
function _getProductMinted(uint256 _productId) internal view returns (uint64) {
return _productData[_productId].numberMinted;
}
/**
* Gets the product tokens burn
*/
function _getProductBurned(uint256 _productId) internal view returns (uint64) {
return _productData[_productId].numberBurned;
}
/**
* Gets the product locked status (minting)
*/
function _getProductLocked(uint256 _productId) internal view returns (bool) {
return _productData[_productId].locked;
}
/**
* Sets the locked condition for a product (minting)
*/
function _setProductLocked(uint256 _productId, bool _locked) internal {
ProductData storage product = _productData[_productId];
if (product.quantity == 0) revert ProductNotConfigured();
product.locked = _locked;
emit ProductMintingRestricted(_productId, _locked);
}
/**
* Gets the product frozen status (transfers)
*/
function _getProductFrozen(uint256 _productId) internal view returns (bool) {
return _productData[_productId].frozen;
}
/**
* Sets the frozen condition for a product (transfers)
*/
function _setProductFrozen(uint256 _productId, bool _frozen) internal {
ProductData storage product = _productData[_productId];
if (product.quantity == 0) revert ProductNotConfigured();
product.frozen = _frozen;
emit ProductTransfersRestricted(_productId, _frozen);
}
/**
* Gets the product burnable status (burning)
*/
function _getProductBurnable(uint256 _productId) internal view returns (bool) {
return _productData[_productId].burnable;
}
/**
* Sets the burnable condition for a product (burning)
*/
function _setProductBurnable(uint256 _productId, bool _burnable) internal {
ProductData storage product = _productData[_productId];
if (product.quantity == 0) revert ProductNotConfigured();
product.burnable = _burnable;
emit ProductBurningRestricted(_productId, !_burnable);
}
/**
* Gets the product burnable status (burning)
*/
function _getProductUniqueMetadata(uint256 _productId) internal view returns (bool) {
return _productData[_productId].uniqueMetadata;
}
/**
* Sets the burnable condition for a product (burning)
*/
function _setProductUniqueMetadata(uint256 _productId, bool _isUnique) internal {
ProductData storage product = _productData[_productId];
if (product.quantity == 0) revert ProductNotConfigured();
product.uniqueMetadata = _isUnique;
emit ProductUniqueTokenMetadata(_productId, _isUnique);
}
/**
* Gets the product id for a token
*/
function _getProductId(uint256 _tokenId) internal view returns (uint256 _productId) {
if (_tokenId < _productOffset) revert ProductQueryForImpossibleToken();
_productId = _tokenId / _productOffset;
if (_productData[_productId].quantity == 0)
revert ProductQueryForNonexistantToken();
}
/**
* Gets the next token id for product given a quantity
* Checks that range of tokens wont exceed supply
*/
function _getNextTokenIds(
uint256 _productId,
uint256 _quantity
) internal view returns (uint256 startTokenId, uint256 finalTokenId) {
ProductData memory product = _productData[_productId];
if (product.quantity == 0) revert ProductNotConfigured();
if (product.quantity < product.numberMinted + _quantity)
revert ProductMintsExceedsQuantity();
startTokenId = _productId * _productOffset + product.numberMinted + 1;
finalTokenId = startTokenId + _quantity - 1;
}
/**
* Gets the previously minted token id for a product
*/
function _getPrevTokenId(uint256 _productId) internal view returns (uint256) {
return _productId * _productOffset + _getProductMinted(_productId);
}
/**
* Gets the first possible token id for a product
*/
function _getMinTokenId(uint256 _productId) internal view returns (uint256) {
return _productId * _productOffset + 1;
}
/**
* Gets the last possible token id for a product
*/
function _getMaxTokenId(uint256 _productId) internal view returns (uint256) {
return _productId * _productOffset + _getProductQuantity(_productId) + 1;
}
/**
* Gas spent here starts off proportional to the maximum mint batch size.
* It gradually moves to O(1) as tokens get transferred around in the collection over time.
*/
function _ownershipOf(uint256 tokenId) internal view returns (TokenOwnership memory) {
uint256 curr = tokenId;
//this will revert if the product is unconfigured
uint256 productId = _getProductId(tokenId);
uint256 minTokenId = _getMinTokenId(productId);
unchecked {
if (minTokenId <= curr)
if (curr <= _getPrevTokenId(productId)) {
TokenOwnership memory ownership = _ownerships[curr];
if (!ownership.burned) {
if (ownership.addr != address(0)) {
return ownership;
}
// Invariant:
// There will always be an ownership that has an address and is not burned
// before an ownership that does not have an address and is not burned.
// Hence, curr will not underflow.
while (minTokenId < curr) {
curr--;
ownership = _ownerships[curr];
if (ownership.addr != address(0)) {
return ownership;
}
}
}
}
}
revert OwnerQueryForNonexistentToken();
}
/**
* @dev See {IERC721-ownerOf}.
*/
function ownerOf(uint256 tokenId) public view override returns (address) {
return _ownershipOf(tokenId).addr;
}
/**
* @dev See {IERC721Metadata-name}.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev See {IERC721Metadata-symbol}.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev See {IERC721Metadata-tokenURI}.
*/
function tokenURI(
uint256 tokenId
) public view virtual override returns (string memory) {
if (!_exists(tokenId)) revert URIQueryForNonexistentToken();
string memory baseURI = _baseURI();
return
bytes(baseURI).length != 0
? string(abi.encodePacked(baseURI, tokenId.toString()))
: "";
}
/**
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty
* by default, can be overriden in child contracts.
*/
function _baseURI() internal view virtual returns (string memory) {
return "";
}
/**
* @dev See {IERC721-approve}.
*/
function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721ArtistProduct.ownerOf(tokenId);
if (to == owner) revert ApprovalToCurrentOwner();
if (_msgSender() != owner)
if (!isApprovedForAll(owner, _msgSender())) {
revert ApprovalCallerNotOwnerNorApproved();
}
_approve(to, tokenId, owner);
}
/**
* @dev See {IERC721-getApproved}.
*/
function getApproved(uint256 tokenId) public view override returns (address) {
if (!_exists(tokenId)) revert ApprovalQueryForNonexistentToken();
return _tokenApprovals[tokenId];
}
/**
* @dev See {IERC721-setApprovalForAll}.
*/
function setApprovalForAll(address operator, bool approved) public virtual override {
if (operator == _msgSender()) revert ApproveToCaller();
_operatorApprovals[_msgSender()][operator] = approved;
emit ApprovalForAll(_msgSender(), operator, approved);
}
/**
* @dev See {IERC721-isApprovedForAll}.
*/
function isApprovedForAll(
address owner,
address operator
) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
/**
* @dev See {IERC721-transferFrom}.
*/
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory _data
) public virtual override {
_transfer(from, to, tokenId);
if (to.isContract())
if (!_checkContractOnERC721Received(from, to, tokenId, _data)) {
revert TransferToNonERC721ReceiverImplementer();
}
}
/**
* @dev Returns whether `tokenId` exists.
*
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
*
* Tokens start existing when they are minted (`_mint`),
*/
function _exists(uint256 tokenId) internal view returns (bool) {
uint256 productId = _getProductId(tokenId);
return
_getMinTokenId(productId) <= tokenId &&
tokenId <= _getPrevTokenId(productId) &&
!_ownerships[tokenId].burned;
}
/**
* @dev Equivalent to `_safeMint(to, productId, quantity, '')`.
*/
function _safeMint(
address to,
uint256 productId,
uint256 quantity
) internal returns (uint256 startTokenId, uint256 finalTokenId) {
return _safeMint(to, productId, quantity, "");
}
/**
* @dev Safely mints `quantity` tokens and transfers them to `to`.
*
* Requirements:
*
* - If `to` refers to a smart contract, it must implement
* {IERC721Receiver-onERC721Received}, which is called for each safe transfer.
* - `quantity` must be greater than 0.
*
* Emits a {Transfer} event.
*/
function _safeMint(
address to,
uint256 productId,
uint256 quantity,
bytes memory _data
) internal returns (uint256 startTokenId, uint256 finalTokenId) {
uint256 startCount = _mintCounter;
if (to == address(0)) revert MintToZeroAddress();
if (quantity == 0) revert MintZeroQuantity();
if (_getProductLocked(productId)) revert ProductMintingDisabled();
//this validates against that total minted wont exceed supply
(startTokenId, finalTokenId) = _getNextTokenIds(productId, quantity);
_beforeTokenTransfers(address(0), to, startTokenId, quantity);
// Overflows are incredibly unrealistic.
// balance or numberMinted overflow if current value of either + quantity > 1.8e19 (2**64) - 1
// updatedIndex overflows if _currentIndex + quantity > 1.2e77 (2**256) - 1
unchecked {
_addressData[to].balance += uint64(quantity);
_addressData[to].numberMinted += uint64(quantity);
_ownerships[startTokenId].addr = to;
_ownerships[startTokenId].startTimestamp = uint64(block.timestamp);
_productBalance[to][productId] += quantity;
uint256 currTokenId = startTokenId;
//increment final token to save on comparison gas in the loops
finalTokenId++;
if (to.isContract()) {
do {
if (_productArtist[productId] != address(0)) {
emit Transfer(address(0), _productArtist[productId], currTokenId);
emit Transfer(_productArtist[productId], to, currTokenId);
} else {
emit Transfer(address(0), to, currTokenId);
}
if (
!_checkContractOnERC721Received(
address(0),
to,
currTokenId++,
_data
)
) {
revert TransferToNonERC721ReceiverImplementer();
}
} while (currTokenId < finalTokenId);
// Reentrancy protection
if (_mintCounter != startCount) revert();
} else {
do {
if (_productArtist[productId] != address(0)) {
emit Transfer(address(0), _productArtist[productId], currTokenId);
emit Transfer(_productArtist[productId], to, currTokenId);
} else {
emit Transfer(address(0), to, currTokenId);
}
++currTokenId;
} while (currTokenId < finalTokenId);
}
_mintCounter += quantity;
_productData[productId].numberMinted += uint64(quantity);
}
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}
/**
* @dev Mints `quantity` tokens and transfers them to `to`.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `quantity` must be greater than 0.
*
* Emits a {Transfer} event.
*/
function _mint(
address to,
uint256 productId,
uint256 quantity
) internal returns (uint256 startTokenId, uint256 finalTokenId) {
if (to == address(0)) revert MintToZeroAddress();
if (quantity == 0) revert MintZeroQuantity();
if (_getProductLocked(productId)) revert ProductMintingDisabled();
//this validates against that total minted wont exceed supply
(startTokenId, finalTokenId) = _getNextTokenIds(productId, quantity);
_beforeTokenTransfers(address(0), to, startTokenId, quantity);
// Overflows are incredibly unrealistic.
// balance or numberMinted overflow if current value of either + quantity > 1.8e19 (2**64) - 1
// updatedIndex overflows if _currentIndex + quantity > 1.2e77 (2**256) - 1
unchecked {
_addressData[to].balance += uint64(quantity);
_addressData[to].numberMinted += uint64(quantity);
_ownerships[startTokenId].addr = to;
_ownerships[startTokenId].startTimestamp = uint64(block.timestamp);
_productBalance[to][productId] += quantity;
uint256 currTokenId = startTokenId;
//increment final token to save on comparison gas in the loops
finalTokenId++;
do {
if (_productArtist[productId] != address(0)) {
emit Transfer(address(0), _productArtist[productId], currTokenId);
emit Transfer(_productArtist[productId], to, currTokenId);
} else {
emit Transfer(address(0), to, currTokenId);
}
++currTokenId;
} while (currTokenId < finalTokenId);
_mintCounter += quantity;
_productData[productId].numberMinted += uint64(quantity);
}
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* 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) private {
TokenOwnership memory prevOwnership = _ownershipOf(tokenId);
if (prevOwnership.addr != from) revert TransferFromIncorrectOwner();
bool isApprovedOrOwner = (_msgSender() == from ||
isApprovedForAll(from, _msgSender()) ||
getApproved(tokenId) == _msgSender());
if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved();
if (to == address(0)) revert TransferToZeroAddress();
uint256 productId = _getProductId(tokenId);
if (_getProductFrozen(productId)) revert ProductTransfersDisabled();
_beforeTokenTransfers(from, to, tokenId, 1);
// Clear approvals from the previous owner
_approve(address(0), tokenId, from);
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as tokenId would have to be 2**256.
unchecked {
_addressData[from].balance -= 1;
_addressData[to].balance += 1;
_productBalance[from][productId] -= 1;
_productBalance[to][productId] += 1;
TokenOwnership storage currSlot = _ownerships[tokenId];
currSlot.addr = to;
currSlot.startTimestamp = uint64(block.timestamp);
// If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it.
// Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls.
TokenOwnership storage nextSlot = _ownerships[tokenId + 1];
if (nextSlot.addr == address(0)) {
// This will suffice for checking _exists(nextTokenId),
// as a burned slot cannot contain the zero address.
if (tokenId != _getPrevTokenId(productId)) {
// This confirms the current token is not the last token in the product
nextSlot.addr = from;
nextSlot.startTimestamp = prevOwnership.startTimestamp;
}
}
}
emit Transfer(from, to, tokenId);
_afterTokenTransfers(from, to, tokenId, 1);
}
/**
* @dev Equivalent to `_burn(tokenId, false)`.
*/
function _burn(uint256 tokenId) internal virtual {
_burn(tokenId, false);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId, bool approvalCheck) internal virtual {
TokenOwnership memory prevOwnership = _ownershipOf(tokenId);
address from = prevOwnership.addr;
if (approvalCheck) {
bool isApprovedOrOwner = (_msgSender() == from ||
isApprovedForAll(from, _msgSender()) ||
getApproved(tokenId) == _msgSender());
if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved();
}
uint256 productId = _getProductId(tokenId);
if (!_getProductBurnable(productId)) revert ProductBurningDisabled();
_beforeTokenTransfers(from, address(0), tokenId, 1);
// Clear approvals from the previous owner
_approve(address(0), tokenId, from);
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as tokenId would have to be 2**256.
unchecked {
AddressData storage addressData = _addressData[from];
addressData.balance -= 1;
addressData.numberBurned += 1;
// Keep track of who burned the token, and the timestamp of burning.
TokenOwnership storage currSlot = _ownerships[tokenId];
currSlot.addr = from;
currSlot.startTimestamp = uint64(block.timestamp);
currSlot.burned = true;
_productBalance[from][productId] -= 1;
// If the ownership slot of tokenId+1 is not explicitly set, that means the burn initiator owns it.
// Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls.
TokenOwnership storage nextSlot = _ownerships[tokenId + 1];
if (nextSlot.addr == address(0)) {
// This will suffice for checking _exists(nextTokenId),
// as a burned slot cannot contain the zero address.
if (tokenId != _getPrevTokenId(productId)) {
// This confirms the current token is not the last token in the product
nextSlot.addr = from;
nextSlot.startTimestamp = prevOwnership.startTimestamp;
}
}
}
emit Transfer(from, address(0), tokenId);
_afterTokenTransfers(from, address(0), tokenId, 1);
// Overflow not possible, as _burnCounter cannot be exceed _mintCounter times.
unchecked {
_burnCounter++;
_productData[productId].numberBurned++;
}
}
/**
* @dev Approve `to` to operate on `tokenId`
*
* Emits a {Approval} event.
*/
function _approve(address to, uint256 tokenId, address owner) private {
_tokenApprovals[tokenId] = to;
emit Approval(owner, to, tokenId);
}
/**
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target contract.
*
* @param from address representing the previous owner of the given token ID
* @param to target address that will receive the tokens
* @param tokenId uint256 ID of the token to be transferred
* @param _data bytes optional data to send along with the call
* @return bool whether the call correctly returned the expected magic value
*/
function _checkContractOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory _data
) private returns (bool) {
try
IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data)
returns (bytes4 retval) {
return retval == IERC721Receiver(to).onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert TransferToNonERC721ReceiverImplementer();
} else {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
}
/**
* @dev Hook that is called before a set of serially-ordered token ids are about to be transferred. This includes minting.
* And also called before burning one token.
*
* startTokenId - the first token id to be transferred
* quantity - the amount to be transferred
*
* 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, `tokenId` will be burned by `from`.
* - `from` and `to` are never both zero.
*/
function _beforeTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual {}
/**
* @dev Hook that is called after a set of serially-ordered token ids have been transferred. This includes
* minting.
* And also called after one token has been burned.
*
* startTokenId - the first token id to be transferred
* quantity - the amount to be transferred
*
* Calling conditions:
*
* - When `from` and `to` are both non-zero, `from`'s `tokenId` has been
* transferred to `to`.
* - When `from` is zero, `tokenId` has been minted for `to`.
* - When `to` is zero, `tokenId` has been burned by `from`.
* - `from` and `to` are never both zero.
*/
function _afterTokenTransfers(
address from,
address to,
uint256 startTokenId,
uint256 quantity
) internal virtual {}
}
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.0;
import "@thirdweb-dev/contracts/eip/interface/IERC165.sol";
import "@thirdweb-dev/contracts/eip/interface/IERC721.sol";
/// @title EIP-721 Metadata Update Extension
interface IERC4906 is IERC165, IERC721 {
/// @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: MIT
pragma solidity ^0.8.20;
/// @author Syky - Nathan Rempel
import "@thirdweb-dev/contracts/eip/interface/IERC721A.sol";
interface IERC721ArtistProduct is IERC721A {
/*//////////////////////////////////////////////////////////////
Structs
//////////////////////////////////////////////////////////////*/
// Compiler will pack this into a single 256bit word.
struct ProductData {
// Keeps track of quantity, Realistically, 2**64-1 is more than enough.
uint64 quantity;
// Keeps track of mint count with minimal overhead for tokenomics.
uint64 numberMinted;
// Keeps track of burn count with minimal overhead for tokenomics.
uint64 numberBurned;
//Toggles if mints are disabled for this product
//Defaults to enabled (locked = false)
bool locked;
//Toggles if transfers are disabled for this product
//Defaults to enabled (frozen = false)
bool frozen;
//Toggles if burns are enabled for this product
//Defaults to disabled (burnable = false)
bool burnable;
//Toggles if tokens within the product have unique metadata
//Defaults to non-unique (uniqueMetadata = false)
bool uniqueMetadata;
}
/*//////////////////////////////////////////////////////////////
Methods
//////////////////////////////////////////////////////////////*/
/**
* @dev Returns the total amount of tokens stored by the contract product.
*
* Burned tokens are calculated here, use `_getProductMinted()` if you want to count just minted tokens.
*/
function productSupply(uint256 productId) external view returns (uint256);
function productBalanceOf(
address owner,
uint256 productId
) external view returns (uint256);
/*//////////////////////////////////////////////////////////////
Events
//////////////////////////////////////////////////////////////*/
/// @dev Emitted when a product is created or has its supply modified
event ProductQuantityUpdated(
uint256 indexed productId,
uint64 indexed oldQuantity,
uint64 indexed newQuantity
);
/// @dev Emitted when specific product mints are enabled or disabled
event ProductMintingRestricted(uint256 indexed productId, bool indexed isRestricted);
/// @dev Emitted when specific product transfers are enabled or disabled
event ProductTransfersRestricted(
uint256 indexed productId,
bool indexed isRestricted
);
/// @dev Emitted when specific product burns are enabled or disabled
event ProductBurningRestricted(uint256 indexed productId, bool indexed isRestricted);
/// @dev Emitted when specific products toggle unique token metadata within product
event ProductUniqueTokenMetadata(
uint256 indexed productId,
bool indexed isUniquePerToken
);
/*//////////////////////////////////////////////////////////////
Errors
//////////////////////////////////////////////////////////////*/
/// @dev The product scale must be set prior to minting.
error ProductOffsetAfterMinting();
/// @dev The product scale must be set prior to minting.
error ProductOffsetOutsideRange(uint256 requested, uint256 minumum, uint256 maximum);
/// @dev The productId must not exceed the maximum for the scale.
error ProductIdExceedsOffsetMaximum(uint256 requested, uint256 maximum);
/// @dev The product quantity must not exceed the maximum for the scale.
error ProductQuantityExceedsOffsetMaximum(uint256 requested, uint256 maximum);
/// @dev The product quantity must equal or exceed the number minted.
error ProductMintsExceedsQuantity();
/// @dev The product must be configured by setting a quantity.
error ProductNotConfigured();
/// @dev The token ID is impossible given the product scale.
error ProductQueryForImpossibleToken();
/// @dev The token ID cannot exist given configured product
error ProductQueryForNonexistantToken();
/// @dev Minting is disabled for this product
error ProductMintingDisabled();
/// @dev Burning are disabled for this product
error ProductBurningDisabled();
/// @dev Transfers are disabled for this product
error ProductTransfersDisabled();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @author Syky - Nathan Rempel
interface IProductCreators {
/*//////////////////////////////////////////////////////////////
Structs
//////////////////////////////////////////////////////////////*/
struct ProductCreatorData {
uint256 index;
mapping(uint256 => CreatorData) creators;
}
struct CreatorData {
address creatorAddress;
string creatorInfo;
}
/*//////////////////////////////////////////////////////////////
Methods
//////////////////////////////////////////////////////////////*/
/// @dev Returns the base URI of the contract.
function getProductCreators(
uint256 _productId
) external view returns (CreatorData[] memory);
/**
* @dev Sets base URI for the token metadata of the contract.
* Only module admin can call this function.
*/
function setProductCreators(
uint256 _productId,
CreatorData[] calldata _creators
) external;
/*//////////////////////////////////////////////////////////////
Events
//////////////////////////////////////////////////////////////*/
/// @dev Emitted when the base URI is updated.
event ProductCreatorsUpdated(uint256 indexed productId, CreatorData[] creators);
/*//////////////////////////////////////////////////////////////
Errors
//////////////////////////////////////////////////////////////*/
/// @dev Module admin is required
error ProductCreatorNotAuthorized();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @author Syky - Nathan Rempel
import "./IProductCreators.sol";
import "../../eip/interface/IERC4906.sol";
interface IProductDataConnector is IERC4906 {
/*//////////////////////////////////////////////////////////////
Methods
//////////////////////////////////////////////////////////////*/
function setDataContract(address dataContract_) external;
function getDataContract() external view returns (address);
function getProductCreators(
uint256 _productId
) external view returns (IProductCreators.CreatorData[] memory);
function setBaseURI(string calldata _uri) external;
function setTokenURI(uint256 _tokenId, string calldata _uri) external;
function setTokenURIs(
uint256 _tokenId,
uint256 _quantity,
string[] calldata _uris
) external;
function setTokenURIs(uint256[] calldata _tokenIds, string[] calldata _uris) external;
function setProductURI(uint256 _productId, string calldata _uri) external;
/// @dev Emitted when the base URI is updated.
event BaseURIUpdated(string prevURI, string newURI);
/// @dev Emitted when the token URI is updated.
event TokenURIUpdated(uint256 indexed tokenId, string prevURI, string newURI);
/// @dev Emitted when the product URI is updated.
event ProductURIUpdated(uint256 indexed tokenId, string prevURI, string newURI);
error ProductDataContractNotConfigured();
error ProductDataNotAuthorized();
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @author Syky - Nathan Rempel
import "./interface/IProductDataConnector.sol";
import "../base/interface/IProductDataContract.sol";
abstract contract ProductDataConnector is IProductDataConnector {
address private _dataContract;
/*//////////////////////////////////////////////////////////////
Public getters
//////////////////////////////////////////////////////////////*/
function getDataContract() external view virtual returns (address) {
return _dataContract;
}
function getProductCreators(
uint256 _productId
) external view virtual returns (IProductCreators.CreatorData[] memory) {
if (_dataContract == address(0)) return new IProductCreators.CreatorData[](0);
return IProductDataContract(_dataContract).getProductCreators(_productId);
}
/*//////////////////////////////////////////////////////////////
Admin setters
//////////////////////////////////////////////////////////////*/
function setDataContract(address dataContract_) external virtual onlyData {
_dataContract = dataContract_;
}
function setBaseURI(string calldata _uri) external virtual onlyData {
_setBaseUri(_uri);
}
function setTokenURI(
uint256 _tokenId,
string calldata _uri
) external virtual onlyData {
_setTokenUri(_tokenId, _uri);
}
function setTokenURIs(
uint256 _tokenId,
uint256 _quantity,
string[] calldata _uris
) external virtual onlyData {
_setTokenUris(_tokenId, _quantity, _uris);
}
function setTokenURIs(
uint256[] calldata _tokenIds,
string[] calldata _uris
) external virtual onlyData {
_setTokenUris(_tokenIds, _uris);
}
function setProductURI(
uint256 _productId,
string calldata _uri
) external virtual onlyData {
_setProductUri(_productId, _uri);
}
/*//////////////////////////////////////////////////////////////
Modifiers
//////////////////////////////////////////////////////////////*/
/// @dev Modifier that checks if the data contract has been configured
modifier onlyConfigured() {
_notConfigured();
_;
}
/// @dev Modifier that checks module admin permissions
modifier onlyData() {
_checkAuth();
_;
}
/*//////////////////////////////////////////////////////////////
Internal functions
//////////////////////////////////////////////////////////////*/
function _checkAuth() internal virtual {
if (!_canSetData()) revert ProductDataNotAuthorized();
}
/// @dev Returns whether product data can be set in the given execution context.
function _canSetData() internal view virtual returns (bool);
function _notConfigured() internal virtual {
if (_dataContract == address(0)) revert ProductDataContractNotConfigured();
}
function _tieredURI(
uint256 _tokenId,
uint256 _productId,
bool _isUnique
) internal view returns (string memory) {
if (_dataContract == address(0)) return "";
return
IProductDataContract(_dataContract).tieredURI(
_tokenId,
_productId,
_isUnique
);
}
function _setBaseUri(string memory _uri) internal onlyConfigured {
string memory prevURI = IProductDataContract(_dataContract).setBaseURI(_uri);
emit BaseURIUpdated(prevURI, _uri);
}
function _setTokenUri(uint256 _tokenId, string memory _uri) internal onlyConfigured {
string memory prevURI = IProductDataContract(_dataContract).setTokenURI(
_tokenId,
_uri
);
emit TokenURIUpdated(_tokenId, prevURI, _uri);
emit MetadataUpdate(_tokenId);
}
function _setTokenUris(
uint256 _tokenId,
uint256 _quantity,
string[] memory _uris
) internal onlyConfigured {
string[] memory _prevURIs = IProductDataContract(_dataContract).setTokenURIs(
_tokenId,
_quantity,
_uris
);
unchecked {
for (uint i; i < _quantity; ) {
emit TokenURIUpdated(_tokenId + i, _prevURIs[i], _uris[i]);
++i;
}
emit BatchMetadataUpdate(_tokenId, _tokenId + _quantity - 1);
}
}
function _setTokenUris(
uint256[] memory _tokenIds,
string[] memory _uris
) internal onlyConfigured {
string[] memory _prevURIs = IProductDataContract(_dataContract).setTokenURIs(
_tokenIds,
_uris
);
uint256 length = _prevURIs.length;
for (uint i; i < length; ) {
emit TokenURIUpdated(_tokenIds[i], _prevURIs[i], _uris[i]);
emit MetadataUpdate(_tokenIds[i]);
unchecked {
++i;
}
}
}
function _setProductUri(
uint256 _productId,
string memory _uri
) internal onlyConfigured {
string memory prevURI = IProductDataContract(_dataContract).setProductURI(
_productId,
_uri
);
emit ProductURIUpdated(_productId, prevURI, _uri);
}
function _runBeforeExtensions(
uint256 _productId,
address _from,
address _to,
uint256 _startTokenId,
uint256 _quantity
) internal {
if (_dataContract == address(0)) return;
IProductDataContract(_dataContract).runBeforeExtensions(
_productId,
_from,
_to,
_startTokenId,
_quantity
);
}
function _runAfterExtensions(
uint256 _productId,
address _from,
address _to,
uint256 _startTokenId,
uint256 _quantity
) internal {
if (_dataContract == address(0)) return;
IProductDataContract(_dataContract).runAfterExtensions(
_productId,
_from,
_to,
_startTokenId,
_quantity
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title SykyArtistToken
* @author Syky - Nathan Rempel
* @notice Powered up token for the future of fashion.
*/
/*
@@@ @@@@@ @@@@@@@ @@@@ @@@@@@@ @@@@@ @@@@@@@ @@@@
@@@@ @@@ @@@@@@ @@ @@@@@@ @@ @@@@@@ @@
@@@@@ @@ @@@@@ @@ @@@@@ @ @@@@@ @@
@@@@@@ @@ @@@@@ @@ @@@@@ @ @@@@ @@
@@@@@@ @ @@@@@ @@ @@@@@ @ @@@@@ @
@@@@@@@ @@@@@ @@ @@@@@ @@@ @@@@@ @
@@@@@@ @@@@@@ @@@@@@@@@@@ @@@@@@
@@@@@@ @@@@@ @@@@@ @@@@@ @@@@@
@@@@@@@ @@@@@ @@@@@ @@@@@ @@@@@
@ @@@@@@ @@@@@ @@@@@ @@@@@ @@@@@
@@ @@@@@ @@@@@ @@@@@ @@@@@ @@@@@
@@@@ @@@@@ @@@@@ @@@@@@ @@@@@@ @@@@@@
@@@@@@ @@@@ @@@@@@@ @@@@@@@ @@@@@@@ @@@@@@@
*/
import "../base/ProductArtistToken.sol";
string constant DEFAULT_NAME = "SYKYGOM";
string constant DEFAULT_SYMBOL = "SYKYGOM";
uint256 constant DEFAULT_SERIES_OFFSET = 6; //Sets token format to be 123'000001
uint128 constant DEFAULT_ROYALTY_BPS = 1000; //Sets royalty to 10%
contract SykyArtistToken is ProductArtistToken {
/*//////////////////////////////////////////////////////////////
Version Info
//////////////////////////////////////////////////////////////*/
string public constant ENV = "MAINNET";
string public constant VER = "1.0.0";
/*//////////////////////////////////////////////////////////////
Constructor
//////////////////////////////////////////////////////////////*/
constructor(
address defaultAdmin_,
address royaltyRecipient_
)
ProductArtistToken(
defaultAdmin_,
DEFAULT_NAME,
DEFAULT_SYMBOL,
DEFAULT_SERIES_OFFSET,
royaltyRecipient_,
DEFAULT_ROYALTY_BPS
)
{}
}