Well, I need the following help. I'm developing a game, and in this game my ERC20 coin will have by default 1billion of the token in a wallet called "Rewards". That way, my contract for that currency when I implement it will automatically mine 1billion for the "Rewards" address, so far, ok.

At some point I need to withdraw coins from my wallet (rewards) and move to another wallet (players).

For this situation I found an alternative, which I don't believe is the safest, because it's about adding in the frontend of my game the option for web3 to access my wallet (rewards) through PrivateKey, but as I said, this transaction by if it were handled on the frontend, my privateKey would be exposed and anyone could steal my account.

That said, what did I do? I created in my contract a method called "withdrawRewards", my idea is basically to do the "transfer" process that the ERC20 contract already has, the only difference that would not go through approve, and I would be validating based on a signature that I would be generating using the ECDSA lib, but what's the problem with that? The problem is that the variable "_balances" is private and I can't access it, even though the contract is extending the ERC20.

My question is, how can I do this in the best and safest way possible?

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract MyToken is ERC20, Pausable, AccessControl, ERC20Burnable, Ownable  {
    using SafeMath for uint256;

    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    mapping (address => uint256) private _nonces;
    address private sigAddres;

    //wallet 1kkk
    // address public rewardsAddr          = address(0x59D6fcF2e8210cDC481389c21f2D6); 
    address public rewardsAddr          = address(0x961aCa5d56EfD5971aF8F);     
    //Tax address
    address public taxAddr   = address(0x90475bB7492F3c5949475FDD1);

    //tax buy/sell initial 5%     
    uint256 private transferFees = 5;
    bool private initializedFees = true; 

    constructor() ERC20("TOKEN COIN", "TOKEN") {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(PAUSER_ROLE, msg.sender);
        // _mint(rewardsAddr, 1000000000 * 10 ** decimals());
        _mint(owner(), 1000000000 * 10 ** decimals());
        _setupRole(MINTER_ROLE, msg.sender);                

    * @dev Fallback function if ether is sent to address 
    receive() external payable {

     * @dev overrides transfer for tokenomics
    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal virtual override{
        // require(tokenDenylist[msg.sender] == false, "Address in deny list");
        require(amount > 0, "ERC20: transfer amount must be greater than zero");
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");                

        if(hasFees(sender, recipient)){
           uint256 feesTotal = amount.mul(transferFees).div(100);
           amount = amount - feesTotal;
           //wallet transfer fees
           super._transfer(sender, taxAddr, feesTotal);

       super._transfer(sender, recipient, amount);

    function withdrawRewards(address _player, uint256 _amount, uint256 _nonce, bytes memory _signature)
    public virtual isValidSignature(_player, _amount, _nonce, _signature) returns(bool){
        uint256 rewardsBalance = _balances[rewardsAddr];
        require(rewardsBalance >= _amount, "ERC20: transfer amount exceeds balance");
        if(_nonces[_player] >= _nonce){
            revert("Withdraw already used");
        unchecked {
            _balances[rewardsAddr] = rewardsBalance - _amount;
        _balances[_player] += _amount;

        _nonces[_player] = _nonce;

        emit Transfer(rewardsAddr, _player, _amount);

        return true;
    function hasFees(address sender, address recipient) private view returns(bool){
        if(initializedFees == true && 
            (transferFees > 0) 
            && (sender != taxAddr)  
            // && (sender != holdersAddr)              
            && (sender != owner()) 
            && (recipient != taxAddr)
            // && (recipient != holdersAddr)
            && (recipient != rewardsAddr)){
                return true;
            return false;

    function pause() public onlyRole(PAUSER_ROLE) {

    function unpause() public onlyRole(PAUSER_ROLE) {

    function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
        _mint(to, amount);        

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        super._beforeTokenTransfer(from, to, amount);

    modifier isValidSignature(address _player, uint256 _amount, uint256 _nonce, bytes memory _signature) {
        bytes32 message = keccak256(abi.encodePacked(_player, _amount, _nonce));
        assert(ECDSA.recover(message, _signature) == sigAddres);        

    function setSig(address _sig) public onlyOwner{
        sigAddres = _sig;

    function updateAddrFees(uint _fee) public onlyOwner{
        transferFees = _fee;

    function initFees(bool _startFee) public onlyOwner{
        initializedFees = _startFee;

1 Answer 1


For the signature part I'd use something like EIP-2612 permit – 712-signed approvals.

For the rewards part you have to use _transfer. If you want to skip the fee related changes you could call the parent function with ERC20._transfer(..).

function withdrawRewards(address _player, uint256 _amount, uint256 _nonce, bytes memory _signature)
public virtual isValidSignature(_player, _amount, _nonce, _signature) returns(bool){
    uint256 rewardsBalance = balanceOf(rewardsAddr);
    require(rewardsBalance >= _amount, "ERC20: transfer amount exceeds balance");
    require(_nonces[_player] < _nonce, "Withdraw already used");

    ERC20._transfer(rewardsAddr, _player, _amount);

    _nonces[_player] = _nonce;

    emit Transfer(rewardsAddr, _player, _amount);

    return true;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.