2024-06-03 14:06:45 +08:00

125 lines
4.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
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 NFTLock is ERC721Holder, HasSignature, TimeChecker, Pausable {
using EnumerableSet for EnumerableSet.UintSet;
uint256 public immutable _CACHED_CHAIN_ID;
address public immutable _CACHED_THIS;
address public verifier;
struct NFTInfo {
uint256 tokenId;
bool isMint;
}
mapping(address nft => mapping(uint256 tokenId => address user)) public lockedOriginal;
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 user, 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;
}
function lock(address nft, uint256[] calldata tokenIds) external whenNotPaused{
require(tokenIds.length <= 100, "tokenIds too many");
address to = _msgSender();
for (uint256 i = 0; i < tokenIds.length; i++) {
INFT(nft).transferFrom(to, address(this), tokenIds[i]);
lockedOriginal[nft][tokenIds[i]] = to;
lockedRecords[nft][to].add(tokenIds[i]);
}
emit Lock(nft, to, tokenIds);
}
/**
* @dev unlock or mint nft
* 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) whenNotPaused {
require(nftList.length <= 100, "tokenIds too many");
address to = _msgSender();
bytes32 messageHash = getMessageHash(to, nft, nftList, _CACHED_THIS, _CACHED_CHAIN_ID, signTime, saltNonce);
checkSigner(verifier, messageHash, signature);
for (uint256 i = 0; i < nftList.length; i++) {
if (nftList[i].isMint) {
INFT(nft).mint(to, nftList[i].tokenId);
} else {
require(lockedOriginal[nft][nftList[i].tokenId] == to, "not owner");
INFT(nft).transferFrom(address(this), to, nftList[i].tokenId);
lockedRecords[nft][to].remove(nftList[i].tokenId);
delete lockedOriginal[nft][nftList[i].tokenId];
}
}
_useSignature(signature);
emit UnLock(nft, to, saltNonce, nftList);
}
/** ------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 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].isMint));
}
return keccak256(encoded);
}
}