// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; import {HasSignature} from "../core/HasSignature.sol"; import {TimeChecker} from "../utils/TimeChecker.sol"; interface INFT { function mint(address to, uint256 tokenID) external; function transferFrom(address from, address to, uint256 tokenId) external; function burn(uint256 tokenId) external; function ownerOf(uint256 tokenId) external view returns (address); } contract NFTLockV2 is HasSignature, TimeChecker, Pausable { using EnumerableSet for EnumerableSet.UintSet; uint256 public immutable _CACHED_CHAIN_ID; address public immutable _CACHED_THIS; address public verifier; uint256 public maxBatch = 100; struct NFTInfo { uint256 tokenId; address to; bool isMint; } mapping(address nft => bool status) public supportNftList; event UnLock(address indexed nft, address indexed user, uint256 nonce, NFTInfo[] nftList); event Lock(address indexed nft, address indexed sender, address indexed to, uint256[] tokenIds); event VerifierUpdated(address indexed verifier); constructor(uint256 _duration, address _verifier) TimeChecker(_duration) { _CACHED_CHAIN_ID = block.chainid; _CACHED_THIS = address(this); verifier = _verifier; } /** * from eoa or passport */ function lock(address nft, address to, uint256[] calldata tokenIds) external whenNotPaused{ require(tokenIds.length <= maxBatch, "tokenIds too many"); require(to != address(0), "to can't be zero"); require(supportNftList[nft], "not support nft"); address _sender = _msgSender(); for (uint256 i = 0; i < tokenIds.length; i++) { address owner = INFT(nft).ownerOf(tokenIds[i]); require(owner == _sender, "not owner"); INFT(nft).burn(tokenIds[i]); } emit Lock(nft, _sender, to, tokenIds); } /** * @dev mint nft */ function unlockOrMint( address nft, NFTInfo[] calldata nftList, uint256 signTime, uint256 saltNonce, bytes calldata signature ) external signatureValid(signature) timeValid(signTime) { require(nftList.length <= maxBatch, "tokenIds too many"); address _sender = _msgSender(); bytes32 messageHash = getMessageHash(_sender, nft, nftList, _CACHED_THIS, _CACHED_CHAIN_ID, signTime, saltNonce); checkSigner(verifier, messageHash, signature); _useSignature(signature); for (uint256 i = 0; i < nftList.length; i++) { require(nftList[i].isMint, "mint only"); INFT(nft).mint(nftList[i].to, nftList[i].tokenId); } emit UnLock(nft, _sender, saltNonce, nftList); } function addSupportNftList(address[] calldata nftList) external onlyOwner { for (uint256 i = 0; i < nftList.length; i++) { supportNftList[nftList[i]] = true; } } function removeSupportNft(address nftAddress) external onlyOwner { require(supportNftList[nftAddress], "can't remove"); delete supportNftList[nftAddress]; } /** * @dev update verifier address */ function updateVerifier(address _verifier) external onlyOwner { require(_verifier != address(0), "address can not be zero"); verifier = _verifier; emit VerifierUpdated(_verifier); } function getMessageHash( address _to, address _nft, NFTInfo[] memory _ids, address _contract, uint256 _chainId, uint256 _signTime, uint256 _saltNonce ) public pure returns (bytes32) { bytes memory encoded = abi.encodePacked(_to, _nft, _contract, _chainId, _signTime, _saltNonce); for (uint256 i = 0; i < _ids.length; ++i) { encoded = bytes.concat(encoded, abi.encodePacked(_ids[i].tokenId)); encoded = bytes.concat(encoded, abi.encodePacked(_ids[i].to)); encoded = bytes.concat(encoded, abi.encodePacked(_ids[i].isMint)); } return keccak256(encoded); } }