// 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 amount mapping(address user => mapping(address token => uint256 amount)) public claimedAmount; event EventERC20Wallet(address erc20, address wallet); event EventVerifierUpdated(address indexed verifier); event EventTokenClaimed( address indexed user, address indexed token, address passport, uint256 amount, uint256 nonce ); 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), "TokenClaimer: 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, "TokenClaimer: ERC20 wallet not changed"); erc20Wallets[erc20] = wallet; emit EventERC20Wallet(erc20, wallet); } /** * @dev claim CEC with signature */ function claim( address passport, address token, uint256 amount, uint256 signTime, uint256 saltNonce, bytes calldata signature ) external signatureValid(signature) timeValid(signTime) nonReentrant whenNotPaused { require(passport != address(0), "TokenClaimer: passport address can not be zero"); require(erc20Wallets[token] != address(0), "TokenClaimer: token is not supported"); require(amount > 0, "TokenClaimer: amount is zero"); address user = _msgSender(); bytes32 criteriaMessageHash = getMessageHash( user, passport, token, amount, _CACHED_THIS, _CACHED_CHAIN_ID, signTime, saltNonce ); checkSigner(verifier, criteriaMessageHash, signature); _useSignature(signature); claimedAmount[user][token] += amount; IERC20(token).safeTransferFrom(erc20Wallets[token], user, amount); emit EventTokenClaimed(user, token, passport, amount, saltNonce); } function getMessageHash( address _user, address _passport, address _token, uint256 _amount, address _contract, uint256 _chainId, uint256 _signTime, uint256 _saltNonce ) public pure returns (bytes32) { bytes memory encoded = abi.encodePacked( _user, _passport, _token, _amount, _contract, _chainId, _signTime, _saltNonce ); return keccak256(encoded); } }