125 lines
4.5 KiB
Solidity
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);
|
|
}
|
|
}
|