// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {ModuleBase} from "../ModuleBase.sol";
import {ErrNotCharacterOwner, ErrNotApprovedOrExceedApproval} from "../../libraries/Error.sol";
import {Events} from "../../libraries/Events.sol";
import {IMintModule4Note} from "../../interfaces/IMintModule4Note.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
/**
* @title ApprovalMintModule
* @notice This is a simple MintModule implementation, inheriting from the IMintModule4Note interface.
*/
contract ApprovalMintModule is IMintModule4Note, ModuleBase {
struct ApprovedInfo {
uint256 approvedAmount;
uint256 mintedAmount;
}
// characterId => noteId => address => ApprovedInfo
mapping(uint256 => mapping(uint256 => mapping(address => ApprovedInfo))) internal _approvedInfo;
// solhint-disable-next-line no-empty-blocks
constructor(address web3Entry_) ModuleBase(web3Entry_) {}
/**
* @dev The data should an abi encoded bytes, containing (in order): an address array and an uint256
*/
/// @inheritdoc IMintModule4Note
function initializeMintModule(
uint256 characterId,
uint256 noteId,
bytes calldata data
) external override onlyWeb3Entry returns (bytes memory) {
if (data.length > 0) {
(address[] memory addresses, uint256 approvedAmount) = abi.decode(
data,
(address[], uint256)
);
_setApprovedAmount(characterId, noteId, addresses, approvedAmount);
}
return data;
}
/**
* @notice Set the approved addresses for minting and the approvedAmount allowed to be minted. <br>
* The approvedAmount is 0 by default, and you can also revoke the approval for addresses by
* setting the approvedAmount to 0.
* @param characterId The character ID of the note owner.
* @param noteId The ID of the note.
* @param addresses The Addresses to set.
* @param approvedAmount The amount of NFTs allowed to be minted.
*/
// solhint-disable-next-line comprehensive-interface
function setApprovedAmount(
uint256 characterId,
uint256 noteId,
address[] calldata addresses,
uint256 approvedAmount
) external {
// msg.sender should be the character owner
address owner = IERC721(web3Entry).ownerOf(characterId);
if (msg.sender != owner) revert ErrNotCharacterOwner();
_setApprovedAmount(characterId, noteId, addresses, approvedAmount);
}
/**
* @notice Process minting and check if the caller is eligible.
*/
/// @inheritdoc IMintModule4Note
function processMint(
address to,
uint256 characterId,
uint256 noteId,
bytes calldata
) external override onlyWeb3Entry {
ApprovedInfo storage approval = _approvedInfo[characterId][noteId][to];
if (approval.approvedAmount <= approval.mintedAmount) {
revert ErrNotApprovedOrExceedApproval();
} else {
++approval.mintedAmount;
}
}
/**
* @notice Returns the approved info indicates the approved amount and minted amount of an address.
* @param characterId ID of the character to query.
* @param noteId ID of the note to query.
* @param account The address to query.
* @return approvedAmount The approved amount that the address can mint.
* @return mintedAmount The amount that the address has already minted.
*/
// solhint-disable-next-line comprehensive-interface
function getApprovedInfo(
uint256 characterId,
uint256 noteId,
address account
) external view returns (uint256 approvedAmount, uint256 mintedAmount) {
approvedAmount = _approvedInfo[characterId][noteId][account].approvedAmount;
mintedAmount = _approvedInfo[characterId][noteId][account].mintedAmount;
}
function _setApprovedAmount(
uint256 characterId,
uint256 noteId,
address[] memory addresses,
uint256 approvedAmount
) internal {
uint256 len = addresses.length;
for (uint256 i = 0; i < len; ) {
_approvedInfo[characterId][noteId][addresses[i]].approvedAmount = approvedAmount;
unchecked {
++i;
}
}
emit Events.SetApprovedMintAmount4Addresses(characterId, noteId, approvedAmount, addresses);
}
}
@openzeppelin/contracts/token/ERC721/IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/
interface IERC721 is IERC165 {
/**
* @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 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function ownerOf(uint256 tokenId) external view returns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
*
* Emits a {Transfer} event.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
/**
* @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 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: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* 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 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 the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function getApproved(uint256 tokenId) external view returns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
contracts/interfaces/IMintModule4Note.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
interface IMintModule4Note {
/**
* @notice Initialize the MintModule for a specific note.
* @param characterId The character ID of the note to initialize.
* @param noteId The note ID to initialize.
* @param data The data passed from the user to be decoded.
* @return bytes The returned data of calling initializeMintModule.
*/
function initializeMintModule(
uint256 characterId,
uint256 noteId,
bytes calldata data
) external returns (bytes memory);
/**
* @notice Processes the mint logic. <br>
* Triggered when the `mintNote` of web3Entry is called, if mint module of note is set.
* @param to The receive address of the NFT.
* @param characterId The character ID of the note owner.
* @param noteId The note ID.
* @param data The data passed from the user to be decoded.
*/
function processMint(
address to,
uint256 characterId,
uint256 noteId,
bytes calldata data
) external;
}
contracts/libraries/Error.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
/// @dev Character ID not exists
error ErrCharacterNotExists(uint256 characterId);
/// @dev Not owner of address
error ErrNotAddressOwner();
/// @dev Caller is not the owner of character
error ErrNotCharacterOwner();
/// @dev Note has been locked
error ErrNoteLocked();
/// @dev Handle does not exist
error ErrHandleExists();
/// @dev Social token address does not exist
error ErrSocialTokenExists();
/// @dev Handle length too long or too short
error ErrHandleLengthInvalid();
/// @dev Handle contains invalid characters
error ErrHandleContainsInvalidCharacters();
/// @dev Operator has not enough permission for this character
error ErrNotEnoughPermission();
/// @dev Operator has not enough permissions for this note
error ErrNotEnoughPermissionForThisNote();
/// @dev Target address already has primary character
error ErrTargetAlreadyHasPrimaryCharacter();
/// @dev Note has been deleted
error ErrNoteIsDeleted();
/// @dev Note does not exist
error ErrNoteNotExists();
/// @dev Array length mismatch
error ErrArrayLengthMismatch();
/// @dev Caller is not web3Entry contract
error ErrCallerNotWeb3Entry();
/// @dev Caller is not web3Entry contract, and not the owner of character
error ErrCallerNotWeb3EntryOrNotOwner();
/// @dev Token id already exists
error ErrTokenIdAlreadyExists();
/// @dev Character does not exist
error ErrNotExistingCharacter();
/// @dev Token id of linklist does not exist
error ErrNotExistingLinklistToken();
/// @dev Invalid web3Entry address
error ErrInvalidWeb3Entry();
/// @dev Not approved by module or exceed the approval amount
error ErrNotApprovedOrExceedApproval();