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 {
payable(owner()).transfer(msg.value);
}
/**
* @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) {
_pause();
}
function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function _beforeTokenTransfer(address from, address to, uint256 amount)
internal
whenNotPaused
override
{
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;
}
}