155 lines
5.7 KiB
Solidity
155 lines
5.7 KiB
Solidity
// 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 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;
|
|
uint256 public maxBatch = 100;
|
|
|
|
struct NFTInfo {
|
|
uint256 tokenId;
|
|
address to;
|
|
bool isMint;
|
|
}
|
|
mapping(address nft => mapping(uint256 tokenId => address user)) public passportOriginal;
|
|
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;
|
|
}
|
|
/**
|
|
* 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), "not support nft");
|
|
require(supportNftList[nft], "not support nft");
|
|
address _sender = _msgSender();
|
|
for (uint256 i = 0; i < tokenIds.length; i++) {
|
|
passportOriginal[nft][tokenIds[i]] = to;
|
|
addressOriginal[nft][tokenIds[i]] = _sender;
|
|
lockedRecords[nft][to].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(passportOriginal[nft][nftList[i].tokenId] == _sender, "not owner");
|
|
require(addressOriginal[nft][nftList[i].tokenId] == nftList[i].to, "not owner");
|
|
delete passportOriginal[nft][nftList[i].tokenId];
|
|
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 passportOriginal[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);
|
|
}
|
|
}
|