[Ethereum] Common Gas Wallet for ERC20 Wallets

addresseserc-20gaswallets

We have a wallet structure where users send tokens to individual user wallet addresses generated for them, we then sweep all the funds in the user's wallets to a centralized pool. The individual wallets act as receiving address for the user funds. Normally when an ETH transfer happens, the transaction fee goes from the funds and we bear the cost.

When an ERC20 token deposit happens, we now have to send ETH to the address to cover the transaction cost of the sweep. This is far from a good solution (it leaves behind transaction change and is hard to implement)

The Naive Solution: Just send ETH into a user's wallet every time I detect an ERC20 deposit into that wallet, so that said wallet can pay for the gas to transfer.

Address Factory Contract: Inspired by this answer, creating a contract that would generate address for the contract. The shortfall being gas per address generation.

Looks like Meta Transactions are a way of going about this, relevant link.

Best Answer

Our particular constraints:

  • using addresses as identification
  • minimal UX trade-off

The solution: A contract that generates receiver contracts

The receiver address producing contract:

contract Factory {

address public owner;
mapping ( uint256 => address ) public receiversMap;
uint256 public receiverCount = 0;

constructor() public {
    /* 
        Deployer's address ( Factory in our case )
        do not pass this as a constructor argument because 
        etherscan will have issues displaying our validated source code
    */
    owner = msg.sender;
}

/*
    @notice Transfer Ownership of this contract to another address
    @param newOwner - Address of the next Owner of the contract
*/
function transferOwner(address newOwner) public {
    require (msg.sender == owner);
    owner = newOwner;
}

/*
    @notice Create a number of receiver contracts
    @param number  - 0-255 
*/
function createReceivers( uint8 number ) public {
    require(msg.sender == owner);

    for(uint8 i = 0; i < number; i++) {
        // Create and index our new receiver
        receiversMap[++receiverCount] = new Receiver();
    }
    // add event here if you need it
}

/*
    @notice Send funds in a receiver to another address
    @param ID       - Receiver indexed ID
    @param tracker  - ERC20 token tracker ( DAI / MKR / etc. )
    @param amount   - Amount of tokens to send
    @param receiver - Address we're sending tokens to
    @return true if transfer succeeded, false otherwise 
*/
function sendFundsFromReceiverTo( uint256 ID, address tracker, uint256 amount, address receiver ) public returns (bool) {
    require(msg.sender == owner);
    return Receiver( receiversMap[ID] ).sendFundsTo( tracker, amount, receiver);
}

/*
    Batch Collection - Should support a few hundred transansfers

    @param tracker           - ERC20 token tracker ( DAI / MKR / etc. )
    @param receiver          - Address we're sending tokens to
    @param contractAddresses - we send an array of addresses instead of ids, so we don't need to read them ( lower gas cost )
    @param amounts           - array of amounts 

*/
function batchCollect( address tracker, address receiver, address[] contractAddresses, uint256[] amounts ) public {
    require(msg.sender == owner);

    for(uint256 i = 0; i < contractAddresses.length; i++) {

        // add exception handling
        Receiver( contractAddresses[i] ).sendFundsTo( tracker, amounts[i], receiver);
    }
}
}

The receiver contract:

contract Receiver {

address public owner;

constructor() public {
    /* 
        Deployer's address ( Factory in our case )
        do not pass this as a constructor argument because 
        etherscan will have issues displaying our validated source code
    */
    owner = msg.sender;
}

/*
    @notice Transfer Ownership of this contract to another address
    @param newOwner - Address of the next Owner of the contract
*/
function transferOwner(address newOwner) public {
    require (msg.sender == owner);
    owner = newOwner;
}

/*
    @notice Send funds owned by this contract to another address
    @param tracker  - ERC20 token tracker ( DAI / MKR / etc. )
    @param amount   - Amount of tokens to send
    @param receiver - Address we're sending these tokens to
    @return true if transfer succeeded, false otherwise 
*/
function sendFundsTo( address tracker, uint256 amount, address receiver) public returns ( bool ) {
    // callable only by the owner, not using modifiers to improve readability
    require(msg.sender == owner);

    // Transfer tokens from this address to the receiver
    return ERC20(tracker).transfer(receiver, amount);
}

// depending on your system,  you probably want to suicide this at some
// point in the future, or reuse it for other clients
}

The answer is based on Micky Socaci's solution here. The entire solution, with the necessary modifications, is hosted on My Github with a Creative Commons License so it can be dropped into any codebase.

A star would be appreciated if this helps you :).

https://github.com/Meshugah/ERC20-CommonGasWallet

Related Topic