// SPDX-License-Identifier: MIT pragma solidity 0.8.10; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; contract ERC721Staking is Ownable, ReentrancyGuard, Pausable, ERC721Holder { uint256 public cooldownSeconds = 1 days; struct Staker { address user; address nft; uint256 tokenId; uint64 start; uint64 stakeTime; } // nft address => token id => Staker mapping(address => mapping(uint256 => Staker)) public stakingMap; // User-selectable stake NFT mapping(address => bool) public erc721Supported; // User-selectable stake time, in seconds mapping(uint64 => bool) public periods; // event of stake event Staked(address indexed user, Staker[] infos); // event of redeem event Redeem(address indexed user, Staker[] infos); // event of update ERC721 support event EditNFTSuppout(address nftToken, bool status); // event of update periods event EditPeriods(uint64 period, bool status); // event of update cooldownSeconds event EditCooldownSeconds(uint256 oldVal, uint256 newVal); function stake( address[] calldata nfts, uint256[] calldata tokenIds, uint64[] calldata staketimes ) external nonReentrant whenNotPaused { require( nfts.length == tokenIds.length, "ERC721Staking: nfts length != tokenIds length" ); require( nfts.length == staketimes.length, "ERC721Staking: nfts length != staketimes length" ); address account = msg.sender; uint64[] memory _beginTimes = new uint64[](nfts.length); uint64[] memory _stakeTimes = new uint64[](nfts.length); Staker[] memory _infos = new Staker[](nfts.length); for (uint256 i = 0; i < nfts.length; i++) { require( erc721Supported[nfts[i]] == true, "ERC721Staking: nft is not supported" ); require( periods[staketimes[i]] == true, "ERC721Staking: staketime is not supported" ); IERC721(nfts[i]).safeTransferFrom(account, address(this), tokenIds[i]); _beginTimes[i] = uint64(block.timestamp); _stakeTimes[i] = staketimes[i]; stakingMap[nfts[i]][tokenIds[i]] = Staker( account, nfts[i], tokenIds[i], _beginTimes[i], _stakeTimes[i] ); _infos[i] = stakingMap[nfts[i]][tokenIds[i]]; } emit Staked(account, _infos); } function redeem( address[] calldata nfts, uint256[] calldata tokenIds ) external nonReentrant { require( nfts.length == tokenIds.length, "ERC721Staking: nfts length != ids length" ); address account = msg.sender; // check if ids are valid Staker[] memory _infos = new Staker[](tokenIds.length); for (uint256 i = 0; i < tokenIds.length; i++) { require( stakingMap[nfts[i]][tokenIds[i]].user != address(0), "ERC721Staking: tokenId is not valid" ); require( stakingMap[nfts[i]][tokenIds[i]].user == account, "ERC721Staking: user is not the owner of this nft" ); require( stakingMap[nfts[i]][tokenIds[i]].start + cooldownSeconds <= block.timestamp, "ERC721Staking: cooldown time is not reached" ); IERC721(nfts[i]).safeTransferFrom(address(this), account, tokenIds[i]); _infos[i] = stakingMap[nfts[i]][tokenIds[i]]; delete stakingMap[nfts[i]][tokenIds[i]]; } emit Redeem(account, _infos); } /** * @dev update ERC721 support */ function updateERC721Support( address nftToken, bool status ) external onlyOwner { erc721Supported[nftToken] = status; emit EditNFTSuppout(nftToken, status); } function updatePeriods(uint64 period, bool status) external onlyOwner { periods[period] = status; emit EditPeriods(period, status); } function updateCooldownSeconds(uint256 _cooldownSeconds) external onlyOwner { uint256 oldVal = cooldownSeconds; cooldownSeconds = _cooldownSeconds; emit EditCooldownSeconds(oldVal, _cooldownSeconds); } }