// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; 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; } contract NFTLockMain is ERC721Holder, 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 => mapping(uint256 tokenId => address user)) public addressOriginal; mapping(address nft => mapping(address user => EnumerableSet.UintSet tokenIdSet)) private lockedRecords; 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; } /** * lock NFT * from eoa only * @param nft nft address * @param to passport address for game * @param tokenIds nft token id list */ function lock(address nft, address to, uint256[] calldata tokenIds) external whenNotPaused{ require(tokenIds.length <= maxBatch, "tokenIds too many"); require(to != address(0), "passport can't be zero"); require(supportNftList[nft], "not support nft"); address _sender = _msgSender(); for (uint256 i = 0; i < tokenIds.length; i++) { addressOriginal[nft][tokenIds[i]] = _sender; lockedRecords[nft][_sender].add(tokenIds[i]); INFT(nft).transferFrom(_sender, address(this), tokenIds[i]); } emit Lock(nft, _sender, to, tokenIds); } /** * @dev unlock or mint nft * from passport only * if tokenId not exists, mint it * if exists and user is owner, unlock it */ 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++) { if (nftList[i].isMint) { INFT(nft).mint(nftList[i].to, nftList[i].tokenId); } else { require(addressOriginal[nft][nftList[i].tokenId] == _sender, "not owner"); delete addressOriginal[nft][nftList[i].tokenId]; lockedRecords[nft][_sender].remove(nftList[i].tokenId); INFT(nft).transferFrom(address(this), nftList[i].to, nftList[i].tokenId); } } emit UnLock(nft, _sender, saltNonce, nftList); } /** * @dev unlock nft * from game svr only */ function unlockWithSvr(address nft, uint256[] calldata tokenIds) external onlyOwner{ require(tokenIds.length <= maxBatch, "tokenIds too many"); for (uint256 i = 0; i < tokenIds.length; i++) { address _sender = addressOriginal[nft][tokenIds[i]]; delete addressOriginal[nft][tokenIds[i]]; lockedRecords[nft][_sender].remove(tokenIds[i]); INFT(nft).transferFrom(address(this), _sender, tokenIds[i]); } } /** ------get------- **/ function lockedNum(address token, address user) public view returns (uint256) { return lockedRecords[token][user].length(); } function lockedNft(address token, address user) public view returns (uint256[] memory) { return lockedRecords[token][user].values(); } function updateBatch(uint256 _maxBatch) external onlyOwner { maxBatch = _maxBatch; } 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), "NFTClaimer: 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); } }