// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {HasSignature} from "../core/HasSignature.sol"; import {TimeChecker} from "../utils/TimeChecker.sol"; contract TokenClaim is HasSignature, ReentrancyGuard, Pausable, TimeChecker { using SafeERC20 for IERC20; uint256 public immutable _CACHED_CHAIN_ID; address public immutable _CACHED_THIS; address public verifier; mapping(address token => address wallet) public erc20Wallets; // store user's claimed status mapping(address user => mapping(address token => uint256 claimedBit)) public claimedBitMap; event EventERC20Wallet(address erc20, address wallet); event EventVerifierUpdated(address indexed verifier); event EventTokenClaimed(address indexed user, address indexed token, address account, uint256 amount, uint256 bit); constructor(address _wallet, address _token, address _verifier, uint256 _duration) TimeChecker(_duration) { _CACHED_CHAIN_ID = block.chainid; _CACHED_THIS = address(this); erc20Wallets[_token] = _wallet; verifier = _verifier; } /** * @dev update verifier address */ function updateVerifier(address _verifier) external onlyOwner { require(_verifier != address(0), "TokenClaim: address can not be zero"); verifier = _verifier; emit EventVerifierUpdated(_verifier); } /** * @dev update pause state */ function pause() external onlyOwner { _pause(); } /** * @dev update unpause state */ function unpause() external onlyOwner { _unpause(); } /** * @dev update ERC20 wallet */ function updateERC20Wallet(address erc20, address wallet) external onlyOwner { require(erc20Wallets[erc20] != wallet, "TokenClaim: ERC20 wallet not changed"); erc20Wallets[erc20] = wallet; emit EventERC20Wallet(erc20, wallet); } /** * @dev claim CEC with signature * @param account address which eligible to claim, only for event log * @param token address of token * @param vals array of amount, bit, signTime, saltNonce * @param signature signature of claim */ function claim( address account, address token, uint256[4] calldata vals, // amount, bit, signTime, saltNonce bytes calldata signature ) external signatureValid(signature) timeValid(vals[2]) nonReentrant whenNotPaused { require(erc20Wallets[token] != address(0), "TokenClaim: token is not supported"); require(vals[0] > 0, "TokenClaim: amount is zero"); uint256 current = claimedBitMap[account][token]; require(current & vals[1] == 0, "TokenClaim: condition check failed"); address user = _msgSender(); bytes32 criteriaMessageHash = getMessageHash( user, account, token, _CACHED_THIS, _CACHED_CHAIN_ID, vals ); checkSigner(verifier, criteriaMessageHash, signature); _useSignature(signature); claimedBitMap[account][token] = current | vals[1]; IERC20(token).safeTransferFrom(erc20Wallets[token], user, vals[0]); emit EventTokenClaimed(user, token, account, vals[0], vals[1]); } function getMessageHash( address _user, address _account, address _token, address _contract, uint256 _chainId, uint256[4] calldata _vals ) public pure returns (bytes32) { bytes memory encoded = abi.encodePacked( _user, _account, _token, _contract, _chainId ); for (uint256 i = 0; i < _vals.length; i++) { encoded = bytes.concat(encoded, abi.encodePacked(_vals[i])); } return keccak256(encoded); } }