4

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.

  • It is not clear why is necessary to have a factory contract and to generate addresses. You could think in a balance contract, the tokens get stored in the master contract and then a withdrawal/collection fucntion can be called to "sent" all the tokens to the cold wallet. – Jaime Sep 17 at 17:22
  • We need to peg each transaction to their corresponding user. The balance contract you mention doesn't necessarily have a way to identify which user made a transaction to the contract. – Vignesh Karthikeyan Sep 18 at 7:41
  • It does, every token transaction will emit a transfer event in the ERC20 token contract with the amount, sender and receiver. You will need a backend checking transactions to your master contract. The gain with this approach is reuced use of gas, if your service have many users this could be a substantial reduction in costs unless you are making your users to pay for this. Also the backend implementation is fairly simple for this application. – Jaime Sep 18 at 8:55
  • have u seen gsn.openzeppelin.com ? for users' contract you can use CREATE2 wallet-like contract medium.com/authereum/gas-spectrum-transactions-bd34b65107b – rstormsf Sep 26 at 6:34
0

I assume you can't identify the token transferrers just based on the sending address - otherwise this would be trivial (just follow the Transfer events).

I personally don't quite understand why meta transactions aren't a big thing already. They enable all sorts of scenarios especially in easy user on-boarding. Sure, they have some issues and are a bit tricky to implement but once it's once implemented properly it should work just fine. So my suggestion is to go for meta transactions.

Let's spend a little time thinking whether there is any possibility to recognize the sender by some other means than sender address in a regular token transfer. There are two ways to transfer tokens: a direct transfer and an approved retrieval.

  1. transfer(address recipient, uint256 amount). The recipient has to be some wallet address you control. So either a specially created wallet or a general wallet. This is pretty much your naive solution. But another idea might be to think whether you can use the amount in some fashion. If you have only a few users and lots of tokens (where small token amounts don't make much difference) you could identify the users by the amount of tokens. So for example if three users need to send 5000 tokens you could instruct first one to send 5003 tokens, second to send 5006 and third 5009. Not the best possible solution but easy&cheap to implement and therefore maybe worth considering.

  2. approve(address spender, uint256 amount) plus transferFrom(address sender, address recipient, uint256 amount). The same considerations as in the first option as the user only initiates the approve transaction and you do the rest.

  • Thanks for the response Lauri. The first solution, as you pointed out, is a nice solution but is a bit of a UX trade-off. The second solution would require the user to approve the transaction. We'd like transaction approval to be up to our discretion. – Vignesh Karthikeyan Sep 23 at 6:42
  • Well the user has to do something - be it approval or transfer. He has to initiate some transaction to get the tokens going (at least a meta transaction). – Lauri Peltonen Sep 23 at 6:46
  • Between the two actions required, we'd like to do the approval. The proposed factory contract solution allows us to receive tokens at no UX trade off. – Vignesh Karthikeyan Sep 23 at 6:49
0

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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