5

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.

4
  • 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, 2019 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. Sep 18, 2019 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, 2019 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, 2019 at 6:34

2 Answers 2

3

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

8
  • Hello! Could you please tell, do I understand correctly that the Factory contract can transfer tokens on behalf of the Receiver contract, and the Receiver contract should not call any additional methods before transfer? Do common tokens (for example, usdt) allow such an operation?
    – Oroffe
    Apr 8, 2020 at 18:16
  • @Oroffe To your first question, Yes. To the Second, I'm not sure, I haven't seen the contract but I highly doubt it. Common tokens do not require such an operation as the main contract holds token address metadata and as external user you just interface with the contract(paying gas to call Transfer, for example). Like my answer if this helps! And Star my Git Repo if that does! :) Apr 9, 2020 at 6:27
  • I'm sorry, I didn't put my question well. I'm trying to figure out whether a transfer of ERC20 token can be considered invalid if it's made on behalf of the contract, but another address pays the fee. The thing is, I am trying to test your scheme locally and it doesn't work. In my private testnet I imported two contracts: this factory and USDT. Then I called the method for creating a Receiver. It worked well and created a new contract. Then I sent some money via USDT interface to its address. The money arrived.
    – Oroffe
    Apr 9, 2020 at 21:33
  • But when I try to transfer them via the factory method, the transaction successfully gets to the block, but the contract execution fails. I'm trying to figure out whether I am doing something wrong, or not all ERC20 tokens support the ability to make transfers via this factory interface. In USDT code I didn't find anything that could make transfer invalid, so I am a bit confused. I would be very grateful if you could help me clarify this issue.
    – Oroffe
    Apr 9, 2020 at 21:33
  • Please like and star the git and we can take this to chat. Apr 10, 2020 at 6:54
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.

3
  • 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. Sep 23, 2019 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). Sep 23, 2019 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. Sep 23, 2019 at 6:49

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.