Skip to main content





// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";

using SafeERC20 for IERC20;

* @title DonaSwapTokenMigrator
* @dev This contract allows users to claim tokens based on provided vouchers.
* It implements the Ownable pattern, ensuring that only the contract owner
* can perform certain operations such as setting the signer address and
* managing claimed tokens. Additionally, it utilizes EIP712 for signature
* verification to ensure the validity of vouchers.
contract DonaSwapTokenMigrator is Ownable(msg.sender), EIP712 {
IERC20 public token;
mapping(address => bool) public hasClaimed;
address public signer = address(0x29E4711B8b2FDAFf30f70372a98A9Ad9f5A91cfa);

string private constant SIGNING_DOMAIN = "TokenDrop-Voucher";
string private constant SIGNATURE_VERSION = "1";

// Struct representing a TokenDropVoucher
struct TokenDropVoucher {
uint256 amount;
uint256 expires;
address wallet;
/// @notice the EIP-712 signature of all other fields in the struct.
/// For a voucher to be valid, it must be signed by an account with the signer.
bytes signature;

// Constructor initializing the contract with the specified token
token = _token;

// Internal function to hash the TokenDropVoucher struct
function _hash(TokenDropVoucher memory voucher) internal view returns (bytes32) {
return _hashTypedDataV4(keccak256(abi.encode(
keccak256("Struct(uint256 amount,uint256 expires,address wallet)"),

// Internal function to verify the signature of a TokenDropVoucher
function _verify(TokenDropVoucher memory voucher) internal view returns (address) {
bytes32 digest = _hash(voucher);
return ECDSA.recover(digest, voucher.signature);

// Function to set a new signer address, accessible only by the contract owner
function setSigner(address _signer) public onlyOwner {
signer = _signer;

// Function to unset the claim status of a wallet, accessible only by the contract owner
function unsetClaim(address wallet) public onlyOwner {
hasClaimed[wallet] = false;

// Function to claim tokens based on a voucher, requires a valid signature from the signer
function claimTokens(uint256 amount, uint256 expires, address wallet, bytes memory signature) external {
require(!hasClaimed[wallet], "Tokens already claimed");
require(block.number <= expires, "Voucher expired");

TokenDropVoucher memory voucher = TokenDropVoucher({amount: amount, expires: expires, wallet: wallet, signature: signature});
address _signer = _verify(voucher);
require(_signer == signer, "Invalid signature");

hasClaimed[voucher.wallet] = true;
token.safeTransfer(voucher.wallet, voucher.amount);

// Function to withdraw ERC20 tokens, accessible only by the contract owner
function withdrawERC20(IERC20 _token, address to, uint256 amount) public onlyOwner {
uint256 erc20balance = _token.balanceOf(address(this));
require(amount <= erc20balance, "Balance is too low");
require(amount > 0, "Balance must be higher than zero");

if (amount == 0) {
amount = erc20balance;

_token.transfer(to, amount);

// Function to withdraw native assets, accessible only by the contract owner
function withdraw() public onlyOwner {
address payable receiver = payable(msg.sender);

uint256 _balance = address(this).balance;
require(_balance > 0, "Balance must be higher than zero");